# 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 |