-
用户网络不好的时候,可能会频繁点击,导致重复提交。
比如:频繁点击导致重复下单。 -
服务之间相互调用接口的时候,因为请求超时了导致接口被重新请求。
比如:服务 A 请求服务 B 的时候超时了,不一定是因为请求没有发送到服务 B,有可能是服 B 响应慢,导致超时。
这就有可能导致,虽然是同一个操作,但是每次执行的结果不一样。
简单来说:每次请求后端都会执行一次,但是这些请求都是同一个操作,只是因为各种机制原因导致出现重复请求。
幂等性就是保证:同一个操作不管执行多少次,它们的执行结果都和第一次是一样。
解决方案
解决方案简单来说就是以下几种:
- 数据库唯一约束。
- 给每条记录设置一个版本号。
- 客户端先申请令牌,然后请求的时候携带令牌,服务端会保存这个令牌,如果这个令牌已经存在了,就是重复请求。
比如:超时后重新请求也会使用相同令牌。
比较完善的设计
如果是 http 想保证幂等性的话,服务端可以将接口名、用户 ID、请求参数组合为 key,然后使用 redis 的 setNX 写入这个 key:
- 成功,就说明 key 不存在,然后将设置过期时间为 30s。
- 失败,就说明是重复提交。
如果是订阅者想保证幂等性的话,订阅者可以将消息的唯一 ID当作 key:
- 如果消息发送的的毫秒时间戳超过 2 小时,就拒绝执行并发送到私信队列。
- 否则就使用消息 ID 查询 redis 和 mysql:
- 如果 redis 中有,就拒绝执行。
- 如果 redis 中没有,但是 mysql 中有并且数据版本为 1,然后开始消费消息。
- 如果 redis 中没有,但是 mysql 中有并且数据版本为 2,并且上次的消费时间超过了 30 分钟,就将版本回退到 1,然后开始消费消息。
- 如果 redis 中没有,但是 mysql 中有并且数据版本为 3,就说名消息已经消费成功了不会再消费了,只是会写入 redis,防止一直查询数据库。
- 如果都没有的话,就将消息写入数据库并且数据版本设置为 1,然后开始消费消息。
消费消息,又会经历:
- setNX 写入这个 key,并且设置超时时间为 30s。
- 设置成功之后才开始真正的处理消息,处理的时候每隔 10s 将 key 的过期时间重置为 30s,然后设置消息的上次消费时间为当前时间。
- 消息消费成功后,将数据版本设置为 3。
消息超过有效时间(2 小时)还没有消费,那么就说明系统有问题了,需要人工介入。
重置 key 的过期时间,是为了程序崩溃后,消息补偿机制重新发送消息后可以消费。