在编程中,幂等(idempotent)特点就是其任意多次执行所产生的影响均与一次执行的影响相同。幂等的场景有很多,这里不再列举。然而很多人对幂等有一些误解:提到幂等就侃侃而谈悲观锁、乐观锁和分布式锁。这是不对的,其实幂等并不是并发场景下的才有问题,幂等的核心是确保唯一性,重点是防止数据重复。
关于并发
可以用幂等的手段来解决部分并发场景,也可以使用并发的手段来解决部分幂等,也可参阅之前文章。
回到主题,如何确保唯一性呢?
唯一索引
大概80%的人都会想到:在数据库中建立唯一索引,用作幂等记录,可以防止插入重复的数据。一般采用的手段都是:幂等函数中先执行一次查询操作,如存在幂等记录则返回第一次执行的结果,如不存在幂等记录则继续执行。
好处:很简单就能满足分布式场景,并发场景下也可以保证只有一个线程插入成功。
缺点:如果所以场景都先落数据到数据库并结合事务使用,成功后再向下执行,一方面存储了很多一次性的数据浪费空间,另一方面数据库性能可能会成为瓶颈。
状态机约束
在单据相关的业务,基本会涉及到状态机。通过约束状态机的跳转,就可以做到有限状态机的幂等。比如:订单状态的流转最好满足正向更新,即后一次更新一定大于等于前一次。
CAS之version 字段
该方案应用非常广泛,version 字段每次更新自动增长,作为乐观锁(compare and sweep)依据,理论上没有性能瓶颈,但局限性很大:仅适用于update。
SETNX
在分布式场景中,新增除了上面利用唯一索引外,还可以使用redis、memcache和zookeeper都可以实现唯一数据。以redis的SETNX:『SET if Not eXists』(如果不存在,则 SET) 。它的使用很特别巧妙,利用一个较短的时间窗口(key的有效期)便可保障一段时间内唯一(并发)。
消息有序
对于有些业务如果上面的手段不再适合时,消息端的有序变得尤为重要,应从集群消费端上加以控制(必定单机控制手段很多,比如:一致性 hash 负载均衡策略),可以修改负载均衡策略或mq拉取的规则(如订单号取模),保证同一笔订单的变更始终会被推送到集群中某一台机器上处理,再分配到其中一个内存队列中强制排队来确保他们的顺序性,这样就保证一个订单始终在一个内存队列中,并由一个线程去消费,是不是有点RocketMQ局部有序性的味道。优势很明显,放弃唯一索引和SETNX,高性能。但该方案并发完美,如果集群中某一台宕机会需考虑单机控制手段是否满足。
很多时候我们要做到真正幂等,主要考虑分布式集群的环境、去重、并发等。
标签:场景,索引,状态机,并发,SETNX,解疑,分布式 From: https://blog.51cto.com/alex/6164858