RabbitMQ服务之主题订阅篇
相关声明:
1、转载请标明出处:
http://blog.csdn.net/why_2012_gogo/article/details/54091632
2、本系列文章均来自于实际项目、官网、网络资源整理而来,并自己进行修改、优化及调试,内容仅供参考;
在上一篇文章中,我们介绍了RabbitMQ服务的路由转发机制,该机制使接收端可以设置过滤接收满足条件的类型消息,它需要接收端设置的路由条件或规则必须完全匹配,才能接收到对应消息,不清楚的同学,可以参看文章《RabbitMQ服务之路由篇》。而在这里,我们继续介绍一种更加灵活的转发器机制,称之为“主题订阅”。对于它的原理,这里不做赘述,在最开始的第一篇文章就已经介绍,不清的读者可以参考文章《RabbitMQ服务之入门篇》。
l 转发器
l 例子
一、转发器
1、主题说明
这里要介绍的转发器,我们称之为“主题订阅”转发器,理由是消息接收端可以设置自己想要接收到的消息,所以不同接收端设置不同的主题过滤条件,而发送端同样发送所有类型的消息,这就形成了一种类似根据某种主题类型条件转发机制。那么我们该做怎样的处理才能正常使用,这里先提前了解下它的使用规则吧,后面的例子会详细介绍。
需要额外说明的是,主题转发机制规则为不完全匹配,也就是接收端可以设置指定类型规则过滤条件,实际上使用了类正则的过滤条件,例如:order.*会接收到order.pay及order.delete等条件的消息。
NOTE:
*代表匹配一个关键字;
#代表匹配一个或多个关键字;
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服务topic主题转发器的使用特点。我们同样设定两个消息接收端,和一个消息发送端;一个接收端依旧负责保存记录到文件,另一个接收端依旧打印记录到控制台,不同的是保存的记录有所过滤,只保存订单满足”order.*”类型的消息,而打印的记录也只是打印积分满足”score.*”类型消息,而发送消息端会同时发送”order.*”、”score.*”三种类型消息,预期情况下,接收端一只保存满足”order.*”消息,接收端二只打印满足”score.*”的消息,那么接下来我们来验证下这个预期的目标。
1、基类
BaseObject.java:
publicclassBaseObject {
staticLogger log = LogManager.getLogger(BaseObject.class.getSimpleName());
publicstatic void debug(String debug) {
log.debug(debug);
}
publicstatic void debug(Object obj) {
if(null!= obj)
log.debug(obj.toString());
}
publicstatic void error(String error) {
log.error(error);
}
}
该类为所有对象继承的基类,其中LogManager为Java中的Log4j的使用,这里不做相关介绍,请读者查阅相关资料。
BaseConnector.java:
publicclass BaseConnectorextendsBaseObject {
protectedChannel channel;
protectedConnection connection;
publicBaseConnector()throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory(); //新建链接工厂并绑定主机地址
factory.setHost("127.0.0.1"); //设置MabbitMQ所在主机ip或者主机名
connection = factory.newConnection(); //创建连接
channel = connection.createChannel(); //创建频道
}
}
protectedvoid close() { //关闭频道及链接
try {
channel.close();
connection.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
2、发送端
PublisherHandle.java:
publicclass PublisherHandlerextendsBaseConnector {
publicPublisherHandler()throws IOException,TimeoutException {
super();
}
publicvoid sendMessage(MessageInfo messageInfo,String exchangeName,StringexchangeType,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:
publicclass ReceiverHandlerextendsBaseConnector implements Runnable,Consumer {
privateMessageInfo messageInfo =new MessageInfo();
privateint hashCode;
private String exchangeName; //转发器名字
private Stirng exchangeType; //转发器类型
private String bindingKey; // 主题过滤条件
private volatile Thread thread;
publicReceiverHandler(String exchangeName,String exchangeType,String bindingKey)throws IOException, TimeoutException {
this.exchangeName= exchangeName;
this.exchangeType = exchangeType;
this.bindingKey= bindingKey;
super();
}
publicvoid receiveMessage() {
hashCode = Thread.currentThread().hashCode(); //区分不同工作进程的输出
try{
debug(hashCode+ " [*] Waiting for messages Receiving...");
//不存在队列时创建临时队列此时队列须要为非持久化类型(含消息)
String queueName =channel.queueDeclare().getQueue();
//声明转发器
channel.exchangeDeclare(exchangeName,exchangeType);
//topic & 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
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) {
PublisherHandlerpublisher = null;
ReceiverHandlerreceiver = null;
ReceiverHandlerreceiver2 = null;
try{
receiver= new ReceiverHandler("exchange_topic","topic","order.*"); //接收者1-文件保存
ThreadreceiverThread = new Thread(receiver);
receiverThread.start();
receiver2= new ReceiverHandler("exchange_topic","topic","score.*"); //接收者2-记录打印
ThreadreceiverThread2 = new Thread(receiver2);
receiverThread2.start();
publisher= newPublisherHandler(); //发送者
for(inti=0;i<5;i++) {
MessageInfomsgInfo = new MessageInfo();
String op = "order";
if(i==1)
op= "order.delete";
elseif(i==2)
op= "score.exchange";
elseif(i==3)
op= "score.give";
elseif(i==4)
op= "order.pay";
String message = "【记录操作】" + op +"【记录编号】"+ (i+1) + "【记录日期】"+newSimpleDateFormat("yyyy-MM-dd").format(new Date());
msgInfo.setConsumerTag("demo4_tag"); //回调标志
msgInfo.setChannel("demo4"); //频道
msgInfo.setContent(message); //消息内容
publisher.sendMessage(msgInfo,"exchange_topic","topic",op);
}
}catch (IOException | TimeoutException e) {
e.printStackTrace();
}finally {
publisher.close();
}
}
5、结果显示
保存记录接收端:
打印控制接收端:
上图的结果,已经证实了最开始时我们的预期,只保存满足”order.*”类型消息,只打印满足”score.*”类型的消息到控制台。
消息队列RabbitMQ服务之主题订阅就介绍到这里,由于作者水平有限,如有问题请在评论发言或是QQ群(245389109(新))讨论,谢谢。