什么是生产端的可靠投递?
1.消息落库,对消息状态进行打标
上边这个就是正常的流程
如果因为某些原因,生产者没有正确接收到消息应答,消息没有发出或者应答丢失,那么消息状态还是保持sending,此时:
2.消息的延迟投递,做二次确认,回调检查
这个方法是相对性能提出来的,性能较上面第一种要高
幂等性就是指对一个操作进行一百次一千次重复,操作执行的结果都是一样的
消费端幂等性保障
消费端幂等性保障的一个典型的问题就是:在海量订单产生的业务高峰期,如何避免消息的重复消费?
消费端实现幂等性,就意味着,我们的消息永远不会消费多次,即我们收到了多条一样的消息
1.唯一ID + 指纹码机制,利用数据库主键去重
唯一ID:如数据库的主键id
指纹码:业务规则标识唯一的。如时间戳+银行返回的唯一码。需要注意的是,这个指纹码不一定就是我们系统生产的,可能是我们自己业务规则或者是外部返回的一些规则经过拼接后的东西。其目的:就是为了保障此次操作达到绝对唯一的。
唯一ID+指纹码机制,利用数据库主键去重。如:
Select count(id) from table where id = 唯一ID+指纹码
好处:实现简单
坏处:高并发下有数据库希尔的性能瓶颈
解决方案:对唯一ID进行分库分表进行算法路由
2.利用redis的原子性去实现
生产者投递消息后,如果MQ收到消息会发送确认给生产者,生产者异步监听消息确认
confirm消息确认实现
ReturnListener用于处理不可路由的消息,即消息不可达
实现:
什么是消费端的限流?
巨量的消息瞬间全部推送过来,但是我们单个客户端无法同时处理这么多的数据
RabbitMQ提供了一种QOS服务质量保障功能,即在非自动确认消息的前提下,如果一定数目的消息未被确认前,不消费新的消息
实现:
消息变成死信,就是消息没有消费者去消费:
以上三种情况的消息都会被重新投递到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);
}
}
结果:
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);
}