微服务架构下,处理一个业务请求可能需要调用多个微服务进行处理,以前面的下单并支付场景为例,完成该业务请求需要先后调用订单微服务的下单接口和支付微服务的支付接口,只有这两个接口都调用成功,该业务操作才算执行成功。那么微服务架构中是如何保证同属于一个业务单元的多个操作的原子性以及保证分布式数据一致性的?——答案是分布式事务。
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上并且根据遵循的一致性原则不同,可以分为刚性分布式事务和柔性分布式事务两大类。
遵循ACID原则的刚性事务
刚性事务追求数据的强一致性,比如基于两阶段提交和三阶段提交的分布式事务就属于刚性事务,通过分布式事务,客户端可以看到描述业务执行状态的多个数据的一致性视图,比如下单并支付这个业务操作,客户端要么能够同时查询到下单和支付成功的信息,要么能够同时查询到下单和支付失败的信息,其他不一致的情况对于客户端而言都是不可见的。比如下单成功,支付还在处理;下单成功,支付失败,下单记录正在回滚。也就是说,当订单数据和支付数据不一致时,对于客户端的访问请求应该予以拒绝。
这当然导致了系统可用性的降低,加上刚性事务实现时会导致同步阻塞的问题,锁定资源等问题,会极大的影响系统的吞吐量和设计弹性,所以实际上微服务架构不太会采用刚性事务。
遵循BASE原则的柔性事务
柔性事务只对数据的最终一致性进行保证,允许系统存在一定时间的数据不一致,比如订单记录已经被更新但是支付记录还没落地时,又比如订单记录更新成功但是支付失败订单记录回滚的过程。
在这个不一致窗口内,系统允许客户端对不一致的数据进行访问,因而系统的可用性相比而言会更好,加上其扩展性良好以及吞吐量的优势,一般微服务架构下都会采用柔性事务。柔性事务有多种不同的实现方式,比如基于可靠事件的模式,基于补偿的模式,基于Sagas长事务的模式等,具体的实现原理以及优缺点对比就放到下一篇在详解解释。
6.2 微服务架构下的幂等性问题 6.2.1 幂等性场景在微服务架构下,不同微服务间会有大量的基于http,rpc或者mq消息的网络通信,接口的重复调用以及消息的重复消费可能会经常发生,比如以下这些情况
调用订单创建接口,第一次调用超时,调用方又尝试了一次,但其实第一次调用已经成功,只是调用方没有及时收到响应。
订单支付完成后,需要向MQ发送一条消息,但该消息重复发送了两条。
网络波动导致服务提供方的接口被调用了两次。
用户在使用产品时,无意地触发多笔交易。
某些未关闭的重试机制。
微服务架构应该具有幂等性,当接口被重复调用时,消息被重复消费时,对系统的产生的影响应该和接口被调用一次,消息被消费一次时一样。
6.2.2 CRUD操作的幂等性分析新增请求:不具备幂等性
查询请求:重复查询不会影响系统状态,查询天然具备幂等性
基于主键的更新请求
要更新的值依赖于前值,不具备幂等性。比如update goods set number=number-1 where id=1
要更新的值不依赖于前值,具备幂等新。比如update goods set number=newNumber where id=1
删除请求
基于主键的物理删除(delete)删除具备幂等性
基于主键的逻辑删除(update)也具有幂等性
总结:通常只需要对新增请求和更新请求作幂等性保证。
6.2.3 如何解决幂等性问题全局唯一ID
根据业务生成一个全局唯一ID,在调用接口时会传入该ID,接口提供方会从相应的存储系统比如Redis中去检索这个全局ID是否存在,如果存在则说明该操作已经执行过了,将拒绝本次服务请求;否则将相应该服务请求并将全局ID存入存储系统中,之后包含相同业务ID参数的请求将被拒绝。