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