RabbitMQ学习整理
1、什么是消息队列?
概念:
消息队列(Message Queue,简称MQ),本质是个队列,FIFO先入先出,只不过队列中存放的内容是一些Message
。
2、为什么要用消息队列,应用场景?
不同系统、进程或者线程直接进行通信。
系统解耦,将要做的部分放入队列,便于模块分离。
传统模式
使用队列解耦
缓冲功能,当有大量请求要处理时,可以先入队然后依次处理,保证系统可靠性。
例如:淘宝秒杀活动等。
异步操作,有时候为了快速响应,可以使用队列来实现异步,比如邮箱验证,手机验证码发送等。
3、消息队列的几种模式。
3-1、简单模式(单生产者单消费者)
一个线程负责生产,一个线程负责总队列里取出来消费。
3-2、单生产者多消费者
一个线程生产,多个线程取出来消费。
3-3、订阅/发布模式
一个发布者发送消息,多个订阅者可以同时获取到发布的消息
3-4、路由模式
3-5、主题模式,按规则模糊匹配
3-5、广播模式
生产者发送的消息会发往每个与其绑定的队列。
4、RabbitMQ的几个组成部分
AMQP协议: Advanced Message Queuing Protocol 高级消息队列协议,是一个异步消息传递所使用的应用层协议规范。
组成:
服务主机:接收客户端请求,并作出相应处理
虚拟主机:一个服务器可以开启多个Virtual Host,每个虚拟主机都能提供完整的服务,有自己的权限控制。
生产者:发送消息
消费者:接收消息并处理
交换器:RabbitMQ中,消息不是直接发往队列,而是要先给交换器,然后交换器按照一定的路由规则发送到相对应的队列上。
路由Key:发送消息时要指定交换器和路由Key
绑定:将队列和交换器绑定起来路由Key作为绑定时的关键字
队列:消息的载体,消费者从队列中获取消息,路由器根据路由规则把消息发往对应的队列。
消息:队列中存储的信息单元。
5、工作原理
生产者发送消息时指定交换器名和路由Key,然后交换器根据路由Key与绑定信息进行比对,找到对应的队列后将信息发送出去。
消费者监听某个队列,如果有消息就取出来做对应操作,没有就阻塞。
6、交换器的几个工作模式
Direct:固定名称匹配,只有路由Key与绑定的Key一致才会将消息发送到该队列。
Topic:主题模式,路由Key可以用*#来填充
#可以匹配任意多个单词,*只能匹配一个单词
比如bingdKey x.y x.y.z
a.y a.b.z
x.*只能匹配x.y,而x.#可以匹配x.y 和 x.y.z
Fanout:广播模式
7、Demo
安装RabbitMQ,并启动服务。默认用户名密码guest。创建VirtualHost。
7-1、单生产者单消费者
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "direct", true); //声明队列 String String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, routingKey); //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } |
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "hello-exchange"; channel.exchangeDeclare(exchangeName, "direct", true); String routingKey = "hola"; //发布消息 byte[] messageBodyBytes = "quit".getBytes(); channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes); channel.close(); conn.close(); } } |
7-2、单生产者多消费者
与1:1的类似,只不过两个消费者共同消费一个队列内的信息,复制一份消费者即可。
7-3、订阅/发布模式
生产者发送的消息,发往每个订阅他的消费者那里。所有消费者都可以获取相同的信息。
发布者A
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "王力宏"; channel.exchangeDeclare(exchangeName, "fanout", true); //发布消息 byte[] messageBodyBytes = "王力宏发布的消息:啦啦啦啦啦".getBytes(); channel.basicPublish(exchangeName, "", null, messageBodyBytes); channel.close(); conn.close(); } } |
发布者B
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "赵薇"; channel.exchangeDeclare(exchangeName, "fanout", true); //发布消息 byte[] messageBodyBytes = "赵薇发布的消息:啊啊啊啊".getBytes(); channel.basicPublish(exchangeName, "", null, messageBodyBytes); channel.close(); conn.close(); } } |
订阅者A1、A2
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "fanout", true); //声明队列 String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, ""); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
订阅者B1、B2
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final channel.exchangeDeclare("赵薇", "fanout", true); //声明交换器 String //声明队列 String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, ""); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
7-4、主题模式
生产者
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "topic-exchange"; channel.exchangeDeclare(exchangeName, "topic", true); // String routingKey = "#.B"; //发布消息 for (inti = channel.basicPublish(exchangeName, routingKey, null, "ss".getBytes()); } //byte[] messageBodyBytes = "匹配消息".getBytes(); //channel.basicPublish(exchangeName, routingKey, null, channel.close(); conn.close(); } } |
消费者A
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "topic", true); //声明队列 String String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, routingKey); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
消费者B,routingKey routingKey.A
消费者C,routingKey routingKey.B
7-5、广播模式,不需要管routingKey和bindingKey是否匹配。
生产者
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "fanout-exchange"; channel.exchangeDeclare(exchangeName, "fanout", true); // String routingKey = "hola"; //发布消息 byte[] messageBodyBytes = "群发消息".getBytes(); channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes); channel.close(); conn.close(); } } |
消费者
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "fanout", true); //声明队列 String String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, routingKey); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
8、RabbitMQ与Spring整合
8-1、添加依赖
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>x.x.x</version> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>x.x.xRELEASE</version> </dependency> |
8-2、配置文件中加入rabbit服务连接配置
mq.host=real_host mq.username=guest mq.password=guest mq.port=5672 mq.vhost=real_vhost |
8-3、新建application-mq.xml文件,添加配置信息
主要用来配置连接信息、Producer配置、队列声明、交换器声明、队列与交换器的绑定、队列的监听器配置(即消费者)等。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"> <!-- <!-- <rabbit:connection-factory id="connectionFactory" host="${mq.host}" username="${mq.username}" password="${mq.password}" port="${mq.port}" virtual-host="${mq.vhost}"/> <!-- <rabbit:admin connection-factory="connectionFactory"/> <!-- <!-- spring template声明--> <!-- 可以不指定交换器,在每次发送请求时需要指明发给哪个交换器 <rabbit:template exchange="test" id="amqpTemplate" connection-factory="connectionFactory"/><!-- <!-- <!-- --> <!--定义queue <rabbit:queue name="mq.A" durable="true" <rabbit:queue name="mq.B" durable="true" <rabbit:queue name="mq.C" durable="true" <!-- <rabbit:direct-exchange name="test" durable="true" <rabbit:bindings> <rabbit:binding queue="mq.A" key="key.A"/> <rabbit:binding queue="mq.B" key="key.B"/> <rabbit:binding queue="mq.C" key="key.C"/> </rabbit:bindings> </rabbit:direct-exchange> <!-- queues:监听的队列,多个的话用逗号(,)分隔 ref:监听器 --> <!-- <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1"> <!-- <rabbit:listener queues="mq.A" <rabbit:listener queues="mq.B" <rabbit:listener queues="mq.C" </rabbit:listener-container> </beans> |
生产者:Spring提供的AmqpTemplate,使用注解注入即可使用
@Autowired private AmqpTemplate amqpTemplate; publicvoid sendMsg(String amqpTemplate.convertAndSend("routingKey", "发送了消息A"); } |
监听器:即收到队列的消息后作何处理,要实现ChannelAwareMessageListener
例如:监听器listenerA
@Component publicclass privatefinalstatic Log @Override publicvoid // String System.out.println("A logger.error(msg); } } |
9、持久化机制
有可能遇到程序崩溃或者Rabbit服务器宕机的情况,那么如果没有持久化机制,所有数据都会丢失。
交换器持久化
Durable:是否持久化参数设为True即可
channel.exchangeDeclare(exchangeName, type, true);
队列持久化channel.queueDeclare("A",true,false,false,null).getQueue();
消息持久化
在之前,消息分发给consumer后立即就会被标记为已消费,这时候如果consumber接到了一个消息但是还没有来的及处理就异常退出,那么这个消息的状态是已被消费的,于是就会造成消息丢失的问题。
处理的代码也很简单,一共有两个步骤。第一个把autoAck改成false
//消费结果需要进行确认
channel.BasicConsume("firstTest",
false, consumer);
第二部分就是在我们消费完成后进行确认
//进行交付,确定此消息已经处理完成
channel.BasicAck(deliveryTag: e.DeliveryTag,
multiple: false);
如果没有进行确认queue会把这个消息交给其它的consumer去处理,如果没有交付的代码,那么这个消息会一直存在。
消息持久化步骤:
void basicPublish(String void basicPublish(String throws IOException; void basicPublish(String throws IOException; |
exchange表示exchange的名称
routingKey表示routingKey的名称
body代表发送的消息体
MessageProperties.PERSISTENT_TEXT_PLAIN 可以设置为持久化,类型为文本
MessageProperties.PERSISTENT_BASIC 类型为二进制数据
mandatory当mandatory标志位设置为true时,如果exchange无法找到一个队列取转发,就返回给生产者。
immediate当immediate标志位设置为true时,如果exchange要转发的队列上没有消费者时,就返回给生产者。
10、消息确认机制
概述
RabbitMQ可能会遇到的一个问题,即生成者不知道消息是否真正到达broker,那么有没有高效的解决方式呢?答案是采用Confirm模式。
producer端confirm模式的实现原理
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。
在channel 被设置成 confirm 模式之后,所有被 publish 的后续消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack 。
确认机制的三种实现
- 普通confirm模式:每发送一条消息后,调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm了。
- 批量confirm模式:每发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm。
- 异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法。
普通confirm
//开启Procedure确认机制 channel.confirmSelect(); //发布消息 channel.basicPublish(exchangeName, routingKey,null,message); //消息发送成功的确认,也可以设置超时时间 if (channel.waitForConfirms([long System.out.println("send } else { System.out.println("send } |
批量Confirm
批量发送消息后再进行确认。
异步Confirm
//待确认的序列 SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>()); //开启确认机制 channel.confirmSelect(); //添加处理事件 channel.addConfirmListener(new ConfirmListener() { publicvoid handleAck(longdeliveryTag, booleanmultiple) throws IOException { if (multiple) { confirmSet.headSet(deliveryTag + 1).clear(); } else { confirmSet.remove(deliveryTag); } System.out.println("发送成功..."); } publicvoid handleNack(longdeliveryTag, booleanmultiple) throws IOException { System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple); if (multiple) { confirmSet.headSet(deliveryTag + 1).clear(); } else { confirmSet.remove(deliveryTag); } System.err.println("发送失败..."); } }); //发送消息 for (inti = 0; i < 10; i++) { //获取下个发送序号 longnextSeqNo = channel.getNextPublishSeqNo(); channel.basicPublish(exchangeName, routingKey, null, ("车票ID:" + i).getBytes()); //加入待处理集合中 confirmSet.add(nextSeqNo); //休息0.2s Thread.sleep(200); } |
Consumer端的确认
自动确认,默认是自动确认,即获取消息后,直接确认。
手动确认,给当前消息设置状态,当手动ack后服务端才会删除该消息,如果返回nack,重新入队。
//手动确认 booleanautoAck = false; channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) { @Override publicvoid handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties byte[] body) throws IOException { //…其他处理操作 longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); } }); |
附件:
https://blog.csdn.net/wangshuminjava/article/details/80998992
深入解读RabbitMQ工作原理及简单使用
https://blog.csdn.net/boonya/article/details/64904706
RabbitMQ深入学习指导
v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}
Normal
0
false
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:1.0pt;}
table.MsoTableGrid
{mso-style-name:网格型;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-priority:39;
mso-style-unhide:no;
border:solid windowtext 1.0pt;
mso-border-alt:solid windowtext .5pt;
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-border-insideh:.5pt solid windowtext;
mso-border-insidev:.5pt solid windowtext;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:1.0pt;}
1、什么是消息队列?
概念:
消息队列(Message
Queue,简称MQ),本质是个队列,FIFO先入先出,只不过队列中存放的内容是一些Message
。
2、为什么要用消息队列,应用场景?
不同系统、进程或者线程直接进行通信。
系统解耦,将要做的部分放入队列,便于模块分离。
传统模式
使用队列解耦
缓冲功能,当有大量请求要处理时,可以先入队然后依次处理,保证系统可靠性。
例如:淘宝秒杀活动等。
异步操作,有时候为了快速响应,可以使用队列来实现异步,比如邮箱验证,手机验证码发送等。
3、消息队列的几种模式。
3-1、简单模式(单生产者单消费者)
一个线程负责生产,一个线程负责总队列里取出来消费。
3-2、单生产者多消费者
一个线程生产,多个线程取出来消费。
3-3、订阅/发布模式
一个发布者发送消息,多个订阅者可以同时获取到发布的消息
3-4、路由模式
3-5、主题模式,按规则模糊匹配
3-5、广播模式
生产者发送的消息会发往每个与其绑定的队列。
4、RabbitMQ的几个组成部分
AMQP协议: Advanced Message Queuing
Protocol 高级消息队列协议,是一个异步消息传递所使用的应用层协议规范。
组成:
服务主机:接收客户端请求,并作出相应处理
虚拟主机:一个服务器可以开启多个Virtual
Host,每个虚拟主机都能提供完整的服务,有自己的权限控制。
生产者:发送消息
消费者:接收消息并处理
交换器:RabbitMQ中,消息不是直接发往队列,而是要先给交换器,然后交换器按照一定的路由规则发送到相对应的队列上。
路由Key:发送消息时要指定交换器和路由Key
绑定:将队列和交换器绑定起来路由Key作为绑定时的关键字
队列:消息的载体,消费者从队列中获取消息,路由器根据路由规则把消息发往对应的队列。
消息:队列中存储的信息单元。
5、工作原理
生产者发送消息时指定交换器名和路由Key,然后交换器根据路由Key与绑定信息进行比对,找到对应的队列后将信息发送出去。
消费者监听某个队列,如果有消息就取出来做对应操作,没有就阻塞。
6、交换器的几个工作模式
Direct:固定名称匹配,只有路由Key与绑定的Key一致才会将消息发送到该队列。
Topic:主题模式,路由Key可以用*#来填充
#可以匹配任意多个单词,*只能匹配一个单词
比如bingdKey x.y x.y.z
a.y a.b.z
x.*只能匹配x.y,而x.#可以匹配x.y 和 x.y.z
Fanout:广播模式
7、Demo
安装RabbitMQ,并启动服务。默认用户名密码guest。创建VirtualHost。
7-1、单生产者单消费者
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "direct", true); //声明队列 String String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, routingKey); //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } |
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "hello-exchange"; channel.exchangeDeclare(exchangeName, "direct", true); String routingKey = "hola"; //发布消息 byte[] messageBodyBytes = "quit".getBytes(); channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes); channel.close(); conn.close(); } } |
7-2、单生产者多消费者
与1:1的类似,只不过两个消费者共同消费一个队列内的信息,复制一份消费者即可。
7-3、订阅/发布模式
生产者发送的消息,发往每个订阅他的消费者那里。所有消费者都可以获取相同的信息。
发布者A
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "王力宏"; channel.exchangeDeclare(exchangeName, "fanout", true); //发布消息 byte[] messageBodyBytes = "王力宏发布的消息:啦啦啦啦啦".getBytes(); channel.basicPublish(exchangeName, "", null, messageBodyBytes); channel.close(); conn.close(); } } |
发布者B
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "赵薇"; channel.exchangeDeclare(exchangeName, "fanout", true); //发布消息 byte[] messageBodyBytes = "赵薇发布的消息:啊啊啊啊".getBytes(); channel.basicPublish(exchangeName, "", null, messageBodyBytes); channel.close(); conn.close(); } } |
订阅者A1、A2
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "fanout", true); //声明队列 String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, ""); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
订阅者B1、B2
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final channel.exchangeDeclare("赵薇", "fanout", true); //声明交换器 String //声明队列 String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, ""); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
7-4、主题模式
生产者
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "topic-exchange"; channel.exchangeDeclare(exchangeName, "topic", true); // String routingKey = "#.B"; //发布消息 for (inti = channel.basicPublish(exchangeName, routingKey, null, "ss".getBytes()); } //byte[] messageBodyBytes = "匹配消息".getBytes(); //channel.basicPublish(exchangeName, routingKey, null, channel.close(); conn.close(); } } |
消费者A
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "topic", true); //声明队列 String String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, routingKey); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
消费者B,routingKey routingKey.A
消费者C,routingKey routingKey.B
7-5、广播模式,不需要管routingKey和bindingKey是否匹配。
生产者
publicclass publicstaticvoid //创建连接工厂 ConnectionFactory factory = new factory.setUsername("guest"); factory.setPassword("guest"); //设置 factory.setHost("localhost"); //建立到代理服务器到连接 Connection conn = factory.newConnection(); //获得信道 Channel channel = conn.createChannel(); //声明交换器 String exchangeName = "fanout-exchange"; channel.exchangeDeclare(exchangeName, "fanout", true); // String routingKey = "hola"; //发布消息 byte[] messageBodyBytes = "群发消息".getBytes(); channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes); channel.close(); conn.close(); } } |
消费者
publicclass publicstaticvoid ConnectionFactory factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); //建立到代理服务器到连接 Connection //获得信道 final //声明交换器 String channel.exchangeDeclare(exchangeName, "fanout", true); //声明队列 String String //绑定队列,通过键 hola将队列和交换器绑定起来 channel.queueBind(queueName, exchangeName, routingKey); while(true) { //消费消息 booleanautoAck = false; String channel.basicConsume(queueName, autoAck, consumerTag, new @Override publicvoid Envelope AMQP.BasicProperties byte[] body) throws String String System.out.println("消费的路由键:" + routingKey); System.out.println("消费的内容类型:" + contentType); longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); System.out.println("消费的消息体内容:"); String System.out.println(bodyStr); } }); } } } |
8、RabbitMQ与Spring整合
8-1、添加依赖
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>x.x.x</version> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>x.x.xRELEASE</version> </dependency> |
8-2、配置文件中加入rabbit服务连接配置
mq.host=real_host mq.username=guest mq.password=guest mq.port=5672 mq.vhost=real_vhost |
8-3、新建application-mq.xml文件,添加配置信息
主要用来配置连接信息、Producer配置、队列声明、交换器声明、队列与交换器的绑定、队列的监听器配置(即消费者)等。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"> <!-- <!-- <rabbit:connection-factory id="connectionFactory" host="${mq.host}" username="${mq.username}" password="${mq.password}" port="${mq.port}" virtual-host="${mq.vhost}"/> <!-- <rabbit:admin connection-factory="connectionFactory"/> <!-- <!-- spring template声明--> <!-- 可以不指定交换器,在每次发送请求时需要指明发给哪个交换器 <rabbit:template exchange="test" id="amqpTemplate" connection-factory="connectionFactory"/><!-- <!-- <!-- --> <!--定义queue <rabbit:queue name="mq.A" durable="true" <rabbit:queue name="mq.B" durable="true" <rabbit:queue name="mq.C" durable="true" <!-- <rabbit:direct-exchange name="test" durable="true" <rabbit:bindings> <rabbit:binding queue="mq.A" key="key.A"/> <rabbit:binding queue="mq.B" key="key.B"/> <rabbit:binding queue="mq.C" key="key.C"/> </rabbit:bindings> </rabbit:direct-exchange> <!-- queues:监听的队列,多个的话用逗号(,)分隔 ref:监听器 --> <!-- <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1"> <!-- <rabbit:listener queues="mq.A" <rabbit:listener queues="mq.B" <rabbit:listener queues="mq.C" </rabbit:listener-container> </beans> |
生产者:Spring提供的AmqpTemplate,使用注解注入即可使用
@Autowired private AmqpTemplate amqpTemplate; publicvoid sendMsg(String amqpTemplate.convertAndSend("routingKey", "发送了消息A"); } |
监听器:即收到队列的消息后作何处理,要实现ChannelAwareMessageListener
例如:监听器listenerA
@Component publicclass privatefinalstatic Log @Override publicvoid // String channel.backAck(message.getMessage??.getDeliveryTag(), false); System.out.println("A logger.error(msg); } } |
9、持久化机制
有可能遇到程序崩溃或者Rabbit服务器宕机的情况,那么如果没有持久化机制,所有数据都会丢失。
交换器持久化
Durable:是否持久化参数设为True即可
channel.exchangeDeclare(exchangeName, type, true);
队列持久化channel.queueDeclare("A",true,false,false,null).getQueue();
消息持久化
在之前,消息分发给consumer后立即就会被标记为已消费,这时候如果consumber接到了一个消息但是还没有来的及处理就异常退出,那么这个消息的状态是已被消费的,于是就会造成消息丢失的问题。
处理的代码也很简单,一共有两个步骤。第一个把autoAck改成false
//消费结果需要进行确认
channel.BasicConsume("firstTest",
false, consumer);
第二部分就是在我们消费完成后进行确认
//进行交付,确定此消息已经处理完成
channel.BasicAck(deliveryTag: e.DeliveryTag,
multiple: false);
如果没有进行确认queue会把这个消息交给其它的consumer去处理,如果没有交付的代码,那么这个消息会一直存在。
消息持久化步骤:
void basicPublish(String void basicPublish(String throws IOException; void basicPublish(String throws IOException; |
exchange表示exchange的名称
routingKey表示routingKey的名称
body代表发送的消息体
MessageProperties.PERSISTENT_TEXT_PLAIN 可以设置为持久化,类型为文本
MessageProperties.PERSISTENT_BASIC 类型为二进制数据
mandatory当mandatory标志位设置为true时,如果exchange无法找到一个队列取转发,就返回给生产者。
immediate当immediate标志位设置为true时,如果exchange要转发的队列上没有消费者时,就返回给生产者。
10、消息确认机制
概述
RabbitMQ可能会遇到的一个问题,即生成者不知道消息是否真正到达broker,那么有没有高效的解决方式呢?答案是采用Confirm模式。
producer端confirm模式的实现原理
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。
在channel 被设置成 confirm 模式之后,所有被 publish 的后续消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack 。
确认机制的三种实现
- 普通confirm模式:每发送一条消息后,调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm了。
- 批量confirm模式:每发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm。
- 异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法。
普通confirm
//开启Procedure确认机制 channel.confirmSelect(); //发布消息 channel.basicPublish(exchangeName, routingKey,null,message); //消息发送成功的确认,也可以设置超时时间 if (channel.waitForConfirms([long System.out.println("send } else { System.out.println("send } |
批量Confirm
批量发送消息后再进行确认。
异步Confirm
//待确认的序列 SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>()); //开启确认机制 channel.confirmSelect(); //添加处理事件 channel.addConfirmListener(new ConfirmListener() { publicvoid handleAck(longdeliveryTag, booleanmultiple) throws IOException { if (multiple) { confirmSet.headSet(deliveryTag + 1).clear(); } else { confirmSet.remove(deliveryTag); } System.out.println("发送成功..."); } publicvoid handleNack(longdeliveryTag, booleanmultiple) throws IOException { System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple); if (multiple) { confirmSet.headSet(deliveryTag + 1).clear(); } else { confirmSet.remove(deliveryTag); } System.err.println("发送失败..."); } }); //发送消息 for (inti = 0; i < 10; i++) { //获取下个发送序号 longnextSeqNo = channel.getNextPublishSeqNo(); channel.basicPublish(exchangeName, routingKey, null, ("车票ID:" + i).getBytes()); //加入待处理集合中 confirmSet.add(nextSeqNo); //休息0.2s Thread.sleep(200); } |
Consumer端的确认
自动确认,默认是自动确认,即获取消息后,直接确认。
手动确认,给当前消息设置状态,当手动ack后服务端才会删除该消息,如果返回nack,重新入队。
//手动确认 booleanautoAck = false; channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) { @Override publicvoid handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties byte[] body) throws IOException { //…其他处理操作 longdeliveryTag = envelope.getDeliveryTag(); //确认消息 channel.basicAck(deliveryTag, false); } }); |
附件:
https://blog.csdn.net/wangshuminjava/article/details/80998992
深入解读RabbitMQ工作原理及简单使用
https://blog.csdn.net/boonya/article/details/64904706
RabbitMQ深入学习指导
v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}
Normal
0
false
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:1.0pt;}
table.MsoTableGrid
{mso-style-name:网格型;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-priority:39;
mso-style-unhide:no;
border:solid windowtext 1.0pt;
mso-border-alt:solid windowtext .5pt;
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-border-insideh:.5pt solid windowtext;
mso-border-insidev:.5pt solid windowtext;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:1.0pt;}
RabbitMQ学习整理的更多相关文章
- RabbitMQ学习系列三-C#代码接收处理消息
RabbitMQ学习系列三:.net 环境下 C#代码订阅 RabbitMQ 消息并处理 http://www.80iter.com/blog/1438251320680361 http://www. ...
- RabbitMQ学习系列三:.net 环境下 C#代码订阅 RabbitMQ 消息并处理
上一篇已经讲了Rabbitmq如何在Windows平台安装 不懂请移步: RabbitMQ学习系列二:.net 环境下 C#代码使用 RabbitMQ 消息队列 一.理论 .net环境下,C#代码订阅 ...
- 官网英文版学习——RabbitMQ学习笔记(十)RabbitMQ集群
在第二节我们进行了RabbitMQ的安装,现在我们就RabbitMQ进行集群的搭建进行学习,参考官网地址是:http://www.rabbitmq.com/clustering.html 首先我们来看 ...
- js数组学习整理
原文地址:js数组学习整理 常用的js数组操作方法及原理 1.声明数组的方式 var colors = new Array();//空的数组 var colors = new Array(3); // ...
- RabbitMQ学习系列(四): 几种Exchange 模式
上一篇,讲了RabbitMQ的具体用法,可以看看这篇文章:RabbitMQ学习系列(三): C# 如何使用 RabbitMQ.今天说些理论的东西,Exchange 的几种模式. AMQP协议中的核心思 ...
- RabbitMQ学习系列(三): C# 如何使用 RabbitMQ
上一篇已经讲了Rabbitmq如何在Windows平台安装,还不了解如何安装的朋友,请看我前面几篇文章:RabbitMQ学习系列一:windows下安装RabbitMQ服务 , 今天就来聊聊 C# 实 ...
- TweenMax学习整理--特有属性
TweenMax学习整理--特有属性 构造函数:TweenMax(target:Object, duration:Number, vars:Object) target:Object -- 需要缓 ...
- RabbitMQ学习资源
AMQP协议 AMQP协议介绍 AMQP协议基本概念 官方入门教程 安装文档 官方安装文档 Linux下 RabbitMQ的安装与配置 windows下安装 ubuntu下安装 RabbitMQ 集群 ...
- HttpClient学习整理
HttpClient简介HttpClient 功能介绍 1. 读取网页(HTTP/HTTPS)内容 2.使用POST方式提交数据(httpClient3) 3. 处理页面重定向 ...
随机推荐
- jenkins slave Windows 2008 R2
布置jenkins,添加节点(win2008R2) 配置节点参考: http://www.cnblogs.com/juddhu/archive/2013/07/18/3198191.html 生效la ...
- C#构造函数详解和析构函数详解
首先来了解下构造函数的定义: C#构造函数是一种特殊的成员函数,它的作用主要用于为对象分配存储空间,对数据成员进行初始化. 接下来看一下他的语法定义形式: |访问修饰符| 标识符 (|参数列表|) | ...
- ssh免密码登录、secureCRT免密码登录详解
再放一张真机实现图: 接下来就详细讲述实现细节. 实现过程中吃了不少苦头,这个不对,那个不通.好在慢慢一点点摸索出来了,经验分享在这里. 希望能终结网上ssh免密码登录,以及SecureCRT免密码登 ...
- 【Jquery】jquery刷新页面(局部及全页面刷新)
下面介绍全页面刷新方法:有时候可能会用到window.location.reload()刷新当前页面.parent.location.reload()刷新父亲对象(用于框架)opener.locati ...
- 2.2、Softmax Regression算法实践
Softmax Regression算法实践 有了上篇博客的理论知识,我们可以利用实现好的函数,来构建Softmax Regression分类器,在训练分类器的过程中,我们使用多分类数据作为训练数据: ...
- mvc 请求处理管道
原文 http://blog.csdn.net/wulex/article/details/41514795 当一个asp.net mvc应用程序提出请求,为了响应请求,包含一些请求执行流程步骤! 在 ...
- 3 hql语法及自定义函数(含array、map讲解) + hive的java api
本博文的主要内容如下: .hive的详细官方手册 .hive支持的数据类型 .Hive Shell .Hive工程所需依赖的jar包 .hive自定义函数 .分桶4 .附PPT hiv ...
- idea 激活
激活时选择License server,填入 http://idea.wlphp.com:1017 点击Active即可 2DZ8RPRSBU-eyJsaWNlbnNlSWQiOiIyRFo4UlBS ...
- PyCharm激活码问题
1.打开激活窗口 2.选择 Activate new license with License server (用license server 激活) 3.在 License sever addres ...
- 用IDM下载博客图片
前言 写博客的人一定都会有一个图床,将图片存在那里.发现自己以前没有注意图片来源问题,随手就贴在博客上面了.现在有不少图片都挂了,换句话来说有可能自己目前用的图床不提供服务了,那所有的图片都有可能丢失 ...