用户
 找回密码
 入住 CI 中国社区
搜索
查看: 20373|回复: 47
收起左侧

[Web] 关于CodeIgniter实现BigPipe的一些讨论

  [复制链接]
发表于 2012-7-3 21:02:25 | 显示全部楼层 |阅读模式
本帖最后由 方达 于 2012-7-19 21:06 编辑

【该问题已有 2 种解决方案,附在贴子最后】
在一个实际的网站中经常会有这样的场景:
一个页面中有一些页面模块是固定不变、或者服务器能较快处理生成页面的(如一个页面的header、footer)
而另一些页面模块是需要相当长的时间才能生成(如2~3秒)

遇到这些情况,我们通常会将那些能较快生成的页面片段先返回给浏览器,
然后等那些需要较长时间生成的页面模块准备好后,再返回给浏览器。
(http1.1 的chunked encoding分块编码可以支持)
通过这种方式来达到较好的用户体验,不必让用户干等着空白的页面。

在我不用CI用纯粹php写的时候可以这样写(纯测试写法):
PHP复制代码
 
//1
includeheader.php’;
@ob_flush();
flush();
sleep(3);
//2
echo 'content';
@ob_flush();
flush();
sleep(3);
//3
include ‘footer.php’;
 
复制代码


这样的效果是,1,2,3这三块会在不同的时间在浏览器中显示。
而且相比较于ajax技术,这种方式不需要浏览器向后端发送许多xmlhttp请求(确切的说浏览器只有一个request),
减轻了服务器的压力,这对访问量较大的网站来说是有意义的。

可是在我用CI来实现这种效果的过程中,却碰到了很诡异的问题。
比如我要加载2个页面:
PHP复制代码
 
[font=monospace]$this->load->view('skeleton');
$this->load->view('content');
[/font]
复制代码

我在许多地方都加了类似flush之类的函数,可是浏览器给我的感觉,
CI总是将所有页面都准备好了之后,在最后同一时间同时返回给浏览器的。
也就是说,浏览器的响应时间会阻塞在用时最长的那个页面块上。

之后我阅读了Output.php, 认为_dispaly()函数可能是返回浏览器前最后调用的函数。
(因为在代码中log_message()写道:Final output sent to browser)
于是我这样写
PHP复制代码
 
$this->load->view('skeleton');
$this->output->_dispaly('');
sleep(2);
$this->load->view('content');
 
复制代码

为了说明问题,还在Output.php中的_dispaly()函数内做了log_message输出时间。

  1. INFO  - 2012-07-03 20:53:04 --> echo part 1
  2. [align=left]DEBUG - 2012-07-03 20:53:04 --> File loaded: application/views/skeleton.php[/align]INFO  - 2012-07-03 20:53:04 --> end echo part1
  3. INFO  - 2012-07-03 20:53:04 --> call _display
  4. INFO  - 2012-07-03 20:53:04 --> start output
  5. INFO  - 2012-07-03 20:53:04 -->part1 time:[color=#ff0000][color=Black]1341319984[/color][/color][color=Black] [/color][color=#ff0000][color=Black]//第一部分,time为time()[/color][/color]
  6. DEBUG - 2012-07-03 20:53:04 --> Final output sent to browser
  7. DEBUG - 2012-07-03 20:53:04 --> Total execution time: 0.0734
  8. INFO  - 2012-07-03 20:53:04 --> start sleep
  9. INFO  - 2012-07-03 20:53:06 --> end sleep
  10. INFO  - 2012-07-03 20:53:06 --> echo part 2
  11. DEBUG - 2012-07-03 20:53:06 --> File loaded: application/views/content.php
  12. INFO  - 2012-07-03 20:53:06 --> end echo part 2
  13. INFO  - 2012-07-03 20:53:06 --> call _display
  14. INFO  - 2012-07-03 20:53:06 --> start output
  15. INFO  - 2012-07-03 20:53:06 --> part2 time:[color=#ff0000][color=Black]1341319986[/color][/color][color=Black] [/color][u]//第二部分,time为time()[/u]
  16. DEBUG - 2012-07-03 20:53:06 --> Final output sent to browser
  17. DEBUG - 2012-07-03 20:53:06 --> Total execution time: 0.0734
复制代码

从log文件看,按正常思路浏览器应该先显示skeleton,然后显示content了,
可实际情况是,等了约2秒之后(因为part1和part2间sleep了2秒)。
两个模块的内容一起显示了,即显示速度阻塞在了用时最长的页面块。

我迷茫了,Codeigniter究竟在_display()之后对数据做了什么呢?难道又做了buffer?
翻了文档,没有找到如何实现这类情况的方案,而codeigniter对页面加载却是这样默认地实现的。
这算不算是CI的缺陷呢?
我该如何做呢?

//这个帖子是我一天前发的,通过各位的提示,及自己的尝试,今天一个很偶然的机会让我解决了这个问题
根据实际测试结果,我得出如下一个解决方案:(并非唯一)
1.对第一个flush的view,包含
HTML复制代码
 
<meta http-equiv="Content-Type" content="text/html"; charset=utf-8">
 
复制代码

2.后端controller一个可选的实现:
PHP复制代码
 
ob_start();
echo $this->load->view('skeleton',array(),TRUE);
ob_flush();
flush();
echo $this->load->view('content',array(),TRUE);
ob_flush();
flush();
 
复制代码

经过实际测试,是work的,我用CI版本是2.1.1

各位可以写这样两个php文件来测试:
test_one.php:
PHP复制代码
 
<meta http-equiv="Content-Type" content="text/html"; charset=utf-8">
<?php
echo 'part1';
ob_flush();
flush();
sleep(2);
echo 'part2';
ob_flush();
flush();
sleep(2);
echo 'part3';
?>
 
复制代码

test_two.php:
PHP复制代码
 
<?php
echo 'part1';
ob_flush();
flush();
sleep(2);
echo 'part2';
ob_flush();
flush();
sleep(2);
echo 'part3';
?>
 
复制代码

对如上两个文件,test_one在实测时,浏览器有flush的效果,而test_two在实测时,阻塞约4秒后才全部显示

另外补充一下,@太尉天上飞 的观点可以参考:

  1. 个别浏览器第一个缓冲区数据必须大于一定长度才会输出,一般开头我们会echo str_pad('', 1000);
  2. 例如:
  3. &lt;?php
  4. ob_end_clean();
  5. echo str_pad('',1024);
  6. echo 'a';
  7. flush();
  8. for($i = 0; $i&lt;10; $i++)
  9. {
  10.         echo $i.'&lt;br /&gt;';
  11.         flush();
  12.         sleep(1);
  13. }
复制代码


Fin.
发表于 2012-7-3 21:31:55 | 显示全部楼层
亲~~~很厉害。

我小白的认为,可能只有静态内容才能分块分时间输出。

动态内容,只能由上到下,同时输出吧。


比如:

上部分视图有个 $a=a;
下部分视图有个 echo $a;

假设上部分内容需要花很多时间输出,下部分比较快。

按lz思路,要先显示下么? 那不是违背php的程序逻辑了么,会出现没有 $a 的错误吧。
 楼主| 发表于 2012-7-3 21:58:24 | 显示全部楼层
cdm 发表于 2012-7-3 21:31
亲~~~很厉害。

我小白的认为,可能只有静态内容才能分块分时间输出。

谢谢你一个回答我呢!而且我这么晚问问题有点不好意思...

首先,其实在实际编写中,不同的页面块不太会写成有相互关联的情况。
两个不同的视图a与b,更有可能的情况是:a是好友列表模块;b是最近来访者模块
(也就是说两个视图一般不会有
view1:$a = 'a';
view2:echo $a
这样的两个view间有逻辑关联的情况


我觉得我应该说一下我想这么做的原因。
其实我的终极目标是想实现Bigpipe,之前其实发过一个帖子,但没说清楚所以换了一种问法。
Bigpipe说穿了其实就是后端和前端并行运作的一种实现,有点像流水线的意思。
一般一个页面的加载,是
1.服务器完全准备好所有页面
2.发给浏览器
可以看到服务器在工作的时候,浏览器是空闲的、没有事情做的。

如果能像cpu流水线一样,将页面分成许多低耦合的页面块。
先返回给浏览器能较快生成的页面骨架(包含解析后续到来的页面块的js文件),让浏览器先工作起来;
同时服务器再同时生成其他需要用较多时间的页面块,一旦准备好就返回个json对象让之前的js文件来解析。
那么,这样流水线般的加载会十分的快。
(当然,要保证骨架页面先返回,因为有分析后续到来的json对象的js文件,这可以通过server端串行的来加载从而达到这一目的)

要实现这种效果,我到现在还觉得应该是等骨架页面准备好后,立即flush让浏览器接收到;
然后在逐步加载其他页面块,再flush。
不用框架的方式我自己写过例子,可以逐步的显示内容。
可用codeigniter来实现时,却不知道如何是好。
我试了不少方法,但浏览器总是在最后时刻一起给我结果的。

评分

参与人数 1威望 +5 收起 理由
Hex + 5 很给力!

查看全部评分

发表于 2012-7-3 22:11:19 | 显示全部楼层
本帖最后由 cdm 于 2012-7-3 22:12 编辑

亲~~~   这个貌似不关框架的事,严格说起来也不太关php的事。

虽然没用过,但Bigpipe 原理大约是用js来加载php对吧?

同时显示的原因:可能是你在控制器里
load->view('a');
load->view('b');
load->view('c');同时加载几个视图了……


我的猜测,Bigpipe 应该是只加载一个视图a ,然后在这个视图中使用js技术加载另外的视图 b、c……(代码块)。

评分

参与人数 1威望 +5 收起 理由
方达 + 5 你的直觉是对的,这应该和浏览器设置有关.

查看全部评分

 楼主| 发表于 2012-7-3 22:12:36 | 显示全部楼层
cdm 发表于 2012-7-3 21:31
亲~~~很厉害。

我小白的认为,可能只有静态内容才能分块分时间输出。

回过头来说具体细节一点的问题,其实想问_dispaly()之后是不是codeigniter还会buffer一下呢?
Output.php中,_dispaly()里说好的"Final output sent to browser",但实际情况却好像不是这样的。

CI在启动时会不会对php本身的buffer机制作一些设置呢?还是CI有它自己的buffer机制,就算“Final output sent to browser”还要折腾一下呢?
发表于 2012-7-3 22:16:01 | 显示全部楼层
本帖最后由 cdm 于 2012-7-3 22:17 编辑
方达 发表于 2012-7-3 22:12
回过头来说具体细节一点的问题,其实想问_dispaly()之后是不是codeigniter还会buffer一下呢?
Output.php ...

  不了解核心的代码~~

但估计不可能最后还要 buffer 的,因为缓存不是有开关的么,听说默认是关着的。
 楼主| 发表于 2012-7-3 22:18:49 | 显示全部楼层
cdm 发表于 2012-7-3 22:11
亲~~~   这个貌似不关框架的事,严格说起来也不太关php的事。

虽然没用过,但Bigpipe 原理大约是用js来加 ...

对的!
我控制器里就是
load->view('a');
load->view('b');
load->view('c');
这样写的~~

另外对bigpipe的实现我比较确定,因为看了提出这个问题的人的第一篇文章,和其他较多的阐述这一问题的文章。
视图是由服务器php文件来主动返回的,并不是js去请求的
(因为如果js请求的话,会生成额外的浏览器到服务器的请求,这就不能避免像ajax这样虽然实现异步加载,但会生成不少额外请求的情况了)
发表于 2012-7-3 22:20:06 | 显示全部楼层
CI应该是组合好了所有的页面之后 合并成一个buffer 一起发送给浏览器 所有你看来其速度是最慢的那个块
具体的原因我没看核心代码 但是应该差不多了
 楼主| 发表于 2012-7-3 22:26:46 | 显示全部楼层
本帖最后由 方达 于 2012-7-3 22:28 编辑
暗夜星辰 发表于 2012-7-3 22:20
CI应该是组合好了所有的页面之后 合并成一个buffer 一起发送给浏览器 所有你看来其速度是最慢的那个块
具体 ...

谢谢你回复我!

如果真的是组合好所有页面,合并成一个buffer,再一起发送给浏览器。那么真的好伤心呢,因为会出现这样的情况:
比如我想用CI来做个人人网,当加载个人主页的时候,我把和广告有关的页面写成一个view;当加载整个个人主页的时候,别的功能都很顺利,唯独广告这个view生成出现了问题,岂不是因为广告这个view的问题导致了整个页面加载出现问题的情况了么~~

codeigniter不会这么不厚道吧~~(哎呦,刚才一下字看到你的头像突然隐隐地吓了一下...对不住= = )
发表于 2012-7-3 23:27:24 | 显示全部楼层
1、首先$this->load->view('skeleton'); 只是加载视图,结果被写入缓冲区而不直接输出。
2、即使想实现flush效果,可ob_start();echo $this->load->view('skeleton', array(), TRUE); ob_flush();
3、通过flush先显示头部内容,是否遇到html结构不完整问题???

本版积分规则