RabbitMQ消息确认
SpringBoot与RabbitMQ整合后,对RabbitClient的“确认”进行了封装、使用方式与RabbitMQ官网不一致;
消息发布确认
- 生产者给交换机发送消息后、若是不管了,则会出现消息丢失;
- 解决方案1: 交换机接受到消息、给生产者一个答复ack, 若生产者没有收到ack, 可能出现消息丢失,因此重新发送消息;
- 解决方案1隐藏问题:若是交换机发送了ack, 出现网络延迟,则生产者没有收到ack, 就会出现消息重复发送问题, 进而衍生幂等性问题;
- 隐藏问题解决方案1:在数据库中增加一张去重表,设置唯一索引; 生产者在消息内容中,翻入唯一ID,消费者消费时、先从数据库查询是否存在,存在则不处理该消息;
- 适用于并发低、业务严谨的场景
- 隐藏问题解决方案2:利用Redis的String的setnx,若key存在,则不处理、若key存在,则执行业务;
- 适用于短时间处理大量消息,且 key不会重复;
-这就是大名鼎鼎的幂等性问题,贼讨厌这些专有名词;
- 适用于短时间处理大量消息,且 key不会重复;
- 隐藏问题解决方案1:在数据库中增加一张去重表,设置唯一索引; 生产者在消息内容中,翻入唯一ID,消费者消费时、先从数据库查询是否存在,存在则不处理该消息;
业务开发中的幂等性
- 前端保存数据时、点击多次保存按钮,插入多条数据;
- 解决方案 :前端限制按钮点击、 数据库设置业务唯一索引;
- 消息推送中,可能出现多条内容一样的消息,又不可以重复处理 ,需要幂等性处理;
上家公司中,后台给app客户端推送系统消息时、配置给所有用户推送消息, 其他服务给我的应用消息服务推送 RabbitMQ消息, 正常来说, 每次推送的消息, 设备ID和用户ID合起来唯一的,结果其他服务业务数据存在问题,有些旧数据没有清除, 导致相通的设备ID,用户ID, 一次给设备用户推送了十几条,安卓客户端当当当的响, 直接惊动了产品经理; 经过排查上,是上游数据有问题,代码又很老,其他服务负责人排查了好几天, 把问题数据清楚了, 结果后面又产生了问题数据;- 解决方案:由于会一次性处理几万条推送消息,因此对业务要求速度高,因此利用Redis的String的setNx, 以taskId + mobileDevId + userId + tenantId 组成了唯一key,若是存在,则不处理; key有限时间为60分钟, 就成功处理了该问题;
- 吐槽:上游的业务问题,让下游服务做业务保证,属实离谱;
{
“taskId” :“xxxx”;
“mobileDevId” : “xxxx”;
“userId”:“xxx”;
“tenantId” : “xxx”;
“其他字段”: “…”
}
RabbitMQ发布确认与返回
SpringBoot发布确认与返回
配置:
第二个参数因为过时,所以要配置第三个参数为correlated,表示用来确认消息;
#生产者
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-confirm-type=correlated
生产者:
- 通过RabbitTempldate#setConfirmCallback设置确认回调, 即交换器发送给ack给生产者,生产者调用ConfirmCallback回调, 若出现异常cause,则可重新推送;
通过RabbitTempldate#setReturnCallback设置返回回调; - 通过template#waitForConfirms(xxx)表示等待xxx毫秒后确认,超时返回false;
- 若返回false, 则进行业务补救处理;
public class ConfirmProducer implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback{
@Autowired
private RabbitTemplate template;
@Autowired
private DirectExchange confirmExchange;
AtomicInteger index = new AtomicInteger(0);
AtomicInteger count = new AtomicInteger(0);
private final String[] keys = {
"sms", "mail"};
@Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() throws IOException {
//短信
String sms = "{userName: xxx; phone:xxx}";
HashMap<String, Object> map = new HashMap<>();
map.put("userName", "hanxin");
map.put("phone", index