前言
RabbitMq是基于AMQP协议的开源消息代理软件,与spring的整合在上一篇中已经介绍了。如果想细致了解AMQP可以去spring官网上查看(https://spring.io/projects/spring-amqp#overview)。
如何保证消息投递成功
1.开启应答模式
spring.rabbitmq.publisher-confirms=true
Confirm模式只管有无投递到exchange,而不管有无发送到队列当中。
2.手动应答
spring.rabbitmq.listener.simple.acknowledge-mode=manual
消费者端需要手动应答,可以当作限流使用。
3.返回模式
spring.rabbitmq.publisher-returns=true
用于 消息未投递到queue上的反馈。
4.设置重试次数
如果消费端消费失败不做任何处理的话,会不停的将消息重新塞入队列中,最终耗尽磁盘资源。
spring.rabbitmq.listener.simple.retry.enabled = true
spring.rabbitmq.listener.simple.retry.initial-interval = 1000
spring.rabbitmq.listener.simple.retry.max-attempts = 3
或者在业务代码里做处理,try catch住会有问题的代码,并做出相应的处理。
生产者代码
@Service
public class SendMqService implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
private static Logger LOGGER = LoggerFactory.getLogger(SendMqService.class);
private final RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
@Autowired
public SendMqService(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public <T> void send(String exchange, String routingKey, T msg) {
String sign = UUID.randomUUID().toString();
CorrelationData data = new CorrelationData(sign);
LOGGER.info("sign:{};exchange:{},routingKey:{}发送mq消息:{}", sign, exchange, routingKey, JSON.toJSONString(msg));
rabbitTemplate.setMandatory(true);
this.rabbitTemplate.convertAndSend(exchange, routingKey, msg,data);
LOGGER.info("sign:{};发送mq消息成功", sign);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
//默认自动应答,检测是否投递到exchange。
if (ack) {
System.out.println("确认消息发送成功:" + correlationData);
} else {
//消息投递失败,消息满了?各种场景对应不同的处理
System.out.println("消息发送失败:" + s);
}
}
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
//返回模式 消息为投递到queue时的反馈
System.out.println(message.getMessageProperties().getCorrelationId() + " 发送失败");
}
}
生产者代码
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "omg.test.queue.fan", durable = "true"),
exchange = @Exchange(value = "omg.test.exchange.fan", durable = "true", type = ExchangeTypes.FANOUT)))
public void c1(Message message,Channel channel) throws IOException, InterruptedException {
System.out.println("收到消息"+new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("ACK消息");
}
大量的消息堆积怎么办(ack未应答会导致队列堆积消息)
1.重启
2.消费速率问题,如果不是重要消息,可以直接从控制台上清除。
3.设置队列接受的数量,最大内存大小等。x-max-length x-max-length-bytes
4.消费端限流,push的方式会一直推送消息。可以设置basicQos,即最多同时处理的消息数。
防止消息重复消费
String sign = UUID.randomUUID().toString();
CorrelationData data = new CorrelationData(sign);
rabbitTemplate.convertAndSend(exchange, routingKey, msg,data);
消费端需要将sign做幂等处理,可以入库。
如何保证生产者和消费者的数据一致性问题
1.消费者回调,在消费消息后,调用提前约定好的api,通知生产者。这样做的缺点就是没有避免解耦。
2.补偿机制。生产者发送消息后将信息入库,定义标记位,消费者消费消息后,将状态修改。启定时任务去轮询记录表,捞出未处理的记录,并重新发送。注意设置重试次数。