最近工作中开发的一套系统,其中很多都是需要操作多服务器的,比如需要同时发布数据到 2000 个服务器上,或者同时向 2000 个服务器拉取数据。刚开始的解决方案就是单纯用 PHP 的curl_multi的方式并发处理请求,而且参考了淘宝技术博客的《Rolling cURL: PHP 并发最佳实践》,但是由于网络和数据以及各个服务器等等的一些情况导致这种并发处理的响应时间很慢,因为在并发请求的过程中还包括记录日志,处理数据等逻辑,等待处理结果并返回,所以也不能友好的满足后台操作的体验。

现在重新设计一种方案,利Gearman来实现并发的需求。通过 Client 将请求发送到 Gearman 的 Jobs,在每个 Work 中来再来进行 curl_multi 和数据处理和日志等一些操作,同时用Supervisor来监控 Gearman 以及 Works 的进程,这样可以实现一个并行的多进程和负载均衡的方案。

Gearman 可以做什么

  • 异步处理:图片处理,订单处理,批量邮件/通知之类的
  • 要求高 CPU 或内存的处理:大容量的数据处理,MapReduce 运算,日志聚集,视频编码
  • 分布式和并行的处理
  • 定时处理:增量更新,数据复制
  • 限制速率的 FIFO 处理
  • 分布式的系统监控任务

Gearman 工作原理 使用 Gearman 的应用通常有三部分组成:一个 Client、一个 Worker、一个 任务服务器。 Client 的作用是提出一个 Job 任务 交给 Job Server 任务服务器。Job Server 会去寻找一个 合适的 Worker 来完成这项任务。Worker 执行由 Client 发送过来的 Job,并且将结果通过 Job Server 返回给 Client。Gearman 提供了 Client 和 Worker 的 API,利用这些 API 应用可以同 Gearman Job Server 来进行通信。Gearman 内部 Client 和 Worker 之间的通信都是通过 TCP 连接来进行的。

stac

Gearman 可以将工作的负载分担到不同的机器中。

cluste

安装配置 我只是记录下我安装配置的过程,我在 Ubuntu 和 CentOS 中都试了下。 CentOS YUM 安装

rpm -ivh http://dl.iuscommunity.org/pub/ius/stable/Redhat/6/x86_64/epel-release-6-5.noarch.rpm
yum install -y gearmand

Ubuntu apt 安装

apt-get install gearman

源码编译

yum install uuid-devel libuuid libuuid-devel uuid boost-devel libevent libevent-devel
wget -c https://launchpad.net/gearmand/1.2/1.1.7/+download/gearmand-1.1.7.tar.gz
tar zxvf gearmand-1.1.7.tar.gz
./configure --prefix=/usr/local/gearmand
make && make install

安装好以后启动

gearmand -d

加上-d 参数是表示后台运行,你可以 gearmand -h 来查看其它的选项,启动的时候带上其它配置参数

/usr/sbin/gearmand --pid-file=/var/run/gearman/gearmand.pid --user=gearman --daemon --log-file=/var/log/gearman-job-server/gearman.log --listen=127.0.0.1

安装 PHP Gearman 扩展 我都是用 pcel 来安装的,你也可以下载源码包来编译安装,但是记得要先安装libgearmanre2c,不然扩展编译安装会出错。

pecl install gearman #不成功并提示版本问题可以试试 pecl install gearman-1.0.3,默认好像是1.1.2

编译安装也很简单

wget -c http://pecl.php.net/get/gearman-1.1.1.tgz
tar zxvf gearman-1.1.1.tgz
phpize
./configure
make && make install
echo "extension=gearman.so" >> /etc/php.ini

PHP 接口函数 Gearman 提供很多完善的扩展函数,包括 GearmanClient,GearmanJob,GearmanTask,GearmanWorker,具体可以查看 PHP 官方手册. 这是官方提供的 Example 其中的一个,相当与一个并发的分发任务处理的例子 gearman_client.php

<?php

$client = new GearmanClient();
$client->addServer();

// initialize the results of our 3 "query results" here
$userInfo = $friends = $posts = null;

// This sets up what gearman will callback to as tasks are returned to us.
// The $context helps us know which function is being returned so we can
// handle it correctly.
$client->setCompleteCallback(function(GearmanTask $task, $context) use (&$userInfo, &$friends, &$posts) {
switch ($context)
{
case 'lookup_user':
$userInfo = $task->data();
break;
case 'baconate':
$friends = $task->data();
break;
case 'get_latest_posts_by':
$posts = $task->data();
break;
}
});

// Here we queue up multiple tasks to be execute in *as much* parallelism as gearmand can give us
$client->addTask('lookup_user', 'joe@joe.com', 'lookup_user');
$client->addTask('baconate', 'joe@joe.com', 'baconate');
$client->addTask('get_latest_posts_by', 'joe@joe.com', 'get_latest_posts_by');

echo "Fetching...\n";
$start = microtime(true);
$client->runTasks();
$totaltime = number_format(microtime(true) - $start, 2);

echo "Got user info in: $totaltime seconds:\n";
var_dump($userInfo, $friends, $posts);

gearman_work.php

<?php

$worker = new GearmanWorker();
$worker->addServer();

$worker->addFunction('lookup_user', function(GearmanJob $job) {
// normally you'd so some very safe type checking and query binding to a database here.
// ...and we're gonna fake that.
sleep(3);
return 'The user requested (' . $job->workload() . ') is 7 feet tall and awesome';
});

$worker->addFunction('baconate', function(GearmanJob $job) {
sleep(3);
return 'The user (' . $job->workload() . ') is 1 degree away from Kevin Bacon';
});

$worker->addFunction('get_latest_posts_by', function(GearmanJob $job) {
sleep(3);
return 'The user (' . $job->workload() . ') has no posts, sorry!';
});

while ($worker->work());

我在 3 个终端中都执行了 gearman_work.php

ryan@ryan-lamp:~$ ps aux | grep gearman* | grep -v grep
gearman 1504 0.0 0.1 60536 1264 ? Ssl 11:06 0:00 /usr/sbin/gearmand --pid-file=/var/run/gearman/gearmand.pid --user=gearman --daemon --log-file=/var/log/gearman-job-server/gearman.log --listen=127.0.0.1
ryan 2992 0.0 0.8 43340 9036 pts/0 S+ 14:05 0:00 php /var/www/gearmand_work.php
ryan 3713 0.0 0.8 43340 9036 pts/1 S+ 14:05 0:00 php /var/www/gearmand_work.php
ryan 3715 0.0 0.8 43340 9036 pts/2 S+ 14:05 0:00 php /var/www/gearmand_work.php

来查看下执行 gearman_work.php 的结果 shell

Fetching...
Got user info in: 3.03 seconds:
string(59) "The user requested (joe@joe.com) is 7 feet tall and awesome"
string(56) "The user (joe@joe.com) is 1 degree away from Kevin Bacon"
string(43) "The user (joe@joe.com) has no posts, sorry!"

看到上面的 3.03 seconds,说明 client 请求过去的任务被并行分发执行了。 在实际的生产环境中,为了监测 gearmand 和 work 的进程没有被意外退出,我们可以借助 Supervisor 这个工具,下次我再单独来写个 Supervisor 的笔记。

转载请注明: 转载自Ryan 是菜鸟 | LNMP 技术栈笔记

如果觉得本篇文章对您十分有益,何不 打赏一下

谢谢打赏

本文链接地址: PHP 利用 Gearman 来处理并行多进程问题

知识共享许可协议 本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可