在微服务架构下,保证接口幂等性
在微服务架构下,我们在完成一个订单流程时经常遇到下面的场景:
①一个订单创建接口,第一次调用超时了,然后调用方重试了一次。
②在订单创建时,我们需要去扣减库存,这时接口发生了超时,然后调用方重试了一次。
③当这笔订单开始支付,在支付请求发出之后,在服务端发生了扣钱操作,接口响应超时了,然后调用方重试了一次。
一个订单状态更新接口,调用方连续发送了两个消息,一个是已创建,一个是已付款。但是你先接收到已付款,然后又接收到了已创建。在支付完成订单之后,需要发送一条短信,当一台机器接收到短信发送的消息之后,处理较慢。消息中间件又把消息投递给另外一台机器处理。以上问题,就是在单体架构转成微服务架构之后,带来的问题。当然不是说单体架构下没有这些问题,虽然在单体架构下同样要避免重复请求,但是单体架构出现问题的概率要比这低得多。
为了解决以上问题,就需要保证接口的幂等性,接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。有些接口可以天然的实现幂等性,比如查询接口,对于查询来说,你查询一次和两次,对于系统来说,没有任何影响,查出的结果也是一样。
除了查询功能具有天然的幂等性之外,增加、更新和删除都要保证幂等性。那么如何来保证幂等性呢?
全局唯一ID
如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前根据这个ID是否存在来判断这个操作是否已经执行。如果不存在则把ID存储到存储系统中,比如数据库、redis等;否则,表示该方法已经执行。
使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,而且实现起来比较简单。
去重表
这种方法适用于在业务中有唯一标识插入场景中,比如在上述支付场景中,如果一个订单只会支付一次,那么订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在实现时,把创建支付单据和写入去重表放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,导致创建操作自动回滚。
多版本控制
这种方法适合在更新的场景中,比如要更新商品的名字,这时就可以在更新的接口中增加一个版本号,来做幂等:
boolean updateGoodsName(int id,String newName,int version);
在实现时可以如下:
update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
有限状态机控制
在日常工作过程中,我们经常会遇到状态的变化场景,例如订单状态发生变化,商品状态的变化。我们称这些状态的变化为有限状态机,英文缩写为FSM( F State Machine)。之所以称其为有限,是因为这些场景中的状态往往是可以枚举出来的、数量有限的,所以称其为有限状态机。
这种方法适合在有状态机流转的情况下,比如订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,已成交为7,待付款为10,付款中为20,付款成功为100,付款失败为99。在做状态机更新时,就可以这样控制:
update
order
set status=#{status} where id=#{id} and status<#
状态模式
类似状态机控制策略,我们也可以使用状态模式进行幂等性处理。例如,如果订单状态不是付款中,就不可以将订单状态改为付款成功状态。
小结
以上就是保证接口幂等性的一些方法。大家对于接口幂等性都是怎么看的呢?欢迎在文章下方留言讨论!小编会仔仔细细地阅读每条留言。