Ruby使用RabbitMQ(進階)
看本篇文章之前, 務必先看之前的文章Ruby使用RabbitMQ(基礎)裏面說到了一些基礎內容.
下面, 來講一下更加複雜且常用的方法和注意事項;
概念
在rabbitmq 中生產者會把消息發送給 exchange
,
然後 exchange
再發送給queue, queue再發送給消費者;
exchange沒有queue
如果 exchange 沒有 queue,那麼消息會被丟棄;
當然, 一般情況下, 都會綁定一個queue, 讓它持久化.
這樣, 消息就不會丟失了
exchange 新加入一個 queue-01 , 這個 queue-01 不會收到之前發送過的消息;
一個exchange多個queue
通過上面的圖, 我們可以知道消息都是先經由exchange, 才分發給各個queue的;
那麼, 到底要怎麼分發消息呢?
這就涉及到 exchange
的類型, 它有四種類型;
- direct (默認類型)
- topic
- headers
- fanout
具體的分析, 在下面的 Exchanges 一節.
一個queue多個消費者
其中要注意的是, 消費者在同一個queue中屬於競爭關係;
如果queue中有6條消息, 3個消費者;
那麼, 3個消費者會每個人收到2條消息;
Exchanges
在上文, 我們提到了Exchange.
它會根據不同的類型來分發消息;
注意: exchange 並不會保存消息, queue 保存消息
類型
- fanout
- direct (默認類型)
- topic
- headers
fanout
fanout
是最簡單的exchange類型;
它把接受到的消息, 轉發給全部的queue;
# 生產者
exchange = channel.fanout('fanout-test-02', durable: true)
30.times do |n|
exchange.publish("fanout-message: #{n}")
end
# 消費者
exchange = channel.fanout('fanout-test-02', durable: true)
# 這裏, 如果是不同的 queue, 會收到同樣的消息
queue = channel.queue('fanout-queue-01', durable: true)
queue.bind(exchange)
queue.subscribe(block: true, manual_ack: true) do |_delivery_info, _properties, body|
# 模擬延時任務, 延時1s
sleep 1
puts _delivery_info
puts _properties
puts body
channel.ack(_delivery_info.delivery_tag)
end
direct
direct
類型; 它是rabbitmq 的默認類型;
它根據不同的 routing_key
來分發消息
routing_key中可包含任意數量單詞,最多達255個字節。
在Ruby使用RabbitMQ(基礎), 我們沒有設置 exchange, 彷彿沒有exchange也能夠正常工作;
但是, 在這背後, 其實是代碼上的簡寫而已;
# 生產者
# 實際上, 並不是 queue來發布消息; 而是exchange; 這只是一種簡寫
queue = channel.queue('hello')
queue.publish('message')
# 消費者
queue = channel.queue('hello')
上面的代碼, 實際上是下面的結果
# 生產者
exchange = channel.direct('')
exchange.publish('message', routing_key: 'hello')
# 消費者
exchange = channel.direct('')
queue = channel.queue('hello', exclusive: true)
# 根據不同的 routing_key 來分發消息
queue.bind(exchange, routing_key: 'hello')
如果, 生產者生產了 10條消息(2條warn, 3條info, 5條debug)
那麼, 不同routing_key的queue 就會收到不同的條數
topic
topic
類型的 Exchange
是最爲靈活的.
和上面的 direct
類似;
topic
也是根據 routing_key
來分發消息的;
但是, 更爲靈活;
routing_key 的命名 單詞用 .
連接的字符串
例如
- movies.action
- movies.action.tom
- movies.tragedy
可以使用 #
, *
來匹配不同的key
#
代表多個單詞*
代表一個單詞
所以在 exchange 分發消息時, 我們可以根據消費者設置的不同key, 具有針對性的獲取消息;
例如:
# 發送的消息
['movies.action','movies.action.tom','movies.tragedy',
'movies.comedy', 'fruit.red.apple', 'fruit.yellow.banana']
movies.#
會收到 [‘movies.action’,‘movies.action.tom’,‘movies.tragedy’, ‘movies.comedy’]movies.action.*
會收到 [ ‘movies.action.tom’ ]
# 生產者
exchange = channel.topic('topic-test', durable: true, auto_delete: false)
key = ['movies.action','movies.action.tom','movies.tragedy',
'movies.comedy', 'fruit.red.apple', 'fruit.yellow.banana']
key.each_with_index do |key, index|
exchange.publish("topic: #{key}", routing_key: key, persistent: true)
end
# 消費者
exchange = channel.topic('topic-test', durable: true, auto_delete: false)
# queue = channel.queue('movies-all', durable: true)
queue = channel.queue('movies-action', durable: true)
# queue.bind(exchange, routing_key: 'movies.#')
queue.bind(exchange, routing_key: 'movies.action.*')
queue.subscribe(block: true, manual_ack: true) do |_delivery_info, _properties, body|
puts body
channel.ack(_delivery_info.delivery_tag)
end
Channel
我們一直在使用 channel
來創建 exchange
, queue
那麼, channel
是什麼呢?
在上面的代碼中, 我們可以看到, channel 是由 connection生成的
channel = connection.create_channel
其實, 這個和 AMQP
的設定有關;
一個連接代表了一個真實的 TCP 連接.
然而, 爲了性能, 我們把 channel
處理爲一個虛擬連接;
我們可以在一個應用中使用多個channel, 而僅僅使用一個 TCP連接.