本章我们重点学习一下Rabbit里面的exchange(交换器)的知识。交换器分类RabbitMQ的Exchange(交换器)分为四类:
Direct (default)
Headers
Fanout
Topic
The headers switch allows you to match the header of the AMQP message instead of the routing key, in addition to the headers switch and the direct switch is exactly the same, but the performance is very poor, almost useless, so we do not explain this article.
注意:fanout、topic交换器是没有历史数据的,也就是说对于中途创建的队列,获取不到之前的消息。1、direct交换器direct为默认的交换器类型,也非常的简单,如果路由键匹配的话,消息就投递到相应的队列,
Picture description (max. 50 words)
使用代码:channel.basicPublish("", QueueName, null, message)推送direct交换器消息到对于的队列,空字符为默认的direct交换器,用队列名称当做路由键。direct交换器代码示例发送端:
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
//Declaration Queue parameter Description: Parameter one: queue name, parameter two: whether to persist; parameter three: whether the exclusive mode; parameter four: whether the consumer deletes the queue when disconnected; parameter five: message other parameters "
Channel.queuedeclare (config. QueueName, False, False, false, NULL);
String message = String.Format ("Current time:%s", new Date (). GetTime ());
//Push content parameter description: Parameter one: switch name; parameter two: queue name, parameter three: Other properties of the message-headers information of the route; parameter four: Message body "
Channel.basicpublish (" ", config. QueueName, NULL, Message.getbytes ("UTF-8"));
receiving end, receiving messages continuously:
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
//Declaration Queue parameter Description: Parameter one: queue name, parameter two: whether to persist; parameter three: whether the exclusive mode; parameter four: whether the consumer deletes the queue when disconnected; parameter five: message other parameters "
Channel.queuedeclare (config. QueueName, False, False, false, NULL);
Consumer Defaultconsumer = new Defaultconsumer (channel) {br/> @Override
Byte[] body) throws IOException {
String message = new String (Body, "utf-8");//Message body
system.out.println (workname + "Receive message = +" + message);
Channel.basicack (Envelope.getdeliverytag (), false);//Manual confirmation message "parameter description: Parameter one: The index of the message; parameter two: whether bulk reply, true bulk acknowledgment of messages smaller than the current ID "
}
};
Channel.basicconsume (config. QueueName, False, "", Defaultconsumer);
Receive side, get a single message
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
Channel.queuedeclare (config. QueueName, False, False, false, NULL);
GetResponse resp = channel.basicget (config. QueueName, false);
String message = new String (Resp.getbody (), "UTF-8");
Channel.basicack (Resp.getenvelope (). Getdeliverytag (), false); Message Confirmation
Persistent message gets used: Basic.consume; single message get used: Basic.get.
注意:不能使用for循环单个消息消费来替代持续消息消费,因为这样性能很低;消息的发后既忘特性发后既往只的是接受者不知道消息的来源是谁发送的,如果想要指定消息的发送者,需要包含在发送内容里面,这点就像我们在信件里面注明自己的姓名一样,只有这样才能知道发送者是谁。消息确认看了上面的代码我们可以知道,消息接收到之后必须使用channel.basicAck()方法手动确认(非自动确认删除模式下),那么问题来了。消息收到未确认会怎么样?如果应用程序接收了消息,因为bug忘记确认接收的话,消息在队列的状态会从“Ready”变为“Unacked”,
Picture description (max. 50 words)
如果消息收到却未确认,Rabbit将不会再给这个应用程序发送更多的消息了,这是因为Rabbit认为你没有准备好接收下一条消息。此条消息会一直保持Unacked的状态,直到你确认了消息,或者断开与Rabbit的连接,Rabbit会自动把消息改完Ready状态,分发给其他订阅者。当然你可以利用这一点,让你的程序延迟确认该消息,直到你的程序处理完相应的业务逻辑,这样可以有效的防治Rabbit给你过多的消息,导致程序崩溃。消息确认Demo:
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
Channel.queuedeclare (config. QueueName, False, False, false, NULL);
GetResponse resp = channel.basicget (config. QueueName, false);
String message = new String (Resp.getbody (), "UTF-8");
Channel.basicack (Resp.getenvelope (). Getdeliverytag (), false);
Channel.basicack (Long Deliverytag, Boolean multiple) is the message acknowledgment, parameter 1: The ID of the message, Parameter 2: Whether bulk reply, true bulk acknowledgment of messages smaller than the secondary ID.
总结:消费者消费的每条消息都必须确认。消息拒绝消息在确认之前,可以有两个选择:选择1:断开与Rabbit的连接,这样Rabbit会重新把消息分派给另一个消费者;选择2:拒绝Rabbit发送的消息使用channel.basicReject(long deliveryTag, boolean requeue),参数1:消息的id;参数2:处理消息的方式,如果是true,Rabbib会重新分配这个消息给其他订阅者,如果设置成false的话,Rabbit会把消息发送到一个特殊的“死信”队列,用来存放被拒绝而不重新放入队列的消息。消息拒绝Demo:
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
Channel.queuedeclare (config. QueueName, False, False, false, NULL);
GetResponse resp = channel.basicget (config. QueueName, false);
String message = new String (Resp.getbody (), "UTF-8");
Channel.basicreject (Resp.getenvelope (). Getdeliverytag (), true); Message rejection
2. Fanout Switch--publish/Subscribe mode
fanout有别于direct交换器,fanout是一种发布/订阅模式的交换器,当你发送一条消息的时候,交换器会把消息广播到所有附加到这个交换器的队列上。比如用户上传了自己的头像,这个时候图片需要清除缓存,同时用户应该得到积分奖励,你可以把这两个队列绑定到图片上传的交换器上,这样当有第三个、第四个上传完图片需要处理的需求的时候,原来的代码可以不变,只需要添加一个订阅消息即可,这样发送方和消费者的代码完全解耦,并可以轻而易举的添加新功能了。和direct交换器不同,我们在发送消息的时候新增channel.exchangeDeclare(ExchangeName, "fanout"),这行代码声明fanout交换器。发送端:
Final String exchangename = "Fanoutec"; Exchanger name
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
Channel.exchangedeclare (Exchangename, "fanout"); Declaring a fanout exchanger
String message = "Time:" + new Date (). GetTime ();
Channel.basicpublish (Exchangename, "", NULL, Message.getbytes ("UTF-8"));
The receive message differs from direct, and we need to declare the fanout router and bind to the fanout switch using the default queue.
接收端:
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
Channel.exchangedeclare (Exchangename, "fanout");//Declaration fanout Exchanger
String queuename = Channel.queuedeclare (). Getqueue (); Declaration Queue
Channel.queuebind (queuename, Exchangename, "");
Consumer Consumer = new Defaultconsumer (channel) {br/> @Override
Byte[] body) throws IOException {
String message = new String (Body, "UTF-8");
}
};
Channel.basicconsume (QueueName, true, consumer);
Fanout and direct differ most on the receiving side, and fanout need to bind the queue to the corresponding exchanger for subscribing to the message.
其中channel.queueDeclare().getQueue()为随机队列,Rabbit会随机生成队列名称,一旦消费者断开连接,该队列会自动删除。注意:对于fanout交换器来说routingKey(路由键)是无效的,这个参数是被忽略的。3、topic交换器——匹配订阅模式最后介绍的是topic交换器,topic交换器运行和fanout类似,但是可以更灵活的匹配自己想要订阅的信息,这个时候routingKey路由键就排上用场了,使用路由键进行消息(规则)匹配。假设我们现在有一个日志系统,会把所有日志级别的日志发送到交换器,warning、log、error、fatal,但我们只想处理error以上的日志,要怎么处理?这就需要使用topic路由器了。topic路由器的关键在于定义路由键,定义routingKey名称不能超过255字节,使用“.”作为分隔符,例如:com.mq.rabbit.error。消费消息的时候routingKey可以使用下面字符匹配消息:
"*" can match all content;
"#" matches 0 and more characters;
For example, a "Com.mq.rabbit.error" message was released:
能匹配上的路由键:
cn.mq.rabbit.*
cn.mq.rabbit.#
#.error
cn.mq.#
Unable to match the routing key on:
Cn.mq.
. Error
So if you want to subscribe to all messages, you can use a "#" match.
注意:fanout、topic交换器是没有历史数据的,也就是说对于中途创建的队列,获取不到之前的消息。发布端:
String Routingkey = "Com.mq.rabbit.error";
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
Channel.exchangedeclare (exchangename, "topic");//Declaration topic Exchanger
String message = "Time:" + new Date (). GetTime ();
Channel.basicpublish (exchangename, Routingkey, NULL, Message.getbytes ("UTF-8"));
Receive end:
Connection conn = Connectionfactoryutil.getrabbitconnection ();
Channel channel = Conn.createchannel ();
Channel.exchangedeclare (exchangename, "topic"); Declaring a topic exchanger
String queuename = Channel.queuedeclare (). Getqueue (); declaring queues
String Routingkey = "#.error";
Channel.queuebind (QueueName, Exchangename, Routingkey);
Consumer Consumer = new Defaultconsumer (channel) {br/> @Override
Byte[] body) throws IOException {
String message = new String (Body, "UTF-8");
System.out.println (Routingkey + "| Receive message = +" + message);
}
};
Channel.basicconsume (QueueName, true, consumer);
Extended section-Custom thread pool
如果需要更大的控制连接,用户可自己设置线程池,代码如下:
Import Java.util.concurrent.ExecutorService;
Import java.util.concurrent.Executors;
Executorservice es = Executors.newfixedthreadpool (20);
Connection conn = Factory.newconnection (es);
Actually read the source of the classmate may know, factory.newconnection itself is the default thread pool mechanism, connectionfactory.class part of the source code as follows:
Private Executorservice Sharedexecutor;
Public Connection newconnection () throws IOException, TimeoutException {
Return Newconnection (This.sharedexecutor, Collections.singletonlist (New Address (GetHost (), Getport ()));
}
public void Setsharedexecutor (Executorservice executor) {
This.sharedexecutor = executor;
}
Where This.sharedexecutor is the default thread pool, you can set the thread pool for connectionfactory through the Setsharedexecutor () method, or null if not set.
用户如果自己设置了线程池,像本小节第一段代码写的那样,那么当连接关闭的时候,不会自动关闭用户自定义的线程池,所以用户必须自己手动关闭,通过调用shutdown()方法,否则可能会阻止JVM的终止。官方的建议是只有在程序出现严重性能瓶颈的时候,才应该考虑使用此功能。
RABBITMQ Exchanger Exchange Introduction and Practice