幂等设计
关于幂等设计 https://mp.weixin.qq.com/s/zhYoRSVxNADEA1ujCVaC6A
关于幂等设计
一、业务场景
业务场景一:用户重复下单
业务场景二:用户重复支付
业务场景三:用户转帐重试
二、什么是幂等
幂等的定义:
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。
在数学中,幂等用函数表达式就是:f(x) = f(f(x))。比如求绝对值的函数,就是幂等的,abs(x) = abs(abs(x))。
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。
幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。
这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。
三、幂等场景
1、天然幂等场景:
查询类的读操作,天然是幂等的,多次调用不会有副作用。
2、需要保证幂等的场景
a. 调用下游写接口
b. 写数据库、写Redis等
c. 消息订阅和处理
3、如何保证幂等
大部分幂等涉及到写请求,写请求一般是CRUD
CREATE/INSERT
insert user values (uid,name,age,sex,time)(业务主键,唯一索引,留意自增索引的优势劣势)
READ/SELECT
select * from user where uid=3(天然幂等)
UPDATE
update user set age = 18 where uid=3(执行多次是幂等,绝对值修改)
update user set age++ where uid = 3 (相对值的修改)
DELETE
delete from user where uid=3 (一般不会删除,会做伪删除。)
delete from user where uid in top 10 (多次执行,变化删除)
4、幂等案例 (update)
案例一:消息是否已读
天然幂等
案例二:年龄增加1岁
增加where条件
where age=18
相对修改转换成绝对修改
set age = 19
案例三 电商平台购买商品
商品 价格1万
确认收货
订单状态 (打款,状态修改)
处理流程:
a. 状态判断
b. 打款动作
c. 状态修改
d. 分布式事务
四、常见的问题
1、冗余部署多个服务。
2、存在并发处理的可能性
解决:把并发转串行消息,使用分布式锁
五、案例
1、企业级的电商下单
2、2台服务,1台DB,要求用户信息手机号不重复(条件1:手机号不可以为空;条件2:手机号可以为空)。
六、总结-如何保证幂等
1、基于状态的幂等
这种情况比较简单,只有当满足前置条件时才允许操作,否则不允许更新(例如已经是终态),直接返回。例子:订单支付成功后,不允许重复支付。
2、基于唯一键的幂等
幂等key的选取
与业务强相关,可以是商品id、订单id、用户id,或者日期等,或者是几个业务字段的组合。
全局的唯一性ID Snowflake
几个例子
一个用户每天只能领一张优惠券,通过用户id+优惠券类型+日期字符串即可唯一标识。
B端更新库存,商品id+该商品的版本号
C端扣库存,订单id
值得注意的是,需要区分新增和修改:修改时的幂等key往往需要带上版本号,才能区分是否同一次修改,每次修改对应一个唯一的版本号。(因为乐观锁存在ABA的问题,如果version版本一直是自增的就不会出现ABA的情况啦)。
实现方式
MySQL表中为幂等key建立唯一索引:强幂等,例如资金、订单,绝对不允许重复处理,当插入重复数据时报错。不推荐用Redis实现幂等,一旦Redis出问题,比如节点宕机,可能出现2个client同时获取到锁的情况。
3、消息订阅和处理
MQ通常会保证消息至少发送一次(可能多次),并且在机器实例重启或发版时,consumer group会做rebalance,进而收到重复的消息。因此,消息的幂等处理必不可少。
实现方式
在处理消息前加上锁:如果上锁成功,则继续处理,否则稍后重试。
不存在时才设置,时效即为锁的租期,否则忽略。
接下来的业务处理,如果是自身逻辑需要强幂等则使用上述数据库幂等方式,如果全部依赖下游则依赖下游实现幂等。