⬆︎
×

微服务原理篇:Spring Boot、Spring Cloud、分布式事务…

微服务(Microservice)将单体服务拆分成一组小型服务。拆分完成后,每个小型服务都运行在独立的进程中。服务与服务之间采用轻量级的通信机制来进行沟通(Spring Cloud中基于HTTP请求)。每个服务都按照具体的业务进行构建,例如在电商系统中有订单服务、会员服务、支付服务等,这些拆分的服务都是独立的应用服务,可以独立部署至生产环境中,相互之间不会受影响。故微服务项目中各服务可以根据业务场景独立开发,这是传统单体项目无法实现的。

1 Spring Boot

Web流程
ssm java web

1.1 核心注解与自动装配原理

在Spring Boot项目的引导类上有一个核心注解@SpringBootApplication,该注解对三个注解进行了封装,分别为:

  • @SpringBootConfiguration:组合了@Configuration注解,实现配置文件的功能
  • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
  • @ComponentScan:Spring组件扫描

其中@EnableAutoConfiguration是实现自动化配置(自动装备)的核心注解,该注解通过@Import注解导入对应的配置选择器,核心操作为内部读取该项目及其引用的Jar包的classpath路径下META-INF/spring.factories文件中所配置的类的全类名。

在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。一般条件判断会有类似@ConditionalOnClass的注解,判断是否有对应的class文件,若有则加载该类,将该配置类的所有的Bean放入Spring容器中使用。

1.2 启动流程

Spring Boot项目在启动时, 首先会执行启动引导类main()中的SpringApplication.run(HyplusApplication.class, args);,该方法所做的事情可分为三个部分:

  1. 第一部分:进行SpringApplication的初始化模块,配置基本的环境变量、资源、构造器、监听器
  2. 第二部分:实现应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块
  3. 第三部分:自动化配置模块,该模块作为Spring Boot自动配置核心

运行一个SpringBoot项目有如下几种方式:

  1. 直接使用jar -jar运行
  2. 开发过程中运行main()方法
  3. 可以配置插件,将Spring Boot项目打war包,部署到Tomcat中运行
  4. 直接用maven插件运行:maven spring-boot: run

1.3 常用的起步依赖

以下是Hyplus微服务项目常用的Spring Boot起步依赖:

  • spring-boot-starter-web
  • spring-boot-starter-jdbc
  • mybatis-spring-boot-starter
  • spring-boot-starter-test
  • mybatis-plus-spring-boot-starter
  • spring-boot-starter-data-redis
  • spring-boot-starter-data-elasticsearch
  • spring-boot-starter-data-mongodb
  • spring-boot-starter-amqp
  • spring-cloud-starter-openfeign
  • spring-cloud-starter-alibaba-nacos-discovery
  • ……

1.4 配置文件

由源码可知,Spring Boot项目支持多种配置文件(<include>):

<resources>
    <resource>
        <directory>${basedir}/src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
            <include>**/application*.yml</include>
            <include>**/application*.yaml</include>
            <include>**/application*.properties</include>
        </includes>
    </resource>
    <!-- ... -->
</resources>

会按从上到下的顺序进行加载(yml > yaml > properties),即先加载application.yml,然后加载application.properties。若有相同的配置,先加载的会被后加载的文件覆盖。

假如在启动项目时给了启动参数,则最后生效,会覆盖前面所有相同的配置,例如java -jar --server.port=8089 xx.jar

可以提供多套配置文件以定义多套不同环境配置,在applcation.properties文件中指定当前的环境spring.profiles.active=test来读取application-test.properties文件。如下所示(yaml同理):

applcation.properties
application-dev.properties
application-test.properties
application-prod.properties

2 Spring Cloud

早期一般认为的Spring Cloud五大组件为:

  1. 注册中心:Eureka
  2. 负载均衡:Ribbon
  3. 远程调用:Feign
  4. 服务熔断:Hystrix
  5. 服务网关:Zuul/Gateway

随着Spring Cloud Alibba在国内兴起 , 项目中逐渐改为使用阿里巴巴的组件:

  1. 注册中心/配置中心:Nacos
  2. 负载均衡:Ribbon
  3. 服务调用:Feign
  4. 服务保护:Sentinel
  5. 服务网关:Gateway

2.1 注册中心(Nacos、Eureka)

各种注册中心组件的原理和流程大体上都类似,以Eureka为例:

Eureka

核心功能 :

  1. 服务注册:服务启动时会将服务信息注册到注册中心,例如服务名称、服务的IP、端口号等。
  2. 服务发现:服务调用方调用服务时,根据服务名称从注册中心拉取服务列表,然后根据负载均衡策略选择一个服务, 获取服务的IP和端口号,发起远程调用。
  3. 服务状态监控:服务提供者会定时向注册中心发送心跳,注册中心也会主动向服务提供者发送心跳探测,若长时间没有接收到心跳,就将服务实例从注册中心下线或移除。

使用方式:首先部署注册中心服务 , 然后在微服务中引入注册中心依赖,最后在配置文件中配置注册中心地址即可

spring:
    application:
        name: leadnews-admin
    cloud:
        nacos:
            # 注册中心地址
            discovery:
                server-addr: 124.221.75.8:8848
            # 配置中心地址
            config:
                server-addr: 124.221.75.8:8848
                file-extension: yml

相比于Eureka,Nacos具有以下改进点:

  1. Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式。
  2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除。
  3. Nacos支持服务列表变更的消息推送模式,服务列表更新更及时。
  4. Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式。(Eureka始终采用AP方式)

配置文件的管理:大部分的固定的配置文件都放在服务本地,一些根据环境不同可能会变化的部分放到Nacos中,因为Nacos中主要存放各个微服务共享的配置,需要随着需求动态变更的配置。

2.2 服务通讯

两种通信方式:

  • 同步通信:通过Feign发送HTTP请求调。
  • 异步通信:消息队列,如RabbitMQKafKa等。(详见MQ)

2.3 负载均衡(Ribbon)

服务调用过程中的负载均衡一般使用Spring Cloud的Ribbon组件实现(Feign的底层已自动集成了Ribbon)。客户端调用一般会通过网关,通过网关实现请求的路由和负载均衡:

spring:
    cloud:
        gateway:
            routes:
                # 平台管理
                - id: hymedia
                  uri: lb://leadnews-hymedia
                  predicates:
                    - Path=/hymedia/**
                  filters:
                    - StripPrefix=1

Ribbon常用的负载均衡算法:

  1. RoundRobinRule:简单轮询服务列表来选择服务器。
  2. AvailabilityFilteringRule: 对以下两种服务器进行忽略:
    1. 默认情况下,服务器若3次连接失败,则将该服务器设置短路状态,持续30秒。若再次连接失败,短路的持续时间则呈几何级增加。
    2. 并发数过高的服务器。并发连接数上限可由客户端的ActiveConnectionsLimit属性进行配置。
  3. WeightedResponseTimeRule: 为每一个服务器赋予一个权重值——服务器响应时间越长,该服务器的权重就越小。该规则会随机选择服务器,权重值影响服务器的选择。
  4. ZoneAvoidanceRule:以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类(“机房、机架”),而后再对Zone内的多个服务做轮询。【默认】
  5. BestAvailableRule:忽略短路的服务器,并选择并发数较低的服务器。
  6. RandomRule: 随机选择一个可用的服务器。
  7. RetryRule: 重试机制的选择逻辑。

若要自定义负载均衡规则,可以定义IRule接口的实现类,然后再通过配置类或配置文件配置即可。具体有以下两种方式:

  1. 代码方式:在order-service中的OrderApplication类中定义一个新的IRule
@Bean
public IRule randomRule() {
    return new RandomRule();
}
  1. 配置文件方式:在order-serviceapplication.yml文件中添加新的配置
userservice:    # 给某个微服务配置负载均衡规则,此处以userservice服务为例
    ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule    # 负载均衡规则

2.4 服务网关(Spring Cloud Gateway)

Spring Cloud Gateway是Spring Cloud提供的服务网关组件,是整个微服务的统一入口。在服务网关中可以实现请求路由、统一的日志记录,流量监控、权限校验等一系列相关功能。

项目应用:权限的校验

实现思路:使用Spring Cloud Gateway中的全局过滤器拦截请求(GlobalFilterOrder),从请求头中获取token,然后解析token,若可以进行正常解析则放行,若解析失败则直接返回。

2.5 限流算法

常见的两种限流算法:

  1. 漏桶算法:(注水漏水过程)往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃。桶容量不变。(流出速率恒定)

漏桶算法

  1. 令牌桶算法:令牌桶是一个存放固定令牌数量的桶,按照固定速率r往桶里添加令牌;桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃。当一个请求达到时,会尝试从桶中获取令牌,若有则继续处理请求,若没有则排队等待或直接丢弃。(流出速率可能大于r

令牌桶算法

从作用上看,漏桶和令牌桶算法最明显的区别在于对突发流量(Burst)的处理方式:漏桶算法能够强行限制数据的实时传输(处理)速率,对突发流量不做额外处理;而令牌桶算法能够在限制数据的平均传输速率的同时允许某种程度的突发传输。

对比项 漏桶 令牌桶
能否保证流量曲线平滑 能。所有请求进入桶内,以恒定速率放行,绝对平滑 基本能。在请求量持续高于令牌生成速度时,流量平滑;但请求量在令牌生成速率上下波动时,无法保证曲线平滑
能否应对突增流量 能。请求可以暂存在桶内 能。桶内积累的令牌可以应对突增流量
流量控制精确度

2.6 服务熔断(Hystrix)

使用Hystrix实现的断路器(熔断器)默认是关闭的,若要开启需在引导类上添加注解@EnableCircuitBreaker

断路器状态机包括三个状态:

  1. closed关闭状态):断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换至open状态。
    • 请求错误率超过5%(默认值)
  2. open打开状态):服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。5秒后(默认值)进入half-open状态。
  3. half-open半开状态):放行一次请求,根据执行结果来判断接下来的操作:
    • 请求成功:切换到closed状态
    • 请求失败:切换到open状态

Hystrix

2.7 服务降级

涉及到服务调用的地方都应定义降级,通常的降级逻辑为返回默认值。

实现方式:创建FallbackFactory接口实现类,在对应的Feign客户端接口上通过@FeignClient指定降级类。

2.8 服务监控(Skywalking)

WIP


3 分布式事务

在分布式系统中,一个业务因为跨越不同数据库或跨越不同微服务而包含多个子事务,要求所有子事务同时成功或失败,这即为分布式事务。

分布式事务

【例】某电商系统的下单操作需要请求三个服务来完成:订单服务、账户服务、库存服务。当订单生成完毕后,需要分别请求账户服务和库存服务进行账户余额和库存的扣减。假设均扣减成功,若此时在执行下单的后续操作时发生问题,则订单数据库进行事务回滚,订单生成失败,但账户余额与库存均扣减成功了,造成了分布式问题。分布式事务即用于解决如上所述的不一致问题。

3.1 产生分布式事务的场景

如下若干场景可能产生分布式事务:

  1. 跨库事务:应用某个功能需要操作多个库,不同库中存储不同的业务数据。

跨库事务

  1. 分库分表:对数据量较大或预期未来的数据量较大的表进行水平拆分。
    • 对于此种场景,通常会使用数据库中间件来降低SQL操作的复杂性。【例】对于insert into user(id,name) values (1, "akira"), (2, "teresa"),这条SQL是操作单库的语法,单库情况下可以保证事务的一致性。在分库之后,若希望将1号记录插入分库1,2号记录插入分库2,数据库中间件则要将其改写为2条SQL,分别插入两个不同的分库,还要保证两个库要么都成功,要么都失败。因此基本上所有的数据库中间件都面临着分布式事务问题。

分库分表

  1. 跨服务事务:应用某个功能需要调用多个微服务进行实现,不同的微服务操作的是不同的数据库。
    • Service A完成某个功能需要直接操作数据库,同时需要调用Service B和Service C,而Service B又同时操作了2个数据库,Service C也操作了一个库。需要保证这些跨服务的对多个数据库的操作要么都成功,要么都失败。这是最典型的分布式事务场景。

跨服务事务

3.2 CAP理论

CAP定理由加州大学伯克利分校的Eric Brewer教授提出,其指出WEB服务无法同时满足以下3个属性:

  1. 一致性Consistency):更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。
  2. 可用性Availability):系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
  3. 分区容错性Partition tolerance):分布式系统在遇到任何网络分区故障时,仍需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。

CAP

分布式系统中无法同时保证一致性和可用性的原因:对于分布式系统而言,各节点之间一定会存在网络交互,首先存在网络延迟,其次无法100%确保网络的可用,因此可以认为分区网络故障不可避免。在此条件下,若要保证各节点的一致性,就必须在一个节点数据变更时,将数据同步给另一个节点,同时在数据同步过程中,被同步的节点不能对外提供服务,否则会出现数据不一致。而节点不可对外提供服务,就违背了可用性。故在存在系统分区的场景下,可用性和一致性无法同时满足。

3.3 BASE理论

BASE是CAP理论中AP方案的延伸,核心思想为即使无法做到强一致性(Strong Consistency,即CAP中的一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。其思想包含三方面:

  1. Basically Available(基本可用):分布式系统在出现不可预知的故障时,允许损失部分可用性,但不等于系统不可用。
  2. Soft State(软状态):允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
  3. Eventually Consistent(最终一致性):强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

3.4 常见的解决方案

3.4.1 2PC

两阶段提交(2PC)是一种非常经典的强一致、中心化的原子提交协议。中心化是指协议中有两类节点:中心化协调者节点(Coordinator)和参与者节点(Partcipant)。

两个阶段(【例】订单服务A,需要调用支付服务B支付,支付成功则处理订单状态为待发货状态,否则将购物订单处理为失败状态):

  1. 投票阶段
    1. 事务询问协调者向所有的参与者发送事务预处理请求(Prepare),并开始等待各参与者的响应。
    2. 执行本地事务各个参与者节点执行本地事务操作,但在执行完成后并不会真正提交数据库本地事务,而是先向协调者报告:“这边可以处理了/这边不能处理”。
    3. 各参与者向协调者反馈事务询问的响应:若参与者成功执行了事务操作,则反馈给协调者Yes响应,表示事务可以执行;若没有参与者成功执行事务;则反馈给协调者No响应,表示事务不可以执行。

投票阶段

  1. 提交/执行阶段
    1. 若所有的参与者反馈给协调者的信息都是Yes,则会执行事务提交协调者向所有参与者节点发出Commit请求
    2. 事务提交参与者收到Commit请求之后,就会正式执行本地事务Commit操作,并在完成提交之后释放整个事务执行期间占用的事务资源。

提交/执行局阶段

3.4.2 TCC

TCC(Try-Confirm-Cancel)又称补偿事务,其核心思想是:针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)。

  1. Try阶段:主要对业务系统做检测及资源预留。
  2. Confirm阶段:确认执行业务操作。
  3. Cancel阶段:取消执行业务操作。

TCC

TCC事务的处理流程与2PC两阶段提交类似,但2PC通常都是在跨库的DB层面,而TCC本质上类似一个应用层面的2PC,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口还必须实现幂等。

3.4.3 MQ分布式事务

上述两种分布式事务的解决方案适用于对数据一致性要求较高的场景。如果数据强一致性要求没那么高,可以采用消息中间件(MQ)实现事务最终一致。在支付系统中常使用该方式,因为只要求数据最终一致即可。

【例】向借呗申请借钱,借呗审核通过后支付宝的余额才会增加,但借呗和支付宝有可能不是同一个系统,此时即可使用MQ分布式事务,流程如下:

  1. 找借呗借钱
  2. 借呗借钱审核通过,同步生成借款单
  3. 借款单生成后,向MQ发送消息,通知支付宝转账
  4. 支付宝读取MQ消息,并增加账户余额

MQ分布式事务

上述操作中最复杂的其实是如何保障2、3在同一个事务中执行(本地事务和MQ消息发送在同一个事务执行),借款结束,借呗数据处理完成后,支付宝才能读到消息,然后执行余额增加,完成整个操作。若中途操作发生异常,例如支付宝余额增加发生问题,此时只能通过需要人工解决,没有特别好的办法,但这种事故概率极低。

3.5 Seata架构

Seata事务管理中有三个重要的角色:

  1. 事务协调者(Transaction Coordinator,TC):维护全局和分支事务的状态,协调全局事务提交或回滚。
  2. 事务管理器(Transaction Manager,TM):定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  3. 资源管理器(Resource Manager,RM):管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata

3.5.1 XA模式

XA模式的两个阶段:

  • RM一阶段的工作:① 注册分支事务到TC;② 执行分支业务sqI但不提交;③ 报告执行状态到TC。
  • TC二阶段的工作:TC检测各分支事务执行状态,若都成功,通知所有RM提交事务;若有失败,通知所有RM回滚事务。
  • RM二阶段的工作:接收TC指令,提交或回滚事务。

XA模式牺牲了可用性,保证了强一致性。

XA模式

3.5.2 AT模式

AT模式的两个阶段:

  • 阶段一RM的工作:① 注册分支事务;② 记录undo-log(数据快照);③ 执行业务SQL并提交;④ 报告事务状态。
  • 阶段二提交时RM的工作:删除undo-log即可。
  • 阶段二回滚时RM的工作:根据undo-log恢复数据到更新前。

AT模式牺牲了一致性,保证了可用性。

AT模式

3.5.3 TCC模式

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

  1. Try:资源的检测和预留。
  2. Confirm:完成资源操作业务;要求Try成功Confirm一定要能成功。
  3. Cancel:预留资源释放,可以理解为try的反向操作。

TCC模式的两个阶段(注意与前述补偿事务TCC的区别):

  1. 阶段一RM的工作:① 注册分支事务;② 执行try操作预留资源;③ 报告事务状态。
  2. 阶段二提交时RM的工作:根据各分支事务的状态执行confirm或cancel。

TCC模式

发表评论