耗时任务的异步处理

服务器的访问压力比较大时,我们可以通过负载均衡来将负载分散到多台服务器上.但有些比较耗时的请求.比如:
1. 需要连接邮件服务器,发送一封超长的HTML邮件。
2. 需要对用户上传的图片进行裁剪,生成多份缩略图。
3. 需要将用户上传的文件分发到多台服务器上。

在我们的日常应用中经常遇到,用户需要等待一段时间,这个请求才能完成,在用户上传照片时,照片上传成功后,然后是裁剪,最后生成缩略图,在这么一个过程中,用户只能等待,所以对用户的体验来讲是相当不好的。可能在用户第一次使用过后,下次就很难再让他使用这个图片的上传功能了。

那么有没有一种方法将这些处理过程放在后台慢慢运行呢?答案是肯定的,通过分布式处理可以将这些比较耗时的任务放在后台,甚至分散到多台服务器上去处理,对于这个问题异步处理就派上了用场。

现在有很多开源的软件能实现异步通信,比如ActiveMQ、Hadoop、Gearman和MecacheMQ等,它们巧妙的将计算转移到其他服务器上,而这一切都是隐藏在API中,同时,这些机制都是跨语言的,可以用PHP来分配一个任务,然后将后台的C/C++程序来进行处理,这一切都不是什么问题。

下面通过php和Gearman来演示异步的处理过程.

一、Gearman的安装

tar zxvf gearmand-0.11.tar.gz
cd  gearmand-0.11
./configure --prefix=/usr/local/gearman
make
make install

二、安装Gearman PHP extension.

 tar zxf gearman-0.6.0.tgz
   cd gearman-0.6.0
  phpize
  ./configure
  make

将module目录上的gearman.so拷贝到php的module目录下,我机器的目录在/usr/lib/php5/20060613+lfs/.

cp module/gearman.so /usr/lib/php5/20060613+lfs/.

然后在php.ini中添加
extension = “gearman.so”
最后重启apache服务器.

三、启动Gearman服务

cd /usr/local/gearman/sbin
./gearman -d -u root

Job的默认端口为4730,可以通过

[root@serv_1 sbin]# netstat -nl | grep 4730
tcp        0      0 :::4730                     :::*                        LISTEN

是否启动.

通过bin/gearman这个工具,我们可以来体验Gearman的功能.
启动Worker:

./gearman -w -f wc -- wc -l &
[1] 2547

运行 Client:

./gearman -f wc < /etc/passwd
38

上面的例子中,Worker中定义了一个函数 wc,主要功能用来统计文本的行数,而client将/etc/passwd这个文件的内容传给Worker来处理,最后得出passwd有38行文本.

四、使用php来编写Worker和Client.

这里任务还是用官方的一个例子来说明,将一个字符串反转后输出,为了模拟这个过程耗时,我们将程序sleep1秒种,并将处理函数连续调用100次.
先来看看一般情况下我们的程序处理方式.
1、不使用异步处理的方式.

for ($i = 0; $i < 100; $i ++) {
	my_reverse_function("helloword!")
}
 
function my_reverse_function($str) {
    sleep(1);
    echo strrev($str);
    echo "\n";
}

可以看出这个处理过程是相当耗时的,整个程序处理完成基本需要100秒.

2、使用基于Gearman的php异步处理方式

编写Worker.php

$worker = new GearmanWorker();
$worker->addServer();
//$worker->addServer();
$worker->addFunction("reverse","my_reverse_function");
 
while ($worker->work());
 
function my_reverse_function($job) {
    sleep(1);
    echo strrev($job->workload());
    echo "\n";
}

启动Worker.php.

php Worker.php

这时Worker.php作为一个后台服务,处于一个监听状态,只要有任务过来,马上进行处理.

编写Client.php

$client = new GearmanClient();
$client->addServer();
echo "beginning...\n";
for ($i = 0; $i < 100; $i++) {
    $client->doBackground('reverse',($i+1).'_Hello World!')."\n";
}
echo "finishing...\n";

这里的Client.php就是任务的发起者,这里将连续处理100次字符串反转任务.

运行Client.php

$ php client.php 
beginning...
finishing...

这时Client.php将任务交给了Worker.php来处理,但它本身很快就运行完成,这里再回过来来看Worker.php的运行状态,如下.

$ php Worker.php 
!dlroW olleH_1
!dlroW olleH_2
!dlroW olleH_3
!dlroW olleH_4
!dlroW olleH_5
!dlroW olleH_6
!dlroW olleH_7
!dlroW olleH_8
!dlroW olleH_9
!dlroW olleH_01
!dlroW olleH_11
!dlroW olleH_21
!dlroW olleH_31
!dlroW olleH_41
!dlroW olleH_51
!dlroW olleH_61
....

发现在Client.php运行完成后,Worker.php则是每秒输出一条处理结果,直到所有的任务全部处理完成.

以上主要是说明异步处理是怎么回事,在目前的开发中,怎么将耗时过长的任务进行异步来处理。但在日常的开发中,可能有些任务耗时确实很长,但处理后的结果又需要很快的反馈给用户时,这里我们就需要将处理后的结果即时的保存到数据库中,然后靠专门的程序将结果反馈给用户。可能还会有一个问题,这样异步处理,万一处理失败了怎么办,同样的道理,我们可以将每个任务的处理状态保存起来,然后用程序定时的去扫描,将处理失败的任务再重启执行一次,反复下去,直接它成功完成为止。

参考资料.
http://baike.baidu.com/view/2926980.htm?fr=ala0_1_1
http://cn.php.net/gearman
http://gearman.org

机器人 2010年01月11日 16:00 于 北京 晴

此条目发表在 系统架构 分类目录,贴了 , , 标签。将固定链接加入收藏夹。

耗时任务的异步处理》有 10 条评论

  1. 迷奇 说:

    不是很懂啊。。。

  2. I really enjoyed your writing. The topic is one I have been trying to get info on.

  3. Pingback 引用通告: controllers pid

  4. 增致牛仔 说:

    看不懂的说。。。

  5. 我是打酱油的,,,

  6. 姜吃合向 说:

    杭州东方魅力官方博客http://donfangmeili.qzone.qq.com

    欢迎大家积极光顾

    杭州东方魅力官方网址:www.dongfangmeili.cn

  7. Pingback 引用通告: 韩国食品网上商城

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>