rabbitmq
本文算是實現對入門教程的 java版本翻譯吧。本文中演示代碼地址
1. 安裝
先安裝 erlang (安裝網上提供的教程安裝erlang)
在安裝 rabbitmq-server
下載rabbitmq的安裝包的時候選擇 tar.xz 直接解壓就可以了啟動/停止
#啟動rabbitmq
#進入安裝目錄的sbin 目錄,執行
./rabbitmq-server -detached
#關閉rabbitmq
./rabbitmqctl stop
2.用戶權限
guest/guest 現在只能在localhost使用,不能遠程使用
需要添加用戶和權限
參考文章 RabbitMQ用戶角色及權限控制
例如:
####添加用戶:
$sudo rabbitmqctl add_user user_admin passwd_admin
#####修改角色為 administrator:
$sudo rabbitmqctl set_user_tags user_admin administrator
上面的操作有了,可能還是為在日志中提示 user_admin在 vhost '/' 權限不夠
則執行下面的操作:
######修改權限
$sudo rabbitmqctl set_permissions -p / user_admin '.*' '.*' '.*'
3.rabbitmq 的使用
3.1 工作隊列(workqueues) 模式
注意的點:
- 工作隊列中的消息被所有的消費者共享。
- rabbitmq在路由消息到消費者的時候使用輪詢(round-robin)的方式,找到第n個消費者來消費消息
- ack(ackownledgment)機制確保消息被消費,不出現消息沒有消費就從內存中刪除的情況
3.1.1 ack機制(Message acknowledgment)
RabbitMQ 支持消息確認,當消費者(Consumer)接受到消息并且處理完成之后,回給RabbitMQ 發送一個確認消息,Rabbitmq這個時候可以隨意的刪除這個消息
如果消費者die ,但是沒有發送確認消息,這個時候RabbitMQ會認為消息沒有完全處理,這個時候會這個消息重新放到隊列之中,使用輪詢的方式分配給其他的消費者消費
RabbitMQ 默認開啟了確認機制,使用的時候設置 autoAck = true 來關閉確認機制,設置autoAck = false 則開啟
3.1.2 消息持久化(Message durability)
啟動消息持久化,需要在申明channel的時候設置持久化屬性為true
boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);
一個確定了是否持久化的隊列,不能再修改durable的值
設置隊列為持久化隊列之后,需要設置下消息的屬性,例如設置屬性為MessageProperties (實現了 BasicProperties)
channel.basicPublish("", "task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
使用持久化消息并不能完全保證消息的持久化,應為消息可能先保存在緩存,后面保存到硬盤上,不過對于一般的task是完全足夠的,如果想要確保完全的持久化,可以結合 publisher confirms 來實現
3.1.3 公平分配(Fair dispatch)
通過設置prefetchCount 的值,控制一個消費者接受的任務數量
int prefetchCount = 1;
channel.basicQos(prefetchCount);
如上面的設置,當一個消費者還有沒有處理完的任務的時候,rabbitmq不會分配任務給他
開啟管理功能
rabbitmq-plugins enable rabbitmq_management
web管理界面: http://server-name:15672/
3.2 發布訂閱(Publish/Subscribe)模式
使用發布訂閱模式,一條消息會被發送給多個消費者消費
使用示例說明:
創建一個日志的生產者 EmitLog 來發布日志,使用多個日志接受者ReceiveLogs
創建兩個接受者,一個把日志往硬盤上面寫,一個把日志打印到顯示器。整個過程發布日志
的只有一個。日志被廣播到多個接受者
1.1 交換(Exchanges)
在rabbitmq的消息模型中
生產者-->發送消息的一端
隊列--> 消息緩沖區
消費者-->消費消息的一端
rabbitmq的核心思想中,生產者通常不直接發送消息給一個隊列,事實上,
生產者也不知道把消息發送給哪個隊列
實際上,生成這是把消息發送給一個Exchange,這個Exchange一方面從生產者那邊接受消息,
一方面推送消息給隊列(queue),exchange清楚的知道自己接受的消息要怎么處理——是追加到
一個指定的隊列?還是追加到多個隊列?還是直接丟棄?具體做法要根據exchange的類型(type)
來定
可以通過下面的指令列出exchange的類型
./rabbitmqctl list_exchanges
exchange的類型:
- direct
- topic
- headers
- fanout
之前的例子中沒有使用exchange是因為我們使用了默認的exchange 即使用空字符串"" 來定義的
(默認exchange的消息會被發送到指明的routingKey的隊列中)
channel.basicPublish("", "hello", null, message.getBytes());
現在創建一個exchange并發布
channel.exchangeDeclare("logs", "fanout");
channel.basicPublish( "logs", "", null, message.getBytes());
1.2 臨時隊列(Temporary queues)
使用無參的 queueDeclare() 方法可以創建一個非持久化的、獨立的、自動刪除的、名稱隨機的一個
隊列。例如:產生一個名為 amq.gen-JzTY20BRgKO-HjmUJj0wLg 的隊列
String queueName = channel.queueDeclare().getQueue();
1.3 綁定(Bindings)
之前說過,消息分配到哪個隊列是有exchange來處理的,那么要確定消息去哪兒,就需要明確隊列和exchange
的關系。這個過程稱為binding
channel.queueBind(queueName, "logs", "");
3.3 路由(Routing)
之前的發布訂閱示例中,我們使用的是廣播消息,所有的接受者都能接受。使用routing之后
能夠讓接收者只接收消息的一個子集。例如之前的示例中,只有錯誤級別的日志寫到硬盤,所有的
的錯誤全部打印
1.bindings
在綁定的過程中加上 routingKey,為了同 basic_publish 區分,我們稱之為 binding key
channel.queueBind(queueName, EXCHANGE_NAME, "black");
2. Direct exchange
fanout exchange :只適合無腦的廣播模式
direct exchange :消息會被分配到與消費的routing key 對應的 binding key的隊列上,匹配不上的
消息直接丟棄
如圖:direct 的 exchange X 關聯了兩個隊列 Q1、Q2,Q1的 binding key=orange
,Q2的綁定了兩個key black 和 green
在這種情況下:
routing key=orange的消息會被 exchange X 傳遞給 Q1
routing key=black 或者 routing key=green 的消息會被 exchange X 傳遞給 Q2
3. 多綁定 (Multiple bindings)
如圖,對direct exchange 同時使用 binding key =black 綁定Q1、Q2,這個時候
direct的exchange就同之前的fanout的一樣了,直接廣播消息了
4. 測試
測試時候一個開兩個接收者,一個設置級別為 error ,一個設置為 info,error
然后開兩個生產者 分別發送 error 級別的消息和info級別的消息
觀察接收情況
3.4 主題(Topic)模式
在前面的日志例子中,我們可以通過級別來區分日志,但是我們還想通過日志來源來區分日志
就像unix tool 中的syslog,他能通過 級別(info/warn/error)和 來源(auth/cron/kern)來
路由日志
例如:我們只處理來自“cron” 的 “error”級別的日志,同時打印來自“kern”的所有級別的
日志
要實現這種要,我們就需要使用 topic exchange
1. Topic exchange
topic exchange 的 routing key必須是 一個或者英文單詞,中間使用點隔開的方式,
最大長度是255字節
binding key 必須是相同格式,topic exchange 的邏輯和direct 很相似,消息只會分配到匹配的binding key的隊列
- *(星) 代表一個單詞
- #(哈希)代表零個或者多個單詞
例如:
在這個例子中,接收動物消息,消息會被發送到一個擁有三個單詞(兩個點隔開)的 routing key
格式為 <speed>.<color>.<species>
Q1綁定了 binding key“ *.orange.* ”, Q2 綁定了 “ *.*.rabbit” 和 “lazy.#”
Q1只對顏色為 orange的消息感興趣
Q2關心所有的rabbit 和 lazy的消息
routing key 為 quick.orange.rabbit既會被發送到Q1 也會被發送到 Q2
lazy.orange.elephant --> Q1,Q2
quick.orange.fox --> Q1
lazy.brown.fox --> Q2
lazy.pink.rabbit --> Q2(有兩個binding key 匹配的情況下依然只會被發送一次)
quick.brown.fox --> 匹配不上,直接丟棄
lazy.orange.male.rabbit -->Q2 (匹配上了"lazy.#")
當不使用 * 或者 # 站位符的時候,topic exchange表現的就和direct 是一樣的
3.5 RPC
這一節使用 RabbitMQ 建立一個RPC系統:一個客戶端和一個可擴展的服務器
本示例中在客戶端調用一個call 方法,服務端返回一個Fibonacci 數列的值
關于使用rpc的幾點建議:
本地方法和遠程方法要定義明確,一目了然
系統加注釋,是組件之間的依賴清晰可見
異常處理,當rpc 服務器出現異常的時候,客戶端改如何處理