RabbitMQ的流控機制

RabbitMQ的流控機制

消息發佈

RabbitMQ是使用Erlang語言實現的,其中進程之間的通信是通過send發送消息來完成的。

當一個RabbitMQ實例運行時,就有數百個erlang進程交換消息來相互通信。例如,我們有一個reader進程從網絡讀取AMQP幀。這些幀被轉換成AMQP命令,並被轉發到AMQP的channel進程。這個channel進程需要向特定的exchange詢問這個消息最終應該發往的queue列表。最後,如果AMQP消息需要持久化,則message store進程將接收它並將其寫入磁盤
所以,當我們向RabbitMQ發佈AMQP消息時,有如下erlang消息流:

reader process -> channel process -> exchange process -> message store process

上游的能夠處理消息的數目,是通過下游在處理完消息之後返回的信用憑證來決定的,所以當這條鏈式消息流中某個進程達到性能瓶頸(右邊是下游),必然導致上游所有的進程被阻塞

消費者消費速度

當生產者速度過高導致RabbitMQ隊列堆積了大量消息,控流時RabbitMQ將阻塞新的生產者連接。主觀上消費者消費速率應該至少保持不變,但實際是生產者和消費者的速度均受影響,且不平穩。

消費者消費速度降低,是因爲:

由於隊列現在接收的消息多於消費者可以應付的消息數量,因此與隊列爲空時(消息現在必須排隊等待)相比,隊列花費的CPU時間更多地處理每條消息。這會消耗驅動消費者的CPU時間,不幸的是,由於您選擇了一臺沒有大量備用CPU容量的RabbitMQ機器,您開始最大化掏空CPU。因此,你的排隊不能像以前一樣艱難地推動你的消費者,這反過來又會增加隊列的增長速度。這反過來又必須開始推送消息到磁盤,以釋放內存,這反過來又佔用了你本來就沒有多少的CPU。 到此爲止,你還能掙扎些什麼呢?

解決辦法: 那就是不要再繼續生產了,趕緊讓隊列消耗完先

At this immediate point, you need to get your queues drained. Because your queues are spending more time dealing with new messages arriving than with pushing messages out to consumers, it’s unlikely that throwing more consumers at the queues is going to significantly help. You really need to get the publishers to stop.

而提高消費者消費速率的方法是:

  • 增加消費者數量
  • 使用批量Ack方式
  • 在實際生產環境中,建議針對每個要操作的queue,分別建立生產者線程和消費者線程。
    例如,A進程要向Q1、Q2、Q3隊列生產消息,那麼A進程要啓動3個線程分別操作這3個隊列,否則如果只使用1個線程1個connection向3個隊列生產,當某一個隊列流控時connection被阻塞,那麼就會影響向其他隊列生產的速度。對於消費者也是同樣的道理。
  • 通過服務質量保障qos機制(同樣可以用來做限流)來提高Prefetch count($channel->basic_qos(0, 100, false))
  1. rabbitmq對basic.qos信令的處理

首先,basic.qos是針對channel進行設置的,也就是說只有在channel建立之後才能發送basic.qos信令。

在rabbitmq的實現中,每個channel都對應會有一個rabbit_limiter進程,當收到basic.qos信令後,在rabbit_limiter進程中記錄信令中prefetch_count的值,同時記錄的還有該channel未ack的消息個數。

注:其實basic.qos裏還有另外兩個參數可進行設置(global和prefetch_size),但rabbitmq沒有相應的實現。

  1. 隊列中的消息投遞給消費者時的處理

當rabbitmq要將隊列中的一條消息投遞給消費者時,會遍歷該隊列上的消費者列表,選一個合適的消費者,然後將消息投遞出去。其中挑選消費者的一個依據就是看消費者對應的channel上未ack的消息數是否達到設置的prefetch_count個數,如果未ack的消息數達到了prefetch_count的個數,則不符合要求。當挑選到合適的消費者後,中斷後續的遍歷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章