幂等性要求:记录无重复, 并且多次请求的返回状态值相同
出现场景
- 在POST
form表单时, 保存按钮不小心快速点了两次, 表中产生了两条重复的数据, 只是id不一样。
- 为了解决
接口超时问题, 通常会引入了超时重试机制。第一次请求接口超时了, 请求方没能及时获取返回结果(此时有可能已经成功了), 为了避免返回错误的结果(这种情况不能直接返回失败), 于是会对该请求重试几次, 这样也会产生重复的数据。
- mq消费者在读取消息时, 有时候会读取到
重复消息, 如果处理不好, 也会产生重复的数据。
- 生产者重复发送
- 生产者发送消息的时候, 必须等待Broker的响应。
- 如果Broker的响应由于网络波动一时没有收到, 那么当超出等待时间之后, 通常生产者会因为没有收到响应而认为这条消息发送失败, 又会重复发送一次。
- 最终导致两条消息都发送成功了, 消息队列有两条重复的消息, 这样就导致了消息的重复。
- 消费者重复消费
- 消费者消费消息的时候, 如果业务逻辑已经走完了, 那么就需要提交offset
- 如果恰好此时消费者挂了, 那么被消费但是没有被提交的消息将被认为是消费失败
- 此消息会被分发到其他消费者上, 导致一条消息被重复消费。
分类
- GET请求不会改变记录状态, 因此天然是幂等的
- POST请求可能产生重复记录
- PUT请求
- 如果只是对不同字段赋值字面量, 天然是幂等的, 如
UPDATE t_user SET age=18 WHERE id=1
- 否则, 可能产生非法结果, 如
UPDATE t_user SET age = age + 1 WHERE id=1、UPDATE t_user SET age=18 WHERE age=17
- DELETE请求
- 对于服务端, 多次删除请求只会删除固定记录, 结果是幂等的
- 对于客户端, 由于首次删除已经成功, 后续多次删除表中已无记录, 会返回内部错误
对策
- 悲观锁:
- 首先加行级锁, 然后利用GET的幂等性进行判断, 如
SELECT * FROM t_user WHERE id=1 FOR UPDATE
- 一定要注意, id必须是主键或唯一索引, 否则会升级为表级锁, 数据量大, 非死即伤!!
- 如果不存在记录, 则POST
- 否则, PUT
- 最后释放锁
- 乐观锁:
- 利用版本号机制, 多次PUT请求只有一次能真正执行, 只需返回正常即可
- 分布式锁
- 全局唯一索引
- 针对POST请求, 对某字段添加唯一索引, 之后如果有重复插入, 只需捕获
DuplicateKeyException和MySQLIntegrityConstraintViolationException(spring框架)异常并finally返回正常即可
- 建全局防重表
- 针对DELETE, 可以新建防重表, 每次DELETE都将记录的id插入防重表, 之后捕获异常并返回正常即可
- 基于redis、zookeeper
posted @
2022-08-20 19:32
Blazer96
阅读(
20)
评论()
收藏
举报