消息幂等

背景 & 问题

本位柯苏远写于2024年4月18日22点15分

还是在公司的整个电商业务中要接入sms 短信发送功能。

上一篇文章地址:分布式锁实战 - 掘金 (juejin.cn)

这周到实际开发过程了,首先将整个需求的技术方案设计好了,然后进行开发。整个方案的设计图如下所示:

A服务:表示每个会产生sms消息的触发点,不单单是一个服务,而是很多个。 B服务:对外提供一个插入消息表的接口,所有触发发送sms短信的服务点都调用这个接口。 C服务:是一个轮询服务,对短信表进行轮询,找到待发送的sms 短信,然后调用sms api 发送短信给客户。

红框部分有个问题:

当A向B推送消息的时候,在B收到消息往数据库成功插入数据之后,当要给A服务返回成功标识的时候,由于网络问题接口超时了,A那边收到的返回就是接口超时,那么对于A服务来说我并不知道这个消息到底成功推送没有?A此时应该怎么做?如果重新推送的话可能会在数据库存入一个一模一样的消息。如果不重新推送的话,那消息可能就丢失了。 归根结底就是由于在第一步消息推送的时候如果发送接口超时,在A服务中我不知道如何处理当前超时的消息。

如何解决

再次重申问题:

  1. 如果推送消息接口超时,我按推送失败处理,继续推送这个消息,那么就可能往数据库插入两条一模一样的消息,导致的结果就是客户收到两条一模一样的消息。
  2. 如果推送消息接口超时,我按推送成功处理,不对这个消息再次推送,那么可能造成的问题就更大了,消息丢失了,这肯定不允许。

既然存在两个问题,那么我们就分别想解决方案。

针对问题1:解决思路挺直观的,因为会往数据库插入重复数据,所以我们给每个消息一个唯一id,当这个消息插入数据库之后,这个消息唯一id也存入了,然后对这个消息唯一id字段进行设置唯一键。这样在数据库层面就避免了消息存储重复的可能。

针对问题2:推送接口超时,不推的话,大部分业务是接受不了的,因为消息丢了,所以我们继续推一次这个消息,不用担心会重复发送,因为在存储层面我们用了消息唯一id做唯一键。

疑问

消息的唯一id怎么产生?

唯一id有很多种,用uuid,或者雪花算法都可以,我们这里用的是uuid。

将一个消息和一个uuid绑定的时候,当超时之后,这个消息对应的uuid要不要改?

肯定是不能改的,改了的话,在数据库层面就会重复插入同一条消息

那么不能改的话,当这个消息推送超时,如何解决继续用这个uuid推送?

其实很简单,让uuid变成这个消息的一个属性,然后推送超时,将这个消息弄到一个本地的队列里去,进行重试。一定要注意,每个消息的唯一id是不会变的。

后续安排

这个是我目前在工作中接触的第一个幂等场景,后续再看看其它幂等情况。

总结

  1. 消息重复发送的可能性。
  2. 引入消息唯一id在数据库层面做唯一索引。
  3. 消息推送超时,在内存进行重试,消息的唯一id是和每个消息强绑的,也就是说再重试的时候不能给消息生成新的唯一id
相关推荐
女王大人万岁29 分钟前
Go语言time库核心用法与实战避坑
服务器·开发语言·后端·golang
J_liaty40 分钟前
Spring Boot + MinIO 文件上传工具类
java·spring boot·后端·minio
短剑重铸之日1 小时前
《SpringCloud实用版》Stream + RocketMQ 实现可靠消息 & 事务消息
后端·rocketmq·springcloud·消息中间件·事务消息
没有bug.的程序员1 小时前
Spring Boot 事务管理:@Transactional 失效场景、底层内幕与分布式补偿实战终极指南
java·spring boot·分布式·后端·transactional·失效场景·底层内幕
LuminescenceJ2 小时前
GoEdge 开源CDN 架构设计与工作原理分析
分布式·后端·网络协议·网络安全·rpc·开源·信息与通信
Tony Bai2 小时前
【分布式系统】11 理论的试金石:用 Go 从零实现一个迷你 Raft 共识
开发语言·后端·golang
短剑重铸之日2 小时前
《SpringCloud实用版》统一认证授权:Spring Authorization Server + OAuth2 + JWT 生产级方案
java·后端·spring·jwt·oauth2
浮尘笔记2 小时前
Go语言并发安全字典:sync.Map的使用与实现
开发语言·后端·golang
淡泊if2 小时前
RESTful API设计标准:单体 vs 微服务的最佳实践
后端·微服务·restful
金牌归来发现妻女流落街头3 小时前
【Spring Boot注解】
后端·springboot