常见问题
1.消息如何保证100%投递成功?
什么是生产端的可靠投递?
- 保障消息的成功发出
- 保障MQ节点的成功接收
- 发送端收到MQ节点的正确应答
- 完善的消息补偿机制
1.消息落库,对消息状态进行打标
- step1:消息生成后持久化到数据库,msgStatus = ‘sending’
- step2:生产者发送消息
- step3:MQ节点收到消息后发送应答
- step4:生产者接收到消息应答后,更新消息状态msgStatus = ‘finish’
上边这个就是正常的流程
如果因为某些原因,生产者没有正确接收到消息应答,消息没有发出或者应答丢失,那么消息状态还是保持sending,此时:
- 利用分布式定时任务例如quartz扫描消息表中消息状态为sending的消息进行重新发送
- 每重试一次,重试次数+1
- 当达到一定的重试次数还是没有正确投递,则根据自行策略进行其他处理
2.消息的延迟投递,做二次确认,回调检查
这个方法是相对性能提出来的,性能较上面第一种要高
- 生产者生产两条消息(一条消息时业务消息,一条是延迟确认的消息)并且将第一条消息发送到MQ的业务队列,延迟消息延迟一定时间后发送到MQ的确认队列
- 消费者监听MQ的队列收到生产者的业务消息后,发送一条confirm消息到MQ的confirm确认队列
- 回调服务监听MQ的confirm确认队列,收到消息后,将消息持久化到消息表
- 回调服务也监听延迟确认队列,接收到延迟确认消息后,检查数据库是否有该confirm消息,有则确认发送成功,无则进行消息重试
2.幂等性
幂等性就是指对一个操作进行一百次一千次重复,操作执行的结果都是一样的
消费端幂等性保障
消费端幂等性保障的一个典型的问题就是:在海量订单产生的业务高峰期,如何避免消息的重复消费?
消费端实现幂等性,就意味着,我们的消息永远不会消费多次,即我们收到了多条一样的消息
1.唯一ID + 指纹码机制,利用数据库主键去重
唯一ID:如数据库的主键id
指纹码:业务规则标识唯一的。如时间戳+银行返回的唯一码。需要注意的是,这个指纹码不一定就是我们系统生产的,可能是我们自己业务规则或者是外部返回的一些规则经过拼接后的东西。其目的:就是为了保障此次操作达到绝对唯一的。
唯一ID+指纹码机制,利用数据库主键去重。如:
Select count(id) from table where id = 唯一ID+指纹码
好处:实现简单
坏处:高并发下有数据库希尔的性能瓶颈
解决方案:对唯一ID进行分库分表进行算法路由
2.利用redis的原子性去实现
3.confirm消息确认
生产者投递消息后,如果MQ收到消息会发送确认给生产者,生产者异步监听消息确认
confirm消息确认实现
- channel开启确认模式,channel.confirmSelect()
- channel添加监听,addConfirmListen()
- 编写成功确认handleAck()和失败处理handleNack()的逻辑
4.Return消息机制
ReturnListener用于处理不可路由的消息,即消息不可达
实现:
- channel添加监听addReturnListener()
5.消费端限流
什么是消费端的限流?
巨量的消息瞬间全部推送过来,但是我们单个客户端无法同时处理这么多的数据
RabbitMQ提供了一种QOS服务质量保障功能,即在非自动确认消息的前提下,如果一定数目的消息未被确认前,不消费新的消息
实现:
- 添加channel.basicQos()
6.TTL队列/消息
- RabbitMQ支持消息的有效时间,在消息发送时设定
- RabbitMQ支持队列的过期时间,从消息入队开始计算,只要超过了队列的超时时间设置,那么消息就会被删除
7.死信队列
消息变成死信,就是消息没有消费者去消费:
- 消息被拒绝(basic.reject/basic.nack),并且requeue = false
- 消息TTL过期
- 队列达到最大长度
以上三种情况的消息都会被重新投递到rabbitmq的死信队列
实现:
- 设置死信队列,绑定交换机,exchange = dlx.exchange,queue = dlx.queue,key = #
- 绑定时需要加上一个扩展参数,x-dead-letter-exchange
springboot2.x整合rabbitmq
简单集成使用
队列,交换机及其绑定
@Configuration
public class QueueAndExchangeConfig {
@Bean
public Queue direcQueueA(){
return new Queue("direc_test_a",true);
}
@Bean
public DirectExchange directExchangeA(){
return new DirectExchange("direc_exchange_test_a",true,false);
}
@Bean
public Binding binding_direct(DirectExchange directExchangeA, Queue queue){
return BindingBuilder.bind(queue)
.to(directExchangeA)
.with("test_a");
}
}
消息工具类
@Component
public class MessageUtil {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMsg(String msg,String key,String exchange) {
rabbitTemplate.convertAndSend(exchange, key, msg);
}
}
消息生产者
@Component
public class Producer {
@Autowired
private MessageUtil messageUtil;
public void test(){
messageUtil.sendMsg("hello rabbitMQ","test_a","direc_exchange_test_a");
}
}
消息消费者
@Slf4j
@Component
public class Consumer {
@RabbitListener(queues = "direc_test_a")
public void recevice(String msg){
log.info("接收到的消息:---->"+msg);
}
}
主类开启配置
@EnableRabbit
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
结果:
confirm消息确认和Return消息机制
ConfirmConfig配置类
@Slf4j
@Component
public class ConfirmConfig implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
if(!ack){
log.error("-------消息没有正确确认-------");
}else{
log.info("--------消息正确确认");
}
}
}
ReturnConfig配置类
@Component
@Slf4j
public class ReturnConfig implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
log.info("------->消息没有正确被路由");
}
}
消费端配置手工ACK
@Slf4j
@Component
public class Consumer {
@RabbitHandler
@RabbitListener(queues = "direc_test_a")
public void recevice(Message msg, Channel channel) throws IOException {
log.info("接收到的消息:---->"+new String(msg.getBody()));
channel.basicAck(msg.getMessageProperties().getDeliveryTag(),false);
//处理错误消息,拒觉错误消息重新入队
channel.basicNack(msg.getMessageProperties().getDeliveryTag(),false,false);
channel.basicReject(msg.getMessageProperties().getDeliveryTag(),false);
}
}
开启配置
# 生产者
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.template.mandatory=true
# 消费者
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=1
spring.rabbitmq.listener.simple.max-concurrency=5
配置rabbitmqTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private ConfirmConfig confirmConfig;
@Autowired
private ReturnConfig returnConfig;
public void sendMsg(String msg,String key,String exchange) {
rabbitTemplate.setReturnCallback(returnConfig);
rabbitTemplate.setConfirmCallback(confirmConfig);
rabbitTemplate.convertAndSend(exchange, key, msg);
}