幂等性

发布时间 2023-08-16 14:52:51作者: 江南烟雨行舟
  1. 用户网络不好的时候,可能会频繁点击,导致重复提交。
    比如:频繁点击导致重复下单。

  2. 服务之间相互调用接口的时候,因为请求超时了导致接口被重新请求。
    比如:服务 A 请求服务 B 的时候超时了,不一定是因为请求没有发送到服务 B,有可能是服 B 响应慢,导致超时。

这就有可能导致,虽然是同一个操作,但是每次执行的结果不一样。

简单来说:每次请求后端都会执行一次,但是这些请求都是同一个操作,只是因为各种机制原因导致出现重复请求。

幂等性就是保证:同一个操作不管执行多少次,它们的执行结果都和第一次是一样。

解决方案

解决方案简单来说就是以下几种:

  1. 数据库唯一约束。
  2. 给每条记录设置一个版本号。
  3. 客户端先申请令牌,然后请求的时候携带令牌,服务端会保存这个令牌,如果这个令牌已经存在了,就是重复请求。
    比如:超时后重新请求也会使用相同令牌。

比较完善的设计

如果是 http 想保证幂等性的话,服务端可以将接口名、用户 ID、请求参数组合为 key,然后使用 redis 的 setNX 写入这个 key:

  • 成功,就说明 key 不存在,然后将设置过期时间为 30s。
  • 失败,就说明是重复提交。

如果是订阅者想保证幂等性的话,订阅者可以将消息的唯一 ID当作 key:

  1. 如果消息发送的的毫秒时间戳超过 2 小时,就拒绝执行并发送到私信队列。
  2. 否则就使用消息 ID 查询 redis 和 mysql:
    • 如果 redis 中有,就拒绝执行。
    • 如果 redis 中没有,但是 mysql 中有并且数据版本为 1,然后开始消费消息。
    • 如果 redis 中没有,但是 mysql 中有并且数据版本为 2,并且上次的消费时间超过了 30 分钟,就将版本回退到 1,然后开始消费消息。
    • 如果 redis 中没有,但是 mysql 中有并且数据版本为 3,就说名消息已经消费成功了不会再消费了,只是会写入 redis,防止一直查询数据库。
    • 如果都没有的话,就将消息写入数据库并且数据版本设置为 1,然后开始消费消息。

消费消息,又会经历:

  1. setNX 写入这个 key,并且设置超时时间为 30s。
  2. 设置成功之后才开始真正的处理消息,处理的时候每隔 10s 将 key 的过期时间重置为 30s,然后设置消息的上次消费时间为当前时间。
  3. 消息消费成功后,将数据版本设置为 3。

消息超过有效时间(2 小时)还没有消费,那么就说明系统有问题了,需要人工介入。

重置 key 的过期时间,是为了程序崩溃后,消息补偿机制重新发送消息后可以消费。