.Net微服务实战之必须得面对的分布式问题(二)

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: .Net微服务实战之必须得面对的分布式问题(二)

分布式事务

  

分布式事务分刚性事务与柔性事务,刚性事务对应ACID理论,而柔性事务也就是最终一致性,对应BASE理论。最终一致性指如果数据再一段时间内没有被另外的数据操作所更改,那它最终会达到与强一致性过程相同的结果。

  

分布式系统场景下很少使用xa事务,主要原因是xa事务是基于基础设施层面的强一致性事务,场景主要在一个服务多个数据源,追求强一致性,复杂度高,吞吐量低。

  

而最终一致性方案更多是基于服务应用层的弱一致性事务,场景主要是多服务多数据源与多服务单数据源,满足了BASE理论的三个特点:基本可用软状态最终一致性

  

以订单支付为例讲述下BASE理论,客户在A平台发起了订单支付,订单支付时状态为支付中,完成后支付后,等待支付系统的回调,但是这个时候,A平台的回调API接口异常了,订单状态无法同步为已支付状态,这个时候客户看到订单的金额支付出去了,但是去搜索订单模块的时候发现还是未支付,于是反馈给了客服,开发部经过一段时间的问题定位与排查,发现是回调API挂了于是重启后,数分钟订单状态就同步成已完成了。


BASE理论
基本可用(Basically Available) 分布式系统在出现不可预知故障时,允许损失部分可用性
软状态(Soft state) 允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性
最终一致性(Eventually consistent) 系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态

  

从上面的例子来看,支付中就是软状态,回调API服务虽然挂了,但是前台系统还是可以提供给客户端查询使用就是基本可用,只不过订单状态不对,当然最后服务也恢复后达成数据最终一致性

 

分布式数据一致性方案

名称

场景

优点

缺点

异步请求/回调

跨网络环境、同网络环境

实现简单

强业务

TCC

跨网络环境、同网络环境

有现成的框架、实现简单

强业务

基于消息可靠的最终一致性

同网络环境

有现成的框架、通用性强

中间件依赖

  

分布式事务方案常见的主要有这几种:异步请求/回调、TCC、基于消息可靠的最终一致性,TCC与基于消息可靠的最终一致性在Java和.Net都是有现成的框架,而异步请求/回调更多是与支付机构对接的场景会比较多,实现简单、通用性强,如果团队技术能力不足也可以使用该方案代替。

  

异步请求/回调更多是应对并发处理的异步解决方案,查过相关资料并没有纳入相关分布式事务方案中,但是在我的实际工作经验中该方案也是可以达成最终一致性。


异步请求/回调


image.png

该方案在与支付机构对接的场景比较常见,其核心以业务发起请求,被调用端以数据优先入库,稍后异步处理,处理完成后则回调请求业务端提供的API。

  

这种异步处理方式一般获取结果的方式推拉结合,外部系统主动回调给本地称之为推,本地系统每隔一段时间主动查询外部系统结果称为拉,两者可以按照业务的时效性结合策略使用。

     

公司内部系统之间也可以这么做,业务系统请求对接系统,被请求后数据库直接入库,然后通过定时调度任务异步做业务处理,业务处理成功还是失败都修改状态,最后由回调调度任务把业务处理的状态、处理信息回调给业务系统的回调API,为了避免回调调度任务因故障无法回调,可以设置策略由业务系统主动查询对接系统提供的查询API,推拉结合保证了系统可用性和数据时效性。


TCC


image.png

  

TCC是Try、Comfirm、Cancel三个单词的缩写,Try是资源预留、锁定,Comfirm是确认提交,Cancel是指撤销。 一个资源的处理需要提供三个接口,从业务侵入性来看是比较强的。

  

TCC的执行步骤与2PC有点相似,先进入预提交阶段,对A、B、C三个资源的分别进行try处理,如果try请求成功,相应的资源就会被修改成中间状态,可以理解成被冻结。接下来就会根据每个资源try后的情况判断如何执行。如果全部try成功,则会进入Comfirm处理,只要能try成功就能Comfirm成功。如果其中一个资源try失败了,则会对所有进行Cancel处理。

  

TCC与2PC看起来相似,但还是有区别的,TCC是应用服务层面的,而2PC则是基础设施层,而2PC因为是强一致性基于遵守ACID,在事务未提交时处于阻塞状态,如果失败则会事务回滚,而TCC是没有事务回滚的,每个阶段处理都穿透到数据库都是Commit操作。


基于消息的最终一致性


image.png


该方案其实是ebay多年前提出的本地消息表的解决方案,该方案的核心点在于,执行本地事务后再提交队列消息,这两步骤操作因为非原子性的跨进程操作,因为需要保证发送到消息队列的消息能正常发布与正常的消费,这就是我们常说的保证消息可靠,那么在执行本地事务的时候,本地业务表与消息凭据表会作为一个原子性事务提交到数据库,消息凭据表会记录着消息队列的消息序列化数据,如果本地事务提交成功了,但是发送消息队列的时候失败了,就会通过后台线程(进程)查询消息凭据表,把未发送成功的消息反序列化出来重新发起。

  

无论再消息发布端还是消息消费端都会因为与消息队列交互后,修改消息凭据表状态的情况,如果与消息队列交互是正常的,但是修改消息凭据状态失败了,补偿服务仍然会进行不必要的重发,那么这个场景容易导致数据重复创建与覆盖,因此需要关注幂等性的处理了。

  

该方案在.Net有CAP这个分布式事务框架,无需开发人员自己自己实现。


幂等性

  

幂等性的定义,相同的参数在同一个方法里,无论执行一次还是多次都会响应相同的结果

  

举个例子银行转行,A银行账户扣了100元,B银行账户加100元,这样数据一致的。但是在给B账户加100元的时候,B银行系统处理超时,但是其实这个时候B银行是已经处理成功了,只不过没响应回去,那么A银行系统就会重发,如果没有幂等性处理的话,A重试了3次,B账户就会加3次100。一边扣100,一边加300,那么数据就不一致了。

  

对于查询和删除数据的场景都有天然的幂等性,那么我们考虑幂等性处理更多是关注于新建数据更新数据。

  

新建数据的场景,如果没有处理好幂等性,那么就会导致数据重复创建,原因有可能是用户连续点击后发起请求,也有可能是API网关的retry请求。解决方案也相对比较简单,API提供主键参数(流水号)传入,就是由调用端预生成主键(流水号)传入API进行请求,API端生成流水与余额扣减作为同一个事务处理。此时如果因为某个原因进行了两次调用,因为第一次创建成功了,第二次则会因为主键的唯一性抛出了异常,这里需要注意的是得捕获到的唯一键异常应处理成执行成功的响应。

  

更新数据的场景,如果没处理号幂等性,可能会因为RPC框架或者API网关的Retry机制导致重复请求,这样就会造成了ABA数据覆盖问题,所谓的ABA就是,第一次请求A数据已经进行写处理了,接着到了第二次请求B数据进行对A数据进行了修改成功了,但是因为第一次请求因为某个原因导致客户端无法接收到响应,因此API网关或者RPC框架进行了重发,所以第三次把A数据又对已有的B数据进行修改覆盖。针对该问题解决方案主要是使用数据版本判断。

 

幂等性处理方案

场景

问题

方案

新建数据

重复创建

由调用端预生成订单号,唯一键约束

更新数据

ABA覆盖问题

添加版本号判断

  

以上两种方法处理方式从数据库层面解决,相对比较简单直接,侵入性比较强,还有一种方案可以从Web框架层面解决,结合Web框架的AOP与Redis判断,每次请求都会附带一个requestID传入到接口,由Filter拦截后Add到Redis。此方案需要引入Redis,从实现上比前面两个相对复杂,但是通用性相对高一些。


结束

  

该篇到这里就结束了,主要总结了平常在分布式系统不得不去面对的问题,虽然大家会通过一些设计,尽可能去避免,但是唯一不变的是需求的变化,因此我们尽可能优先了解各种处理方案,如有遇到就可针对场景选择合适的方案。

目录
相关文章
|
8天前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
46 3
|
7天前
|
NoSQL Java Redis
开发实战:使用Redisson实现分布式延时消息,订单30分钟关闭的另外一种实现!
本文详细介绍了 Redisson 延迟队列(DelayedQueue)的实现原理,包括基本使用、内部数据结构、基本流程、发送和获取延时消息以及初始化延时队列等内容。文章通过代码示例和流程图,逐步解析了延迟消息的发送、接收及处理机制,帮助读者深入了解 Redisson 延迟队列的工作原理。
|
9天前
|
开发框架 NoSQL MongoDB
C#/.NET/.NET Core开发实战教程集合
C#/.NET/.NET Core开发实战教程集合
|
1月前
|
Dubbo Java 应用服务中间件
微服务框架Dubbo环境部署实战
微服务框架Dubbo环境部署的实战指南,涵盖了Dubbo的概述、服务部署、以及Dubbo web管理页面的部署,旨在指导读者如何搭建和使用Dubbo框架。
160 17
微服务框架Dubbo环境部署实战
|
1月前
|
运维 持续交付 API
深入理解并实践微服务架构:从理论到实战
深入理解并实践微服务架构:从理论到实战
73 3
|
1月前
|
SQL 关系型数据库 数据库
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
|
1月前
|
运维 监控 持续交付
深入浅出:微服务架构的设计与实战
微服务,一个在软件开发领域如雷贯耳的名词,它代表着一种现代软件架构的风格。本文将通过浅显易懂的语言,带领读者从零开始了解微服务的概念、设计原则及其在实际项目中的运用。我们将一起探讨如何将一个庞大的单体应用拆分为灵活、独立、可扩展的微服务,并分享一些实践中的经验和技巧。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
66 3
|
1月前
|
自然语言处理 Java 网络架构
解锁跨平台微服务新纪元:Micronaut与Kotlin联袂打造的多语言兼容服务——代码、教程、实战一次打包奉送!
【9月更文挑战第6天】Micronaut是一款轻量级、高性能的Java框架,适用于微服务开发。它支持Java、Groovy和Kotlin等多种语言,提供灵活的多语言开发环境。本文通过创建一个简单的多语言兼容服务,展示如何使用Micronaut及其注解驱动特性实现REST接口,并引入国际化支持。无论是个人项目还是企业应用,Micronaut都能提供高效、一致的开发体验,成为跨平台开发的利器。通过简单的配置和代码编写,即可实现多语言支持,展现其强大的跨平台优势。
45 2
|
2月前
|
开发框架 缓存 前端开发
实战.NET Framework 迁移到 .NET 5/6
从.NET Framework 迁移到.NET 5/6 是一次重要的技术革新,涵盖开发环境与应用架构的全面升级。本文通过具体案例详细解析迁移流程,包括评估现有应用、利用.NET Portability Analyzer 工具识别可移植代码、创建新项目、逐步迁移代码及处理依赖项更新等关键步骤。特别关注命名空间调整、JSON 序列化工具更换及数据库访问层重构等内容,旨在帮助开发者掌握最佳实践,确保迁移过程平稳高效,同时提升应用性能与可维护性。
93 2