一、Gearman架构中的三个角色
client: 请求的发起者,工作任务的需求方(可以是C、PHP、Java、Perl、Mysql udf等等)
Job Server:请求的调度者,负责将client的请求转发给相应的worker(gearmand服务进程创建)
worker: 请求的处理者(可以是C、PHP、Java、Perl等等)
Fig-1
从上图可以看出,Gearman Client API,Gearman Worker API,Gearman Job Server都是由gearman本身提供,
我们在应用中只需要调用即可。目前client与worker api都很丰富。
二、Gearman的吞吐能力
经过的测试,结果如下:
系统环境:ubuntu-14.0.4 1个CPU 4核 2G内存 (虚拟机)
默认启动:./gearmand -d
$ cat client.php
echo "starting...", microtime(true), "\n";
$gmc = new GearmanClient();
$gmc->setCompleteCallBack(function($task){
//echo $task->data(), "\n";
});
$gmc->addServer("127.0.0.1", 4730);
for ($i = 0; $i < 100000; $i++) {
$gmc->addTaskBackground("reserve", "just test it", null, $i);
}
$gmc->runTasks();
echo "end...", microtime(true), "\n";
$ cat worker.php
$gmw = new GearmanWorker();
$gmw->addServer("127.0.0.1", 4730);
$gmw->addFunction("reserve", function($job) {
if ($job->unique() == 99999) {
echo microtime(true), "\n";
}
return strrev($job->workload());
});
while($gmw->work());
启动一个job server实例:job server IP:127.0.0.1 PORT:4730
启动一个worker: php worker.php
worker注册reserve函数,将client的job字符串反转后返回。
client工作任务的消息为:just test it(12字节)
同步:4100/s
异步:25700/s
memcached内存准持久化的吞吐能力测试
./gearmand -d -q libmemcached —libmemcached-servers=127.0.0.1:11211
client投递100000个工作任务:16400/s
三、Gearman典型的部署结构
Fig-2
四、Gearman支持的特性
. 高可用
启动两个job server,他们是独立的服务进程,有各自的内存队列。
当一个job server进程出现故障,另一个job server可以正常调度。
(worker api与client api可以完成job server故障的切换)。
在任何时候我们可以关闭某个worker,即使那个worker正在处理工作任务
(Gearman不会让正在被执行的job丢失的,
由于worker在工作时与Job server是长连接,所以一旦worker发生异常,
Job server能够迅速感知并重新派发这个异常worker刚才正在执行的工作)
. 负载均衡(附gearman协议会详细解释)
job server并不主动分派工作任务,而是由worker从空闲状态唤醒之后到job server主动抓取工作任务。
. 可扩展
松耦合的接口和无状态的job,只需要启动一个worker,注册到Job server集群即可。
新加入的worker不会对现有系统有任何的影响。
. 分布式
gearman是分布式的任务分发框架,worker与job server,client与job server通信基于tcp的socket连接。
. 队列机制
gearman内置内存队列,默认情况队列最大容量为300W,可以配置最大支持2^32-1,即4 294 967 295。
. 高性能
作为Gearman的核心,Job server的是用C/C++实现的,由于只是做简单的任务派发,
因此系统的瓶颈不会出在Job server上。
五、两种工作任务
1. 后台工作任务Background job——时序图
Fig-3
由图可知,client提交完job,job server成功接收后返回JOB_CREATED响应之后,client就断开与job server之间的链接了。
后续无论发生什么事情,client都是不关心的。
同样,job的执行结果client端也没办法通过Gearman消息框架获得。
2. 一般工作任务Non-background job——时序图
Fig-4
由图可知,client端在job执行的整个过程中,与job server端的链接都是保持着的,
这也给job完成后job server返回执行结果给client提供了通路。
同时,在job执行过程当中,client端还可以发起job status的查询。当然,这需要worker端的支持的。
五、关于持久化
对于队列持久化的问题,是一个值得考虑的问题。持久化必然影响高性能。
gearman支持后台工作任务的持久化,支持drizzle、mysql、memcached的持久化。
对于client提交的background job,Job server除了将其放在内存队列中进行派发之外,还会将其持久化到外部的持久化队列中。
一旦Job server发生问题重启,外部持久化队列中的background job将会被恢复到内存中,参与Job server新的派发当中。
这保证了已提交未执行的background job不会由于Job server发生异常而丢失。
并且我测试发现如果开启了持久化,那么后台工作任务会先将工作任务写到持久化介质,然后在入内存队列,再执行。
非后台工作任务,由于client与job server是保持长连接的状态,如果工作任务执行异常,client可以灵活处理,所以无须持久化。
六、Gearman框架中的一个问题
从典型部署结构看出,两个Job server之间是没有连接的。
也就是Job server间是不共享background job的。
如果通过让两个Job server指向同一个持久化队列,可以让两个Job serer互相备份。
但实际上,这样是行不通的。
因为Job server只有在启动时才会将持久化队列中的background job转入到内存队列。
也就是说,Job server1如果宕机且永远不启动,Job server2一直正常运行,
那么Job server1宕机前被提交到Job server1的未被执行的background job将永远都呆在持久化队列中,得不到执行。
另外如果多个job server实例指向同一个持久化队列,
同时重启多个job server实例会导致持久队列中的工作任务被多次载入,从而导致消息重复处理。
七、我建议的部署结构
Fig-5
采用memcached做后台工作任务的准持久化队列,最好memcached和job server在内网的不同机器。
两个机器的两个服务同时挂掉的可能性比较小,同时也保证了高性能。
而且memcached应该为两个相互独立实例,防止其上述的gearman框架中的问题。
我们可以做一个监控脚本,如果某个job server异常退出,可以重启,也最大化的保证了job server的高可用。
八、总结worker的工作流程
. Worker通过CAN_DO消息,注册到Job server上。
. 随后发起GRAB_JOB,主动要求分派任务。
. Job server如果没有job可分配,就返回NO_JOB。
. Worker收到NO_JOB后,进入空闲状态,并给Job server返回PRE_SLEEP消息,告诉Job server:”如果有工作来的话,用NOOP请求我先。”
. Job server收到worker的PRE_SLEEP消息后,明白了发送这条消息的worker已经进入了空闲态。
. 这时如果有job提交上来,Job server会给worker先发一个NOOP消息。
. Worker收到NOOP消息后,发送GRAB_JOB向Job server请求任务。
. Job server把工作派发给worker。
. Worker干活,完事后返回WORK_COMPLETE给Job server。
原文链接: