rabbitmq整合springboot 回调 确认一体化
1.首先我们简单了解一下消息中间件的应用场景
异步处理
场景说明:用户注册后,需要发注册邮件和注册短信,传统的做法有两种1.串行的方式;2.并行的方式
(1)串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西.
(2)并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。
假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并性已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,英爱是写入数据库后就返回.
(3)消息队列
引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理
由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。
应用解耦
场景:双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.
这种做法有一个缺点:
- 当库存系统出现故障时,订单就会失败。
-
订单系统和库存系统高耦合.
引入消息队列
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,获取下单消息,进行库操作。
就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失。
流量削峰
流量削峰一般在秒杀活动中应用广泛
场景:秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
作用:
1.可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)
2.可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
1.用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.
2.秒杀业务根据消息队列中的请求信息,再做后续处理.
以上内容的来源是:https://blog.csdn.net/qq_38455201/article/details/80308771,在此感谢
2.各种消息中间件性能的比较:
TPS比较 一ZeroMq 最好,RabbitMq 次之, ActiveMq 最差。
持久化消息比较—zeroMq不支持,activeMq和rabbitMq都支持。持久化消息主要是指:MQ down或者MQ所在的服务器down了,消息不会丢失的机制。
可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统、社区—RabbitMq最好,ActiveMq次之,ZeroMq最差。
高并发—从实现语言来看,RabbitMQ最高,原因是它的实现语言是天生具备高并发高可用的erlang语言。
综上所述:RabbitMQ的性能相对来说更好更全面,是消息中间件的首选。
3.接下来我们在springboot当中整合使用RabbitMQ
第一步:导入maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
第二步:在application.properties文件当中引入RabbitMQ基本的配置信息
spring.application.name=spirng-boot-rabbitmq-sender spring.rabbitmq.host=127.0.0.1 spring.rabbitmq.port=5672 spring.rabbitmq.username=test spring.rabbitmq.password=1qazxsw@ server.port=80 spring.rabbitmq.publisher-confirms=true spring.rabbitmq.publisher-returns = true spring.rabbitmq.listener.direct.acknowledge-mode=manual spring.rabbitmq.listener.simple.acknowledge-mode=manual
第三步:编写RabbitConfig类,类里面设置很多个EXCHANGE,QUEUE,ROUTINGKEY,是为了接下来的不同使用场景。
package com.rabbit.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SenderConfig { public static final String EXCHANGE = "exchange"; public static final String ROUTINGKEY = "routingkey"; public static final String QUEUE = "message"; public static final String QUEUE1 = "message1"; @Bean public Queue queueMessage() { return new Queue(QUEUE); } @Bean public Queue queueMessage1() { return new Queue(QUEUE1); } @Bean public TopicExchange exchange() { return new TopicExchange(EXCHANGE); } @Bean Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) { return BindingBuilder.bind(queueMessage).to(exchange).with(QUEUE); } @Bean Binding bindingExchangeMessages(Queue queueMessage1, TopicExchange exchange) { return BindingBuilder.bind(queueMessage1).to(exchange).with(QUEUE1); } }
第四步:编写消息的生产者
package com.rabbit.service; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.rabbit.config.SenderConfig; import com.rabbit.model.User; @Component public class HelloSender implements RabbitTemplate.ReturnCallback{ /*@Autowired private AmqpTemplate template;*/ @Autowired private RabbitTemplate rabbitTemplate; public void send() { User user = new User(); user.setUsername("测试"); user.setPassword("12345"); this.rabbitTemplate.setReturnCallback(this); this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (!ack) { System.out.println("HelloSender消息发送失败" + cause + correlationData.toString()); } else { System.out.println("HelloSender 消息发送成功 "); } }); this.rabbitTemplate.convertAndSend(SenderConfig.EXCHANGE, SenderConfig.QUEUE1, user.getUsername()); //template.convertAndSend("exchange", "topic.message", user.getUsername()); } /* public void send1() { User user = new User(); user.setUsername("测试"); user.setPassword("12345"); template.convertAndSend("exchange", "topic.message1", user.getPassword()); }*/ @Override public void returnedMessage(Message message, int replycode, String replytext, String exchange, String routingKey) { System.out.println("消息主题 message:"+message); System.out.println("消息主题 message:"+replycode); System.out.println("描述:"+replytext); System.out.println("消息使用的交换器 exchange:"+exchange); System.out.println("消息使用的路由器 routing:"+routingKey); } /*@Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("消息唯一标识" + correlationData.getId()); System.out.println("确认结果" + ack); System.out.println("失败原因" + cause); }*/ }
第六步:编写消息的消费者,这一步也是最复杂的,因为可以编写出很多不同的需求出来,写法也有很多的不同。
package com.rabbit.config; import java.io.IOException; import java.util.Date; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import com.rabbitmq.client.Channel; @Component public class HelloReceive { @RabbitListener(queues=ReceiveConf.QUEUE) //监听器监听指定的Queue public void process1(String str,Message message, Channel channel) throws IOException { System.out.println("HelloReceiver收到 : " + str +"收到时间"+new Date()); try { //告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); System.out.println("receiver success"); } catch (IOException e) { e.printStackTrace(); //ack返回false,并重新回到队列 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true); System.out.println("receiver fail"); } } @RabbitListener(queues=ReceiveConf.QUEUE1) //监听器监听指定的Queue public void process2(String str) { System.out.println("message1:"+str); } }