RabbitMQ服務之路由篇
相關聲明:
1、轉載請標明出處:
http://blog.csdn.net/why_2012_gogo/article/details/54017756
2、本系列文章均來自於實際項目、官網、網絡資源整理而來,並自己進行修改、優化及調試,內容僅供參考;
在上一篇文章中,我們介紹了RabbitMQ服務的發佈訂閱機制,也就是每個接收端都會接收到消息發送端發送的全部消息,不清楚的同學可以查看文章《RabbitMQ服務之發佈/訂閱》,這種方式不是很靈活,當然也要根據具體的業務需求來選擇使用它。這裏我們繼續介紹另一種消息的轉發機制,稱之爲“路由轉發”,對應的轉換器類型爲“direct”,它可以讓接收端可以有選擇的接收自己喜歡的消息,具體的關於“direct”轉發器的原理,請查看文章《RabbitMQ服務之入門篇》。
l 轉發器
l 例子
一、轉發器
1、路由說明
這裏要介紹的轉發器,我們稱之爲“路由”轉發器,理由是消息接收端可以設置自己想要接收到的消息類型,所以不同接收端設置不同的過濾條件,而發送端同樣發送所有類型的消息,就形成了一種類似路由條件的轉發機制。那麼我們該做怎樣的處理才能正常使用,那就先提前瞭解下它的使用規則吧,後面的例子會詳細介紹羅列。
需要額外說明的是,路由轉發機制規則爲完全匹配,也就是接收端設置精確的過濾條件,只會接收完全匹配條件的消息!
2、兩端處理
在接收端和發送端,我們都需要設置統一的路由匹配條件bindingKey,對於發送端,需要這樣來處理:
channel.basicPublish(EXCHANGE_NAME,bindingKey,
MessageProperties.PERSISTENT_TEXT_PLAIN,"message".getBytes());
而對於接收端,我們需要這樣處理(兩個參數分別爲:轉發器名字和類型):
channel.exchangeDeclare(EXCHANGE_NAME,EXCHANGE_TYPE);
我們需要將轉發器與隊列進行綁定,具體如下:
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, bindingKey);
如何處理接收端返回消息,應該如下操作:
channel.basicConsume(QUEUE_NAME,true, consume);
二、例子
這裏繼續以上一篇文章中的用戶操作記錄爲例,來說明RabbitMQ服務direct路由轉發器的使用特點。我們同樣設定兩個消息接收端,和一個消息發送端;一個接收端依舊負責保存記錄到文件,另一個接收端依舊打印記錄到控制檯,不同的是保存的記錄有所過濾,只保存訂單”order”類型的消息,而打印的記錄也只是打印積分”score”類型消息,而發送消息端會同時發送”order”、”score”及”order.pay”三種類型消息,預期情況下,接收端一隻保存”order”消息,接收端二隻打印”score”消息,而”order.pay”類型消息則不做處理,那麼接下來我們來驗證下這個預期的目標。
1、基類
BaseObject.java:
public class BaseObject {
static Logger log = LogManager.getLogger(BaseObject.class.getSimpleName());
public static void debug(String debug) {
log.debug(debug);
}
public static void debug(Object obj) {
if(null!= obj)
log.debug(obj.toString());
}
public static void error(String error) {
log.error(error);
}
}
該類爲所有對象繼承的基類,其中LogManager爲Java中的Log4j的使用,這裏不做相關介紹,請讀者查閱相關資料。
BaseConnector.java:
public class BaseConnector extends BaseObject {
protected Channel channel;
protected Connection connection;
public BaseConnector()throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory(); //新建鏈接工廠並綁定主機地址
factory.setHost("127.0.0.1"); //設置MabbitMQ所在主機ip或者主機名
connection = factory.newConnection(); //創建連接
channel = connection.createChannel(); //創建頻道
}
}
protected void close() { //關閉頻道及鏈接
try {
channel.close();
connection.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
2、發送端
PublisherHandle.java:
public class PublisherHandler extends BaseConnector {
public PublisherHandler()throws IOException,TimeoutException {
super();
}
public void sendMessage(MessageInfo messageInfo,String exchangeName,String exchangeType,String bindingKey) {
try{
//聲明轉發
channel.exchangeDeclare(exchangeName,exchangeType);
//給轉發器,發送消息,並設置路由條件
channel.basicPublish(exchangeName,bindingKey, null, SerializationUtils.serialize(messageInfo));
}catch (IOException e) {
debug("RabbitMQSend Message Error:"+e.getMessage());
}
}
publicvoid close() {
super.close();
}
}
3、接收端
ReceiverHandle.java:
public class ReceiverHandler extends BaseConnector implements Runnable,Consumer {
private MessageInfo messageInfo =new MessageInfo();
private int hashCode;
private String exchangeName; //轉發器名字
private Stirng exchangeType; //轉發器類型
private String bindingKey; // 路由過濾條件
private volatile Thread thread;
public ReceiverHandler(String exchangeName,String exchangeType,String bindingKey)throws IOException, TimeoutException {
this.exchangeName= exchangeName;
this.exchangeType = exchangeType;
this.bindingKey= bindingKey;
super();
}
public void receiveMessage() {
hashCode = Thread.currentThread().hashCode(); //區分不同工作進程的輸出
try{
debug(hashCode+ " [*] Waiting for messages Receiving...");
//不存在隊列時創建臨時隊列此時隊列須要爲非持久化類型(含消息)
String queueName =channel.queueDeclare().getQueue();
//聲明轉發器
channel.exchangeDeclare(exchangeName,exchangeType);
//direct & bindingKey
channel.queueBind(queueName,exchangeName,bindingKey);
//指定消費隊列(是否開啓應答模式:默認關閉)
Stringop_result = channel.basicConsume(queueName, true,this);
if("".equals(op_result)){
debug("BasicConsumeConfig Consumer Queue Error!");
}
}catch (IOException e) {
debug("ConsumerDelivery Error,Msg info:" + e.getMessage());
}catch (Exception e) {
debug("ErrorIs Opening,Msg info:" + e.getMessage());
}
}
@Override
public void handleCancel(String arg0)throws IOException {
debug("===handleCancel==="+arg0);
}
@Override
public void handleCancelOk(String arg0) {
debug("===handleCancelOk==="+arg0);
}
@Override
public void handleConsumeOk(String consumeOk) {
}
@Override
public void handleDelivery(String consumerTag, Envelope env,
BasicPropertiesprops, byte[] body) throws IOException {
messageInfo= (MessageInfo) SerializationUtils.deserialize(body);
messageInfo.setHashCode(hashCode);
//下面兩個在單個接收單隻出現一次,這裏都羅列是因爲兩個接收單隻有此處代碼不同,其它地方均相同,只是爲了簡略代碼而已
//記錄操作信息到文件或數據庫
recordToFile(msgInfo.getContent());
//顯示操作信息到控制檯或系統
recordToConsole(msgInfo.getContent());
}
@Override
public void handleRecoverOk(String arg0) {
debug("===handleRecoverOk==="+arg0);
}
@Override
public void handleShutdownSignal(String arg0, ShutdownSignalException arg1) {
debug("===handleShutdownSignal==="+arg0+"===ShutdownSignalException==="+arg1.getMessage());
}
@Override
public void run() {
receiveMessage();
}
public void start() {
if(thread == null){
thread = new Thread(this);
thread.start();
}
}
}
recordToFile方法:
//記錄操作到文件
void recordToFile(String msg) {
FileOutputStreamout = null;
try{
StringlocalDir = AppUnit.class.getClassLoader().getResource("").getPath();
StringopFileName = "操作記錄【" +newSimpleDateFormat("yyyy-MM-dd").format(new Date())+"】"+".txt";
Filefile = new File(localDir,opFileName);
out= new FileOutputStream(file,true);
out.write((msg+"\r\n").getBytes());
out.flush();
out.close();
}catch(Exception e) {
e.printStackTrace();
}finally {
try{
out.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
recordToConsole方法:
//記錄顯示到控制檯
void recordToConsole(Stringmsg) {
debug("【記錄操作內容】" + msg);
}
4、測試入口
public static void main(String[]args) {
PublisherHandler publisher= null;
ReceiverHandlerreceiver = null;
ReceiverHandlerreceiver2 = null;
try{
receiver= new ReceiverHandler("exchange_direct","direct","order"); //接收者1-文件保存
ThreadreceiverThread = new Thread(receiver);
receiverThread.start();
receiver2= new ReceiverHandler("exchange_direct","direct","score"); //接收者2-記錄打印
ThreadreceiverThread2 = new Thread(receiver2);
receiverThread2.start();
publisher= newPublisherHandler(); //發送者
for(inti=0;i<5;i++) {
MessageInfomsgInfo = new MessageInfo();
Stringop = "order";
if(i==3)
op= "score";
elseif(i==4)
op= "order.pay";
String message = "【記錄操作】" + op +"【記錄編號】"+ (i+1) + "【記錄日期】"+new SimpleDateFormat("yyyy-MM-dd").format(newDate());
msgInfo.setConsumerTag("demo3_tag"); //回調標誌
msgInfo.setChannel("demo3"); //頻道
msgInfo.setContent(message); //消息內容
publisher.sendMessage(msgInfo, "exchange_direct","direct",op);
}
}catch (IOException | TimeoutException e) {
e.printStackTrace();
}finally {
publisher.close();
}
}
5、結果顯示
保存記錄接收端:
打印控制接收端:
上圖的結果,已經證實了最開始時我們的預期,只保存”order”類型消息,只打印”score”類型的消息到控制檯。
RabbitMQ服務之路由篇就介紹到這裏,由於作者水平有限,如有問題請在評論發言或是QQ羣(245389109(新))討論,謝謝。