幂等性(Idempotence)

参考:

1、幂等性的定义

Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.

​ 一次和多次请求某一资源应该具有同样的影响。如果第一次请求对资源产生了副作用,以后的多次请求都不会对资源有副作用。

2、为何需要幂等性

​ 以取钱为例, withdraw(account_id, amount)是从account_id的账号中取走amount数额的钱。但是当网络故障或者出bug,没有收到操作成功的OK信息。页面请求多次提交,那么账号就会多次扣钱。

​ 针对这个问题解决方案有两种:

  • 分布式事务。引入分布式事务的中间件,来保证操作的事务性,保证ACID性,但是随着中间件的引入,整体架构会更加复杂,不够轻量,不利于异构系统的集成。
  • 幂等性设计。通过一些幂等性设计方式,解决此问题。更加轻量级。

3、解决幂等性的方式

(1)悲观锁

​ 独占资源,阻塞其他需要锁的线程。

(2)乐观锁

每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。使用的机制就是CAS(Compare and Swap),CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。但是存在ABA问题,可以通过version版本号自增控制。例如: UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version# 当前线程如果得到的版本号不是之前拿到的,就会重试。

ABA问题:主要是针对当CAS保存的A是一个链表的头指针的情况,CAS只会检查头指针是否变化,不会检查这个链表是否有变化,从而出现问题。解决方式就是每次操作version版本号自增。

(3)去重表/唯一索引

将操作的流水单号orderNo做为去重表的唯一索引,每次请求都根据流水单号向去重表中插入一条数据。因为表中唯一索引而插入失败,则返回操作失败,直到第一次的请求完成(成功或失败)。可以看出防重表作用是加锁的功能。

(4)token令牌

int create_ticket() 
bool idempotent_withdraw(ticket_id, account_id, amount)

create_ticket的语义是获取一个服务器端生成的唯一的处理号ticket_id,它将用于标识后续的操作。idempotent_withdraw和withdraw的区别在于关联了一个ticket_id,一个ticket_id表示的操作至多只会被处理一次,每次调用都将返回第一次调用时的处理结果。这样,idempotent_withdraw就符合幂等性了,客户端就可以放心地多次调用。

idempotent

基于幂等性的解决方案中一个完整的取钱流程被分解成了两个步骤:1.调用create_ticket()获取ticket_id;2.调用idempotent_withdraw(ticket_id, account_id, amount)。虽然create_ticket不是幂等的,但在这种设计下,它对系统状态的影响可以忽略,即使出现未收到token令牌也没有关系,可以重新发请求生成令牌。加上idempotent_withdraw是幂等的,所以任何一步由于网络等原因失败或超时,客户端都可以重试,直到获得结果。

posted @ 2021-02-11 13:10  Jayzou11223  阅读(844)  评论(0编辑  收藏  举报