在微服务架构下,咱们在完成一个订单流程时常常遇到下面的场景:mysql
- 一个订单建立接口,第一次调用超时了,而后调用方重试了一次
- 在订单建立时,咱们须要去扣减库存,这时接口发生了超时,调用方重试了一次
- 当这笔订单开始支付,在支付请求发出以后,在服务端发生了扣钱操做,接口响应超时了,调用方重试了一次
- 一个订单状态更新接口,调用方连续发送了两个消息,一个是已建立,一个是已付款。可是你先接收到已付款,而后又接收到了已建立
- 在支付完成订单以后,须要发送一条短信,当一台机器接收到短信发送的消息以后,处理较慢。消息中间件又把消息投递给另一台机器处理
以上问题,就是在单体架构转成微服务架构以后,带来的问题。固然不是说单体架构下没有这些问题,在单体架构下一样要避免重复请求。可是出现的问题要比这少得多。redis
为了解决以上问题,就须要保证接口的幂等性,接口的幂等性实际上就是接口可重复调用,在调用方屡次调用的状况下,接口最终获得的结果是一致的。有些接口能够自然的实现幂等性,好比查询接口,对于查询来讲,你查询一次和两次,对于系统来讲,没有任何影响,查出的结果也是同样。sql
除了查询功能具备自然的幂等性以外,增长、更新、删除都要保证幂等性。那么如何来保证幂等性呢?数据库
若是使用全局惟一ID,就是根据业务的操做和内容生成一个全局ID,在执行操做前先根据这个全局惟一ID是否存在,来判断这个操做是否已经执行。若是不存在则把全局ID,存储到存储系统中,好比数据库、redis等。若是存在则表示该方法已经执行。架构
从工程的角度来讲,使用全局ID作幂等能够做为一个业务的基础的微服务存在,在不少的微服务中都会用到这样的服务,在每一个微服务中都完成这样的功能,会存在工做量重复。另外打造一个高可靠的幂等服务还须要考虑不少问题,好比一台机器虽然把全局ID先写入了存储,可是在写入以后挂了,这就须要引入全局ID的超时机制。微服务
使用全局惟一ID是一个通用方案,能够支持插入、更新、删除业务操做。可是这个方案看起来很美可是实现起来比较麻烦,下面的方案适用于特定的场景,可是实现起来比较简单。spa
这种方法适用于在业务中有惟一标的插入场景中,好比在以上的支付场景中,若是一个订单只会支付一次,因此订单ID能够做为惟一标识。这时,咱们就能够建一张去重表,而且把惟一标识做为惟一索引,在咱们实现时,把建立支付单据和写入去去重表,放在一个事务中,若是重复建立,数据库会抛出惟一约束异常,操做就会回滚。设计
这种方法插入而且有惟一索引的状况,好比咱们要关联商品品类,其中商品的ID和品类的ID能够构成惟一索引,而且在数据表中也增长了惟一索引。这时就可使用InsertOrUpdate操做。在mysql数据库中以下:版本控制
1
2
3
4
|
insert into goods_category (goods_id,category_id,create_time,update_time)
values(#{goodsId},#{categoryId},now(),now())
on DUPLICATE KEY UPDATE
update_time=
now()
|
这种方法适合在更新的场景中,好比咱们要更新商品的名字,这时咱们就能够在更新的接口中增长一个版本号,来作幂等code
1
|
boolean updateGoodsName(int id,String newName,int version);
|
在实现时能够以下
1
|
update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
|
这种方法适合在有状态机流转的状况下,好比就会订单的建立和付款,订单的付款确定是在以前,这时咱们能够经过在设计状态字段时,使用int类型,而且经过值类型的大小来作幂等,好比订单的建立为0,付款成功为100。付款失败为99
在作状态机更新时,咱们就这能够这样控制
1
|
update `order` set status=#{status} where id=#{id} and status<#{status}
|
以上就是保证接口幂等性的一些方法。