「如何设计」:sql
为什么会存在须要服务的幂等,在互联网中因为网络的不稳定和一些业务重复确认设计,对一个接口的调用存在重试的机制,为了确保执行同一个请求执行一次和执行屡次的效果是同样的,因此就存在了幂等的设计。 举个例子,若是在转帐的交易中,A给B进行一笔转帐,若是没有幂等性,极可能就由于各类缘由致使了A给B进行了多笔转帐,在银行系统中,这个就是重大的灾难。bash
服务的幂等可能划分为2个层面,一个是从接口的请求层面,一个是从业务层面考虑。网络
从请求层面考虑,就是一个接口得保证请求一个和请求屡次获得的效果是一致的。 若是用数据表达式是这样的架构
f...f(f(x)) = f(x)
x是参数
f是执行函数
复制代码
把相同的参数传给执行函数,无论执行了多少次,结果是一致的。并发
从业务角度出来
例如一个用户在一次购买中不能重复下单
例如库存剩下了1个商品,如今有10我的抢购,怎么保证不超卖
例如在MQ的生产者是不须要保证幂等,极可能把同一条消息发送屡次,须要保证MQ消费端去重,MQ消费者保证这批消息只会执行一个。分布式
先了解下为什么会出现不幂等的缘由,由于retry重试,若是取消retry机制,是否就能杜毫不幂等呢,答应应该是确定的,但取消retry是否现实,咱们来看看究竟在什么场合会出现retry函数
用户进行下订单,调用下单接口超时,调用方又发起一次建立下单接口。微服务
用户下单进行扣减库存,调用扣减库存接口超时了,调用方又发起一次扣减库存接口。post
下单完毕后,生产者发送一条MQ,MQ超时没有及时响应ACK,生产者又再发送一条MQ,消费者连续就收到了两条MQ
从整个系统或业务层面其实很难去作到去retry,因此在一些接口的幂等性仍是须要咱们本身来作。
读没有形成数据的改变,只有写请求才会形成数据改变。
究竟在哪些层会形成数据的改变
反向代理?网关?业务逻辑?数据访问?
从架构层面出发,哪些层会对数据形成改变,只有形成数据改变的层才须要作出幂等,很显然,数据访问层直接操做DB和Cache (业务逻辑层也可能访问操做cache),从请求层面来看,咱们须要对数据访问层进行幂等操做。
从数据层面出发,数据访问层 也就提供了 CRUD 四个请求层面,先站在数据层出发,看看是否能够对数据访问层进行必定改造让数据访问层达到幂等性
insert into user (name,pm) values ('petty',18)
复制代码
因为user表的主键是自增ID,因此每次插入都会新增一条,能够考虑进行改造,让prikey程序实现而不依赖数据库,或者建立具有unique的索引,确保不会出现重复录入的行。
insert into user (id,name,pm) values (100,'petty',18)
[执行成功]
insert into user (id,name,pm) values (100,'petty',18)
[error : Duplicate entry '100' for key 'PRIMARY']
复制代码
其实不管是否考虑到幂等性,在分布式服务中,都应该尽可能避免使用数据库直接生成Id的方式去建立主键。
对数据库的update操做简单来讲,有【绝对值】修改,【相对值】修改 set [ num = 100 | num++ ]:
绝对值修改,例如商品下架
update product status = -1 where pid=1
复制代码
属于自然幂等,如论执行多少次,结果都一直,不须要改造
(可能有人会有疑问说,若是又有另一个线程把status修改为0,而后retry又把status修改为-1,那就达不到幂等效果,其实这个是另一个问题了,这个不是本接口幂等的问题,而是业务隔离的问题)
相对值修改,年龄增长:
update xx set pm=pm++ where id = 100
复制代码
这个就是明显的不具有幂等性,解决思路,如今程序层中查出数值进行计算
update xx set pm=pm++ where id = 100 and pm = 18
or
update xx set pm=19 where id = 100
复制代码
这里能够在数据层面解决幂等性,不过这里多查了一次sql也会带来性能上的一个问题。
delete from xx order by pm desc limit 10
复制代码
delete 道理跟 update 一致,回到业务中,除非咱们想drop掉整个表,否则其实不能够 delete 掉相对的条件的范围,通常都是须要查询出来哪些确切要delete的id,而后针对这批id进行delete
上面从数据层面对CRUD作幂等处理,不过幂等性更可能是考虑到业务场景,看一个例子。
例如咱们有一个订单,假如订单的状态有:未确认->已经建立->已付款->已确认
update order set status = 已经建立 where orderid=1 and status = 未确认
update order set status = 已经建立 where orderid=1 and status = 未确认
【无效】
复制代码
经过在设计状态字段,使用状态机的机制,确保status必须按照业务的流程往下走,这样在第二次更新时也会无效达到了幂等性的效果。
刚说到在MQ的消费场景中,可能出现屡次消费的状况,这个状况只能由消费者本身解决,举个例子:
当用户下订单由于生产者不须要幂等产生了2个MQ,分别msg=xx001,msg=xx002,他们的orderid都是001,两个消息都发送给了MQ,MQ再将两个消息发送给2个消费者。两个消费者如今用orderid为key,组成了一把分布式锁,两个消费者同时去获取这一把锁,不过它们之间只能有一个消费者获取成功,或者成功的消费者将消费这个mq并执行,或者不成功的消费者将取消执行。
分布式锁主要起到的做用是解决并行的问题,将本来可能出问题的并行转为串性。
分布式锁解决不了的问题:
当一个消费线程获取锁以后,很快就执行了,而后把锁释放掉。另一个线程由于MQ时延等问题在第一个线程执行完以后才接收到MQ,又获取到分布式锁,又将接口再一次执行。
分布式锁其实并不解决幂等性问题,可是能够看得出来,这两次接口的执行并非并发执行,两次是一个串行关系,只要是串行关系,那能够借助状态机的机器去解决。这个时候就变成了一个业务隔离的问题
这个场景适合业务中有惟一插入的场景,例如在支付的场景中,订单只能被支付一次,能够把orderid做为一个惟一标示。新建一张去重表,并把orderid做为惟一索引,在写入订单表时,联通去重表一块儿写入并放在同一个事务中,若是重复调用,会由去重表抛出惟一索引约束的异常,进行回滚。