RabbitMQ服务之键值匹配篇

RabbitMQ服务之键值匹配篇

 

相关声明:

1、转载请标明出处:

http://blog.csdn.net/why_2012_gogo/article/details/54136778

2、本系列文章均来自于实际项目、官网、网络资源整理而来,并自己进行修改、优化及调试,内容仅供参考;

 

在上一篇文章中,我们介绍了RabbitMQ服务的主题订阅转发机制,该机制使接收端可以设置过滤条件,接收满足条件的类型消息,它需要接收端设置的主题条件或规则需要模糊匹配,然后接收到符合正则主题的对应消息,不清楚的同学,可以参看文章《RabbitMQ服务之主题订阅篇》。而在这里,我们继续介绍一种不常使用的转发器机制,称之为“键值匹配”。对于它的原理,这里不做赘述,在最开始的第一篇文章就已经介绍,不清的读者可以参考文章《RabbitMQ服务之入门篇》

 

l   转发器

l   例子

 

 

一、转发器

1、主题说明

这里要介绍的转发器,我们称之为“键值匹配”转发器,理由是消息接收端可以设置自己想要接收到的消息,不同接收端设置不同的键值对过滤条件,同时接收端必须设置”x-match”,它的匹配方式分为any和all两种,前者代表只要完全匹配任何一个条件就可以收到对应匹配的消息,而后者则代表必须匹配所有设置的条件,否则不能收到任何消息,而发送端同样发送所有类型的消息,这就形成了一种类似根据键值对类型条件转发机制。那么我们该做怎样的处理才能正常使用,这里先提前了解下它的使用规则吧,后面的例子会详细介绍。

 

需要额外说明的是,键值headers转发机制规则为完全匹配,也就是接收端可以设置指定类型规则过滤条件,实际上使用了类Hashtable键值对的过滤条件。

 

NOTE:

any代表匹配任意个条件键值对;

all代表必须匹配所有条件键值对;

 

 

2、两端处理

在接收端,我们都需要设置的键值对匹配方式bindingKey(any或是all),然后再行对发送端和接收端设置统一的Map<k,v>键值对条件。对于发送端,需要这样来处理:

channel.exchangeDeclare(EXCHANGE_NAME,EXCHANGE_TYPE); 

Builder props =newBasicProperties.Builder();

props.headers(HEADERS_MAP);  // 键值对MAP<K,V>

channel.basicPublish(EXCHANGE_NAME""props.build()"message".getBytes());

 

而对于接收端,我们需要这样处理(两个参数分别为:转发器名字和类型):

channel.exchangeDeclare(EXCHANGE_NAME,EXCHANGE_TYPE); 

 

我们需要将转发器与队列进行绑定,具体如下:

mqBasic.getHeadersMap().put("x-match",bindingKey);  //匹配方式any/all

channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "",HEADERS_MAP);  //键值对MAP

 

如何处理接收端返回消息,应该如下操作:

channel.basicConsume(QUEUE_NAME,true, consume);

 

 

二、例子

这里我们继续以上一篇文章中的用户操作记录为例,来说明RabbitMQ服务headers转发器的使用特点。我们同样设定两个消息接收端,和一个消息发送端;一个接收端依旧负责保存记录到文件,另一个接收端依旧打印记录到控制台,不同的是保存的记录有所过滤,只保存订单满足(“order.pay”,”order.pay”)类型的消息,而打印的记录也只是打印积分满足(”score.exchange”, ”score.exchange”)类型消息,而发送消息端会同时发送多种类型消息,预期情况下,接收端一只保存满足(“order.pay”,”order.pay”)消息,接收端二只打印满足(”score.exchange”, ”score.exchange”)消息,那么接下来我们来验证下这个预期的目标。

 

1、基类

BaseObject.java:

publicclassBaseObject {

    staticLogger 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:

publicclass BaseConnectorextends 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:

publicclass PublisherHandlerextends BaseConnector {

   

    public PublisherHandler()throws IOException,TimeoutException {

       super();

    }

 

    public void sendMessage(MessageInfo messageInfo,String exchangeName,String exchangeType) {

       try{

           //声明转发器

           channel.exchangeDeclare(exchangeName,exchangeType);

           //给转发器,发送消息,并设置键值对匹配过滤条件

           Builder props =newBasicProperties.Builder();

           props.headers(messageInfo.getHeadersMap());         

           channel.basicPublish(exchangeName,"", props.build(),                          SerializationUtils.serialize(messageInfo));

       }catch (IOException e) {

           debug("RabbitMQSend Message Error:"+e.getMessage());

       }

    }

   

    publicvoid close() {

       super.close();

    }  

}

 

这里我们修改了MessageInfo.java消息类,内容如下:

publicclassMessageInfo implements Serializable {

    privatestatic final long serialVersionUID = 1L;

   

    privateString channel;  //消息渠道

    privateString content;  //消息内容

    privateString consumerTag; //接收TAG

    privateboolean consumeOk;  //收到消息状态

   

    private Map<String,Object> headersMap; //针对headers转发器的内容载体

   

    privateint hashCode;    //异步线程标志

   

    publicString getChannel() {

       returnchannel;

    }

 

    publicvoid setChannel(String channel) {

       this.channel= channel;

    }

 

    publicString getContent() {

       returncontent;

    }

 

    publicvoid setContent(String content) {

       this.content= content;

    }

 

    publicString getConsumerTag() {

       returnconsumerTag;

    }

 

    publicvoid setConsumerTag(String consumerTag) {

       this.consumerTag= consumerTag;

    }

 

    publicint getHashCode() {

       returnhashCode;

    }

 

    publicvoid setHashCode(int hashCode) {

       this.hashCode= hashCode;

    }

 

    publicboolean isConsumeOk() {

       returnconsumeOk;

    }

 

    publicvoid setConsumeOk(boolean consumeOk) {

       this.consumeOk= consumeOk;

    }

 

    publicMap<String, Object> getHeadersMap() {

       returnheadersMap;

    }

 

    publicvoid setHeadersMap(Map<String, Object> headersMap) {

       this.headersMap= headersMap;

    }

}

 

 

3、接收端

ReceiverHandle.java:

publicclass ReceiverHandlerextends BaseConnector implements Runnable,Consumer {

    private MessageInfo messageInfo =new MessageInfo();

    private int hashCode;

    private String  exchangeName;   //转发器名字

    private Stirng  exchangeType;   //转发器类型

    private  String bindingKey; // 键值过滤方式 any/all

    private  Map<String,Object>  headers;  // 键值匹配条件MAP<K,V>

   

    private volatile Thread thread;

 

    public ReceiverHandler(String exchangeName,String exchangeType,String   bindingKey, Map<String,Object> headers)throws IOException, TimeoutException {

       this.exchangeName= exchangeName;

       this.exchangeType = exchangeType;

       this.bindingKey= bindingKey;

       this.headers = headers;

       super();

    }

   

    public void receiveMessage() {

       hashCode = Thread.currentThread().hashCode(); //区分不同工作进程的输出

       

       try{

           debug(hashCode+ " [*] Waiting for messages Receiving...");

          

           //不存在队列时创建临时队列此时队列须要为非持久化类型(含消息)

           String  queueName =channel.queueDeclare().getQueue();

           //声明转发器

           channel.exchangeDeclare(exchangeName,exchangeType);

           //headers & bindingKey

           headers.put("x-match",bindingKey); // any/all

           channel.queueBind(queueName,exchangeName, "",headers);

           //指定消费队列(是否开启应答模式:默认关闭)

           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

    publicvoid handleCancel(String arg0)throws IOException {

       debug("===handleCancel==="+arg0);

    }

 

    @Override

    publicvoid handleCancelOk(String arg0) {

       debug("===handleCancelOk==="+arg0);

    }

 

    @Override

    publicvoid handleConsumeOk(String consumeOk) {

    }

 

    @Override

    publicvoid 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

    publicvoid handleRecoverOk(String arg0) {

       debug("===handleRecoverOk==="+arg0);

    }

 

    @Override

    publicvoid handleShutdownSignal(String arg0, ShutdownSignalException arg1) {

       debug("===handleShutdownSignal==="+arg0+"===ShutdownSignalException==="+arg1.getMessage());

    }

 

    @Override

    publicvoid run() {

       receiveMessage();

    }

   

    publicvoid start() {

       if(thread == null){

         thread = new Thread(this);

         thread.start();

       }

    }

}

 

recordToFile方法:

    //记录操作到文件

    voidrecordToFile(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方法:

    //记录显示到控制台

    voidrecordToConsole(Stringmsg) {

       debug("【记录操作内容】" + msg);

    }

 

 

4、测试入口

publicstatic void main(String[]args) {

       PublisherHandler publisher = null;

       ReceiverHandler receiver = null;

       ReceiverHandler receiver2 = null;

       try{

           Map<String,Object> headers =newHashtable<String,Object>();

           headers.put("order.pay","order.pay"); //匹配任意

      

            receiver = new ReceiverHandler("exchange_headers","headers","any",headers); //接收者1-文件保存

           Thread receiverThread = new Thread(receiver);

           receiverThread.start();

 

           headers.put("score.exchange"," score.exchange ");   //完全匹配两个键值条件必须都满足才能收到消息

           headers.put("score.give"," score.give");

 

           receiver2= new ReceiverHandler("exchange_headers","headers","all",headers); //接收者2-记录打印

           Thread receiverThread2 = new Thread(receiver2);

           receiverThread2.start();

          

           Map<String,Object> headers2= new Hashtable<String,Object>();

           publisher= new PublisherHandler(mqBasic);    //发送者

           for(inti=0;i<5;i++) {

              MessageInfomsgInfo = new MessageInfo();

              if(i==1)

                  headers2.put("order.delete","order.delete");

              elseif(i==2)

                  headers2.put("score.exchange","score.exchange");

              elseif(i==3)

                  headers2.put("score.give","score.give");

              elseif(i==4)

                  headers2.put("order.pay","order.pay");

                 

              String message = "【记录操作】" + headers2.toString() +"【记录编号】"+ (i+1) + "【记录日期】"+new SimpleDateFormat("yyyy-MM-dd").format(newDate());

              msgInfo.setConsumerTag("demo5_tag");   //回调标志

              msgInfo.setChannel("demo5");    //频道

              msgInfo.setContent(message);    //消息内容

              msgInfo.setHeadersMap(headers2);  //装入map<k,v>

              publisher.sendMessage(msgInfo,"");

           }     

       }catch (IOException | TimeoutException e) {

           e.printStackTrace();

       }finally {

           publisher.close();

       }

}

 

 

5、结果显示

保存记录接收端:

 

 

打印控制接收端:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

消息队列RabbitMQ服务之键值对匹配就介绍到这里,由于作者水平有限,如有问题请在评论发言或是QQ群(245389109(新))讨论,谢谢。

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云水之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值