Rabbitmq之高级特性——百分百投递消息&消息确认模式&消息返回模式实现
rabbitmq的高级特性:
如何保障消息的百分之百成功?
要满足4个条件:生产方发送出去,消费方接受到消息,发送方接收到消费者的确认信息,完善的消费补偿机制
解决方案,1)消息落库,进行消息状态打标
该解决方案需要对对数据库进行两次io操作,如果数据量很大,将会导致瓶颈的发生,本流程是首先将业务入库,发送消息前,将发送的消息入库消息状态可设置为0,两次写入数据库后,发送消息,消息发送后,mq broker 接收并处理消息,完成后发送回馈消息确认信息,同过确认监听服务器,监听到确认消息后,将其数据库状态更改为1,说明消息发送成功,但是过程中可能出现各种情况导致不能反馈消息,我们需要在添加一个定时轮询任务,比如说设置最大轮询次数为3,时间间隔为5min,每一次当超过5分钟就去检查一次数据库消息状态,如果还是0则,再次发送消息,知道消息状态变为1为止。
2)延迟投递,二次确认,回调检查
该模式适用于高并发的场景,也就是并发量非常的,对系统性能要求较高的场合,该模式减少一次主业务的io操作,首先业务落库,然后生成两条消息,首先发出去一次消息,然后5min之后再次发送消息(延迟投递),第一条发送后,broker端收到后转给消费端,正常处理后,发送一个相应消息(再次生成一条的消息)投递出去step4:send confirm,发送到callback服务中,如果callback服务收确认消息,那么callback将消息写入数据库,5min后发送的第二条消息,进入broker中,此时,broker将消息投递到callback中的监听,收到后检查数据库,如果检查消息已经存在,说明消费者正常消费,如果,因各种情况发现消费者没有正产消费,那么callback要进行补偿,发起一个RPC通信告诉上游说消息发送失败,上游程序再次生成消息,重新走上述流程。
幂等性:在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。消息重复投递,或者高并发下,消息仅仅消费一次。
消息确认模式:
生产方:
1 package com.zxy.demo.rabbitmq; 2 3 import java.io.IOException; 4 import java.util.concurrent.TimeoutException; 5 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.ConfirmListener; 8 import com.rabbitmq.client.Connection; 9 import com.rabbitmq.client.ConnectionFactory; 10 11 public class Producter { 12 13 public static void main(String[] args) throws IOException, TimeoutException { 14 // TODO Auto-generated method stub 15 ConnectionFactory factory = new ConnectionFactory(); 16 factory.setHost("192.168.10.110"); 17 factory.setPort(5672); 18 factory.setUsername("guest"); 19 factory.setPassword("guest"); 20 factory.setVirtualHost("/"); 21 Connection conn = factory.newConnection(); 22 Channel channel = conn.createChannel(); 23 String exchange001 = "exchange_001"; 24 String queue001 = "queue_001"; 25 String routingkey = "mq.topic"; 26 String body = "hello rabbitmq!"; 27 // 添加消息确认模式========消息确认模式重要添加 28 channel.confirmSelect(); 29 channel.basicPublish(exchange001, routingkey, null, body.getBytes()); 30 // 添加一个确认监听========消息确认模式重要添加 31 channel.addConfirmListener(new ConfirmListener() { 32 33 @Override 34 public void handleNack(long deliveryTag, boolean multiple) throws IOException { 35 System.out.println("-----------nck------------"+deliveryTag); 36 37 } 38 39 @Override 40 public void handleAck(long deliveryTag, boolean multiple) throws IOException { 41 System.out.println("-----------ck------------"+deliveryTag); 42 43 } 44 }); 45 } 46 47 }
消费方:
1 package com.zxy.demo.rabbitmq; 2 3 import java.io.IOException; 4 import java.util.concurrent.TimeoutException; 5 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.Connection; 8 import com.rabbitmq.client.ConnectionFactory; 9 10 public class Receiver { 11 12 public static void main(String[] args) throws IOException, TimeoutException { 13 // TODO Auto-generated method stub 14 ConnectionFactory factory = new ConnectionFactory(); 15 factory.setHost("192.168.10.110"); 16 factory.setPort(5672); 17 factory.setUsername("guest"); 18 factory.setPassword("guest"); 19 factory.setVirtualHost("/"); 20 Connection conn = factory.newConnection(); 21 Channel channel = conn.createChannel(); 22 String exchange001 = "exchange_001"; 23 String queue001 = "queue_001"; 24 String routingkey = "mq.*"; 25 channel.exchangeDeclare(exchange001, "topic", true, false, null); 26 channel.queueDeclare(queue001, true, false, false, null); 27 channel.queueBind(queue001, exchange001, routingkey); 28 // 自定义消费者 29 MyConsumer myConsumer = new MyConsumer(channel); 30 // 进行消费(消费queue001队列中的消息进行消费,签收模式为自动签收,消费的具体处理类是myConsumer) 31 channel.basicConsume(queue001, true, myConsumer); 32 } 33 34 }
自定义消费类:
1 package com.zxy.demo.rabbitmq; 2 3 import java.io.IOException; 4 5 import com.rabbitmq.client.AMQP.BasicProperties; 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.DefaultConsumer; 8 import com.rabbitmq.client.Envelope; 9 10 /** 11 * 可以继承,可以实现,实现的话要覆写的方法比较多,所以这里用了继承 12 * 13 */ 14 public class MyConsumer extends DefaultConsumer{ 15 private Channel channel; 16 public MyConsumer(Channel channel) { 17 super(channel); 18 // TODO Auto-generated constructor stub 19 this.channel=channel; 20 } 21 22 @Override 23 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 24 throws IOException { 25 System.out.println("消费标签:"+consumerTag); 26 System.out.println("envelope.getDeliveryTag():==="+envelope.getDeliveryTag()); 27 System.out.println("envelope.getExchange():==="+envelope.getExchange()); 28 System.out.println("envelope.getRoutingKey():==="+envelope.getRoutingKey()); 29 System.out.println("body:==="+new String(body)); 30 } 31 32 33 }
确认模式,主要点有两个,一、在channel上添加确认模式channel.comfirmSelect(),
二、添加确认监听器,实现AK和NAK两个方法来处理两种返回情况。
返回模式大同小异:返回模式需要在发布消息时
采用含有mandatory(托管、强制)参数的消息发布模式,并设置该参数为true========这个与消息确认模式有所区别
true则当消息不可达返回处理消息,如果为false则消息不可达删除消息
channel.basicPublish(exchange001, routingkey, true, null, body.getBytes());
然后添加一个返回监听处理没有正确路由的消息,该监听需要实现处理消息的方法。
这两个模式区别较小,消费端没有什么特别的主要是如果返回模式,路由键让其路由不到消费队列即可使返回监听有动作。
代码如下,仅贴出生产端代码,消费如上:
1 package com.zxy.demo.rabbitmq; 2 3 import java.io.IOException; 4 import java.util.concurrent.TimeoutException; 5 6 import com.rabbitmq.client.AMQP; 7 import com.rabbitmq.client.Channel; 8 import com.rabbitmq.client.ConfirmListener; 9 import com.rabbitmq.client.Connection; 10 import com.rabbitmq.client.ConnectionFactory; 11 import com.rabbitmq.client.ReturnListener; 12 import com.rabbitmq.client.AMQP.BasicProperties; 13 14 public class Producter { 15 16 public static void main(String[] args) throws IOException, TimeoutException { 17 // TODO Auto-generated method stub 18 ConnectionFactory factory = new ConnectionFactory(); 19 factory.setHost("192.168.10.110"); 20 factory.setPort(5672); 21 factory.setUsername("guest"); 22 factory.setPassword("guest"); 23 factory.setVirtualHost("/"); 24 Connection conn = factory.newConnection(); 25 Channel channel = conn.createChannel(); 26 String exchange001 = "exchange_001"; 27 String queue001 = "queue_001"; 28 String routingkey = "mq.topic.return"; 29 String body = "hello rabbitmq!===============返回模式"; 30 // 采用含有mandatory(托管、强制)参数的消息发布模式,并设置该参数为true========这个与消息确认模式有所区别 31 channel.basicPublish(exchange001, routingkey, true, null, body.getBytes()); 32 // 添加一个返回监听========消息返回模式重要添加 33 channel.addReturnListener(new ReturnListener() { 34 35 @Override 36 public void handleReturn(int replyCode, 37 String replyText, 38 String exchange, 39 String routingKey, 40 AMQP.BasicProperties properties, 41 byte[] body) 42 throws IOException { 43 System.out.println("=============return 模式============"); 44 System.out.println("replyCode:"+replyCode+"\n" 45 +"replyText:"+replyText+"\n" 46 +"exchange:"+exchange+"\n" 47 +"routingKey:"+routingKey+"\n" 48 +"properties:"+properties.getBodySize()+"\n" 49 +"body:"+new String(body)+"\n"); 50 51 } 52 }); 53 } 54 55 }