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(新))讨论,谢谢。