# 如何保证接口的幂等性

# 什么是幂等性

幂等性是系统服务对外一种承诺,承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的服务会认为外部调用失败是常态,并且失败之后必然会有重试。

# 什么情况下需要幂等

SQL 为例:

  • SELECT col1 FROM tab1 WHER col2=2, 无论执行多少次都不会改变状态,是天然的幂等。

  • UPDATE tab1 SET col1=1 WHERE col2=2 ,无论执行成功多少次状态都是一致的,因此也是幂等操作。

  • UPDATE tab1 SET col1=col1+1 WHERE col2=2 ,每次执行的结果都会发生变化,这种不是幂等的。

  • insert into user(userid,name) values(1,'a')userid 为唯一主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性。
    userid 不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。

  • delete from user where userid=1 ,多次操作,结果一样,具备幂等性

# 如何保证幂等

  • # token 机制

    1. 服务端提供了发送 token 的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取 token ,服务器会把 token 保存到 redis 中。

    2. 然后调用业务接口请求时,把 token 携带过去,一般放在请求头部。

    3. 服务器判断 token 是否存在 redis 中,存在表示第一次请求,然后删除 token , 继续执行业务。

    4. 如果判断 token 不存在 redis 中,就表示是重复操作,直接返回重复标记给 client ,这样就保证了业务代码,不被重复执行。

      # 关键点 先删除 token ,还是后删除 token

      后删除 token :如果进行业务处理成功后,删除 redis 中的 token 失败了,这样就导致了有可能会发生重复请求,因为 token 没有被删除。这个问题其实是数据库和缓存 redis 数据不一致问题,后续会写文章进行讲解。

      先删除 token :如果系统出现问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但 token 已经删除掉了,服务端判断 token 不存在,认为是重复请求,就直接返回了,无法进行业务处理了。

      先删除 token 可以保证不会因为重复请求,业务数据出现问题。出现业务异常,可以让调用方配合处理一下,重新获取新的 token ,再次由业务调用方发起重试请求就 ok 了。

      # token 机制缺点

      业务请求每次请求,都会有额外的请求(一次获取 token 请求、判断 token 是否存在的业务)。其实真实的生产环境中,1 万请求也许只会存在 10 个左右的请求会发生重试,为了这 10 个请求,我们让 9990 个请求都发生了额外的请求。

  • # 乐观锁机制

    这种方法适合在更新的场景中 update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
    根据 version 版本,也就是在操作库存前先获取当前商品的 version 版本号,然后操作的时候带上此 version 号。我们梳理下,我们第一次操作库存时,得到 version1 ,调用库存服务 version 变成了 2 ;但返回给订单服务出现了问题,订单服务又一次发起调用库存服务,当订单服务传如的 version 还是 1 ,再执行上面的 sql 语句时,就不会执行;因为 version 已经变为 2 了, where 条件就不成立。这样就保证了不管调用几次,只会真正的处理一次。
    乐观锁主要使用于处理读多写少的问题

  • # 唯一主键

    这个机制是利用了数据库的主键唯一约束的特性,解决了在 insert 场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键。

    如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关。

  • # 防重表

    使用订单号 orderNo 做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。

  • # 唯一 ID

    调用接口时,生成一个唯一 idredis 将数据保存到集合中(去重),存在即处理过。

Edited on Views times