一、Gearman的概念
Gearman提供了一种通用的应用框架,能够分发任务到不同的机器或者进程当中。 它允许进行并行工作、负载均衡,以及在不同程序语言之间进行函数调用。Gearman能够应用的领域非常广泛,从高可用的网站到数据库的备份。总之,Gearman就是负责分发处理的中枢系统。它的特点包括:
1、开源。
2、多语言。
3、灵活。
4、可嵌入。
5、无单点风险。
二、Gearman 的工作机制。
gearman由 Client、Worker、JobServer三部分组成。 client的作用是创建一个job并传递给jobserver。Jobserver会选择一个合适的worker节点并将job指派给它。Worker会处理来自client的job请求,并将结果通过jobserer返回给client。Gearman提供了client和worker的api,用来和jobserver通信。worker和client通过socket与jobserver通信。
三、gearmand的安装部署。
gearman jobserver 的下载安装步骤。
tar xzf gearmand-X.Y.tar.gz cd gearmand-X.Y ./configure make make install
启动jobserver守护进程。
gearmand -d
下面是一些更多的启动参数。
-b, --backlog=BACKLOG Number of backlog connections for listen. -d, --daemon Daemon, detach and run in the background. -f, --file-descriptors=FDS Number of file descriptors to allow for the process (total connections will be slightly less). Default is max allowed for user. -h, --help Print this help menu. -j, --job-retries=RETRIES Number of attempts to run the job before the job server removes it. Thisis helpful to ensure a bad job does not crash all available workers. Default is no limit. -l, --log-file=FILE Log file to write errors and information to. Turning this option on also forces the first verbose level to be enabled. -L, --listen=ADDRESS Address the server should listen on. Default is INADDR_ANY. -p, --port=PORT Port the server should listen on. -P, --pid-file=FILE File to write process ID out to. -r, --protocol=PROTOCOL Load protocol module. -q, --queue-type=QUEUE Persistent queue type to use. -t, --threads=THREADS Number of I/O threads to use. Default=0. -u, --user=USER Switch to given user after startup. -v, --verbose Increase verbosity level by one. -V, --version Display the version of gearmand and exit. libdrizzle Options: --libdrizzle-host=HOST Host of server. --libdrizzle-port=PORT Port of server. --libdrizzle-uds=UDS Unix domain socket for server. --libdrizzle-user=USER User name for authentication. --libdrizzle-password=PASSWORD Password for authentication. --libdrizzle-db=DB Database to use. --libdrizzle-table=TABLE Table to use. --libdrizzle-mysql Use MySQL protocol. libmemcached Options: --libmemcached-servers=SERVER_LIST List of Memcached servers to use. libsqlite3 Options: --libsqlite3-db=DB Database file to use. --libsqlite3-table=TABLE Table to use. libpq Options: --libpq-conninfo=STRING PostgreSQL connection information string. --libpq-table=TABLE Table to use. http Options: --http-port=PORT Port to listen on.
Gearmand的队列。
client创建job任务之后, 会传递给jobserver服务器, jobserver中会存在一个queue,用来存放job任务。每个worker会从queue中领取任务执行。
默认情况下,job queue会存在于内存中,也就是说, 一旦服务器重启,将会丢失。
持久化队列。
gearman提供了实现将队列持久化的方法,目前支持drizzle/mysql, sqllite, postgrel,memcache等数据库持久化模块。
开启方法:
gearmand -q libdrizzle --libdrizzle-host=10.0.0.1 --libdrizzle-user=gearman \ --libdrizzle-password=secret --libdrizzle-db=some_db \ --libdrizzle-table=gearman_queue --libdrizzle-mysql
四、Gearman 的Client和Worker API。
gearman支持多种api类型,例如 C、PHP、Python、Perl、Java、Lisp
gearman的Client 创建 Job 需要两个主要参数, funcname和data。其中funcame代表该Job注册的处理方法名,会调用worker中相同方法名的处理函数。 data表示job的具体内容,传递给worker使用。
gearman的Worker 创建处理函数也需要两个参数, funcname和function。其中function表示处理函数。
以php举例。
worker的代码如下:
<?php $worker= new GearmanWorker(); $worker->addServer(); $worker->addFunction("reverse", "my_reverse_function"); while ($worker->work()); function my_reverse_function($job) { return strrev($job->workload()); } ?>
worker注册了一个reverse函数,对字符串进行反转。
启动worker命令:
$ php worker.php
client的代码如下:
<?php $client= new GearmanClient(); $client->addServer(); print $client->do("reverse", "Hello World!"); ?>
client调用了reverse方法,并传递一个hello world! 字符串。
执行命令:
$ php client.php
!dlroW olleH
五、一个实际应用例子。
我们来看一个处理图片的集群。
大家都知道,处理图片常用的软件是ImageMagick,但是该软件运行时,对CPU消耗极大。所以单机的情况下,当并发数提高时, 负载就会显著增加。因此,需要搭建一个集群,将处理任务分派到多个worker上。
client和worker的API都采用python脚本。 图片处理软件使用imagemagick。
我们先来看一下client的代码, image_client.py
import gearman;
import tornado.web;
import tornado.ioloop;
import json;
import cPickle
class MainHandler(tornado.web.RequestHandler):
def get(self):
user = self.get_argument("user","");
self.write("You requested the main page. user=" + user);
def post(self):
files = self.request.files;
instrList = self.get_argument("instrList","");
print instrList;
sendjob(files, instrList);
gm_client = gearman.GearmanClient(['127.0.0.1:4730']);
def sendjob(files,instrList):
instrDic = {};
instrDic["files"] = files;
instrDic["instrList"] = instrList;
completed_job_request = gm_client.submit_job("dealImage", cPickle.dumps(instrDic));
print "state:",completed_job_request.state, ", result:", completed_job_request.result;
application = tornado.web.Application([
(r"/", MainHandler),
]);
application.listen(8888);
tornado.ioloop.IOLoop.instance().start();
接着,我们看一下worker代码,image_worker.py
#!/usr/bin/python
import gearman;
import json;
import os;
import datetime;
import random;
import cPickle
def convert_photo_func(source, dest, width, height):
command="convert " + source + " -resize " + width + "x" + height + " " + dest;
status = os.system(command);
return status;
def cut_photo_func(source, dest, coord_x, coord_y, width, height):
command = "convert " + source + " -crop " + width + "x" + height + "+" + coord_x + "+" + coord_y + " " + dest;
status = os.system(command);
return status;
def deal_img(gearman_worker, gearman_job):
data = gearman_job.data;
instrDic = cPickle.loads(data);
statuslist = [];
fileList = instrDic.get("files").get("files");
instrList = instrDic.get("instrList");
file = fileList[0];
filename = file["filename"];
filebody = file["body"];
file_content_type = file["content_type"];
image_ext = ".jpg";
if file_content_type == 'image/png':
image_ext = ".png";
millstime = datetime.datetime.now().microsecond;
randomint = random.randint(1,100000);
source_image_path = "/home/chen-wei/img/source/";
source_file = str(millstime) + str(randomint) + image_ext;
if os.path.exists:
os.mkdir(source_image+path);
fd = open(source_image_path + source_file, 'w');
fd.write(filebody);
fd.close();
for instr in instrList:
print instr;
operator = instr.get("operator");
source = instr.get("source");
dest = instr.get("dest");
width = instr.get("width");
height = instr.get("height");
coord_x = instr.get("coord_x", "");
coord_y = instr.get("coord_y", "");
print source,coord_x, coord_y;
status = -1;
if operator == 'convert':
status = convert_photo_func(source, dest, width, height);
print "status=",status;
elif operator == 'cut':
status = cut_photo_func(source, dest, coord_x, coord_y, width, height);
print "status=",status;
statuslist.append(status);
return str(statuslist);
gm_worker = gearman.GearmanWorker(['127.0.0.1:4730']);
gm_worker.register_task('dealImage', deal_img);
gm_worker.work();
启动worker守护进程
./image_worker.py
执行client调用
./image_client.py
处理结果:
执行完./image_client.py 之后,我们看到多了2张图片。