1.雪崩问题

微服务调用链路中某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。
雪崩图片
雪崩产生的原因:
雪崩产生原因
解决思路:
解决思路
服务保护方案:
1.请求限流
请求限流
2.线程隔离
线程隔离
3.服务熔断
服务熔断
总结:
解决雪崩问题的常见处理方案
常见方案

2.Sentinel(服务保护技术)

2.1如何使用过sentinel

下载jar包
jar包
控制台命令行启动

java "-Dserver.port=8090" "-Dcsp.sentinel.dashboard.server=localhost:8090" "-Dproject.name=sentinel-dashboard" -jar "sentinel-dashboard.jar"

就可以在localhost:8090接口访问到sentinel的控制台

2.2微服务整合

1.引入sentinel依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2.配置控制台,修改yaml文件

spring:
  cloud: 
    sentinel:
      transport:
        dashboard: localhost:8090

3.重新访问端口就能看到簇点链路
所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel监控的资源。默认情况下,Sentinel会监控SpringMVC的每一个Endpoint(接口)。

不过,需要注意的是,我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts路径,默认情况下Sentinel会把路径作为簇点资源的名称,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源,这显然是不合适的,所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径作为簇点资源名:
首先,在cart-service的application.yml中添加下面的配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090
      http-method-specify: true #是否将请求方式作为资源名称

3.请求限流

在簇点链路后面点击流控按钮,即可对其做限流配置:
配置

这样就把查询购物车列表这个簇点资源的流量限制在了每秒6个,也就是最大QPS为6.
拒绝访问状态码429(限流)

4.线程隔离

4.1正常使用

限流可以降低服务器压力,尽量减少因并发流量引起的服务故障的概率,但并不能完全避免服务故障。一旦某个服务出现故障,我们必须隔离对这个服务的调用,避免发生雪崩。

比如,查询购物车的时候需要查询商品,为了避免因商品服务出现故障导致购物车服务级联失败,我们可以把购物车业务中查询商品的部分隔离起来,限制可用的线程资源:
暂时无法在飞书文档外展示此内容
这样,即便商品服务出现故障,最多导致查询购物车业务故障,并且可用的线程资源也被限定在一定范围,不会导致整个购物车服务崩溃。
所以,我们要对查询商品的FeignClient接口做线程隔离。
需要注意的是,默认情况下SpringBoot项目的tomcat最大线程数是200,允许的最大连接是8492,单机测试很难打满。所以需要修改tomcat

server:
  port: 8082
  tomcat:
    threads:
      max: 50 # 允许的最大线程数
    accept-count: 50 # 最大排队等待数量
    max-connections: 100 # 允许的最大连接

测试发现:
此时如果我们通过页面访问购物车的其它接口,例如添加购物车、修改购物车商品数量,发现不受影响:
响应时间非常短,这就证明线程隔离起到了作用,尽管查询购物车这个接口并发很高,但是它能使用的线程资源被限制了,因此不会影响到其它接口。

4.2 fallback

上述只是查询业务的报错还是没有处理,这个我们可以利用fallback对异常做处理
fallback处理

案列:
给feignClient编写fallback逻辑

1.自定义FallbackFactory,编写对某个FeignClient的fallback逻辑

package com.hmall.api.client.fallback;

import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.utils.CollUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;

import java.util.Collection;
import java.util.List;

@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
    @Override
    public ItemClient create(Throwable cause) {
        return new ItemClient() {
            @Override
            public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
                log.error("查询商品信息失败", cause);
                return CollUtils.emptyList();
            }
            @Override
            public void deductStock(List<OrderDetailDTO> details) {
                log.error("扣减库存失败!!", cause);
                throw new RuntimeException(cause);
            }

        };
    }
}

2.将刚才定义的ItemClientFallbackFactory注册成一个bean

package com.hmall.api.config;

import com.hmall.api.client.fallback.ItemClientFallbackFactory;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {

    @Bean
    public ItemClientFallbackFactory itemClientFallbackFactory() {
        return new ItemClientFallbackFactory();
    }
}

3.在ItemClient接口中使用ItemClientFallbackFactory

package com.hmall.api.client;

import com.hmall.api.client.fallback.ItemClientFallbackFactory;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Collection;
import java.util.List;

@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {

    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);

    @PutMapping("/items/deduct")
    void deductStock(@RequestParam("details") List<OrderDetailDTO> details);
}

4.修改cart-service模块的application.yml文件,开启Feign的sentinel功能:

feign:
  sentinel:
    enabled: true # 开启feign对sentinel的支持

接下来,点击查询商品的FeignClient对应的簇点资源后面的流控按钮:

流控规则
流控规则

5.服务熔断

对资源的一种保护,知道它已经挂了,就不走这个服务了,直接走fallback。

熔断思路

在客户端相应路径添加熔断规则即可

6.分布式事务

分布式事务

7. 认识seate

解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。

seate解决思路
解决思路

Seata也不例外,在Seata的事务管理中有三个重要的角色:

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

8.微服务集成seata

黑马资料中有部署seata教程(所以部署跳过)
1.引入依赖
为了方便各个微服务集成seata,我们需要把seata配置共享到nacos,因此trade-service模块不仅仅要引入seata依赖,还要引入nacos依赖:

<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

依赖不需要引入common模块中,只需要引入需要的模块,这里引入了item-server,trade-server,cart-server
2.改造配置
首先在nacos上添加一个共享的seata配置,命名为shared-seata.yaml:
配置
具体yaml:

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.203.129:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

然后在这个三个模块aplicatioin.yaml和bootstrap.yaml进行改造
因为nacos已经配置好了yaml文件所以在代码中配置就会变的很简单
nacos中的配置
nacos配置
这里以cart-server示例:
application.yaml

server:
  port: 8082
feign:
  okhttp:
    enabled: true
  sentinel:
    enabled: true # 开启feign的sentinel
hm:
  db:
    database: hm-cart
  swagger:
    title: "黑马商城购物车服务相关接口"
    package: com.hmall.cart.controller
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090 #配置sentinel控制台地址
      http-method-specify: true #是否将请求方式作为资源名称

bootstrap.yaml:

spring:
  application:
    name: cart-service
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.203.129:8848
      config:
        file-extension: yaml
        shared-configs:
          - data-id: shared-jdbc.yaml
          - data-id: shared-swagger.yaml
          - data-id: shared-log.yaml
          - data-id: shared-seata.yaml

item-server的例子:
application.yaml:

server:
  port: 8081
hm:
  db:
    database: hm-item
  swagger:
    title: "黑马商城商品服务相关接口"
    package: com.hmall.item.controller

bootstrap.yaml:

spring:
  application:
    name: item-service
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.203.129:8848
      config:
        file-extension: yaml
        shared-configs:
          - data-id: shared-jdbc.yaml
          - data-id: shared-swagger.yaml
          - data-id: shared-log.yaml
          - data-id: shared-seata.yaml

trade就不展示了,最后在docker上查看seata日志,能发现这三个服务已经被注册成TM、RM

9.XA模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。seata的xa模式如下:
xa模式
XA模式的优点是什么?

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务
    实现步骤:
    1.首先,我们要在配置文件中指定要采用的分布式事务模式。我们可以在Nacos中的共享shared-seata.yaml配置文件中设置:
    直接进nacos管理,改yaml
seata:
  data-source-proxy-mode: XA

修改yaml文件

2.其次,我们要利用@GlobalTransactional标记分布式事务的入口方法:

10.AT模式

Seata主推的是AT模式,AT模式同样是分阶段提交的事务模型,不过弥补了XA模式中资源锁定周期过长的缺陷。
AT工作模式

AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致

实现步骤:
1.在所需要的数据库中创建有关AT模式的undo_log表

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

其实seata默认不填就是AT模式:
默认AT

更多推荐