RabbitMQ学习(二):Java使用RabbitMQ要点知识
转 https://blog.csdn.net/leixiaotao_java/article/details/78924863
1、maven依赖
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.3</version>
- </dependency>
- <dependency>
- <groupId>com.rabbitmq</groupId>
- <artifactId>amqp-client</artifactId>
- <version>3.4.1</version>
- </dependency>
2、RabbitMQ重要方法介绍(基本常用的)
2.1、创建连接
- // 创建连接工厂
- ConnectionFactory cf = new ConnectionFactory();
- // 设置rabbitmq服务器IP地址
- cf.setHost("*.*.*.*");
- // 设置rabbitmq服务器用户名
- cf.setUsername("***");
- // 设置rabbitmq服务器密码
- cf.setPassword("***");
- // 指定端口,默认5672
- cf.setPort(AMQP.PROTOCOL.PORT);
- // 获取一个新的连接
- connection = cf.newConnection();
- // 创建一个通道
- channel = connection.createChannel();
- //关闭管道和连接
- channel.close();
- connection.close();
2.2、声明队列
- /**
- * 申明一个队列,如果这个队列不存在,将会被创建
- * @param queue 队列名称
- * @param durable 持久性:true队列会再重启过后存在,但是其中的消息不会存在。
- * @param exclusive 是否只能由创建者使用,其他连接不能使用。
- * @param autoDelete 是否自动删除(没有连接自动删除)
- * @param arguments 队列的其他属性(构造参数)
- * @return Queue.DeclareOk:宣告队列的声明确认方法已成功声明。
- * @throws java.io.IOException if an error is encountered
- */
- channel.queueDeclare("testQueue", true, false, false, null);
此方法一般由Producer调用创建消息队列。如果由Consumer创建队列,有可能Producer发布消息的时候Queue还没有被创建好,会造成消息丢失的情况。
2.3、声明Exchange
- /**
- * 声明一个 exchange.
- * @param exchange 名称
- * @param type exchange type:direct、fanout、topic、headers
- * @param durable 持久化
- * @param autoDelete 是否自动删除(没有连接自动删除)
- * @param arguments 队列的其他属性(构造参数)
- * @return 成功地声明了一个声明确认方法来指示交换。
- * @throws java.io.IOException if an error is encountered
- */
- channel.exchangeDeclare("leitao","topic", true,false,null);
2.4、将queue和Exchange进行绑定(Binding)
- /**
- * 将队列绑定到Exchange,不需要额外的参数。
- * @param queue 队列名称
- * @param exchange 交换机名称
- * @param routingKey 路由关键字
- * @return Queue.BindOk:如果成功创建绑定,则返回绑定确认方法。
- * @throws java.io.IOException if an error is encountered
- */
- channel.queueBind("testQueue", "leitao", "testRoutingKey");
2.5、发布消息
- /**
- * 发布一条不用持久化的消息,且设置两个监听。
- * @param exchange 消息交换机名称,空字符串将使用直接交换器模式,发送到默认的Exchange=amq.direct。此状态下,RoutingKey默认和Queue名称相同
- * @param routingKey 路由关键字
- * @param mandatory 监听是否有符合的队列
- * @param immediate 监听符合的队列上是有至少一个Consumer
- * @param BasicProperties 设置消息持久化:MessageProperties.PERSISTENT_TEXT_PLAIN是持久化;MessageProperties.TEXT_PLAIN是非持久化。
- * @param body 消息对象转换的byte[]
- * @throws java.io.IOException if an error is encountered
- */
- channel.basicPublish("",queueName,true,false,MessageProperties.TEXT_PLAIN,SerializationUtils.serialize(object));
当exchange的值为空字符串或者是amq.direct时,此时的交换器类型默认是direct类型,可以不用单独声明Exchange,也不用单独进行Binding,系统默认将queue名称作为RoutingKey进行了绑定。
两个传入参数的含义
mandatory
当mandatory标志位设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返回给生产者(Basic.Return + Content-Header + Content-Body);当mandatory设置为false时,出现上述情形broker会直接将消息扔掉。
immediate
当immediate标志位设置为true时,如果exchange在将消息路由到queue(s)时发现对于的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或者多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。
概括来说,mandatory标志告诉服务器至少将该消息route到一个队列中,否则将消息返还给生产者;immediate标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。
注意:在RabbitMQ3.0以后的版本里,去掉了immediate参数的支持,发送带immediate=true标记的publish会返回如下错误:
com.rabbitmq.client.AlreadyClosedException: connection is already closed due to connection error;protocol method: #method<connection.close>(reply-code=540, reply-text=NOT_IMPLEMENTED - immediate=true, class-id=60, method-id=40)。
为什么取消支持:immediate标记会影响镜像队列性能,增加代码复杂性,并建议采用“TTL”和“DLX”等方式替代。
2.6、接收消息
- /**
- * 设置消费批量投递数目,一次性投递10条消息。当消费者未确认消息累计达到10条时,rabbitMQ将不会向此Channel上的消费者投递消息,直到未确认数小于10条再投递
- * @param prefetchCount 投递数目
- * @param global 是否针对整个Channel。true表示此投递数是给Channel设置的,false是给Channel上的Consumer设置的。
- * @throws java.io.IOException if an error is encountered
- */
- channel.basicQos(10,false);
- //整个传输管道最多15条,具体分到每个消费者身上又不能大于10条
- channel.basicQos(15,true);
- /**
- * 开始一个非局部、非排他性消费, with a server-generated consumerTag.
- * 执行这个方法会回调handleConsumeOk方法
- * @param queue 队列名称
- * @param autoAck 是否自动应答。false表示consumer在成功消费过后必须要手动回复一下服务器,如果不回复,服务器就将认为此条消息消费失败,继续分发给其他consumer。
- * @param callback 回调方法类,一般为自己的Consumer类
- * @return 由服务器生成的consumertag
- * @throws java.io.IOException if an error is encountered
- */
- channel.basicConsume(queueName, false, Consumer);
2.7、Consumer处理消息
- /**
- * 消费者收到消息的回调函数
- * @param consumerTag 消费者标签
- * @param envelope 消息的包装数据
- * @param properties 消息的内容头数据
- * @param body 消息对象的byte[]
- * @throws IOException
- */
- void handleDelivery(String consumerTag,
- Envelope envelope,
- AMQP.BasicProperties properties,
- byte[] body)
- throws IOException;
3、Producer消息确认机制
3.1、什么是生产者消息确认机制?
没有消息确认模式时,生产者不知道消息是不是已经到达了Broker服务器,这对于一些业务严谨的系统来说将是灾难性的。消息确认模式可以采用AMQP协议层面提供的事务机制实现(此文没有这种实现方式),但是会降低RabbitMQ的吞吐量。RabbitMQ自身提供了一种更加高效的实现方式:confirm模式。
消息生产者通过调用Channel.confirmSelect()方法将Channel信道设置成confirm模式。一旦信道被设置成confirm模式,该信道上的所有消息都会被指派一个唯一的ID(从1开始),一旦消息被对应的Exchange接收,Broker就会发送一个确认给生产者(其中deliveryTag就是此唯一的ID),这样消息生产者就知道消息已经成功到达Broker。
confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。
在channel 被设置成 confirm 模式之后,所有被 publish 的后续消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack 。
3.2、开启confirm模式
如上所说生产者通过调用Channel.confirmSelect()方法将Channel信道设置成confirm模式。
注意:已经在transaction事务模式的channel是不能再设置成confirm模式的,即这两种模式是不能共存的。
3.3、普通confirm模式
普通confirm模式是串行的,即每次发送了一次消息,生产者都要等待Broker的确认消息,然后根据确认标记权衡消息重发还是继续发下一条。由于是串行的,在效率上是比较低下的。
(1)重点方法
- /**
- * 等待Broker返回消息确认标记
- * 注意,在非确定的通道,waitforconfirms抛出IllegalStateException。
- * @return 是否发送成功
- * @throws java.lang.IllegalStateException
- */
- boolean waitForConfirms() throws InterruptedException;
(2)部分使用代码如下:
- //注意:返回的时候Return在前,Confirm在后
- channel.confirmSelect();
- int i=1;
- while (i<=50) {
- //发布消息
- channel.basicPublish("",queueName,true,MessageProperties.TEXT_PLAIN,SerializationUtils.serialize(object));
- //等待Broker的确认回调
- if(channel.waitForConfirms())
- System.out.println("send success!");
- else
- System.out.println("send error!");
- i++;
- }
3.4、批量confirm模式
批量confirm模式是异步的方式,效率要比普通confirm模式高许多,但是此种方式也会造成线程阻塞,想要进行失败重发就必须要捕获异常。网络上还有采用waitForConfirms()实现批量confirm模式的,但是只要一条失败了,就必须把这批次的消息统统再重发一次,非常的消耗性能,因此此文不予考虑。
(1)重点代码
- /**
- * 等待直到所有消息被确认或者某个消息发送失败。如果消息发送确认失败了,
- * waitForConfirmsOrDie 会抛出IOException异常。当在非确认通道上调用时
- * ,会抛出IllegalStateException异常。
- * @throws java.lang.IllegalStateException
- */
- void waitForConfirmsOrDie() throws IOException, InterruptedException;
(2)部分代码如下:
- //注意:返回的时候Return在前,Confirm在后
- channel.confirmSelect();
- int i=1;
- while (i<=50) {
- //发布消息
- channel.basicPublish("",queueName,true,MessageProperties.TEXT_PLAIN,SerializationUtils.serialize(object));
- i++;
- }
- channel.waitForConfirmsOrDie();
3.5、ConfirmListener监听器模式
RabbitMQ提供了一个ConfirmListener接口专门用来进行确认监听,我们可以实现ConfirmListener接口来创建自己的消息确认监听。ConfirmListener接口中包含两个回调方法:
- /**
- * 生产者发送消息到exchange成功的回调方法
- */
- void handleAck(long deliveryTag, boolean multiple) throws IOException;
- /**
- * 生产者发送消息到服务器broker失败的回调方法,服务器丢失了此消息。
- * 注意,丢失的消息仍然可以传递给消费者,但broker不能保证这一点。
- */
- void handleNack(long deliveryTag, boolean multiple) throws IOException;
其中deliveryTag是Broker给每条消息指定的唯一ID(从1开始);multiple表示是否接收所有的应答消息,比如multiple=true时,发送100条消息成功过后,我们并不会收到100次handleAck方法调用。
(1)重要方法
- //注册消息确认监听器
- channel.addConfirmListener(new MyConfirmListener());
(2)部分使用代码如下:
- //注意:返回的时候Return在前,Confirm在后
- channel.confirmSelect();
- //注册消息确认监听器
- channel.addConfirmListener(new MyConfirmListener());
- //注册消息结果返回监听器
- channel.addReturnListener(new MyReturnListener());
- int i=1;
- while (i<=50) {
- //发布消息
- channel.basicPublish("",queueName,true,MessageProperties.TEXT_PLAIN,SerializationUtils.
- serialize(object));
- i++;
- }
- //自定义的消息确认监听器
- public class MyConfirmListener implements ConfirmListener{
- /**
- * 生产者发送消息到exchange成功的回调方法
- * 消息被Exchange接受以后,如果没有匹配的Queue,则会被丢弃。但是可以设置ReturnListener监听来监听有没有匹配的队列。
- * 因此handleAck执行了,并不能完全表示消息已经进入了对应的队列,只能表示对应的exchange成功的接收了消息。
- * 消息被exchange接收过后,还需要通过一定的匹配规则分发到对应的队列queue中。
- */
- public void handleAck(long deliveryTag, boolean multiple) throws IOException {
- //注意:deliveryTag是broker给消息指定的唯一id(从1开始)
- System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)成功!multiple="+multiple);
- }
- /**
- * 生产者发送消息到服务器broker失败的回调方法,服务器丢失了此消息。
- * 注意,丢失的消息仍然可以传递给消费者,但broker不能保证这一点。(不明白,既然丢失了,为啥还能发送)
- */
- public void handleNack(long deliveryTag, boolean multiple) throws IOException {
- System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)失败!服务器broker丢失了消息");
- }
- }
- //自定义的结果返回监听器
- /**
- * 实现此接口以通知交付basicpublish失败时,“mandatory”或“immediate”的标志监听(源代码注释翻译)。
- * 在发布消息时设置mandatory等于true,监听消息是否有相匹配的队列,
- * 没有时ReturnListener将执行handleReturn方法,消息将返给发送者
- */
- public class MyReturnListener implements ReturnListener {
- public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
- BasicProperties properties, byte[] body) throws IOException {
- System.out.println("消息发送到队列失败:回复失败编码:"+replyCode+";回复失败文本:"+replyText+";失败消息对象:"+SerializationUtils.deserialize(body));
- }
- }
4、Consumer消息确认机制
为了保证消息从队列可靠地到达消费者,RabbitMQ提供消息确认机制(message
acknowledgment)。消费者在注册消费者时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(或磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。
当noAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息(web管理界面上的Ready状态);一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息(web管理界面上的Unacked状态)。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
(1)重要方法
- /**
- *1. 开始一个非局部、非排他性消费, with a server-generated consumerTag.
- * 注意:执行这个方法会回调handleConsumeOk方法,在此方法中处理消息。
- * @param queue 队列名称
- * @param autoAck 是否自动应答。false表示consumer在成功消费过后必须要手动回复一下服务器,如果不回复,服务器就将认为此条消息消费失败,继续分发给其他consumer。
- * @param callback 回调方法类
- * @return 由服务器生成的consumertag
- * @throws java.io.IOException if an error is encountered
- */
- String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
- /**
- *2
- consumer处理成功后,通知broker删除队列中的消息,如果设置multiple=true,表示支持批量确认机制以减少网络流量。
- 例如:有值为5,6,7,8 deliveryTag的投递
- 如果此时channel.basicAck(8, true);则表示前面未确认的5,6,7投递也一起确认处理完毕。
- 如果此时channel.basicAck(8, false);则仅表示deliveryTag=8的消息已经成功处理。
- */
- void basicAck(long deliveryTag, boolean multiple) throws IOException;
- /**3
- consumer处理失败后,例如:有值为5,6,7,8 deliveryTag的投递。
- 如果channel.basicNack(8, true, true);表示deliveryTag=8之前未确认的消息都处理失败且将这些消息重新放回队列中。
- 如果channel.basicNack(8, true, false);表示deliveryTag=8之前未确认的消息都处理失败且将这些消息直接丢弃。
- 如果channel.basicNack(8, false, true);表示deliveryTag=8的消息处理失败且将该消息重新放回队列。
- 如果channel.basicNack(8, false, false);表示deliveryTag=8的消息处理失败且将该消息直接丢弃。
- */
- void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
- /**4
- 相比channel.basicNack,除了没有multiple批量确认机制之外,其他语义完全一样。
- 如果channel.basicReject(8, true);表示deliveryTag=8的消息处理失败且将该消息重新放回队列。
- 如果channel.basicReject(8, false);表示deliveryTag=8的消息处理失败且将该消息直接丢弃。
- */
- void basicReject(long deliveryTag, boolean requeue) throws IOException;
(2)部分使用代码如下:
- //this表示自己的Consumer
- channel.basicConsume(queueName, false, this);
- ...
- @Override
- public void handleDelivery(String arg0, Envelope envelope, BasicProperties arg2, byte[] body) throws IOException {
- if (body == null)
- return;
- Map<String, Object> map = (Map<String, Object>) SerializationUtils.deserialize(body);
- /**
- * 专门处理奇数消息的消费者
- */
- int tagId = (Integer) map.get("tagId");
- if (tagId % 2 != 0) {
- //处理消息
- System.out.println("接收并处理消息:"+tagId);
- //通知服务器此消息已经被处理了
- channel.basicAck(envelope.getDeliveryTag(), false);
- }else{
- //通知服务器消息处理失败,重新放回队列。false表示处理失败消息不放会队列,直接删除
- channel.basicReject(envelope.getDeliveryTag(), true);
- }
- }
5、Demo项目整体代码
此demo就是向RabbitMQ服务器上面发送20个消息,消息体是map,里面装的是tagId=数字。然后注册了两个消费者,分别处理奇数和偶数。
5.1、连接工具类
- /**
- * 连接工具类
- */
- public class ConnectionUtil {
- Channel channel;
- Connection connection;
- String queueName;
- public ConnectionUtil(String queueName) throws IOException {
- this.queueName = queueName;
- // 创建连接工厂
- ConnectionFactory cf = new ConnectionFactory();
- // 设置rabbitmq服务器IP地址
- cf.setHost("*.16.0.*");
- // 设置rabbitmq服务器用户名
- cf.setUsername("*");
- // 设置rabbitmq服务器密码
- cf.setPassword("*");
- cf.setPort(AMQP.PROTOCOL.PORT);
- // 获取一个新的连接
- connection = cf.newConnection();
- // 创建一个通道
- channel = connection.createChannel();
- /**
- *申明一个队列,如果这个队列不存在,将会被创建
- * @param queue 队列名称
- * @param durable 持久性:true队列会再重启过后存在,但是其中的消息不会存在。
- * @param exclusive 是否只能由创建者使用
- * @param autoDelete 是否自动删除(没有连接自动删除)
- * @param arguments 队列的其他属性(构造参数)
- * @return 宣告队列的声明确认方法已成功声明。
- * @throws java.io.IOException if an error is encountered
- */
- channel.queueDeclare(queueName, true, false, false, null);
- }
- public void close() throws IOException{
- channel.close();
- connection.close();
- }
- }
5.2、具体生产者
- /**
- * 消息生产者
- */
- public class MessageProducer {
- private ConnectionUtil connectionUtil;
- public MessageProducer(ConnectionUtil connectionUtil){
- this.connectionUtil=connectionUtil;
- }
- /**
- * 发送消息到队列中
- */
- public void sendMessage(Serializable object) throws IOException{
- /**
- * Publish a message
- * @param exchange 消息交换机名称,空字符串将使用直接交换器模式,发送到默认的Exchange=amq.direct
- * @param routingKey 路由关键字
- * @param mandatory 监听是否有符合的队列
- * @param BasicProperties 设置消息持久化:MessageProperties.PERSISTENT_TEXT_PLAIN是持久化;MessageProperties.TEXT_PLAIN是非持久化
- * @param body 消息对象
- * @throws java.io.IOException if an error is encountered
- */
- connectionUtil.channel.basicPublish("", connectionUtil.queueName, true, MessageProperties.TEXT_PLAIN, SerializationUtils.serialize(object));
- System.out.println("MessageProducer发送了一条消息:"+object);
- }
- }
5.3、公共消费者父类
- /**
- * 消息消费者基础类
- */
- public class MessageConsumer implements Consumer {
- //消费者标签,注册成功时由rabbitmq服务器自动生成
- protected String consumerTag;
- protected ConnectionUtil connectionUtil;
- public MessageConsumer(ConnectionUtil connectionUtil){
- this.connectionUtil=connectionUtil;
- }
- public void basicConsume(){
- try {
- /**
- * 设置消费投递数目,一次性投递10条消息。当消费者未确认消息达到10条时,rabbitMQ将不会向此消费者投递消息,直到未确认数小于10条再投递
- * @param prefetchCount 投递数目
- * @param global 是否针对整个Channel。true表示此投递数是给Channel设置的,false是给Channel上的Consumer设置的。
- * @throws java.io.IOException if an error is encountered
- */
- connectionUtil.channel.basicQos(10,false);//表示每个消费者最多10条
- connectionUtil.channel.basicQos(15,true);//整个传输管道最多15条,具体分到每个消费者身上又不能大于10条
- /**
- * 开始一个非局部、非排他性消费, with a server-generated consumerTag.
- * 执行这个方法会回调handleConsumeOk方法
- * @param queue 队列名称
- * @param autoAck 是否自动应答。false表示consumer在成功消费过后必须要手动回复一下服务器,如果不回复,服务器就将认为此条消息消费失败,继续分发给其他consumer。
- * @param callback 回调方法类
- * @return 由服务器生成的consumertag
- * @throws java.io.IOException if an error is encountered
- */
- connectionUtil.channel.basicConsume(connectionUtil.queueName, false, this);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- * 收到消息时的回调函数
- */
- public void handleDelivery(String arg0, Envelope arg1, BasicProperties arg2, byte[] arg3) throws IOException {
- //子类重写覆盖具体操作
- }
- /**
- * 消费者注册成功回调函数
- */
- public void handleConsumeOk(String consumerTag) {
- this.consumerTag=consumerTag;
- System.out.println("消费者:"+consumerTag+",注册成功!");
- }
- /**
- * 手动取消消费者注册成功回调函数
- * 当调用Channel类的void basicCancel(String consumerTag) throws IOException;方法触发此回调函数
- */
- public void handleCancelOk(String consumerTag) {
- System.out.println(consumerTag+" 手动取消消费者注册成功!");
- }
- /**
- * 当消费者因为其他原因被动取消注册时调用,比如queue被删除了。
- */
- public void handleCancel(String consumerTag) throws IOException {
- System.out.println("因为外部原因消费者:"+consumerTag+" 取消注册!");
- }
- /**
- * 当通道或基础连接被关闭时调用
- */
- public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
- System.out.println("通道或基础连接被关闭");
- }
- /**
- * Called when a <code><b>basic.recover-ok</b></code> is received
- * in reply to a <code><b>basic.recover</b></code>. All messages
- * received before this is invoked that haven't been <i>ack</i>'ed will be
- * re-delivered. All messages received afterwards won't be.
- * @param consumerTag the <i>consumer tag</i> associated with the consumer
- */
- public void handleRecoverOk(String consumerTag) {
- }
- }
5.4、具体的消费者
- /**
- * 专门处理偶数消息的消费者
- */
- public class EvenConsumer extends MessageConsumer {
- public EvenConsumer(ConnectionUtil connectionUtil) {
- super(connectionUtil);
- }
- @Override
- public void handleConsumeOk(String consumerTag) {
- this.consumerTag=consumerTag;
- System.out.println("EvenConsumer消费者:"+consumerTag+",注册成功!");
- }
- @Override
- public void handleDelivery(String arg0, Envelope envelope, BasicProperties arg2, byte[] body) throws IOException {
- if (body == null)
- return;
- Map<String, Object> map = (Map<String, Object>) SerializationUtils.deserialize(body);
- int tagId = (Integer) map.get("tagId");
- if (tagId % 2 == 0) {
- //处理消息
- System.out.println("EvenConsumer接收并处理消息:"+tagId);
- //通知服务器此消息已经被处理了
- connectionUtil.channel.basicAck(envelope.getDeliveryTag(), false);
- }else{
- //通知服务器消息处理失败,重新放回队列。false表示处理失败消息不放会队列,直接删除
- connectionUtil.channel.basicReject(envelope.getDeliveryTag(), true);
- }
- }
- }
- /**
- * 专门处理奇数消息的消费者
- */
- public class OddConsumer extends MessageConsumer {
- public OddConsumer(ConnectionUtil connectionUtil) {
- super(connectionUtil);
- }
- @Override
- public void handleConsumeOk(String consumerTag) {
- this.consumerTag=consumerTag;
- System.out.println("OddConsumer消费者:"+consumerTag+",注册成功!");
- }
- @Override
- public void handleDelivery(String arg0, Envelope envelope, BasicProperties arg2, byte[] body) throws IOException {
- if (body == null)
- return;
- Map<String, Object> map = (Map<String, Object>) SerializationUtils.deserialize(body);
- int tagId = (Integer) map.get("tagId");
- if (tagId % 2 != 0) {
- //处理消息
- System.out.println("OddConsumer接收并处理消息:"+tagId);
- //通知服务器此消息已经被处理了
- connectionUtil.channel.basicAck(envelope.getDeliveryTag(), false);
- }else{
- //通知服务器消息处理失败,重新放回队列。false表示处理失败消息不放会队列,直接删除
- connectionUtil.channel.basicReject(envelope.getDeliveryTag(), true);
- }
- }
- }
5.5、监听器
- /**
- *producer发送确认事件。
- */
- public class MyConfirmListener implements ConfirmListener{
- /**
- * 生产者发送消息到exchange成功的回调方法
- * 消息被Exchange接受以后,如果没有匹配的Queue,则会被丢弃。但是可以设置ReturnListener监听来监听有没有匹配的队列。
- * 因此handleAck执行了,并不能完全表示消息已经进入了对应的队列,只能表示对应的exchange成功的接收了消息。
- * 消息被exchange接收过后,还需要通过一定的匹配规则分发到对应的队列queue中。
- */
- public void handleAck(long deliveryTag, boolean multiple) throws IOException {
- //注意:deliveryTag是broker给消息指定的唯一id(从1开始)
- System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)成功!multiple="+multiple);
- }
- /**
- * 生产者发送消息到服务器broker失败的回调方法,服务器丢失了此消息。
- * 注意,丢失的消息仍然可以传递给消费者,但broker不能保证这一点。
- */
- public void handleNack(long deliveryTag, boolean multiple) throws IOException {
- System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)失败!服务器broker丢失了消息");
- }
- }
- /**
- * 实现此接口以通知交付basicpublish失败时,“mandatory”或“immediate”的标志监听(源代码注释翻译)。
- * 在发布消息时设置mandatory等于true,监听消息是否有相匹配的队列,
- * 没有时ReturnListener将执行handleReturn方法,消息将返给发送者 。
- * 由于3.0版本过后取消了支持immediate,此处不做过多的解释。
- */
- public class MyReturnListener implements ReturnListener {
- public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
- BasicProperties properties, byte[] body) throws IOException {
- System.out.println("消息发送到队列失败:回复失败编码:"+replyCode+";回复失败文本:"+replyText+";失败消息对象:"+SerializationUtils.deserialize(body));
- }
- }
5.6、客户端
- public class Client {
- public static void main(String[] args) {
- new Client();
- }
- public Client(){
- try {
- //发消息
- publishMessage();
- //注册消费者
- addConsumer();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- public void publishMessage() throws IOException, InterruptedException{
- ConnectionUtil connectionUtil=new ConnectionUtil("testqueue");
- MessageProducer producer=new MessageProducer(connectionUtil);
- connectionUtil.channel.confirmSelect();
- //注意:返回的时候Return在前,Confirm在后
- connectionUtil.channel.addConfirmListener(new MyConfirmListener());
- connectionUtil.channel.addReturnListener(new MyReturnListener());
- int i=1;
- while (i<=10) {
- HashMap<String, Object> map=new HashMap<String, Object>();
- map.put("tagId", i);
- producer.sendMessage(map);
- i++;
- }
- }
- public void addConsumer() throws IOException{
- ConnectionUtil connectionUtil=new ConnectionUtil("testqueue");
- OddConsumer odd=new OddConsumer(connectionUtil);
- odd.basicConsume();
- EvenConsumer even=new EvenConsumer(connectionUtil);
- even.basicConsume();
- }
- }
5.7、测试结果
- MessageProducer发送了一条消息:{tagId=1}
- MessageProducer发送了一条消息:{tagId=2}
- MessageProducer发送了一条消息:{tagId=3}
- Exchange接收消息:1(deliveryTag)成功!multiple=false
- Exchange接收消息:2(deliveryTag)成功!multiple=false
- MessageProducer发送了一条消息:{tagId=4}
- Exchange接收消息:3(deliveryTag)成功!multiple=false
- MessageProducer发送了一条消息:{tagId=5}
- Exchange接收消息:4(deliveryTag)成功!multiple=false
- MessageProducer发送了一条消息:{tagId=6}
- Exchange接收消息:5(deliveryTag)成功!multiple=false
- MessageProducer发送了一条消息:{tagId=7}
- Exchange接收消息:6(deliveryTag)成功!multiple=false
- MessageProducer发送了一条消息:{tagId=8}
- Exchange接收消息:7(deliveryTag)成功!multiple=false
- Exchange接收消息:8(deliveryTag)成功!multiple=false
- MessageProducer发送了一条消息:{tagId=9}
- Exchange接收消息:9(deliveryTag)成功!multiple=false
- MessageProducer发送了一条消息:{tagId=10}
- Exchange接收消息:10(deliveryTag)成功!multiple=false
- OddConsumer消费者:amq.ctag-z8s8LaSgYvo02jktCZrCYA,注册成功!
- OddConsumer接收并处理消息:1
- OddConsumer接收并处理消息:3
- OddConsumer接收并处理消息:5
- OddConsumer接收并处理消息:7
- OddConsumer接收并处理消息:9
- EvenConsumer消费者:amq.ctag-LpN6Q5VvNY3wCof2lXqS4A,注册成功!
- EvenConsumer接收并处理消息:4
- EvenConsumer接收并处理消息:8
- EvenConsumer接收并处理消息:2
- EvenConsumer接收并处理消息:10
- EvenConsumer接收并处理消息:6
6、Demo完整源码下载地址
RabbitMQ学习(二):Java使用RabbitMQ要点知识的更多相关文章
- RabbitMQ学习笔记五:RabbitMQ之优先级消息队列
RabbitMQ优先级队列注意点: 1.只有当消费者不足,不能及时进行消费的情况下,优先级队列才会生效 2.RabbitMQ3.5以后才支持优先级队列 代码在博客:RabbitMQ学习笔记三:Java ...
- RabbitMQ(二) Java使用RabbitMQ
2-1 RabbitMQ 生产者消息发送 创建 Maven 项目 Send 加入依赖 <dependency> <groupId>com.rabbitmq</groupI ...
- rabbitmq学习二
rabbitmq的六种工作模式: 这里简单介绍下六种工作模式的主要特点: 简单模式:一个生产者,一个消费者 work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一. 订阅模式:一个生产者发送 ...
- RabbitMQ学习系列一安装RabbitMQ服务
RabbitMQ学习系列一:windows下安装RabbitMQ服务 http://www.80iter.com/blog/1437026462550244 Rabbit MQ 是建立在强大的Erla ...
- RabbitMQ(二):RabbitMQ高级特性
RabbitMQ是目前非常热门的一款消息中间件,不管是互联网大厂还是中小企业都在大量使用.作为一名合格的开发者,有必要了解一下相关知识,RabbitMQ(一)已经入门RabbitMQ,本文介绍Rabb ...
- 大数据学习笔记——Java篇之基础知识
Java / 计算机基础知识整理 在进行知识梳理同时也是个人的第一篇技术博客之前,首先祝贺一下,经历了一年左右的学习,从完完全全的计算机小白,现在终于可以做一些产出了!可以说也是颇为感慨,个人认为,学 ...
- RabbitMQ学习3----运行和管理RabbitMQ
1.服务为管理 Erlang天生就是为了让应用程序无需知道对方是否存在同一台机器上即可互相通信. Erlang节点:Erlang虚拟机的每个实例.多个Erlang应用程序可以运行在同一个节点之上.节点 ...
- RabbitMq学习笔记——MingW编译RabbitMQ C
1.安装cmak,下载地址:https://cmake.org/download/,当前最新版本3.15.1,下载cmake-3.15.1-win64-x64.msi 注意:安装时勾选将bin目录添加 ...
- 消息队列RabbitMQ(二):RabbitMQ的系统架构概述
前言 RabbitMQ是基于AMQP协议的,要想深入理解RabbitMQ,就必须先了解AMQP是个什么东东? AMQP协议 AMQP即Advanced Message Queuing Protocol ...
- RabbitMQ学习笔记四:RabbitMQ命令(附疑难问题解决)
本来今天是想做RabbitMQ之优先级队列的,但是,在RabbitMQ Server创建queue时,增加优先级的最大值,头脑发热写了9999999,导致电脑内存直接飙到100%,只能重启电脑,并卸载 ...
随机推荐
- 两数之和LeetCode
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元 ...
- elasticsearc 参考资料
_source 和store http://stackoverflow.com/questions/18833899/in-elasticsearch-what-happens-if-i-set-st ...
- 基于Vue的省市区三级联动插件
官网地址:https://distpicker.uine.org/ 安装: npm install v-distpicker --save 局部注册: import VDistpicker from ...
- LeetCode:灯泡开关2
题目 现有一个房间,墙上挂有 n 只已经打开的灯泡和 4 个按钮.在进行了 m 次未知操作后,你需要返回这 n 只灯泡可能有多少种不同的状态. 假设这 n 只灯泡被编号为 [1, 2, 3 ..., ...
- CC07:清除行列
题目 请编写一个算法,若N阶方阵中某个元素为0,则将其所在的行与列清零. 给定一个N阶方阵int[][](C++中为vector>)mat和矩阵的阶数n,请返回完成操作后的int[][]方阵(C ...
- js 检查字符串中是否包含中文(正则)
function CheckChinese(val){ var reg = new RegExp("[\\u4E00-\\u9FFF]+","g"); if(r ...
- Python面向对象之结构与成员
1.面向对象结构分析: ----面相对象整体大致分为两块区域: --------第一部分:静态字段(静态变量)部分 --------第二部分:方法部分 --每个大区域可以分为多个小部分: class ...
- (转)nginx限制上传大小和超时时间设置说明/php限制上传大小
nginx限制上传大小和超时时间设置说明/php限制上传大小 原文:http://www.cnblogs.com/kevingrace/p/6093671.html 现象说明:在服务器上部署了一套后台 ...
- ms sqlserver 清除数据库日志脚本
USE [master] GO ALTER DATABASE F360DW SET RECOVERY SIMPLE WITH NO_WAIT GO ALTER DATABASE F360DW SET ...
- ssh登录出现 Host key verification failed. 问题
我们使用ssh链接linux主机时,可能出现“Hostkey verification failed.“的提示,ssh连接不成功.可能的提示信息如下: @@@@@@@@@@@@@@@@@@@@@@@@ ...