02.26 点一下按钮调两次接口?浅谈接口设计的幂等性

在单体架构时代,就存在着接口幂等性的问题,只不过到了分布式、高并发的场景之后,接口幂等性的问题会更加明显。

幂等性的概念

那么什么是幂等性呢?

当用户对同一操作请求了一次或者多次,最终的结果是一致的,并不会因为多次请求产生副作用;比如同一个订单支付了两次,最后应该只扣客户一次钱。

查询和删除:查询具有天然的幂等性,在数据不变的前提下,相同查询条件查询一次和查询多次的结果都是一样的;删除也一样,相同的条件删除一次和删除多次,可能删除的数据量不一样,但是数据库中的数据不会因为执行了多次删除而不同。

新增和修改:如果不做幂等性处理,可能就会产生问题;执行多次新增操作,可能会导致一模一样的数据产生了多条(主键自动生成);修改操作,如果只是把某些字段更新成固定的值,不会有幂等性问题,但是如果新值要在旧值上做处理做计算,如增加多少、减少多少,那么多次执行的结果就会有差异。

那么为了保证接口的幂等性,有哪些方法呢?


保证幂等性的解决方案

1. 唯一索引

使用唯一索引,可以有效的防止新增脏数据:当表中存在唯一索引的时候,并发新增相同数据的时候就会报错,不过这在单库单表的时候才有效,如果项目数据量很大,采用了分库分表的策略,就不能再通过数据库的唯一性索引来解决幂等性的问题了。


2. 悲观锁

获取数据的时候加锁获取;

<code>select * from table where col='xxx' for update;/<code>

这里要注意, where 条件中的字段必须是主键或者有索引,否则会锁全表。

3. 分布式锁

在业务系统执行插入或更新操作的时候,先要获取分布式锁,然后做操作,之后释放锁;分布式锁保证在一个时间内,只会有一个线程对数据进行操作。


4. 全局唯一 ID

每次请求,都生成一个全局的唯一 ID,接口调用的时候携带者这个 ID,而业务操作方在之执行前判断这个 ID 是否已经在本地存在,如果不存在,则执行交易后记录 ID(存到数据库或Redis中,表示该交易已经执行);如果已经存在,表示交易已经执行过了,不能再次执行。

许多分布式架构中,生成全局唯一 ID 都会被作为一个基础的微服务,当然这个服务的可靠性要求极高,或者可以使用全局唯一 ID 算法,由每个应用自己生成。不过总的来说,引入全局唯一 ID 这个方案,实现起来还是非常繁琐的。


点一下按钮调两次接口?浅谈接口设计的幂等性


5. 数据版本号

这个方案算是乐观锁的一种;在数据中增加版本号的概念,那么在做数据修改,把当前数据的版本号带上,修改的时候要按照版本号判断数据是否发生过更改。如果没有发生过更改,则执行业务操作,并更新版本号(这种方法适合在更新的场景中)。


下面的代码,意会一下:

<code>updateDate(Object obj , int version);update set obj , version+1 where ... and version = 入参version;/<code>


6. 业务状态

有些业务流程,每一步都是有状态的,比如网上购物可能会有:订单创建、付款、发货,那么付款之前保单状态为“待付款”,付款之后可以将保单的状态修改为“待发货”;那么如果发起重复扣款的话,第二次扣款的时候保单状态已经变化了,就会扣款失败。


点一下按钮调两次接口?浅谈接口设计的幂等性


7. 去重表

如果业务中有唯一性的标识时,可以使用去重表,把这个唯一性的表示保存到去重表中,如果重复插入,那么会被校验住。

比如上面的场景,一个订单只会付款一次,那么在付款的时候,把订单号作为唯一性的标识,保存到去重表中,可以保证付款操作只会发生一次;这个方法也用到了唯一 ID,不过和全局唯一 ID 不同,这个唯一 ID 是针对具体业务的。

点一下按钮调两次接口?浅谈接口设计的幂等性


分享到:


相關文章: