一篇文章:php
Gearman介紹、調研、測試與原理分析html
gearman是什麼?mysql
它是分佈式的程序調用框架,可完成跨語言的相互調用,適合在後臺運行工做任務。最初是2005年perl版本,2008年發佈C/C++版本。目前大部分源碼都是(Gearmand服務job Server)C++,各個API實現有各類語言的版本。PHP的Client API與Worker API實現爲C擴展,在PHP官方網站有此擴展的中英文文檔。linux
gearman架構中的三個角色git
client:請求的發起者,工做任務的需求方(能夠是C、PHP、Java、Perl、Mysql udf等等)github
Job Server:請求的調度者,負責將client的請求轉發給相應的worker(gearmand服務進程建立)sql
worker:請求的處理者(能夠是C、PHP、Java、Perl等等)ubuntu
gearman是如何工做的?api
從上圖能夠看出,Gearman Client API,Gearman Worker API,Gearman Job Server都是由gearman自己提供,咱們在應用中只須要調用便可。目前client與worker api都很豐富。服務器
gearman的吞吐能力
通過的測試,結果以下:
系統環境:ubuntu-14.0.4 1個CPU 4核 2G內存 (虛擬機)
默認啓動:./gearmand -d
client.php
<?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";
worker.php
<?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典型的部署結構
從上圖能夠看出,gearman支持的特性:
1.高可用
啓動兩個job server,他們是獨立的服務進程,有各自的內存隊列。當一個job server進程出現故障,另外一個job server能夠正常調度。(worker api與client api能夠完成job server故障的切換)。在任什麼時候候咱們能夠關閉某個worker,即便那個worker正在處理工做任務(Gearman不會讓正在被執行的job丟失的,因爲worker在工做時與Job server是長鏈接,因此一旦worker發生異常,Job server可以迅速感知並從新派發這個異常worker剛纔正在執行的工做)
2.負載均衡(附gearman協議會詳細解釋)
job server並不主動分派工做任務,而是由worker從空閒狀態喚醒以後到job server主動抓取工做任務。
3.可擴展
鬆耦合的接口和無狀態的job,只須要啓動一個worker,註冊到Job server集羣便可。新加入的worker不會對現有系統有任何的影響。
4.分佈式
gearman是分佈式的任務分發框架,worker與job server,client與job server通訊基於tcp的socket鏈接。
5.隊列機制
gearman內置內存隊列,默認狀況隊列最大容量爲300W,能夠配置最大支持2^32-1,即4 294 967 295。
6.高性能
做爲Gearman的核心,Job server的是用C/C++實現的,因爲只是作簡單的任務派發,所以系統的瓶頸不會出在Job server上。
兩種工做任務
後臺工做任務Background job——時序圖
由圖可知,client提交完job,job server成功接收後返回JOB_CREATED響應以後,client就斷開與job server之間的連接了。後續不管發生什麼事情,client都是不關心的。一樣,job的執行結果client端也沒辦法經過Gearman消息框架 得到。
通常工做任務Non-background job——時序圖
由圖可知,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實例會致使持久隊列中的工做任務被屢次載入,從而致使消息重複處理。
我建議的部署結構
採用memcached作後臺工做任務的準持久化隊列,最好memcached和job server在內網的不一樣機器。兩個機器的兩個服務同時掛掉的可能性比較小,同時也保證了高性能。並且memcached應該爲兩個相互獨立實例,防止其上述的gearman框架中的問題。咱們能夠作一個監控腳本,若是某個job server異常退出,能夠重啓,也最大化的保證了job server的高可用。
關於gearman的管理工具
目前有一個如今的管理工具,https://github.com/brianlmoon/GearmanManager,可是隻支持php-5.2,不過能夠自行修改支持php-5.4,我建議若是使用PHP做爲worker進程,使用php-5.4以上的版本。該工具的設計方法能夠借鑑,能夠比較好的管理gearman worker。時間有限,尚未深刻研究。
應用場景
1.結合linux crontab,php腳本負責產生job,將任務分發到多臺服務器週期性的併發執行。能夠取代目前咱們比較多的crontab的工做任務。
2.郵件短信發送
3.異步log
4.跨語言相互調用(對於密集型計算的需求,能夠用C實現,PHP直接調用)
5.其餘耗時腳本
gearman安裝(unbuntu)
1.下載
#wget https://launchpadlibrarian.net/165674261/gearmand-1.1.12.tar.gz
2.安裝依賴包
#sudo apt-get install libboost1.55-all-dev gperf libevent libevent-dev uuid libmemcached-dev
#tar zxvf gearmand-1.1.12.tar.gz
#cd gearmand-1.1.12.tar.gz
#./configure --prefix=/home/phpboy/Server/gearman
#make & make install
3.啓動
a)默認啓動
./gearman -d
b)支持memcached準持久化
./gearmand -d -q libmemcached --libmemcached-servers=127.0.0.1:11211
4.安裝php的gearman擴展
#wget http://pecl.php.net/get/gearman-1.1.2.tgz
#tar zxvf gearman-1.1.2.tgz#cd gearman-1.1.2
#phpize
#./configure --with-php-config=php-config
#make & make install
5.php client api與php worker api的測試,能夠用上面個人測試的示例
附gearmand(job server的啓動參數簡單說明)
-b, –backlog=BACKLOG 鏈接請求隊列的最大值
-d, –daemon Daemon 守護進程化
-f, –file-descriptors=FDS 可打開的文件描述符數量
-h, –help
-l, –log-file=FILE Log 日誌文件
-L, –listen=ADDRESS 開啓監聽的地址
-p, –port=PORT 開啓監聽的端口
-P, –pid-file=FILE File pid file
-r,–protocol=PROTOCOL 使用的協議
-q, –queue-type=QUEUE 持久化隊列類型
-t, –threads=THREADS I/O線程數量
-u, –user=USER 進程的有效用戶名
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.
附gearman通訊協議,我的翻譯與理解:
總括
Gearman工做在TCP上,默認端口爲4730,client與job server、worker與job server的通訊都基於此tcp的socket鏈接。client是工做任務的發起者,worker是能夠註冊處理函數的工做任務執行者,job server爲工做的調度者。協議包含請求報文與響應報文兩個部分,全部發向job server的數據包(TCP報文段的數據部分)認爲是請求報文,全部從job server發出的數據包(TCP報文段的數據部分)認爲是響應報文。worker或者client與job server間的通訊是基於二進制數據流的,但在管理client也有基於行文本協議的通訊。
請求的報文體
響應的報文體
、
二進制包
請求報文與響應報文是由二進制包封裝。一個二進制包由頭header和可選的數據部分data組成。
header的組成
a.報文類別,請求報文或者響應報文,4個字節
"\0REQ" 請求報文
"\0RES" 響應報文
b.包類型,高(大)字節序(網絡字節序),4個字節可能的類型有
類型值 名稱 報文類型 發送者
1 CAN_DO REQ Worker
2 CANT_DO REQ Worker
3 RESET_ABILITIES REQ Worker
4 PRE_SLEEP REQ Worker
5 (unused) - -
6 NOOP RES Worker
7 SUBMIT_JOB REQ Client
8 JOB_CREATED RES Client
9 GRAB_JOB REQ Worker
10 NO_JOB RES Worker
11 JOB_ASSIGN RES Worker
12 WORK_STATUS REQ Worker
13 WORK_COMPLETE REQ Worker
14 WORK_FAIL REQ Worker
15 GET_STATUS REQ Client
16 ECHO_REQ REQ Client/Worker
17 ECHO_RES RES Client/Worker
18 SUBMIT_JOB_BG REQ Client
19 ERROR RES Client/Worker
20 STATUS_RES RES Client
21 SUBMIT_JOB_HIGH REQ Client
22 SET_CLIENT_ID REQ Worker
23 CAN_DO_TIMEOUT REQ Worker
24 ALL_YOURS REQ Worker
25 WORK_EXCEPTION REQ Worker
26 OPTION_REQ REQ Client/Worker
27 OPTION_RES RES Client/Worker
28 WORK_DATA REQ Worker
29 WORK_WARNING REQ Worker
30 GRAB_JOB_UNIQ REQ Worker
31 JOB_ASSIGN_UNIQ RES Worker
32 SUBMIT_JOB_HIGH_BG REQ Client
33 SUBMIT_JOB_LOW REQ Client
34 SUBMIT_JOB_LOW_BG REQ Client
35 SUBMIT_JOB_SCHED REQ Client
36 SUBMIT_JOB_EPOCH REQ Client
c.可選數據部分長度,高(大)字節序(網絡字節序),4個字節,可表示的值爲4294967295
數據部分,數據部分的各個部分爲null字符分隔。
具體各包類型的說明
client和worker均可以發送的請求報文:
ECHO_REQ
當job server收到此包類型的請求報文時,就簡單的產生一個包類型爲ECHO_RES,同時將請求報文的數據部分做爲響應報文的數據部分的報文。主要用於測試或者調試
如:
Client -> Job Server
00 52 45 51 \0REQ 報文類型
00 00 00 a0 16 (Packet type: ECHO_ERQ)
00 00 00 04 4 (Packet length)
74 65 73 74 test (Workload)
client和worker均可以接收的響應報文:
ECHO_RES
當job server響應ECHO_REQ報文時發送的包類型爲ECHO_RES的響應報文
如:
Job Server -> Client
00 52 45 53 \0RES 報文類型
00 00 00 a1 17 (Packet type: ECHO_ERS)
00 00 00 04 4 (Packet length)
74 65 73 74 test (Workload)
ERROR
當job server發生錯誤時,須要通知client或者worker
client發送的請求報文:(僅能由client發送的請求報文)
SUBMIT_JOB, SUBMIT_JOB_BG,SUBMIT_JOB_HIGH, SUBMIT_JOB_HIGH_BG,SUBMIT_JOB_LOW, SUBMIT_JOB_LOW_BG
當client有一個工做任務須要運行,就會提交相應的請求報文,job server響應包類型爲JOB_CREATED數據部分爲job handle的響應報文。SUBMIT_JOB爲普通的工做任務,client獲得狀態更新及通知任務已經完成的響應;SUBMIT_JOB_BG爲異步的工做任務,client不關心任務的完成狀況;SUBMIT_JOB_HIGH爲高優先級的工做任務,SUBMIT_JOB_HIGH_BG爲高優先級的異步任務;SUBMIT_JOB_LOW爲低優先級的工做任務,SUBMIT_JOB_LOW_BG爲低優先級的異步任務。
如:
Client -> Job Server
00 52 45 51 \0REQ (報文類型)
00 00 00 07 7 (Packet type: SUBMIT_JOB)
00 00 00 0d 13 (Packet length)
72 65 76 65 72 73 65 00 reverse\0 (Function)
00 \0 (Unique ID)
74 65 73 74 test (Workload)
SUBMIT_JOB_SCHED
和SUBMIT_JOB_BG相似,此類型的工做任務不會當即執行,而在設置的某個時間運行。
如:
Client -> Job Server
00 52 45 51 \0REQ (報文類型)
00 00 00 23 35 (Packet type: SUBMIT_JOB_SCHED)
00 00 00 0d 13 (Packet length)
72 65 76 65 72 73 65 00 reverse\0 (Function)
00 \0 (Unique ID)
01 \0 (minute 0-59)
01 \0 (hour 0-23)
01 \0 (day of month 1-31)
01 \0 (day of month 1-12)
01 \0 (day of week 0-6)
74 65 73 74 test (Workload)
SUBMIT_JOB_EPOCH
和SUBMIT_JOB_SCHED做用同樣,只是將設置的時間定爲了uinx時間戳GET_STATUS獲取某個工做任務執行的狀態信息
OPTION_REQ
設置client與job server鏈接的選項
client獲取的響應報文:
JOB_CREATED響應包類型爲SUBMIT_JOB*的請求報文,數據部分爲job handle
WORK_DATA, WORK_WARNING, WORK_STATUS, WORK_COMPLETE,WORK_FAIL, WORK_EXCEPTION
對於後臺運行的工做任務,任務執行信息能夠經過包類型爲上面的值來查看。
STATUS_RES
響應包類型爲GET_STATUS的請求報文,一般用來查看一個後臺工做任務是否已經完成,以及完成的百分比。
OPTION_RES
響應包類型爲OPTION_REQ的請求報文
worker發送的請求報文:
CAN_DO
通知job server能夠執行給定的function name的任務,此worker將會放到一個鏈表,當job server收到一個function name的工做任務時,worker爲被喚醒。
CAN_DO_TIMEOUT
和CAN_DO相似,只是針對給定的function_name的任務設置了一個超時時間。
CANT_DO
worker通知job server已經不能執行給定的function name的任務
RESET_ABILITIES
worker通知job server不能執行任何function name的任務
PRE_SLEEP
worker通知job server它將進入sleep階段,而以後此worker會被包類型爲NOOP的響應報文喚醒。
GRAB_JOB
worker向job server抓取工做任務,job server將會響應NO_JOB或者JOB_ASSIG
NGRAB_JOB_UNIQ
和GRAB_JOB相似,可是job server在有工做任務時將會響應JOB_ASSIGN_UNIQ
WORK_DATA
worker請求報文的數據部分更新client
WORK_WARNING
worker請求報文表明一個warning,它應該被對待爲一個WARNING
WORK_STATU
Sworker更新某個job handle的工做狀態,job server應該儲存這些信息,以便響應以後client的GET_STATUS請求
WORK_COMPLETE
通知job server及全部鏈接的client,數據部分爲返回給client的數據
WORK_FAIL
通知job server及全部鏈接的client,工做任務執行失敗
WORK_EXCEPTION
通知job server及全部鏈接的client,工做任務執行失敗並給出相應的異常
SET_CLIENT_ID
設置worker ID,從而job server的控制檯及報告命令能夠標識各個worker,數據部分爲worker實例的標識
ALL_YOURS
暫未實現
worker獲取的響應報文:
NOOP
job server喚醒sleep的worker,以即可以開始抓取工做任務
NO_JOB
job server響應GRAB_JOB的請求,通知worker沒有等待執行的工做任務
JOB_ASSIGN
job server響應GRAB_JOB的請求,通知worker有須要執行的工做任務
JOB_ASSIGN_UNIQ
job server響應GRAB_JOB_UNIQ的請求,和JOB_ASSIGN同樣,只是爲client傳遞了一個惟一標識
基於上述的協議描述一個完整的例子
worker註冊能夠執行的工做任務
Worker -> Job Server
00 52 45 51 \0REQ (Magic)
00 00 00 01 1 (Packet type: CAN_DO)
00 00 00 07 7 (Packet length)
72 65 76 65 72 73 65 reverse (Function)
worker檢測或者抓取工做任務
Worker -> Job Server
00 52 45 51 \0REQ (Magic)
00 00 00 09 9 (Packet type: GRAB_JOB)
00 00 00 00 0 (Packet length)
job server響應worker的抓取工做(沒有工做任務)
00 52 45 53 \0RES (Magic)
00 00 00 0a 10 (Packet type: NO_JOB)
00 00 00 00 0 (Packet length)
worker通知job server開始sleep
00 52 45 51 \0REQ (Magic)
00 00 00 04 4 (Packet type: PRE_SLEEP)
00 00 00 00 0 (Packet length)
client提交工做任務
Client -> Job Server
00 52 45 51 \0REQ (Magic)
00 00 00 07 7 (Packet type: SUBMIT_JOB)
00 00 00 0d 13 (Packet length)
72 65 76 65 72 73 65 00 reverse\0 (Function)
00 \0 (Unique ID)
74 65 73 74 test (Workload)
job server響應client的SUBMIT_JOB請求,返回job handle
00 52 45 53 \0RES (Magic)
00 00 00 08 8 (Packet type: JOB_CREATED)
00 00 00 07 7 (Packet length)
48 3a 6c 61 70 3a 31 H:lap:1 (Job handle)
job server喚醒worker
Job Server -> Worker
00 52 45 53 \0RES (Magic)
00 00 00 06 6 (Packet type: NOOP)
00 00 00 00 0 (Packet length)
worker的抓取工做任務
Worker -> Job Server
00 52 45 51 \0REQ (Magic)
00 00 00 09 9 (Packet type: GRAB_JOB)
00 00 00 00 0 (Packet length)
job server分配工做任務給worker
Job Server -> Worker
00 52 45 53 \0RES (Magic)
00 00 00 0b 11 (Packet type: JOB_ASSIGN)
00 00 00 14 20 (Packet length)
48 3a 6c 61 70 3a 31 00 H:lap:1\0 (Job handle)
72 65 76 65 72 73 65 00 reverse\0 (Function)
74 65 73 74 test (Workload)
worker完成工做任務通知job server
00 52 45 51 \0REQ (Magic)
00 00 00 0d 13 (Packet type: WORK_COMPLETE)
00 00 00 0c 12 (Packet length)
48 3a 6c 61 70 3a 31 00 H:lap:1\0 (Job handle)
74 73 65 74 tset (Response)
job server通知client完成了工做任務
Job Server -> Client
00 52 45 53 \0RES (Magic)
00 00 00 0d 13 (Packet type: WORK_COMPLETE)
00 00 00 0c 12 (Packet length)
48 3a 6c 61 70 3a 31 00 H:lap:1\0 (Job handle)
74 73 65 74 tset (Response)
每一個client與job server是全雙工通訊,在一個socket能夠完成多個工做任務的投遞,可是收到任務的執行結果的順序可能與投遞的順序不一致。
總結worker的工做流程:
1. Worker經過CAN_DO消息,註冊到Job server上。
2. 隨後發起GRAB_JOB,主動要求分派任務。
3. Job server若是沒有job可分配,就返回NO_JOB。
4. Worker收到NO_JOB後,進入空閒狀態,並給Job server返回PRE_SLEEP消息,告訴Job server:」若是有工做來的話,用NOOP請求我先。」
5. Job server收到worker的PRE_SLEEP消息後,明白了發送這條消息的worker已經進入了空閒態。
6. 這時若是有job提交上來,Job server會給worker先發一個NOOP消息。
7. Worker收到NOOP消息後,發送GRAB_JOB向Job server請求任務。
8. Job server把工做派發給worker。
9. Worker幹活,完過後返回WORK_COMPLETE給Job server。