Beanstalkd
是一個簡單、高效的工作隊列系統,其最初設計目的是通過後臺異步執行耗時任務方式降低高容量Web應用的頁面延時。而其簡單、輕量、易用等特點,和對任務優先級、延時 超時重發等控制,以及衆多語言版本的客戶端的良好支持,使其可以很好的在各種需要隊列系統的場景中應用。
1. Beanstalkd
介紹
1.1 核心概念
Beanstalkd
使用Producer-Consumer
設計模式,無論是其協議結構還是使用方式都是類Memcached
風格的。以下是Beanstalkd
設計思想中核心概念:
job
- 任務
job
是一個需要異步處理的任務,是Beanstalkd
中的基本單元,job
需要放在一個tube
中。
Beanstalkd
中的任務(job
)類似於其它隊列系統中的消息(message
)的概念,詳細參考任務生命週期
tube
- 管道
管道即某一種類型的任務隊列,其類似與消息的主題(topic
),是Producer
和Consumer
的操作對象。
一個Beanstalkd
中可以有多個管道, 每個管道都有自己的發佈者(Producer
)和消費者Consumer
,管道之間互相不影響。
producer
- 生產者
任務(job
)的生產者,通過put
命令來將一個job
放到一個tube
中。
consumer
- 消費者
任務(job
)的消費者,通過reserve
、release
、bury
、delete
命令來獲取或改變job
的狀態。
1.2 任務生命週期
Beanstalkd
中的任務(job
)替代了消息(message
)的概念,任務會有一系列的狀態。任務的生命週期如下:
一個Beanstalkd
任務可能會包含以下狀態:
READY
- 需要立即處理的任務。當producer
直接put
一個任務時,任務就處於READY
狀態,以等待consumer
來處理。當延時 (DELAYED
) 任務到期後會自動成爲當前READY
狀態的任務DELAYED
- 延遲執行的任務。當任務被延時put
時,任務就處於DELAYED
狀態。等待時間過後,任務會被遷移到READY
狀態。當消費者處理任務後,可以用將消息再次放回DELAYED
隊列延遲執行RESERVED
- 已經被消費者獲取,正在執行的任務。當consumer
獲取了當前READY
的任務後,該任務的狀態就會遷移到RESERVED
狀態,這時其它的consumer
就不能再操作該任務。Beanstalkd
會檢查任務是否在TTR
(time-to-run)內完成BURIED
- 保留的任務,這時任務不會被執行,也不會消失。當consumer
完成該任務後,可以選擇delete
、release
或者bury
操作。delete
後,任務會被刪除,生命週期結束;release
操作可以重新把任務狀態遷移回READY
狀態或DELAYED
狀態,使其他consumer
可以繼續獲取和執行該任務bury
會拔任務休眠,等需要該任務時,再將休眠的任務kick
回READY
;也可能過delete
刪除BURIED
狀態的任務DELETED
- 消息被刪除,Beanstalkd
不再維持這些消息。即任務生命週期結束。
任務優先級(priority
)
任務 (job
) 可以有0~2^32
個優先級,0
表示優先級最高。Beanstalkd
採用最大最小堆 (Min-max heap) 處理任務優先級排序, 任何時刻調用 reserve 命令的消費者總是能拿到當前優先級最高的任務, 時間複雜度爲 O(logn)
任務延時(delay
)
Beanstalkd
中可以通過兩種方式延時執行任務: 生產者發佈任務時指定延時;或者當任務處理完畢後, 消費者再次將任務放入隊列延時執行 (RELEASE with delay)。這種機制可以實現分佈式定時任務,這種任務機制的優勢是:如果某個消費者節點故障,任務超時重發(time-to-run
)以保證任務轉移到其它節點執行。
任務超時重發(time-to-run
)
Beanstalkd
把任務返回給消費者後:消費者必須在預設的TTR
(time-to-run) 時間內發送delete
、release
或者bury
命令改變任務狀態;否則Beanstalkd
會認爲消息處理失敗,然後把任務交給另外的消費者節點執行。如果消費者預計在TTR
時間內無法完成任務, 可以發送touch
命令,以使Beanstalkd
重新計算TTR
任務預留(buried
)
當RESERVED
狀態的任務因爲某些原因無法執行時,消費者可以將其設置爲buried
狀態,這時Beanstalkd
會繼續保留這些任務。在具備任務執行條件時,再通過kick
將任務遷移回READY
狀態。
2. Beanstalkd
安裝使用
Beanstalkd
分爲服務端
和客戶端
兩部分。可以在其官網查找相關安裝包及安裝方法:
- 服務端:http://kr.github.io/beanstalkd/download.html
- 客戶端:https://github.com/kr/beanstalkd/wiki/client-libraries
2.1 服務端
要使用Beanstalkd
,首先需要在一或多臺機器安裝並運行beanstalkd
服務端。安裝beanstalkd
需要Linux (2.6.17 or later) 、Mac OS X、或 FreeBSD,可以通過源碼編譯或安裝包來安裝。
源碼安裝
下載、解壓並進入源碼目錄後,執行make
或make install
命令即可:
$ sudo make // 或 $ sudo make install // 或 $ sudo make install PERFIX=/usr/bin/beanstalkd
安裝包安裝
在Unbuntu或Debian系統中,可以使用以下命令安裝:
$ sudo apt-get install beanstalkd
在CentOS或RHEL系統中,首先需要更新EPEL
源,然後再使用yum
命令安裝。
在RHEL6
中使用以下命令更新源:
su -c 'rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm'
在RHEL7
中:
su -c 'rpm -Uvh http://download.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm'
執行安裝:
$ sudo yum install beanstalkd
運行beanstalkd
前臺運行:
$ beanstalkd
這時,beanstalkd
會保持在前臺,可以通過control+C
命令結束進程。
要使beanstalkd
後臺運行,可以在命令結尾增加&
:
$ beanstalkd &
啓動beanstalkd
時還可以增加一些啓動選項:
$ beanstalkd -l 127.0.0.1 -p 11300 &
在以上命令中,我們通過-l
指定了監聽地址、-p
參數指定了監聽端口、&
指定爲後臺運行。
beanstalkd
運行參數
Beanstalkd
安裝後,就可以通過beanstalkd
命令來啓動或配置Beanstalkd
。該命令的使用格式如下:
beanstalkd [OPTIONS]
可選[OPTIONS]
參數有:
-b DIR
- wal目錄-f MS
- 指定MS毫秒內的 fsync (-f0 爲"always fsync")-F
- 從不 fsync (默認)-l ADDR
- 指定監聽地址(默認爲:0.0.0.0)-p PORT
- 指定監聽端口(默認爲:11300)-u USER
- 用戶與用戶組-z BYTE
- 最大的任務大小(默認爲:65535)-s BYTE
- 每個wal文件的大小(默認爲:10485760)-c
- 壓縮binlog(默認)-n
- 不壓縮binlog
2.2 客戶端
客戶端包含了Beanstalkd
設計概念中的任務生產者(Producer
)和消費者(Consumer
)。Beanstalkd
有很多語言版本客戶端的實現,點擊Beanstalkd 客戶端查找自大所需要的版本,如果都不能滿足需要,還可以根據Beanstalkd 協議自行實現。
筆者日常工作中,接觸Node.js語言較多,以下用一個Node.js版本的Beanstalkd 客戶端:fivebeans
爲例,簡單演示Beanstalkd
的任務處理流程。
安裝fivebeans
模塊後,創建一個consumer
客戶端。代碼如下:
var fivebeans = require('fivebeans'); var consumer = new fivebeans.client('192.168.3.218', 11300); // 連接服務端 consumer.connect(); // 臨聽名爲 itbilu 的tube consumer.watch('itbilu', function(err, numwatched) {}) // 收到任務後,處理任務 consumer.reserve(function(err, jobid, payload) { console.log('收到任務:%s,任務內容:%s', jobid, payload); });
再創建一個producer
客戶端。代碼如下:
var fivebeans = require('fivebeans'); var producer = new fivebeans.client('192.168.3.218', 11300); // 連接服務端 producer.connect(); // 使用名爲 itbilu 的tube producer.use('itbilu', function(err, tubename) {}); // 向tube 發佈任務 producer.put(1024, 0, 2, '內容', function(err, jobid) { console.log('發佈了一個任務,任務ID:%s', jobid); });
完成並保存代碼後,首先運行客戶端consumer.js
:
node consumer.js
然後運行客戶端producer.js
:
node producer.js
producer
運行後,會自動put
一個任務,而consumer
處於監聽狀態,就會收到這個任務:
// procuer 運行後 發佈了一個任務,任務ID:1 // consumer 收到任務後 收到任務:1,任務內容:內容