如何理解编程接口的幂等性?
幂等性是编程中一个很重要的概念。特别是我们在设计编程接口时,往往需要考虑接口是否满足幂等性。那么,什么是幂等性呢?幂等性具体应用于哪些场景?实现幂等性有哪些可行的方案?本文一一为您揭秘。
什么是幂等性
幂等原本是一个数学概念,用数学公式表示为:
1 | f(f(x)) = f(x) |
引入到计算机编程领域,幂等性是指客户端对服务端接口调用一次和调用多次,对服务端状态的影响是相同的。对于HTTP请求,一次请求和多次请求,如果对被请求资源本身的影响完全相同,则可认为请求是幂等的。同样地,对于一个微服务接口,如果多次调用和一次调用的效果一样,则认为这个接口是幂等的。
我们用幂等性的上述定义来检查数据库的增删改查。显然,查询和删除是幂等的。新增和更新操作,则不是幂等的。举反例如下:
- 新增客户记录时,每执行一次insert操作,都会在数据库表中添加一条记录。
- 更新文章浏览量时,需要在原有浏览量数值上执行+1操作,每执行一次,浏览量数值都会随之改变。
幂等是服务端对外的一种承诺,承诺只要调用服务端成功,无论客户端调用多少次,都不用担心因重复调用打乱服务端的状态。
什么场景下需要幂等性
- 不允许重复提交时。 比如电商系统下订单时,用户重复点击下单按钮,后台应避免重复下单、重复扣款。
- 客户端引入失败重试机制时。在某些情况下,服务端执行操作成功而未来得及把操作结果返回给客户端,客户端误认为服务端操作失败,自动发起重试。
如何实现幂等性
实现幂等性,总体思路就是先判断再插入/更新。也就是check if exist then insert
和check if not updated then update
类似的操作。这类操作往往不是原子的,需要加锁。具体有以下几种方案:
使用唯一索引
适用于业务上要求只存在一条记录的数据表,比如用手机号来作为客户的唯一标识,则针对手机号建立唯一索引,这样在插入相同手机号的客户记录时,数据库就会报错并回滚插入操作,从而保证幂等性。token校验
借助外部系统存储的token来判断是否重复提交。具体做法是,服务端先生成一个token保存下来,然后发给客户端使用。客户端在提交时,请求中必须带上该token。服务端收到客户端请求后,在执行具体操作前,先校验客户端传入的token,看是否和服务端已存储的token一致。若一致则表明是第一次请求,可以开绿灯放行,并在操作完成后删除或更新token。若不一致或未找到,则认为是重复提交,不予放行。我们常见的csrf_token
就是为防止表单重复提交而使用的。悲观锁
悲观锁一般配合事务一起使用,用于记录更新频繁的场景。在更新某条记录前,需要先使用select ... where id=111 for update
语法来对记录加锁,然后再执行更新操作。乐观锁
乐观锁只需要在更新数据的瞬间锁表,往往比悲观锁更高效。一般用增加记录版本号的方式来实现乐观锁。比如下面这个微博计数表:
weibo_id | repost_count | version |
---|---|---|
1001 | 37 | 4 |
每次转发,都需要将转发数+1。这里我们用version
字段来保存记录版本号。更新时,为保证幂等性,需执行如下SQL:
1 | update t_weibo_count set repost_count=repost_count+1, version=version+1 where id=1001 and version=4 |
这样,如果是第一次请求,version
匹配,更新成功,version
+1。而后续的请求,无论执行多少次,因为version
在第一次请求后已经+1,和SQL中的version
对不上,因此更新不会被执行。
分布式锁
分布式锁可以变相地认为是一种token校验机制。业务系统在插入或更新数据前,需要先获得锁。操作完成后,需要释放锁。在锁定期间,其他客户端只能等待。分布式锁一般用redis和zookeeper实现。有限状态机
有些领域对象比如订单,它的状态较为固定,转化途径也比较确定。我们在构造订单更新SQL时,可以带上订单状态字段,类似上面乐观锁的情况。若订单状态不匹配,则认为是重复操作,不予执行。
总结
幂等性规定多次操作和一次操作对系统状态的影响是想同的。幂等性同时也是接口对外部调用方的一种承诺。在接口和系统设计中,我们需要充分衡量某个接口是否需要实现成幂等性的。实现幂等性的主要思路就是先判断再插入/更新,可通过判断唯一索引、单次有效token、是否有锁、状态是否匹配等方式来识别并无视重复操作,达到幂等性的要求。