RabbitMQ能開啟的最大連線數

馳馳的老爸發表於2015-01-16

RabbitMQ自帶了顯示能夠接受的最大連線數,有2種比較直觀的方式:
1. rabbitmqctl命令。

1
2
3
4
5
6
7
8
9
10
11
12
n$ rabbitmqctl status
Status of node 'rabbit@10-101-17-13' ...
[{pid,23658},
 ......
 {file_descriptors,
     [{total_limit,924},
      {total_used,10},
      {sockets_limit,829},
      {sockets_used,10}]},
 ......
]
...done.

2. rabbitmq_management WebUI外掛。

本文關注當RabbitMQ可用連線數耗盡時客戶端的影響以及如何增加最大連線數預設值。

RabbitMQ的socket連線數(socket descriptors)是檔案描述符(file descriptors,fd)的一個子集。也就是說,RabbitMQ能同時開啟的最大連線數和最大檔案控制程式碼數(檔案系統,管道)都是受限於作業系統關於檔案描述符數量的設定,兩者是此消彼長的關係。初始時,可用socket描述符與可用fd數量的比率大概在0.8-0.9左右,這個值並不固定,當socket描述符有剩餘時,RabbitMQ會使用盡量多的檔案描述符用於磁碟檔案讀寫。隨著伺服器建立越來越多的socket連線,檔案控制程式碼開始回收,數量減少。總之,RabbitMQ會優先將檔案描述符用於建立socket連線,寧可犧牲頻繁開啟/關閉檔案帶來的磁碟操作效能損耗,這種取捨很容易理解,作為網路伺服器,當然優先保障網路吞吐率了。因此,對於高併發連線數的多佇列讀寫時,佇列效能會稍微差那麼一點,比如用RabbitMQ做RPC。

當伺服器建立的socket連線已經達到限制(sockets_limit)時,伺服器不再接受新連線。這裡要區分清楚,RabbitMQ不再接收的是AMQP連線,而不是傳輸層的TCP連線,通過簡單抓包分析即可清楚流程:

1
2
3
4
5
6
7
8
$ sudo tcpdump host 10.101.17.13 and port 5672
17:24:12.214186 IP 10.101.17.166.56925 > 10.101.17.13.amqp: Flags [S], seq 3319779561, win 65535, options [mss 1368,nop,wscale 5,nop,nop,TS val 1006381554 ecr 0,sackOK,eol], length 0
17:24:12.214231 IP 10.101.17.13.amqp > 10.101.17.166.56925: Flags [S.], seq 1636058035, ack 3319779562, win 14480, options [mss 1460,sackOK,TS val 24529834 ecr 1006381554,nop,wscale 5], length 0
17:24:12.218795 IP 10.101.17.166.56925 > 10.101.17.13.amqp: Flags [.], ack 1, win 4110, options [nop,nop,TS val 1006381560 ecr 24529834], length 0
17:24:12.243184 IP 10.101.17.166.56925 > 10.101.17.13.amqp: Flags [P.], seq 1:9, ack 1, win 4110, options [nop,nop,TS val 1006381583 ecr 24529834], length 8
17:24:12.243201 IP 10.101.17.13.amqp > 10.101.17.166.56925: Flags [.], ack 9, win 453, options [nop,nop,TS val 24529841 ecr 1006381583], length 0
17:24:22.247907 IP 10.101.17.166.56925 > 10.101.17.13.amqp: Flags [F.], seq 9, ack 1, win 4110, options [nop,nop,TS val 1006391550 ecr 24529841], length 0
17:24:22.284914 IP 10.101.17.13.amqp > 10.101.17.166.56925: Flags [.], ack 10, win 453, options [nop,nop,TS val 24532352 ecr 1006391550], length 0

line 2-4是TCP握手包,成功建立TCP連線。line 5開始客戶端向伺服器端傳送AMQP協議頭字串“AMQP0091”,共8個位元組,開始AMQP握手。line 6是伺服器回給客戶端的ack包,但未傳送AMQP connection.start方法,導致客戶端一直等到超時(line 7-8),傳送FIN包關閉TCP連線。至此,AMQP連線建立失敗。

從客戶端(Java SDK)來看上述這個過程,客戶端通過ConnectionFactory例項的newConnection()方法建立一條AMQP連線。在網路層,它首先通過java.net.Socket與伺服器建立一條TCP連線,傳送協議協商字串“AMQP0091”,然後啟動MainLoop執行緒,通過封裝的Frame例項來迴圈讀取幀(readFrame()),注意readFrame()方法可能會有一個SocketTimeoutException的超時異常,這個超時時間是由socket例項setSoTimeout方法寫入,預設是10s,由AMQConnection.HANDSHAKE_TIMEOUT常量指定。當超時發生在AMQP連線握手階段時,就丟擲一個SocketTimeoutException異常,發生在其他階段(除心跳超時)時,什麼都不做繼續下一個迴圈:

1
2
3
4
Caused by: java.net.SocketTimeoutException: Timeout during Connection negotiation
 at com.rabbitmq.client.impl.AMQConnection.handleSocketTimeout(AMQConnection...
 at com.rabbitmq.client.impl.AMQConnection.access$500(AMQConnection.java:59)
 at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:541)

這裡的socket讀取超時很容易跟連線超時搞混。連線超時由ConnectionFactory例項的setConnectionTimeout()方法指定,對應著網路層Socket例項connect()方法中的timeout引數,指的是完成TCP三次握手的超時時間;而讀取超時是從socket中讀取位元組流的等待時間,前文已經說過,由Socket例項的setSoTimeout()指定。這在各種網路庫中應該很常見,比如HttpClient。

私以為這種情況下更好的設計是應該由RabbitMQ主動斷開與客戶端的TCP連線,減少客戶端等待時間。

最後一個問題,如何增加RabbitMQ的能夠同時開啟的連線數。通過前文可知,最大併發連線數由此程式可開啟的最大檔案描述符數量(乘以一個比例係數)決定,因此只要增加單個程式可開啟的檔案描述符數量即可。有幾個常規方法,按作用範圍可以歸納為以下幾類:

1. 程式級別。在啟動指令碼rabbitmq-server中加入ulimit -n 10240命令(假設將最大檔案描述符數量設定為10240,下同),相當於在shell中執行,由此shell程式fork出來的程式都能繼承這個配置。

2. 使用者級別。修改/etc/security/limits.conf檔案,新增以下配置,重新登入生效:

1
2
user    soft    nofile    10240
user    hard    nofile    10240

3. 系統級別。

1
# echo 10240 > /proc/sys/fs/file-max

上述設定只是針對proc檔案系統,相當於修改了作業系統的執行時引數,重啟後失效。要想永久生效,需要修改/etc/sysctl.conf檔案,加入配置項fs.file-max=10240。

一個程式能開啟的最大檔案描述符數量受限於上述三個級別配置中的最小值。理論上,系統級別的配置數值必須要大於使用者級別,使用者級別的要大於程式級別的,只有這樣配置才是安全的,否則程式容易因為開啟檔案數量問題受到來自作業系統的種種限制。作業系統為什麼要限制可開啟的檔案描述符數量?為了系統安全。因為檔案描述符本質上是一種記憶體中的資料結構,如果不加以限制,很容易被程式無意或惡意耗盡記憶體,比如fork bomb

相關文章