了解微服务
首先我们需要知道微服务是一种软件架构风格,它将一个大型的应用程序划分成一组松散耦合的细粒度小型服务,这些服务各自独立运行在自己的进程中,每个服务都围绕着具体的业务功能进行构建,并能够被独立地部署到生产环境中。微服务之间通过轻量级的通信机制(如HTTP)相互协议,共同为用户提供最终价值。
总的来说微服务提倡将单体应用划分成为一组松散耦合的细粒度小型服务,辅助轻量级的协议,互相协调,互相配合,为用户提供最终价值
微服务进化史
微服务的进化史可以追溯到互联网规模的不断扩大和技术需求的激增。随着业务的增长,架构系统也经历了从单一应用到分布式服务,再到微服务架构的演讲过程。
单体架构
在早期,大多数应用都采用了单体架构,即所有的功能都能打包成一个独立的应用程序包(如jar包或war包)。除了容器基本没有外部依赖,部署到一个JEE容器(Tomcat,JBoss,WebLogic)里,包含了DO/DAO,Service,UI等所有逻辑。
特点:
①开发简单,集中式管理。
②易于测试:单体应用一旦部署,所有的服务或其特性都可以使用了,简化了测试的过程。并不需要额外测试服务间的依赖,测试均可在部署完成后开始。
③易于部署和升级:相较于微服务架构中的每个服务的独部署,单体系统只需要将单个目录下的服务程序统一部署和升级。
④较低的维护成本:只需要维护单个系统即可。运维主要包括配置、部署、监控与告警和日志收集四大方面,相对于单体系统,微服务架构中的每个服务系统都需要独立的配置、部署、监控和日志收集、,成本呈指数级增长。
劣势:
①效率低:开发都在改同一个项目的代码,相互等待,冲突不断。
②维护难:所有的代码都耦合在一起,对于新人来说不知道如何下手。
③不灵活:构建时间长,任何小的修改都需要重构整个项目,比较耗时。
④稳定性差:一个微小的问题,都可能导致整个应用挂掉
⑤扩容缩容能力受限:单体应用只能作为一个整体进行扩展,影响范围大,无法根据业务模块的需要进行单个模块的伸缩。
⑥发布的影响的范围较大:每次发布都是整个系统进行发布,发布会导致整个系统重启,对于大型的综合系统挑战比较大,如果将各个模块拆分,哪个部分只做了修改,只发布哪个部分所在的模块就好了。
垂直拆与分布式服务
过渡阶段,为了应对单体架构的局限性,系统开始采用垂直拆分的方式,将应用程序按照业务功能拆分多个独立的应用。这种垂直应用架构虽然能在一定程度上缓解了单体架构的问题,但是随着业务的进一步复杂化,应用之间的交互和依赖关系也变得越来越复杂。此时,面向服务框架(SOA)开始兴起,SOA全称为Service-Oriented Architecture,是一种设计复杂软件系统的架构风格。它的核心理念是将应用程序划分为一系列粗粒度互相协作的服务。这些服务具有明确的接口,并通过企业服务总线(ESB)进行通信。然而,SOA的复杂性和重量级特性也限制了它的进一步应用。
特点:
①松耦合:相较于单体应用,SOA架构通过服务之间的松耦合关系,降低了服务之间的依赖性,有利于系统的模块化和维护。
②可重用性:服务应该能够被多次重用,以免减少开发的时间和成本。
③粗粒度:服务通常执行较大的任务,而不是细小的功能。
④标准化接口:使用标准的通信协议和消息格式,如SOAP,REST等。
⑤服务注册发现:服务可以在服务中心进行注册,以便其它服务或客户端可以发现并调用它们。
劣势:
①系统复杂性高:SOA构架中涉及多个服务间的协作和通信,系统的复杂度高,开发、测试和维护成本相对较高。
②性能问题:由于服务间的通信需要通过网络进行,可能存在网络延迟和性能损失,对系统的性能造成影响。
③安全性难以保障:SOA架构中涉及多个服务之间的通信,需要对数据传输进行加密和安全控制,保障系统的安全性比较困难。
④部署和运维难度加大:SOA架构中涉及多个服务端部署和管理,需要专门的运维团队进行管理,增加系统的复杂性和运维成本。
⑤扩展性一般:SOA虽然在单体应用基础上降低了耦合性,但相较于微服务而言,SOA服务之间的耦合性还是较高,扩展时可能需要考虑多个服务的协同工作。
微服务
自微服务架构兴起以来,它得到了广泛的应用和发展。越来越多的企业开始采用微服务架构来构建自己的应用程序,以提高开发效率、降低维护成本并增强系统的可伸缩性和可靠性。随着容器技术(如Docker)和容器编排平台(如Kubernetes)的成熟,微服务架构的实现变得更加简单和高效。这些技术为微服务提供了轻量级、可移植和一致性的部署环境,并提供了自动化的服务管理和扩展能力。
常见微服务框架
- Doubbo:zookeeper + Spring MVC/SpringBoot
- 配套通信方式:RPC
- 注册中心:zookeeper / redis
- 配置中心:diamond
- SpringCloud: 全家桶 + 轻松嵌入第三方工具组件(Netflix)
- 配套通信方式 : http restful
- 注册中心: eruka /consul /nacos
- 配置中心: config /nacos
- 断路由: hystrix /Sentinel
- 网关: zuul/Gateway
- 分布式追踪系统: sleuth +zipkin
微服务优势
单一职责
微服务架构中的每个服务都是具有业务逻辑的,符合高内聚、低耦合原则以及单一职责原则的单元,表达我只做某件事。
轻量级通信
通过REST API+JSON模式或者RPC框架,实现服务间互相协作的轻量级通信机制。
独立性
每个服务相互独立,如果需要版本升级只需要改变当前服务本身即可,就可以独立完成开发、测试、部署、运维。
进程隔离,单独部署,跑在自己的进程里
在微服务的架构中,应用程序由多个服务组成,每个服务都是高度自治的独立业务实体==,可以运行在独立的进程中,不同的服务能非常容易的部署到不同的主机上,实现高度自治和高度隔离==。
混合技术栈和混合部署方式
团队可以为不同的服务组件使用不同的技术栈和不同的部署方式(公有云、私有云、混合云)。
分布式管理
组件可以彼此独立的进行扩容和治理,从而减少了因必须缩放整个应用程序而产生的浪费和成本,也避免了因为单个功能可能面临过多的负载。
安全可靠,可维护
从架构上对运维提供了友好的支撑,在安全、可维护的基础上规范化发布流程,支持数据存储容灾(数据存储备份的最高层次)、业务模块隔离、访问权限控制、编码安全检测等。
分布式和微服务的区别
①概念层面
分布式系统
:是指一组独立的计算机节点通过网络协同工作,共同完成一个大任务,这些节点各自拥有本地资源并在分布式环境中相互协作。分布式系统的设计目标是为了提高系统的可靠性、可用性、可扩展性和稳定性。微服务架构
:是一种特殊的分布式系统实现方式,它强调将单一的应用程序拆分成一系列小型、相对独立的服务、每个服务都是小型的、完整的业务单元,包含自己的业务逻辑、数据库以及对外暴露的API接口。每个微服务都可以独立开发、部署、扩展和维护,并可以使用不同的技术栈。微服务架构的核心理念是围绕业务能力组织服务,强调服务之间的松耦合和高内聚。
②粒度划分
分布式系统
:分布式系统中的服务划分粒度可大可小,可以包含多个紧密相关的业务功能。微服务架构
:微服务倾向于更为细粒度的服务划分,每个服务只专注于一个特定的业务功能,并力求做到一件事并做好。
③目标
分布式系统
:分布式旨在解决大型系统的计算能力和存储能力扩展问题,通过负载均衡、冗余备份等方式提高系统整体的性能和稳定性。微服务架构
:微服务架构则着重于软件工程方面优化,如简化开发流程、提升团队协作效率、加速新功能上线、适应快速变化的业务需求等。微服务进一步提升了系统的灵活性,但也带来了服务间通信、数据一致性、运维复杂性等方面挑战。
Nacos
分布式配置中心
同类:Apollo
属性
dataId
:配置信息的唯一标识符,用于区分不同的配置信息。不写的话默认是服务名。
group
:用于将不同的dataId分组,使得配置管理更加灵活。默认情况下,所有的配置信息都属于DEFAULT_GROUP,但是可以根据实际需要创建更多的分组来管理不同配置的集合。
namespace
:进行环境隔离的命名空间,它可以是开发环境、测试环境和生产环境等。
特点
- 支持动态配置更新,当配置信息在配置中心发生变化时,客户端可以实时获取到最新的配置值,而无需重启应用。这种能力极大的提高了配置的灵活性和响应速度,使得系统能够更快地适应变化。
- Nacos提供了统一的配置管理平台,可以集中管理各个应用程序的配置信息。这种集中化的管理方式简化了配置的维护过程,降低了配置的出错率,并提高了配置的可管理性。
- Nacos支持多环境部署,如开发环境、测试环境和生产环境等。通过命名空间(Namespace)的划分,可以很容易地实现不同环境之间的配置隔离,避免了配置信息的混乱和冲突。
- Nacos支持集群部署,具有高可用性和可扩展性。这意味着即使部分节点出现故障,整个配置中心仍然能够正常工作,保证了系统的稳定性和可靠性。同时,随着业务的发展,可以轻松地通过增加节点来扩展配置中心的容量。
- Nacos支持配置变更的监听和通知机制。客户端可以订阅感兴趣的配置项,并在配置发生变化时得到通知。这种机制使得客户端能够及时地响应配置的变化,保证了系统的实时性和一致性。
- Nacos支持多种配置格式,如文本、JSON、YAML等。这使得它能够满足不同应用程序的配置需求,提高了配置的灵活性和兼容性。、
- Nacos与微服务架构中的服务注册与发现功能无缝整合,共同构建了一个完整的微服务治理体系。这使得微服务架构中的配置管理更加便捷和高效。
- 通过Nacos的配置中心化管理,可以显著降低配置的维护成本。运维人员无需在每个节点上单独维护配置文件,只需在配置中心进行统一的配置管理即可。
- 开发人员可以更加专注于业务逻辑的实现,而无需过多地关注配置的管理和更新。这有助于提高开发效率,并减少因配置错误而导致的问题。
Nacos配置中心动态刷新原理
Nacos使用轮询机制来实现实时配置更新。具体过程如下:
①客户端发起请求:客户端通过后台线程发起一个 HTTP请求到Nacos服务端,用于监听配置变化。
②客户端挂起连接:Nacos服务端接收请求后,会挂起(hold)这个HTTP并连接一段时间(例如30秒),在此期间服务端监控配置文件的变化。
③无变化情况:若在这段时间内没有检测到配置文件有任何变更,服务端将释放连接并向客户端返回一个指示,表示配置没有更新。
④配置变化情况:如果在挂起期间检测到配置文件发生变化,服务端会立即释放连接并将最新的配置推送给客户端。
⑤循环轮询:无论哪种情况,客户端在接收完响应后,会在短暂延迟(如10ms)之后重新发起一个新的HTTP请求,从而形成循环轮询机制以持续监听配置更新。这种方式使得客户端能够及时获取到最新的配置信息,同时减少了不必要的频繁请求。
热更新
使用注解***@RefreshScope***
在Spring Cloud中,@RefreshScope
注解用于标记那些需要动态刷新的Bean。对于Feign客户端,虽然直接在接口上使用@RefreshScope
注解可能不起作用(因为Feign客户端是由Spring Cloud OpenFeign自动创建的),但可以在与Feign客户端相关的配置上使用此注解。不过需要注意的是,@RefreshScope
注解对于Feign客户端的某些配置(如服务URL)可能并不总有效,因为这些配置可能是在Feign客户端初始化时读取的,并且之后不会改变。
Nacos与application.properties同时配置同一个key?谁的优先级更高?为什么?
一般来说在SpringCloud项目中,如果结合了Nacos作为配置中心,并且在项目中同时存在bootstrap.properties(bootstrap.yml)和appliction.properties(appliction.yml)文件,那么Nacos配置的优先级会高于application.properties中的配置。
这是由于在Spring Cloud项目中,bootstrap.properties或bootstrap.yml文件用于应用程序上下文创建之前加载配置,这些配置通常包括与配置中心(如Nacos)、服务注册中心(如Eureka)等相关配置。而application.properties或aplication.yml文件则用于加载程序本身的配置。因此,当两者都包含相同配置key时,bootstrap.properties或bootstrap.yml文件中的配置将会覆盖application.properties或aplication.yml文件中的配置。
这种分布式配置的优势?
①集中管理
- 统一配置源:分布式配置中心允许所有服务的配置信息集中存储和管理,避免了传统方式每个服务都需要维护自己配置文件的繁琐
- 简化配置资源:通过统一的界面和API,可以方便地对所有服务的配置进行查看、修改和更新,降低了配置管理的复杂性和出错率。
②***动态更新***
- 实时生效:分布式配置中心支持配置的动态更新,当配置发生变化时,可以实时推送到各个服务实例,无需要重启服务即可使配置生效。
- 减少停机时间:由于无需重启服务,因此在配置更新过程中可以大大减少系统的停机时间,提高系统的可用性和稳定性。
③***版本控制***
- 配置版本管理:分布式配置中心通常提供版本管理功能,可以方便地查看和回滚到历史配置版本,保证了配置的可追溯性和安全性。
- 减少人为错误:通过版本控制,可以有效避免因手动修改配置文件而导致的配置错误和不一致问题。
④***环境隔离***
- 多环境支持:分布式配置中心支持不同环境的配置隔离(如开发环境、测试环境、生产环境等),可以确保不同环境使用正确的配置信息。
- 提高部署效率:通过环境隔离,可以简化部署流程,提高部署效率,降低部署风险。
⑤***高可用性和容错性***
- 高可用架构:分布式配置中心通常采用高可用架构设计,能够确保在部分节点故障时仍然能提供服务,提高了系统的可靠性和稳定性。
- 容错机制:配置中心通常具备丰富的容错机制(如数据备份、容灾恢复等),能够在出现故障时快速恢复服务。
⑥***扩展性和灵活性***
- 支持多配置格式:分布式配置中心通常支持多种配置格式(如JSON、YAML、Properties等),可以满足不同服务的需求。
- 易于集成:配置中心通常提供丰富的SDK和OpenAPI,可以方便地与其他框架和系统进行集成。
服务注册中心
核心功能:
服务注册 :
Nacos 客户端会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据存储信息存储在双层的内存Map中或数据库中。
服务心跳:
在服务注册后,Nacos客户端会维护一个定时心跳来持续通知Nacos 服务端,说明服务一直处于一个可用状态,防止被剔除。默认5s发送一次心跳。
服务同步:
Nacos服务端集群之间会相互同步服务实例,用来保证服务信息的一致性。
服务发现:
服务消费者在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉去服务端最近的注册表信息更新到本地缓存。
服务健康检查:
Nacos Server会开启一个定时任务用来注册服务实例的健康情况,对于超过15s没有收到客户端心跳实例会将它的healthy属性设置为false(客户端服务现时不会发现),如果实例超过30s没有收到心跳,直接剔除的实例如果恢复发送注册则会重新注册。
Nacos和Eureka的区别
Nacos | Eureka | |
---|---|---|
服务发现 | Nacos采用定时拉取和订阅推送两种模式 | Eureka只支持定时拉取模式 |
实例类型 | Nacos有永久实例和临时实例两种 | Eureka只有临时实例 |
健康检测 | Nacos对临时实例采用心跳检测,对永久实例采用主动请求 | Eureka只支持心跳模式 |
主要区别:
功能范围:
Eureka:仅提供服务发现和注册功能,它是Spring Cloud Netflix组件之一,与Spring Cloud紧密集成。
Nacos:除了提供服务发现和注册外,还提供动态配置管理、服务元数据信息管理以及健康检查等。Nacos是一个更全面的服务管理系统。
配置管理:
Eureka:没有内置的配置管理功能。通常需要Spring Cloud Config 等其他工具配合使用,才能实现配置管理。
Nacos:内置了动态配置服务,可以直接管理和应用各种配置,无需依赖其他组件。
数据一致性:
Eureka:采用的是AP(可用性和分区容错性)设计,它不保证每次服务列表都是最新的,但可以保证高可用性。
Nacos:可以在AP模式和CP(一致性和分区容错性)模式之间切换。在CP模式下,Nacos可以提供更强的数据的一致性保证。
健康检查:
Eureka:提供基本的健康检查,并且客户端可以自定义健康检查策略。
Nacos:提供更多健康检查配置选项,如支持TCP、HTTP、MySQL等多样健康检查方式。
维护和更新:
Eureka:在Spring Cloud Netflix项目中,Eureka2.x的开发已经停止,而Eureka1.x的维护也只限于关键问题的修复。
Nacos:作为一个较新的项目,获得了阿里巴巴的积极维护和社区的广泛支持,持续更新和增加新特性。
社区和生态支持:
Eureka:由于Spring Cloud Netflix进入维护模式,社区对Eureka的支持可能不如Nacos活跃。
Nacos:作为 一部分的阿里巴巴的中间件生态,拥有强大的社区支持和不断地技术迭代。
运行模式:
Eureka:主要是以客户端发现的模式运行,服务实例信息由客户端缓存并定时从服务器刷新。
Nacos:支持服务端的服务发现,并且客户端也可以通过长连接实时接收配置更新和服务列表变更的推送。
RestfulApi &RestTemplate
RestfulApi
RestfulApi是一种设计风格,代表了使用HTTP协议构建web服务的一种架构原则。REST的核心思想是,通过URL定位资源,使用HTTP方法(GET、POST、PUT、DELETE等)来描述资源的操作。
HTTP方法:
GET:用于检索资源,应该是幂等的,即多次请求具有相同的效果。
POST:用于创建新资源,请求体通常包含要创建的资源数据。
PUT:用于更新已有资源的全部信息,也是幂等的。
PATCH:用于更新资源的部分属性,非幂等。
DELETE:用于删除资源,幂等。
特点:
- 资源:一切都被视为资源,每个资源都有唯一的URL。
- 表现层:资源有多种表现形式(如JSON、XML等),客户端和服务端通过协商决定资源的表现形式。
- 无状态:服务器不会保存客户端的状态信息,客户端每次请求都必须携带足够的信息以便服务器识别。
- 统一的接口:使用标准的HTTP方法对资源进行操作。
RestTemplate
RestTemplate是Spring框架提供的一个同步客户端类,它基于模板方法设计模式,用于简化与HTTP服务的通信。它提供了从Restful Web Service发送同步请求并处理响应。使用RestTemplate,可以像调用本地方法一样方便地调用远程REST服务。
特点:
- 同步阻塞:RestTemplate发送请求并等待响应,它是同步阻塞的。
- 多种请求方式:支持GET、POST、PUT、DELETE等HTTP方法。
- 灵活的请求定制:可以通过设置请求头、请求体等来自定义HTTP请求。
- 自动转换响应:支持将响应体自动转化为java对象,简化了数据处理。
Ribbon负载均衡器
Spring Cloud Ribbon是基于Netfilx Ribbon 实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列完善的配置,如超时,重试机制等。通过LoadBalancer获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。
常见的负载均衡器
服务端负载均衡
在消费端和服务提供方中间使用独立的代理方式进行负载,有硬件的(如F5),也有软件的(比如Nginx,OpenResty,Kong)
例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在一个服务端先进行负载均衡算法再进行负载均衡算法分配。
客户端负载均衡
客户端根据自己的请求做出负载均衡,Ribbon就属于客户端自己做负载均衡。
Ribbon中常见的负载均衡算法
- 轮询(RoundRobinRule)
- 这是Ribbon的默认负载均衡策略。
- 按照服务列表的顺序依次进行轮询,每个服务实例被轮流使用,直到服务实例出现故障或者返回错误响应,下次请求时将自动排除故障实例,继续轮询其他实例。
- 随机(RandomRule)
- 从服务列表中随机选择一个服务实例进行处理。
- 随机算法可以防止某个服务实例出现负载过高的情况。
- 最少连接数(LeastActiveRule)
- 也被称为“最空闲策略”。
- 根据服务实例当前的连接数来选择最空闲的实例进行负载均衡。
- 在服务实例的连接数相对均衡的情况下,可以保证选择最快速的服务实例。
- 带权重的轮询(WeightedResponseTimeRule)
- 根据服务实例的响应时间和权重来选择最优服务实例。
- 响应时间越短、权重越高的服务实例被选择的概率越大。
- 在启动时,如果统计信息不足,则使用轮询策略,等统计信息足够后切换到带权重的轮询策略。
- 重试(RetryRule)
- 先按照轮询策略获取服务,如果获取服务失败,则在指定时间内进行重试,以获取可用的服务。
Ribbon第一次调用为什么很慢
Ribbon默认采用的是懒加载,Ribbon的DynamicServerListLoadBalancer会将feign客户端进行负载均衡问,并进行调用。由于Client并不是在服务器启动的时候就初始好的,即第一次访问时才会去创建LoadBalanceClient,因此第一次调用耗时并不仅仅包含发送HTTP请求时间,还包含了创建RibbonClient的时间,所以请求时间会很长。
解决方式
采用饥饿加载
在SpringCloud中,可以通过配置
ribbon.eager-load.enable
为true,并指定需要饥饿加载的客户端(ribbon.eager-load.clients
),来实现在服务启动时提前初始化RibbonClient,从而避免第一次调用时的初始化耗时。在yml文件中配置饥饿加载如下:
ribbon: eager-load: enabled: true #开启饥饿加载 clients: userservice #指定对userservice这个服务饥饿加载
注:如果多个服务开启饥饿模式,使用逗号分隔。
OPenFegin(微服务调用组件)
微服务调用插件
支持SpringMVC注解
整合了更多的扩展(请求重试策略、超时控制、请求拦截器)
Feign使用@SpringQueryMap来解决多参数传递问题
openFeign配置文件(日志、超时时间等)
以日志文件为例
日志等级有 4 种,分别是:
NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。
全局配置
注:
若在类上配置@Configuration注解就会全局生效(如果要想单独指定某个服务,就不能加这个注解)
由于feign调试日志时debug级别输出,SpringBoot默认日志时Info,所以feign的debug日志级别就不会输出,一定要结合
logging.level.com.by=debug
@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
局部配置
局部配置,让调用的微服务生效,在@FeignClient注解中指定使用的配置类,局部配置的时候FeignConfig要取代@Configuration注解。
@FeignClient(value ="order-service", configuration =FeignConfig.class) public interface OrderService{ @GetMapping("/hello") public String hello(); }
配置文件配置
logging.level.com.beiyou = debug 开启日志 ##配置feign 的日志级别 #-- default 全局配置 feign.client.config.default.loggerLevel=NONE #-- nacos-a 具体服务名 feign.client.config.nacos-a.loggerLevel=FULL
自定义拦截器
- OpenFeign中的拦截器是对消费者调用服务者的过程进行拦截。
- Spring MVC中的拦截器是对客户端请求服务端的过程进行拦截。
创建请求拦截器
首先,需要创建一个实现了
feign.RequestInterceptor
接口的类,这里以TraceIdFeignInterceptor为例子,重写apply方法,该方法允许你修改请求模板。@Slf4j public class TraceIdFeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { log.debug("请求拦截了"); } }
配置Feign客户端使用自定义拦截器
接着,需要在配置Feign客户端时,使用这个歌=拦截器。可以通过@FeignClient注解指定configuration属性来完成,或者通过创建一个配置类,并在该类中定义拦截器bean,然后将其用于@FeignClient。
全局过滤器
可以在配置类中配置自定义的OpenFeign拦截器
@Configuration public class FeignConfig { @Bean public TraceIdFeignInterceptor feignInterceptor(){ return new TraceIdFeignInterceptor (); } }
局部过滤器
可以在application.properties文件中对自定义的OpenFeign拦截器进行局部配置
。# 拦截器 feign.client.config.order-service.requestInterceptors[0]=com.beiyou.TraceIdFeignInterceptor #自定义拦截器的完整类路径 feign.client.config.order-service.requestInterceptors[1]=com.beiyou.xxxxxxxxx
在@FeignClient注解中指定使用的配置类,局部配置的时候FeignConfig要去掉@Configuration
Sentinel使用(流量防卫兵)
熔断:开 、关、半开半关
QPS和TPS及区别
QPS(Queries Per Second)
QPS指的是每秒钟系统能够处理的查询数量,查询可以是任何类型的请求,比如HTTP请求。数据库查询等。QPS更侧重于衡量服务器处理请求的能力。
特点:关注请求的数量:QPS关注的每秒能够处理多少个请求
TPS(Transactions Per Second)
TPS指的是每秒钟系统能够处理的事务数量。事务通常指的是一个完整的业务操作,例如完成一次订单购买过程、转账等,它通常涉及到多个步骤,包括数据的一致性检查、更新数据库等。
特点:关注业务操作的数量,TPS关注的时每秒能够完成多少个完整的业务操作。
涉及数据一致性:事务往往涉及到数据的一致性要求,比如ACID属性。
常用于后端服务:例如数据库服务、支付服务等。
区别
衡量的对象不同:
QPS关注的是请求的数量,不区分请求的具体内容。
TPS关注的是完整业务操作的数,通常涉及到数据的一致性处理。
应用场景不同:
QPS通常用于衡量接服务的处理能力。
TPS通常用于衡量后端服务或数据库的处理能力。
计算方法不同:
QPS计算的是单位时间内处理的请求总数。
TPS计算的是单位时间内完成的事务总数。
限流和降级的区别
目标不同:
限流是为了避免系统过载,保护系统资源;
降级是为了在服务出现异常时避免整个系统受到影响。
触发条件不同:
限流通常是基于流量大小或频率来触发。
降级则是基于服务的健康状态或异常情况来触发。
行为不同:
限流可能会拒绝部分请求
降级则是对异常服务的代替处理,返回预定义的结果或者错误信息。
Gateway服务网关
保证:
高性能:推荐采用响应式编程模型webFlux
高可用性:发多个Gateway
配置文件
server:
port: 3888
spring:
application:
name: gateway-app
cloud:
gateway:
routes:
- id: httpbin
uri: https://httpbin.org #下游的目标地址
predicates:
- Path=/abc/** #表示二级目录是/abc/的才匹配
#- Method=GET 表示请求必须为get
# - Query=id,d+ 表示请求参数id必须为数字
# - token=123 表示请求头中必须包含token=123
# - Cookie=JSESSIONID,123 表示请求cookie中必须包含JSESSIONID=123
filters:
##在请求目标uri之前,截取路径上面的占位路径的个数
- StripPrefix=1
- AddRequestHeader=token,123 # 可以在请求头上加的东西
- AddRequestParameter=color,green #增加请求参数
- AddResponseHeader=color,green #增加响应头
配置项说明:
-
路由(route):有ID、目标URL、断言集合和过滤器集合组成,如果聚合断言结果为真,则转发到该路由。
-
断言(Predicate):如上配置文件中,参照Java8的新特性Predicate,允许开发人员匹配HTTP请求中的任何内容,如请求头、请求参数、请求路径等。最后根据匹配结果返回一个布尔值。
更多自定义断言可以通过Predicate接口来扩展
-
过滤器(Filter):如上配置文件可以在返回请求之前或之后来修改请求和响应内容。
自定义过滤器可以通过实现GatewayFilter接口或继承AbstractGatewayFilterFactory来创建。
生命周期
- PRE:前置过滤器,这种过滤器在请求被路由到目标服务之前调用。可以通过这种过滤器实现身份认证、在集群中选择请求的微服务、记录调试信息。
- POST:后置过滤器,这种过滤器在路由到微服务以后执行。这种过滤器可以用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
作用范围
- 局部过滤器:GatewayFilter:应用到单个路由上一个分组的路由上(需要在配置文件中配置)
- 全局过滤器:GlobalFilter:应用到所有路由上(无需配置,全局生效)
流量网关和服务网关的区别
***流量网关:***(如Kong、OpenResty、Nignx)是指提供全局性的、与后端业务无关的策略,例如HTTPS证书认证、Web防火墙、全局流量监控、黑白名单等。
***服务网关:***(如Spring Cloud Gateway)是指与业务紧耦合的、提供单个业务域级别的策略,例如服务治理、token认证、负载均衡、traceId全链路跟踪、下游接口耗时等。
总的来说,流量网关负责南北向流量调度及安全防护,微服务网关负责东西向流量调度及服务治理。
Gateway的三大案例
Gateway调用服务默认是轮询的
全局Token过滤器
通过创建一个实现GlobalFilter接口的类,一般该类名自定义为TokenFilter。重写该接口的filter方法,该方法有两个参数:ServerWebExchange exchange, GatewayFilterChain chain
。
ServerWebExchange
是一个非常重要的接口,它代表了服务器和客户端之间的HTTP交换。这个接口封装了请和响应(ServerHttpRequest
)(ServerHttpResponse
)的详细信息,以及用于执行请求处理逻辑(如过滤器链)的上下文,我们可以从exchange里面拿到request和response对其进行处理。
GatewayFilterChain
是用于处理HTTP请求的过滤器链的抽象。每个过滤器都可以选择继续传递请求到链中的下一个过滤器。
①首先在该类定义成员变量并初始化
private static final String TOKEN = "token";
@Value("${cn.smart.token.key}")
private String key;
@Value("${cn.smart.token.whiteList}")
private String whiteList;
private ListString> whiteLists;
TOKEN:定义一个静态常量字符串“token”,用于后续从请求头中获取token的键名。
key:通过@Value(${cn.smart.token.key})注解从配置文件读取JWT的密钥。
whiteList:通过@Value(${cn.smart.token.whiteList})注解从配置文件读取白名单,通常是一个以逗号分隔的URL路径列表。
whiteLists:用于存储解析后的白名单路径。
②初始化方法
@PostConstruct
public void init() {
whiteLists = StrUtil.split(whiteList, ",");
}
init() 方法:在Spring容器创建并初始化该TokenFilter类的实例后,会自动调用此方法。
该方法使用Hutool中的StrUtil工具类将
whiteList
字符串按逗号分隔,并存储到whiteLists
列表中。
③过滤器逻辑类(filter方法)
@Override
public MonoVoid> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (whiteLists.contains(path)){
return chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
//从请求头上获取token
ListString> tokens = request.getHeaders().get(TOKEN);
//判断token是否传了token
if (ObjectUtil.isEmpty(tokens)) {
log.error("请传token");
//插入响应码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//请求结束
return response.setComplete();
}
//如果存在token则获取第一个token走下去
String token = tokens.get(0);
//判断传过来的token是否为空
if (ObjectUtil.isEmpty(token)) {
log.error("token不能为空");
//插入响应码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//请求结束
return response.setComplete();
}
//拿到token,进行验证
boolean b = false;
//使用Hutool工具进行校验
try {
JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256(key.getBytes())).validateDate();
b = true;
} catch (Exception ex) {
log.error(ex.getMessage());
}
if (!b) {
log.error("非法token");
//插入响应码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//请求结束
return response.setComplete();
}
/*执行链*/
return chain.filter(exchange);
}
- 获取请求路径:从exchange(ServerWebExchange的实例对象)获取URL路径。
- 白名单检查:如果请求的URL路径在白名单whiteLists中,则直接调用chain.filter(change)将请求转发给下一个过滤器或者是目标服务,不进行token验证。
- 检查请求头中的token(两种情况)
- 从请求头上获取名为TOKEN的token列表。
- 如果请求头中没有token(即token列表为空),则记录错误日志,设置响应状态码为
HttpStatus.UNAUTHORIZED
(401未授权),并结束请求。- 如果存在token列表但第一个token为空,同样记录错误日志,设置响应状态码为
HttpStatus.UNAUTHORIZED
,并结束请求。- JWT验证
- 提取出第一个token,使用JWT验证工具(使用Hutool中的校验JWT
JWTValidator
和JWTSignerUtil
工具类)进行验证。验证包括算法(如HS256)和日期(确保token未过期)。- 如果验证失败(捕获到异常或
b
为false
),则记录错误日志,设置响应状态码为HttpStatus.UNAUTHORIZED
,并结束请求。- 继续请求处理:如果token验证成功,则调用
chain.filter(exchange)
将请求传递给下一个过滤器或目标服务。
全链路跟踪TraceId
和全局Token过滤器一样,实现GlobalFilter接口,不同的只是业务逻辑。
全链路跟踪主要是根据用户请求头上的TraceId来追踪用户二点访问行踪。
①首先在该类定义成员变量并初始化
private static final String TRACE_ID = "traceId";
TRACE_ID:定义一个静态常量字符串“traceId”,用于后续从请求头中获取traceId的键名。
②过滤器逻辑类(filter方法)
@Override
public MonoVoid> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ListString> traceIds = request.getHeaders().get(TRACE_ID);
/*如果TRACE_ID不为空,直接继续往下走*/
if (ObjectUtil.isNotEmpty(traceIds)) {
return chain.filter(exchange);
}
//如果没有TRACE_ID,则生成一个
String traceId = IdUtil.simpleUUID();
// request.getHeaders().set(TRACE_ID,traceId);
ServerHttpRequest req = request.mutate().header(TRACE_ID, traceId).build();
//从请求头上获取token
/*执行链*/
return chain.filter(exchange.mutate().request(req).build());
}
- 获取请求:从
ServerWebExchange
对象中获取ServerHttpRequest
对象,这个对象包含了请求的所有信息,包括请求头、参数等。- 检查TRACE_ID:从请求头中获取名为
TRACE_ID
的值。这里使用了request.getHeaders().get(TRACE_ID)
来获取一个包含所有TRACE_ID
值的列表(尽管在这个场景下,我们期望TRACE_ID
是唯一的,但HTTP头允许同一个键有多个值)。- TRACE_ID存在性检查:使用
ObjectUtil.isNotEmpty(traceIds)
(这里假设ObjectUtil
是一个工具类,用于判断对象是否非空或非空集合)来检查TRACE_ID
是否存在。如果traceIds
列表不为空(即请求头中已存在TRACE_ID
),则直接调用chain.filter(exchange)
将请求传递给下一个过滤器或目标服务,不进行任何修改。- 生成TRACE_ID:如果请求头中不存在
TRACE_ID
,则使用IdUtil.simpleUUID()
(这里假设IdUtil
是一个工具类,用于生成UUID)生成一个新的traceId
。- 修改请求头:通过
request.mutate().header(TRACE_ID, traceId).build()
创建一个新的ServerHttpRequest
对象,该对象包含了原始的请求信息,但添加了一个新的TRACE_ID
请求头。注意,原始请求对象是不可变的,因此我们需要创建一个新的对象来添加或修改请求头。- 更新ServerWebExchange:使用
exchange.mutate().request(req).build()
创建一个新的ServerWebExchange
对象,该对象包含了更新后的ServerHttpRequest
对象(即包含新TRACE_ID
的请求)。这一步是必要的,因为GatewayFilterChain
的filter
方法需要一个ServerWebExchange
对象作为参数,而我们对请求进行了修改。- 继续执行过滤器链:最后,调用
chain.filter(updatedExchange)
将更新后的请求(包含新生成的TRACE_ID
)传递给下一个过滤器或目标服务。
局部过滤器接口耗时
局部过滤器与前两者不同。
①命名规则
关于局部过滤器命名规则有一定的讲究,局部过滤器命名规则 XXXGatewayFilterFactory, 必须以GatewayFilterFactory结尾。
这样我们在在配置文件只需要配置XXX即可。以本案例命名如
LogTimeGatewayFilterFactory
,在配置文件进行如下配置:filters: - LogTime = gte,2000
②继承的抽象类
局部过滤器与全局过滤器还有不同的是继承了
AbstractNameValueGatewayFilterFactory
抽象类。
接下来是关于局部过滤器案例——接口耗时。
①首先在该类定义成员变量并初始化
public static long timeSpan = 0;
②过滤器逻辑类(filter方法)
- 初始化时间跨度:在
apply
方法中,首先通过config.getValue()
获取配置的值(即时间跨度字符串),然后将其转换为long
类型并存储在静态变量timeSpan
中。注意,使用静态变量来存储这种配置通常不是最佳实践,因为它可能导致多线程环境下的数据不一致问题。更好的做法是将timeSpan
作为过滤器实例的字段。- 创建并返回过滤器:
apply
方法返回一个匿名GatewayFilter
实例,该实例在请求通过网关时执行特定的逻辑。- 测量请求处理时间:在过滤器中,首先记录请求开始处理的时间(
startTime
)。然后,通过调用chain.filter(exchange)
将请求传递给过滤器链中的下一个过滤器或目标服务。chain.filter(exchange)
返回一个Mono
,它表示异步操作的完成。- 记录耗时:使用
then
方法,在请求处理完成后(即Mono
完成时),执行一个Runnable
,该Runnable
计算请求的总处理时间(endTime - startTime
),并将其与配置的时间跨度(timeSpan
)进行比较。如果处理时间超过时间跨度,则使用日志记录器(假设为log
)以DEBUG级别记录请求的URI和总处理时间。- 日志记录:如果请求处理时间超过阈值,日志消息将包含请求的URI和耗时(以毫秒为单位)。这对于监控和调试长时间运行的请求非常有用。
当我们的业务中由多个全局过滤器时,如何决定谁先执行
①可以实现Ordered接口,并实现getOrder方法,在getOrder方法内根据返回的int值来决定哪个过滤器先执行,值越小优先级越高。
②在目标过滤器类上添加@Order(int Value)注解,根据Value值决定谁先执行,值越小优先级越高。
什么是CAP原理
CAP原理,又称为CAP原则,是分布式系统中的一个重要概念,它描述了分布式系统在设计时所面临的三个关键特性的权衡问题。具体来说,CAP原理指的是在分布式系统中,Consistency(一致性)、Availability(可用性)和Partition Tolerance(分区容错性)这三个特性最多只能同时满足两个,而无法同时实现全部三个特性。
CAP原理三大特性
Consistency(一致性)
- 指的是分布式系统中的多个节点在同一时间具有相同的数据状态。在分布式系统中,同一份数据可能存在多个不同的实例中,在数据强一致性的要求下,对其中一份数据的修改必须同步到它的所有备份中,以保证所有对该份数据的请求都将返回同样的状态。
Availability(可用性)
- 指的是系统能够及时处理客户端的请求,即服务在接收到客户端请求后,都能够给出响应。好的可用性要求系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
Partition Tolerance(分区容错性)
- 在分布式系统中,不同节点之间通过网络进行通信,由于网络的不可靠性,位于不同网络分区的服务节点都可能会通信失败。分区容错性指的是系统能够容忍网络分区的情况,即使部分节点之间无法通信,系统整体仍然能够继续运行。
CAP原理的权衡
由于CAP原理指出的三个特性无法同时满足,因此在设计分布式系统时,需要根据业务需求进行权衡和取舍。常见的取舍策略有三种:
- CA(一致性和可用性)
- 如果不要求分区容错性(即不允许系统出现网络分区),则可以保证强一致性和高可用性。但是这种策略会牺牲系统的扩展性,因为分布式系统设计的初衷就是能够容忍节点和网络的故障,从而提高系统的可靠性和可用性。
- CP(一致性和分区容错性)
- 如果不要求高可用性,即牺牲一定的响应时间或允许部分请求失败,则可以保证强一致性和分区容错性。这种策略在需要严格数据一致性的场景很有用 ,如分布式数据库等。
- AP(可用性和分区容错性)
- 如果要求高可用性和分区容错性,则可以牺牲一定的数据一致性。这种策略在需要快速响应且能够容忍数据不一致性的场景很有用,如社交网络系统等。
常见分布式事务解决方案
事务的四大特性
A(原子性):一个事务是一个不可分割得工作单位,事务包括的各种操作要么都做,要么都不错。
C(一致性):事务必须时是数据从一个一致性状态变成另个一致性状态,事务的中间状态不能被观察到的。
I(隔离性):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务都是独立的,相互不干扰。
- 读未提交
- 读已提交:解决脏读
- 可重复读:解决虚读
- 串行化:解决幻读
D(持久性):持久性也称永久性,指一个事务一旦提交,它对数据库中的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
redolog 保证事务的持久性
urdolog 保证事务的一致性、原子性
XA协议
两阶段提交
第一阶段:预提交阶段
由事务协调者询问各个事务参与者,是否准备好了执行事务
事务协调者(通常是一个中心节点)向所有事务参与者(参与事务的数据库或其他资源管理器)发送准备请求。
事务参与者收到请求后,执行本地事务操作,但不立即提交。如果操作成功,事务参与者会记录必要的恢复信息(如Undo Log和Redo Log),并回复事务协调者表示准备就绪。
如果所有事务参与者都回复准备就绪,事务协调者继续进入下一个阶段;如果有任何一个事务参与者回复失败,则事务协调者会想所有参与者发送回滚请求,并终止事务。
undo一般用于事务的取消和回滚,记录的是数据修改之前的值;
redo一般用于回复已确认但未写入数据库的数据,记录的是数据修改后的值。
第二阶段:提交阶段(讨论提交和回滚两种情况)
事务提交:
如果准备阶段成功,协调者发起正式提交的事务请求,当所有参与者都回复同意时,则意味着完成事务,具体流程如下:
- 事务协调者会向所有事务参与者发送提交请求。
- 事务参与者收到提交(commit)请求后,执行本地事务的提交操作,并释放整个事务期间锁定的资源。
- 事务参与者完成提交后,像事务协调者发送确认(ACK)信息。
- 当事务协调者收到所有事务参与者的确认消息,整个分布式事务完成。
事务回滚:
如果任意一个参与者节点在第一阶段返回的消息为终止,或者事务协调者节点在第一阶段的询问超时之前无法获取所有参与者阶段的响应消息时,这个事务将会被回滚,具体流程如下:
- 事务协调者向所有参与者发出rollback回滚操作的请求
- 参与者利用阶段一写入的undo信息执行回滚,并释放在整个事务期间内占用的资源
- 参与者在完成事务回滚之后,向事务协调者发送回滚完成的ACK信息
- 协调者收到所有参与者反馈的ACK信息后,取消事务。
2PC缺点
不可否认,两阶段提交的确能够提供原子性的操作,但是仍然存在不足
- 性能问题:执行过程中,所有节点都是事务阻塞性的,但参与者占有公共资源时,其他第三方节点访问公共资源就不得不处于阻塞状态,为了数据的一致性而牺牲了可用性,对性能影响较大,不适合高并发高性能场景
- 可靠性问题:2PC非常依赖于协调者,当协调者发生故障时,尤其是第二阶段,那么所有参与者都会处于锁定事务资源的状态中,而无法继续完成事务操作(如果是协调者挂掉的话,虽然可重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
- 数据一致性问题:在阶段二中,当协调者想参与者
三阶段提交
三阶段提交协议在两阶段提交的基础上增加了一个准备提交阶段,并引入了超时机制,以尝试解决两阶段提交中的一些问题(如单点故障和阻塞)。
第一阶段:准备提交阶段
- 事务协调者向事务参与者发送询问是否可以提交请求。
- 事务参与者评估自己的状态,如果可以提交,则回复“Yes”,否则回复“No”。
- 如果所有参与者都回复“Yes”,则进入下一个阶段;如果任何一个参与回复“No”或超时未回复,则事务协调者会向所有参与者发送中断(Abort)请求,并终止事务。
第二阶段:准备阶段
- 类似于二阶段提交中的准备阶段,事务协调者向所有参与者发送准备(Prepare)请求。
- 参与者执行本地事务操作,记录恢复信息,但不提交。
- 如果参所有参与者都恢复准备就绪,则进入下一个阶段;如果有任何一个参与者回复失败,则是事务协调者会向所有参与者发送回滚请求,并终止事务。
第三阶段:提交阶段
- 事务协调者向所有参与者发送提交请求。
- 参与者收到请求后,执行本地事务的提交操作,并释放锁定的资源。
- 参与者完成提交后,向事务协调者发送确认信息。
- 如果在提交阶段发生超时或者通信故障,参与者可能会根据超时机制自行决定是否提交事务(这取决于具体的实现的和配置)
3PC缺点
与2PC相比,3PC降低了阻塞时长,并且在等待超时后,协调者或参与者会中断事务,避免了协调者单点问题,阶段三中协调者出现问题时,参与者会继续提交事务。
数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 doCommit 指令时,此时如果协调者请求中断事务,而协调者因为网络问题无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
二阶段提交与三阶段提交的区别
- 增加了一个询问阶段
- 加入了超时机制
Seata分布式事务
在分布式系统中,由于数据分布在不同的服务或者数据库中,传统的单机事务已无法满足需求。分布式事务需要确保跨多个服务和数据库的操作要么全部成功,要么全部失败,以保证数据的一致性。
Global_Table(全局事务表)
- 作用:用于存储全局事务的信息。全局事务是指分布式事务中的顶级事务,它管理着整个分布式事务的生命周期。
Branch_Table(分支事务表)
- 作用:用于存储分支事务的信息。分支事务是指在一个全局事务中参与的具体操作,每个数据库操作都是一个分支事务。
Lock_Table(锁表)
- 作用:用于存储锁定信息,确保分支事务在执行过程中不会发生并发冲突。
核心组件
在seata的架构中,一共有三个角色:
TC(Transaction Coordinator)-事务协调者
负责协调全局事务的各个分支事务,并驱动全局事务的提交或回滚。 它类似于一个指挥官,负责全局事务的进度和状态。
TM(Transaction Manager)-事务管理器
管理全局事务的生命周期,包括全局事务的开始、提交和回滚等操作。它通常由业务应用来调用。已启动或结束一个全局事务。
RM(Resource Manager)-资源管理器
管理分支事务的生命周期,负责分指事务的注册、提交和回滚等操作。它通常与具体的数据库或服务绑定,执行实际的业务操作。
其中,TC为单独部署的Server服务端,TM和RM为嵌入到应用中的Client客户端。
工作原理
Seata的工作原理基于两阶段提交(2PC)协议的演变,但进行了优化以适应微服务场景。其过程大致可以分为以下几阶段:
- 全局事务发起:
- 当一个应用程序发起一个分布式事务时,事务管理器(TM)会向事务协调器(TC)请求开始一个全局事务。
- TC为该全局事务分配一个唯一的全事务ID(XID),并通知各个参与者(RM)开始分支事务。
- 分支事务注册:
- 每个参与者(RM)根据全局事务ID注册自己的分支事务,并在执行业务操作之前获取相应资源的锁。
- RM将分支事务的执行结果和必要的回滚日志记录到本地,以确保需要时可以回滚。
- 全局事务提交或回滚:
- 如果全局事务成功完成,TM会向TC发送提交请求。
- TC收到提交请求后,会向所有参与者(RM)发送提交指令,RM执行本地事务的提交操作。
- 如果全局事务发生异常或被取消,TM会向TC发送回滚请求。
- TC收到回滚请求后,会向所有参与者(RM)发送回滚指令,RM根据回滚日志执行本地事务的回滚操作。
优化与特性
Seata在传统的两阶段提交协议基础上进行多项优化,以提高性能和可靠性:
- 异步提交:通过异步方式提交分支事务,减少事务提交过程中的等待时间。
- 全局锁与本地锁:引入全局锁和本地锁来控制并发访问,确保数据一致性和隔离性。
- 可靠消息通信:使用可靠消息队列实现TC和RM之间的通信,确保数据可靠性和顺序性。
- 分布式存储:提供的分布式存储服务来存储全局事务和分支事务的状态信息,实现高可用性和持久性。
TM(Transaction Manager)-事务管理器
管理全局事务的生命周期,包括全局事务的开始、提交和回滚等操作。它通常由业务应用来调用。已启动或结束一个全局事务。
RM(Resource Manager)-资源管理器
管理分支事务的生命周期,负责分指事务的注册、提交和回滚等操作。它通常与具体的数据库或服务绑定,执行实际的业务操作。
其中,TC为单独部署的Server服务端,TM和RM为嵌入到应用中的Client客户端。
工作原理
Seata的工作原理基于两阶段提交(2PC)协议的演变,但进行了优化以适应微服务场景。其过程大致可以分为以下几阶段:
- 全局事务发起:
- 当一个应用程序发起一个分布式事务时,事务管理器(TM)会向事务协调器(TC)请求开始一个全局事务。
- TC为该全局事务分配一个唯一的全事务ID(XID),并通知各个参与者(RM)开始分支事务。
- 分支事务注册:
- 每个参与者(RM)根据全局事务ID注册自己的分支事务,并在执行业务操作之前获取相应资源的锁。
- RM将分支事务的执行结果和必要的回滚日志记录到本地,以确保需要时可以回滚。
- 全局事务提交或回滚:
- 如果全局事务成功完成,TM会向TC发送提交请求。
- TC收到提交请求后,会向所有参与者(RM)发送提交指令,RM执行本地事务的提交操作。
- 如果全局事务发生异常或被取消,TM会向TC发送回滚请求。
- TC收到回滚请求后,会向所有参与者(RM)发送回滚指令,RM根据回滚日志执行本地事务的回滚操作。
优化与特性
Seata在传统的两阶段提交协议基础上进行多项优化,以提高性能和可靠性:
- 异步提交:通过异步方式提交分支事务,减少事务提交过程中的等待时间。
- 全局锁与本地锁:引入全局锁和本地锁来控制并发访问,确保数据一致性和隔离性。
- 可靠消息通信:使用可靠消息队列实现TC和RM之间的通信,确保数据可靠性和顺序性。
- 分布式存储:提供的分布式存储服务来存储全局事务和分支事务的状态信息,实现高可用性和持久性。