# beanstalkd
## 協議
### 描述
beanstalkd 協議 運行在 TCP 層上,并采用了 ASCII 編碼。
大致工作流程為:開啟客戶端連接、發送命令和數據、等待響應、關閉連接。
對于每一個連接而言,服務器會按照接收到指令的順序一一執行這些指令,并且按照同樣的順序,發送響應。
在本協議中,所有涉及到的整數,都是指十進制,且為非負,除非另有說明。
### 命名約定
**只支持 ASCII 字符進行命名**
其支持的字符有:
- 字母( A-Z、a-z )
- 數字( 0-9 )
- 小短橫( - )
- 加號( + )
- 正斜杠( / )
- 英文封號( ; )
- 英文點號( . )
- 美元符( $ )
- 英文下劃線( _ )
- 英文左右小括號( "(" 和 ")" )
> 注意:命名不可以 - 作為開頭,當遇到空格或換行時,會視作命名結束。每個命名的長度,至少為 1+ 個字符。
### Errors
| 錯誤類型 | 描述 |
|-------------|------|
| *OUT_OF_MEMORY\r\n* | 服務器內存不夠分配了,可能需要過一會再嘗試|
| *INTERNAL_ERROR\r\n* | 這說明服務器,也就是 beanstalkd 內部出現了 bug ,理論上不應該出現這個錯誤,如果出現了,請將這個 bug 提交到 google group |
| *BAD_FORMAT\r\n* | 發送的命令格式錯誤。比如,命令行不是以 \r\n 結尾,又或者在該傳入數值的地方傳入了非數值,也可能參數數量錯誤等一切命令格式有誤的可能性。|
| *UNKMOWM_COMMAND\r\n* | 使用了未定義的命令(找不到這個命令),此時可能要檢查是否拼寫有錯 |
### Job 生命周期
一個 Job 由 put 命令創建,在它的生命周期以內,它必將處于以下四種狀態中的一種:「ready」、「reserved」、「delayed」、「buried」
當使用完 put 命令,Job 一般從 「ready」 開始,它會在「ready」隊列中等待,直到有「reserved」命令過來,當接收成功之后,則將該 Job 放入到 「reserved」 隊列。接著,當進程處理完這個 Job 之后,則會發送一個「delete」命令,將這個 Job 從 beanstalkd 中刪除。
| 狀態 | 描述 |
|-----|-----|
| *ready* | 被放入 Tube 之后等待被接收和處理 |
| *reserved* | 當 Job 被 reserve 命令接收,Job 會進入這個狀態,它代表被接收,但還沒有得到其他反饋 |
| *delayed* | 延遲狀態,等時間到了會變成 ready 狀態 |
| *buried* | 預留狀態,一般當消費者處理失敗時,會將它設置為預留 |
圖示 Job 生命周期:
```
put reserve delete
-----> [READY] ---------> [RESERVED] --------> *poof*
```
當然,它也可能經歷更復雜的演化,如下圖:
```
put with delay release with delay
----------------> [DELAYED] <------------.
| |
kick | (time passes) |
| |
put v reserve | delete
-----------------> [READY] ---------> [RESERVED] --------> *poof*
^ ^ | |
| \ release | |
| `-------------' |
| |
| kick |
| |
| bury |
[BURIED] <---------------'
|
| delete
`--------> *poof*
```
### Tubes
一個 beanstalkd 服務允許擁有多個 Tube ,每一個 Tube 包含兩個隊列: ready queue 和 delay queue 。
每個 Job 都必然會存在于某個 Tube 之下。
可以通過 watch 指令關注某個 Tube ,也可以通過 ignore 命令取消關注。
當你使用 watch list 命令時,它會返回你所關注的 tubes 。
當消費者開始接收 Job 的時候,Job 一般來自 watch 了的 Tube。
當一個客戶端連接進來,watch list 最初只有一個名為 default 的 tube 。如果當存入 Job 時沒有使用 use 命令指定 tube ,這個 Job 就會被放入到 default tube 中。
Tubes 會在你使用到它的時候創建,如果 Tube 變空了(沒有 ready Job ,沒有 delayed Job , 沒有 buried Job),且沒有客戶端連接指向它,它就會被刪掉。
## 命令
### 生產者命令
#### put
此命令用于向隊列中插入 job ,命令格式如下:
```
put <pri> <delay> <ttr> <bytes>\r\n
<data>\r\n
```
*它默認會將 job 插入到當前所 use 的 tube , 這點可以參考下面的 use命令*
| 選項 | 描述 |
|---|---|
| *pri* | 這是一個整型數值,代表著這個 job 的優先級,數值越小,排隊排在越前面,優先級最高為 0 ,最后面為 4294967295 |
| *delay* | 這也是一個整型數值,是一個秒數,指多少秒之后將這個 job 放入到 ready queue 中,在那之前,這個 job 都將處于 delayed 狀態 |
| *ttr* | 這也是一個整型數值,是一個描述,指一個 job 被允許處理的最大時間,這個時間從 job 被 reserve 起開始計算,超過時間還未被 delete 、 release 、 bury ,則服務器會自動釋放這個 job,并重新插入到 ready queue 中。此數值最小支持 1 ,如果傳的是 0 ,則服務器會默認將它變成 1 |
| *bytes* | 這是一個數值,用于指明這個 job body 的大小,不包含「\r\n」這兩個字符。這個值必須小于 beanstalkd 配置的 max-job-size , 單位是 bytes |
| *data* | 這是 job body ,上一行的 bytes 就是由此行除卻「\r\n」計算得出的。 |
當成功發送 put 命令后,客戶端要等待響應,響應結果可能是如下幾個:
| 響應 | 描述 |
|----|---|
| *INSERTED <id>\r\n* | 插入成功,id 是一個 interger ,標識新插入的 job |
| *BURIED <id>\r\n* | 如果服務器因為增加優先級隊列而內存不足時會返回這個結果,id 是一個 interger ,標識新插入的 job |
| *EXPECTED_CRLF\r\n* | job body 必須以「\r\n」結尾,這兩個字節不用計入上一行的 bytes 計算中 |
| *JOB_TOO_BIG\r\n* | job body 超出了 max-job-size 的限制 |
| *DRAINING\r\n* | 目標服務器不再接收新的請求,需要嘗試其他服務器,或斷開連接之后晚點再重新嘗試 |
### use
此命令為 Producer 提供,當發送此命令之后,后續的 put 命令,就會把 job 全部放入到此 use 命令指定的 tube 中。如果沒有通過 use 指定 tube , 則會默認將 job 放入到 default tube 中。
```
use <tube>\r\n
```
| 選項 | 描述 |
|---|---|
| *tube* | 一個最大不超過 200 bytes 的名稱,它指定一個 tube ,如果這個 tube 不存在,則會自動創建 |
| 響應 | 描述 |
|---|---|
| *USING <tube>\r\n* | <tube>是接下來開始使用的 tube |
## 消費者命令
從 queue 中消費 job 會使用以下命令:
- reserve
- delete
- release
- bury
### reserve
```
reserve\r\n
```
另外,你還能指定接收的超時時間,如下:
```
reserve-with-timeout <seconds>\r\n
```
這個命令將會返回一個新的、reserved 狀態的 job
如果沒有可用的 job 能被接收,則 beanstalkd 一直等到出現一個可接收的 job 之后再返回。
一旦一個 job 被客戶端接收,客戶端要在 ttr 指定的時間限定內處理 job ,否則,超時的話,服務器會將 job 重新放回 ready queue 中。
可以在 stats-job 命令的 response 中找到 ttr 的值和已經使用掉的時間。
如果處于 ready 狀態的 job 不止一個,beanstalkd 將會選擇一個 priority 最小的 job,如果 priority 相等,則會選擇一個最先 put 的 job 。
> 題外話:這里我懷疑 beanstalkd 的協議有一處寫錯了,原文為 Within each priority, it will choose the one that was reserved first. 我認為應該將 reserved 改為 put 。
如果指定的 timeout seconds 是 0 ,這將導致服務器立即返回 TIME_OUT 的響應(也有可能立即返回一個 job ,這取決于服務器的響應速度以及是否存在可接收的 job )
為 timeout 設置一個合理的 seconds ,可以限制客戶端阻塞等待接收 job 的時間。
| 失敗響應 | 描述 |
|---|---|
| *DEADLINE_SOON\r\n* | ttr 的最后一秒,被服務器設定為安全界限,在此期間,該客戶端不會接收到另外一個 job 。比如:客戶端在安全界限時間里發送了一條 reserve 命令,或者,當一條 reserve 命令在等待反饋時,安全界限時間正好到期,這時候,都將得到一個 DEADLINE_SOON\r\n 的響應 |
| *TIMED_OUT\r\n* | 當使用 reserve-with-timeout 命令,超過時間還未接收到 job ,又或者客戶端連接已經關閉,此時會返回此值 |
**成功響應:**
```
RESERVED <id> <bytes>\r\n
<data>\r\n
```
| 參數 | 描述 |
|---|---|
| id | job 的 id ,一個整型值,在這個 beanstalkd 服務器中具備**全局唯一性** |
| bytes | 表示 job data 的大小,不包含結束符 \r\n |
| data | job data , 之前 put 時放入的 job data ,原模原樣返回 |
### delete
delete 命令用于從服務器完全刪除一個 job , 這一般用于客戶端成功處理 job 之后。
客戶端可以刪除 reserved 的 job , 使 job 進入準備狀態 , 延遲 job ,預留 job 。
```
delete <id>\r\n
```
| 選項 | 描述 |
|---|---|
| id | job 的 id |
| 響應 | 描述 |
|---|---|
| *DELETED\r\n* | 刪除成功 |
| *NOT_FOUND\r\n* | 找不到這個 job ,或者這個 job 并非這臺 client 接收的、或是 job 處于 ready 、 buried 狀態。這很可能發生在 ttr 時間到了之后才發送 delete 命令的情況下。 |
### release
此命令可以把 reserved job 放回 ready 隊列中,同時 job 的狀態也會回到 ready ,release 之后,這個 job 可以被任何其他的客戶端接收。
一般這個命令用在消費者處理 job 失敗的情況下。
```
release <id> <pri> <delay>\r\n
```
|選項|描述|
|---|---|
| id | job id |
| pri | interger ,指定一個新的優先級,數值越小,越早被接收 |
| delay | interger ,指定一個新的延遲,如果設置了預留值,則 job 的狀態會是 delayed ,直到延遲時間到期 |
| 響應 | 描述 |
|---|---|
| *RELEASED\r\n* | 處理成功 |
| *BURIED\r\n* | 因為新增優先級隊列數據結構而導致內存溢出 |
| *NOT_FOUND\r\n* | 沒有找到這個 job 或 此 job 不是當前客戶端接收的 |
### bury
這個命令可以將一個 job 操作為 buried 狀態。
buried job 被存放在一個 FIFO (first input first out ,先進先出)的鏈表中,它不會被服務器再次操作,除非有客戶端對它發起了 kick 命令。
```
bury <id> <pri>\r\n
```
| 選項|描述|
|---|---|
| id | job id|
| pri | 優先級,一個整型數字,越小的越先被接收 |
| 響應 | 描述 |
|---|---|
| *BURIED\r\n* | 操作成功 |
| *NOT_FOUND\r\n* | 找不到 job 或該 job 不是被當前客戶端所接收 |
### touch
此命令能讓當前消費者得到更多的執行 job 的時間。
比如,ttr 是用于避免消費者崩潰而導致 job 丟失,但同樣也會誤傷一批執行時間過長的消費者,實際上消費者沒有崩潰,但執行時間已經超出了 ttr ,此時,通過 touch 命令,可以讓客戶端得到更多的處理時間,不先觸發 ttr 機制。
當然,使用了 touch 命令,只是延長了 ttr 的時間,ttr 的機制仍然存在。
通過這個命令,消費者可以定期告訴服務器,當前處理程序仍處于活躍狀態。
**此命令不受 DEADLINE_SOON影響**
```
touch <id>\r\n
```
|選項|描述|
|---|---|
| id | job id|
|響應|描述|
|---|---|
| *TOUCHED\r\n* | 操作成功 |
| *NOT_FOUND\r\n* | 沒有找到這個 job 或者 該 job 不是這個客戶端接收的 |
### watch
watch 命令會往 watch list 中添加一個 tube ,消費者通過 reserve ,可以接收到 watch list 中任何一個 tube 傳來的 job 。一個新的連接,watch list 中默認存在一個 default tube 。
```
watch <tube>\r\n
```
|選項|描述|
|---|---|
| tube | 200 bytes 以內的字符串,代表著 tube 的名字,如果該 tube 不存在,則會自動創建 |
返回響應:
```
WATCHING <count>\r\n
```
conut 是一個數值,指當前 watch list 中有多少 tube 。
### ignore
此命令用于從 watch list 中移除一個 tube ,移除之后,該消費者不再接收被移除的 tube 內的 job 。
```
ignore <tube>\r\n
```
|選項|描述|
|---|---|
| tube | 200 bytes 以內的字符串,代表著 tube 的名字,如果該 tube 不存在,則會自動創建 |
|失敗響應|描述|
|---|---|
| *NOT_IGNORED\r\n* | 如果當前 watch list 中只存在最后一個 tube,則會返回這個響應 |
成功響應:
```
WATCHING <count>\r\n
```
conut 是一個數值,指當前 watch list 中有多少 tube 。
## 其他命令
### peek
用于客戶端檢查 job ,此命令有四種形態,除了第一個操作以外,其它操作都只針對于當前的 tube 。
```
peek <id>\r\n 根據 id 返回一個 job
peek-ready\r\n 返回下一個 ready job
peek-delayed\r\n 返回下一個剩余延遲時間最短的 delayed job
peek-buried\r\n 返回下一個 buried job
```
| 失敗響應|描述|
|---|---|
| *NOT_FOUND\r\n* | 找不到 job 或沒有該狀態的 job |
成功響應:
```
FOUND <id> <bytes>\r\n
<data>\r\n
```
其中,id 為 job 的 id ,bytes 指 data 的大小(不包含 \r\n ),data 是 job 的具體內容
### kick
此命令只適用于當前指定的 tube 。
此命令能將 job 狀態改成 ready , 它需要傳入一個數字,用于指定需要修改多少個 job 。
比如,你傳入 10 ,則會將隊列中十個 buried 或 delayed 狀態的 job ,修改為 ready 。
如果,指定的隊列中存在 buried job ,則只會修改 buried job,否則,就修改 delayed job 。
```
kick <bound>\r\n
```
|選項|描述|
|---|---|
| bound | 指定要 kick 多少 job|
響應:
```
KICKED <count>\r\n
```
count 表示該操作成功修改了幾個 job 。
### kick-job
這是一個 kick 擴展命令,用于將單獨的一個 job 修改為 ready 。它需要傳入一個 job id 。
如果傳入的 job id 所代表的 job 在當前 tube 中存在,并且該 job 的狀態處于 buried 或 delayed ,則會將這個 job 設置為 ready ,并仍然在當前 tube 中。
```
kick-job <id>\r\n
```
|選項|描述|
|---|---|
| id | job id|
|響應|描述|
|---|---|
| *NOT_FOUND\r\n* | job 不存在或不處于可 kick 的狀態,另外,這也可能發生在內部錯誤上 |
| *KICKED\r\n* | 操作成功 |
### stats-job
此命令用于查看一個 job 的統計信息
```
stats-job <id>\r\n
```
|選項|描述|
|---|---|
| id | job id |
|錯誤響應|描述 |
|---|---|
| *NOT_FOUND\r\n* | job 不存在 |
成功響應:
```
OK <bytes>\r\n
<data>\r\n
```
bytes 指后面 data 的大小,data 則是該 job 的統計信息,是一個 YAML 格式的文本。
data 包含以下 key :
| key | 描述|
|---|---|
| id | job id|
| tube | 此 job 所在的 tube |
| state | job 的狀態 |
| pri | job 的優先級 |
| age | job 在隊列中存在的時間,是一個秒數 |
| time-left | 此 job 距離被放入 ready queue 的剩余秒數,這個時間到達之后,此 job j就會被放入到 ready 隊列。此參數只在 job 狀態為 reserved 和 delayed 時有意義,當狀態為 reserved 時,此參數代表 job 的超時剩余秒數,即 ttr |
| file | 此 job 的 binlog 序號,如果未開啟 binlog ,則此值為 0 |
| reserved | 此 job 被 reserve 的次數 |
| timeouts | 此 job 的超時次數 |
| releases | 此 job 被 released 的次數 |
| buries | 此 job 被 bury 的次數 |
| kicks | 被 kicked的次數 |
### stats-tube
此命令返回 tube 的統計信息,如果這個 tube 存在的話。
```
stats-tube <tube>\r\n
```
| 選項 | 描述 |
|---|---|
| tube | 傳入 tube 的名稱 |
| 失敗響應 | 描述 |
|---|---|
| *NOT_FOUND\r\n* | 不存在這個 tube |
成功響應:
```
OK <bytes>\r\n
<data>\r\n
```
bytes 是指 data 的大小,不包含 「\r\n」。
data 是一個 YAML 格式的文本,它包含了你想要的 tube 的統計信息
下面是 data 的 key :
| key | 描述 |
|---|---|
| name | tube 的 name |
| current-jobs-urgent | 這個 tube 中,優先級小于 1024 的 ready job 數量 |
| current-jobs-ready | 這個 tube 中的 ready job 數量 |
| current-jobs-reserved | 這個 tube 中被 reserve 的 job 數量,不論它是被哪個消費者接收的 |
| current-jobs-delayed | 這個 tube 中處于 delayed 狀態的 job 數量 |
| current-jobs-buried | 這個 tube 中處于 buried 狀態的 job 數量 |
| total-jobs | 此 tube 一共創建過幾個 job |
| current-using | 指向此 tube 的連接數量 |
| current-waiting | 指向此 tube 并且處于接收等待狀態、但還未接收到 job 的連接數量 |
| current-watching | watch 了此 tube 的連接數量 |
| pause | 此 tube 停止服務的秒數 |
| cmd-delete | 此 tube 累計執行了幾次 delete |
| cmd-pause-tube | 此 tube 累計執行了幾次 pause-tube |
| cmd-time-left | 此 tube 幾秒之后提供服務 |
### stats
此命令返回整個服務器系統的統計信息。
```
stats\r\n
```
成功響應:
```
OK <bytes>\r\n
<data>\r\n
```
bytes 是指 data 的大小,但不包括 「\r\n」。
data 是一個 YAML 文本,包含了如下 key :
|key|描述|
|---|---|
| current-jobs-urgent | 優先級小于 1024 的 ready job 數量 |
| current-jobs-ready | ready job 的數量 |
| current-jobs-reserved | 被接受的 job 數量,不區分客戶端(消費者)|
| current-jobs-delayed| delayed job 的數量 |
| current-jobs-buried | buried job 的數量 |
| cmd-put | 累積執行 put 的次數 |
| cmd-peek | 累積執行 peek 的次數 |
| cmd-peek-ready | 累積執行 peek-ready 的次數 |
| cmd-peek-delayed | 累積執行 peek-delayed 的次數 |
| cmd-peek-buried | 累積執行 peek-buried 的次數 |
| cmd-reserve | 累積執行 cmd-reserve 的次數 |
| cmd-use | 累積執行 use 的次數 |
| cmd-watch | 累積執行 watch 的次數 |
| cmd-ignore | 累積執行 ignore 的次數|
| cmd-delete | 累積執行 delete 的次數 |
| cmd-release | 累積執行 release 的次數 |
| cmd-bury | 累積執行 bury 的次數 |
| cmd-kick | 累積執行 kick 的次數 |
| cmd-stats | 累積執行 stats 的次數 |
| cmd-stats-job | 累積執行 stats-job 的次數|
| cmd-stats-tube | 累積執行 stats-tube 的次數 |
| cmd-list-tubes | 累積執行 list-tubes 的次數 |
| cmd-list-tube-used | 累積執行 list-tube-used 的次數 |
| cmd-list-tubes-watched | 累積執行 list-tubes-watched 的次數|
| cmd-pause-tube | 累積執行 pause-tube 的次數|
| job-timeouts | 累積 timeout 的 job 總數|
| total-jobs | 累積創建了幾個 job |
| max-job-size | 最大允許的 job 字節數 |
| current-tubes | 當前有幾個 tube |
| current-connections | 當前有幾個連接 |
| current-producers | 當前有幾個至少發出過一條 put 指令的連接|
| current-workers | 當前有幾個至少發出過一條 reserve 指令的連接 |
| current-waiting | 當前有幾個至少發出過一條 reserve 指令但還未接收到 response 的連接 |
| total-connections | 累積有過幾個連接 |
| pid | 服務器的進程 id |
| version | 當前服務器的版本 |
| rusage-utime | 進程占用用戶 cpu 的時間,分別有「秒」和「微秒」的單位 |
| rusage-stime | 進程占用系統 cpu 的時間,分別有「秒」和「微秒」的單位 |
| uptime| 此進程已運行的秒數 |
| binlog-oldest-index | 最早存儲的 job binlog 索引號 |
| binlog-current-index | 當前的 job binlog 索引號,新的 binlog 會從這里開始寫入,如果未開啟 binlog ,此值為 0 |
| binlog-max-size | 每個 binlog 文件允許分配的最大容量,單位 bytes |
| binlog-record-written | 寫入 binlog 的累積次數 |
| binlog-records-migrated | 以壓縮形式寫入 binlog 的累積次數 |
| id | 一個隨機字符串,用于標記這個進程,在 beanstalkd 開啟時生成 |
| hostname | 主機名,由 uname 決定 |
> 上面這些 key 的信息,自從 beanstalkd 啟動以來就開始累積,如果重啟,就會重新累積。另外,這些數據不存放在 binlog 中
### list-tubes
此命令返回所有存在的 tube 。
```
list-tubes\r\n
```
成功響應:
```
OK <bytes>\r\n
<data>\r\n
```
bytes 是指 data 的大小,不包含「\r\n」。
data 返回一個 YAML 字符串,里面包含了 tube 的列表。
### list-tube-used
此命令返回當前所 use 的 tube 。
```
list-tube-used\r\n
```
成功響應:
```
USING <tube>\r\n
```
tube 是指當前 use 的 tube 名字。
### list-tubes-watched
此命令用于查看當前客戶端 watch-list 中的 tube 。
```
list-tubes-watched\r\n
```
成功響應:
```
OK <bytes>\r\n
<data>\r\n
```
bytes 是指 data 的大小,不包含「\r\n」。
data 是一個包含了 tube list 的 YAML 字符串。
### quit
此命令用于關閉當前連接。
```
quit\r\n
```
### pause-tube
此命令為某個 tube 指定一個時間,在這個時間內,此 tube 內的 job 將不會被 reserve 。
```
pause-tube <tbe-name> <delay>\r\n
```
|選項|描述|
|---|---|
| tube | tube 的名字 |
| delay | 指定一個秒數 |
|響應|描述|
|---|---|
| *PAUSED\r\n* | 操作成功 |
| *NOT_FOUND\r\n* | 沒有這個 tube |