SpringCloud Alibaba微服务分布式架构组件演变

文章目录

    • 1、SpringCloud版本对应
      • 1.1 技术选型依据
      • 1.2 cloud组件演变:
    • 2、Eureka
      • 2.1 Eureka Server : 提供服务注册服务
      • 2.2 EurekaClient : 通过注册中心进行访问
      • 2.3 Eureka自我保护
    • 3、Eureka、Zookeeper、Consul三个注册中心的异同点
      • 3.1 CP架构:
    • 4、Ribbon 负载均衡服务调用
      • 4.1 SpringCloud Load Balance
      • 4.2 总结:
      • 4.3 Ribbon工作流程:
      • 4.4 自定义Ribbon 负载均衡算法:
        • 4.4.1 iRule接口:
        • 4.4.2 Ribbon自带的负载均衡算法:
        • 4.4.3 负载均衡算法替代:
          • 4.4.3.1、在非启动类包及子包下创建配置类
          • 4.4.3.2、定义
          • 4.4.3.3、启动类增加RibbonClient注解
      • 4.5 Ribbon负载均衡算法
        • 4.5.1 轮询算法原理:
        • 4.5.2 轮询算法源码:
        • 4.5.3 手写负载均衡算法
    • 5、OpenFeign 服务接口调用
      • 5.1 概述
      • 5.2 Feign能干什么
      • 5.3 Feign集成了Ribbon
      • 5.4 应用
        • 5.4.1 引入依赖
        • 5.4.2 开启功能
        • 5.4.3 service中远程调用
      • 5.5 超时控制
        • 在yml中开启OpenFeign客户端超时控制
      • 5.6 日志打印
        • 5.6.1 日志级别
        • 5.6.2 配置类
        • 5.6.3 yml指定日志以什么级别监控哪个接口
    • 6.Hystrix 断路器
      • 6.1 问题:服务雪崩
      • 6.2 概念
      • 6.3 服务降级
        • 6.3.1 概念:
        • 6.3.2 触发服务降级的情况:
        • 6.3.3 应用
          • 6.3.3.1 依赖
          • 6.3.3.2 解决的问题
          • 6.3.3.3 生产者:
          • 6.3.3.4 消费者:
          • 6.3.3.5 配置全局fallback方法
          • 6.3.3.6 解耦合
      • 6.4 服务熔断
        • 6.4.1 概念:
        • 6.4.2 注解
        • 6.4.3 应用
        • 6.4.4 原来的主逻辑要如何恢复呢?
      • 6.5 服务限流
        • 6.5.1 概念:
      • 6.6 服务监控 hystrixDashboard
        • 6.6.1 依赖
        • 6.6.2 主启动类添加注解@EnableHystrixDashboard
        • 6.6.3 访问图形化界面
        • 6.6.4 调整需要监控的服务主启动类
        • 6.6.5 输入监控的url
    • 7、Gateway 网关
      • 7.1 特性
      • 7.2 Gateway与Zuul的区别
      • 7.3 Gateway模型
      • 7.4 三大基本概念
        • 7.4.1 Route 路由
        • 7.4.2 Predicate 断言
        • 7.4.3 Filter 过滤
      • 7.5 应用
        • 7.5.1 依赖
        • 7.5.2 yml配置文件
        • 7.5.3 配置类方式配置网关路由
      • 7.6 动态路由
      • 7.7 Predicate 断言
      • 7.8 Filter 过滤器
        • 7.8.1 自定义过滤器
      • 8、Config 服务配置
        • 8.1 作用:
      • 9、SpringCloud Alibaba
      • 10、Nacos 服务注册和配置中心
        • 10.1 应用
          • 10.1.1 依赖
          • 10.1.2 配置文件
        • 10.2 Nacos发现实例模型
        • 10.3 注册中心对比
        • 10.4 Nacos 支持AP和CP模式的切换
          • 10.4.1 何时选择何种模式?
        • 10.5 Nacos 服务配置
          • 10.5.1 SpringCloud原生注解@RefreshScope
          • 10.5.2 配置
          • 10.5.3 分类设计思想
        • 10.6 Nacos 集群是持久化配置
          • 10.6.1 Nacos支持三种部署模式
      • 11、Sentinel 熔断与限流
        • 11.1 是什么?
        • 11.2 特征
        • 11.3 特性
        • 11.4 与Hystrix的区别
        • 11.5 两个部分
        • 11.6 应用
          • 11.6.1 依赖
          • 11.6.2 配置文件
        • 11.7 流量配置规则
          • 11.7.1 直接(默认)
          • 11.7.2 关联
          • 11.7.3 Warm Up 预热
          • 11.7.4 排队等待
        • 11.8 熔断降级
          • 11.8.1 概述
            • RT(平均响应时间,秒级)
            • 异常比列(秒级)
            • 异常数(分钟级)
          • 11.8.2 Sentinel断路器没有半开状态
        • 11.9 热点规则
          • 11.9.1 参数例外项
          • 11.9.2 运行时异常
        • 11.10 系统规则
        • 11.11 @SentinelResource
        • 11.12 熔断框架比较
        • 11.13 Sentinel的规则持久化
          • 11.13.1 依赖
      • 12、 Seata 处理分布式事务
        • 12.1 Seata 简介2
        • 12.2 Seata的安装
          • 12.2.1 修改配置文件
          • 12.2.2 在nacos上创建配置文件 seataServer.yaml
          • 12.2.3 安装路径seata\seata-server-1.6.0\seata\script\config-center下有一个config.txt文件,修改后复制到seata路径下
          • 12.2.4 通过nacos-config.sh将config.txt文件的内容上传到nacos上
          • 12.2.5 通过seata-server.bat启动
        • 12.3 业务说明
        • 12.4 应用
          • 12.4.1 依赖
          • 12.4.2 配置文件
          • 12.4.3 订单服务主业务TOrderServiceImpl
          • 12.4.4 库存服务 TStorageServiceImpl
          • 12.4.5 故障情况
          • 12.4.6 使用Seata对数据源进行代理
        • 12.5 seata原理
          • 12.5.1 业务执行流程
          • 12.5.2 AT模式
            • 一阶段加载
            • 二阶段提交
            • 二阶段回滚

1、SpringCloud版本对应

SpringCloud Alibaba微服务分布式架构组件演变_第1张图片

1.1 技术选型依据

{
    "git": {
        "branch": "0e9bff9f3008546899af1a871def8e3a9cc852ff",
        "commit": {
            "id": "0e9bff9",
            "time": "2023-06-12T10:28:25Z"
        }
    },
    "build": {
        "version": "0.0.1-SNAPSHOT",
        "artifact": "start-site",
        "versions": {
            "spring-boot": "3.1.0",
            "initializr": "0.20.0-SNAPSHOT"
        },
        "name": "start.spring.io website",
        "time": "2023-06-12T10:30:20.458Z",
        "group": "io.spring.start"
    },
    "bom-ranges": {
        "codecentric-spring-boot-admin": {
            "2.4.3": "Spring Boot >=2.3.0.M1 and <2.5.0-M1",
            "2.5.6": "Spring Boot >=2.5.0.M1 and <2.6.0-M1",
            "2.6.8": "Spring Boot >=2.6.0.M1 and <2.7.0-M1",
            "2.7.4": "Spring Boot >=2.7.0.M1 and <3.0.0-M1",
            "3.0.4": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
        },
        "solace-spring-boot": {
            "1.1.0": "Spring Boot >=2.3.0.M1 and <2.6.0-M1",
            "1.2.2": "Spring Boot >=2.6.0.M1 and <3.0.0-M1",
            "2.0.0": "Spring Boot >=3.0.0-M1"
        },
        "solace-spring-cloud": {
            "1.1.1": "Spring Boot >=2.3.0.M1 and <2.4.0-M1",
            "2.1.0": "Spring Boot >=2.4.0.M1 and <2.6.0-M1",
            "2.3.2": "Spring Boot >=2.6.0.M1 and <3.0.0-M1",
            "3.0.0": "Spring Boot >=3.0.0-M1"
        },
        "spring-cloud": {
            "Hoxton.SR12": "Spring Boot >=2.2.0.RELEASE and <2.4.0.RELEASE",
            "2020.0.6": "Spring Boot >=2.4.0.RELEASE and <2.6.0",
            "2021.0.7": "Spring Boot >=2.6.0 and <3.0.0",
            "2022.0.3": "Spring Boot >=3.0.0 and <3.2.0-M1"
        },
        "spring-cloud-azure": {
            "4.8.0": "Spring Boot >=2.5.0.M1 and <3.0.0-M1",
            "5.2.0": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
        },
        "spring-cloud-gcp": {
            "2.0.11": "Spring Boot >=2.4.0-M1 and <2.6.0-M1",
            "3.5.1": "Spring Boot >=2.6.0-M1 and <3.0.0-M1",
            "4.3.1": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
        },
        "spring-cloud-services": {
            "2.3.0.RELEASE": "Spring Boot >=2.3.0.RELEASE and <2.4.0-M1",
            "2.4.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
            "3.3.0": "Spring Boot >=2.5.0-M1 and <2.6.0-M1",
            "3.4.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1",
            "3.5.0": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
            "4.0.0": "Spring Boot >=3.0.0 and <3.1.0-M1"
        },
        "spring-shell": {
            "2.1.10": "Spring Boot >=2.7.0 and <3.0.0-M1",
            "3.0.4": "Spring Boot >=3.0.0 and <3.1.0-M1",
            "3.1.0": "Spring Boot >=3.1.0 and <3.2.0-M1"
        },
        "vaadin": {
            "14.10.1": "Spring Boot >=2.1.0.RELEASE and <2.6.0-M1",
            "23.2.15": "Spring Boot >=2.6.0-M1 and <2.7.0-M1",
            "23.3.13": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
            "24.0.6": "Spring Boot >=3.0.0-M1 and <3.1.0-M1",
            "24.1.0": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"
        },
        "wavefront": {
            "2.0.2": "Spring Boot >=2.1.0.RELEASE and <2.4.0-M1",
            "2.1.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
            "2.2.2": "Spring Boot >=2.5.0-M1 and <2.7.0-M1",
            "2.3.4": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
            "3.0.1": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
        }
    },
    "dependency-ranges": {
        "okta": {
            "1.4.0": "Spring Boot >=2.2.0.RELEASE and <2.4.0-M1",
            "1.5.1": "Spring Boot >=2.4.0-M1 and <2.4.1",
            "2.0.1": "Spring Boot >=2.4.1 and <2.5.0-M1",
            "2.1.6": "Spring Boot >=2.5.0-M1 and <3.0.0-M1",
            "3.0.4": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
        },
        "mybatis": {
            "2.1.4": "Spring Boot >=2.1.0.RELEASE and <2.5.0-M1",
            "2.2.2": "Spring Boot >=2.5.0-M1 and <2.7.0-M1",
            "2.3.1": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
            "3.0.2": "Spring Boot >=3.0.0-M1"
        },
        "pulsar": {
            "0.2.0": "Spring Boot >=3.0.0 and <3.1.0-M1"
        },
        "pulsar-reactive": {
            "0.2.0": "Spring Boot >=3.0.0 and <3.1.0-M1"
        },
        "camel": {
            "3.5.0": "Spring Boot >=2.3.0.M1 and <2.4.0-M1",
            "3.10.0": "Spring Boot >=2.4.0.M1 and <2.5.0-M1",
            "3.13.0": "Spring Boot >=2.5.0.M1 and <2.6.0-M1",
            "3.17.0": "Spring Boot >=2.6.0.M1 and <2.7.0-M1",
            "3.20.5": "Spring Boot >=2.7.0.M1 and <3.0.0-M1",
            "4.0.0-M3": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
        },
        "picocli": {
            "4.7.0": "Spring Boot >=2.5.0.RELEASE and <3.1.0-M1"
        },
        "open-service-broker": {
            "3.2.0": "Spring Boot >=2.3.0.M1 and <2.4.0-M1",
            "3.3.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
            "3.4.1": "Spring Boot >=2.5.0-M1 and <2.6.0-M1",
            "3.5.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1"
        }
    }
}

学习推荐版本,由SpringCloud来决定SpringBoot的版本。

SpringCloud Alibaba微服务分布式架构组件演变_第2张图片

1.2 cloud组件演变:

SpringCloud Alibaba微服务分布式架构组件演变_第3张图片

2、Eureka

包含两个组件:Eureka ServerEureka Client

2.1 Eureka Server : 提供服务注册服务

各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

2.2 EurekaClient : 通过注册中心进行访问

是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载具法的顶载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心
跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

2.3 Eureka自我保护

SpringCloud Alibaba微服务分布式架构组件演变_第4张图片

SpringCloud Alibaba微服务分布式架构组件演变_第5张图片

3、Eureka、Zookeeper、Consul三个注册中心的异同点

组件名 语言 CAP 服务健康检查 对外暴露接口 SpringCloud集成
Eureka Java AP(高可用) 可配支持 HTTP 已集成
Zookeeper Go CP(数据一致性) 支持 HTTP/DNS 已集成
Consul Java CP(数据一致性) 支持 客户端 已集成
Nacos AP(高可用) 支持 客户端 已集成
  • C:Consistency (强一致性)
  • A:Availability (可用性)
  • P:Partition tolerance (分布式分区容错性)

最多只能同时较好的满足两个。
CAP理论的核心是: 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求。

CAP理论关注粒度是数据,而不是整体系统设计的策略。

因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

  • CA-单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
  • CP-满足─致性,分区容忍必的系统,通常性能不是特别高。
  • AP–满足可用性,分区容忍性的系统,通常可能对—致性要求低一些。

SpringCloud Alibaba微服务分布式架构组件演变_第6张图片

3.1 CP架构:

当网络分区出现后,为了保证一致性,就必须拒绝接请求,否则无法保证一致性。

结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。
SpringCloud Alibaba微服务分布式架构组件演变_第7张图片

4、Ribbon 负载均衡服务调用

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。

Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。

在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。

我们很容易使用Ribbon实现自定义的负载均衡算法。

4.1 SpringCloud Load Balance

LB负载均衡(Load Balance)是什么?

将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件Nginx,LVS,硬件F5等。

集中式LB Load Balance

即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,1如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;

Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别

Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的。

Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

4.2 总结:

Ribbon其实就是一个软负载均衡的客户端组件,

他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

在这里插入图片描述

4.3 Ribbon工作流程:

Ribbon在工作时分成两步:

  • 第—步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server.
  • 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
  • 其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

4.4 自定义Ribbon 负载均衡算法:

4.4.1 iRule接口:

在这里插入图片描述

4.4.2 Ribbon自带的负载均衡算法:

com.netflix.loadbalancer.RoundRobinRule:

轮询

com.netflix.loadbalancer.RandomRule:

随机
com.netflix.loadbalancer.RetryRule:

先按照RoundRobinRule的策骼获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务

weightedResponseTimeRule:

对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择

BestAvailableRuleo:

会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发是最小的服务

vailabilityFilteringRule:

先过滤掉故障实例,再选择并发较小的实例:

zoneAvoidanceRule

默认规则,复合判断server所在区域的性能和server的可用性选择服务器

官方文档明确给出了警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

4.4.3 负载均衡算法替代:

4.4.3.1、在非启动类包及子包下创建配置类

在这里插入图片描述

4.4.3.2、定义
/**
 * Myrule : Ribbon 自定义负载均衡算法配置类
 *
 * @author zyw
 * @create 2023/6/18
 */
@Configuration
public class Myrule {

    @Bean
    public IRule getIRule(){
        //定义为随机
        return new RandomRule();
    }
}
4.4.3.3、启动类增加RibbonClient注解

自定义需要指定的服务

/**
 * OderMain80 :
 *
 * @author zyw
 * @create 2023/6/16
 */
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOULD-PAYMENT-SERVICE",configuration = Myrule.class)
@MapperScan("com.zyw.springcloud.dao")
public class OderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OderMain80.class,args);
    }
}

4.5 Ribbon负载均衡算法

4.5.1 轮询算法原理:

在这里插入图片描述

4.5.2 轮询算法源码:

public class RoundRobinRule extends AbstractLoadBalancerRule {
    
private AtomicInteger nextServerCyclicCounter;
    
public RoundRobinRule() {
   nextServerCyclicCounter = new AtomicInteger(0);
}


public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            //获取有正常运行的(可达的)服务集合
            List<Server> reachableServers = lb.getReachableServers();
            //获取可负载服务集合
            List<Server> allServers = lb.getAllServers();
            //获取有正常运行的(可达的)服务的数量
            int upCount = reachableServers.size();
             //获取可负载服务的数量 == 服务器集群总数量
            int serverCount = allServers.size();

            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    //自旋锁
    //rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务器重启后rest接口计数从1开始。
        private int incrementAndGetModulo(int modulo) {
        for (;;) {
            //获取当前值
            int current = nextServerCyclicCounter.get();
            //计算下次值
            int next = (current + 1) % modulo;
            //比较并交换
            if (nextServerCyclicCounter.compareAndSet(current, next))
                //得到当前下标值
                return next;
        }
    }
    
}

4.5.3 手写负载均衡算法

/**
 * LoadBalancer : 自定义负载均衡算法
 *
 * @author zyw
 * @create 2023/6/20
 */
public interface LoadBalancer {

    //收集Eurek上所有活着的服务总数
    ServiceInstance instances(List<ServiceInstance> serviceInstances);

}
/**
 * MyLB : 自定义负载均衡算法实现类
 *
 * @author zyw
 * @create 2023/6/20
 */
@Component
public class MyLB implements LoadBalancer {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement() {
        int currrnt;
        int next;
        do {
            currrnt = this.atomicInteger.get();
            //Integer.MAX_VALUE = 2147483647
            next = currrnt >= 2147483647 ? 0 : currrnt + 1;
            //自选锁,直到得到期望值
        } while (!this.atomicInteger.compareAndSet(currrnt, next));
        System.out.println("第" + next + "次访问");
        return next;
    }

    /**
     * rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务器重启后rest接口计数从1开始。
     * @param serviceInstances
     * @return
     */
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

/**
 * OrderController : 订单系统控制层
 *
 * @author zyw
 * @create 2023/6/16
 */
@Slf4j
@RestController
@RequestMapping("consumer/orderController")
@Api(tags={"订单系统控制层"})
public class OrderController {

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DiscoveryClient discoveryClient;

    @Resource
    private LoadBalancer loadBalancer;

    @GetMapping("/lb")
    @ApiOperation(value = "获取负载服务的端口号", response = String.class)
    public String getPaymentBl(){
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("CLOULD-PAYMENT-SERVICE");
        if (serviceInstances == null || serviceInstances.size() <= 0){
            return null;
        }

        ServiceInstance serviceInstance = loadBalancer.instances(serviceInstances);

        URI uri = serviceInstance.getUri();
        return restTemplate.getForObject(uri+"paymentController/lb",String.class);
    }


}

在这里插入图片描述

5、OpenFeign 服务接口调用

5.1 概述

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。

它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。

Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。

Feign可以与Eureka和Ribbon组合使用以支持负载均衡

5.2 Feign能干什么

Feign旨在使编写Java Http客户端变得更容易。

前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

5.3 Feign集成了Ribbon

利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。

而与Ribbon不同的是,通过feign只需要定义

服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用

SpringCloud Alibaba微服务分布式架构组件演变_第8张图片

5.4 应用

5.4.1 引入依赖

        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>

5.4.2 开启功能

//开启Feign
@EnableFeignClients
@SpringBootApplication
public class OderOpenFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OderOpenFeignMain80.class, args);
    }

}

5.4.3 service中远程调用

@Component
@FeignClient(value = "CLOULD-PAYMENT-SERVICE")
public interface PaymentFeginService {

    @GetMapping("/paymentController/getById")
    public CommonResult getById(@RequestParam("id") Long id);

    @GetMapping("/paymentController/feign/timeout")
    public String paymentFeignTimeout();

}

5.5 超时控制

默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。

为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

在yml中开启OpenFeign客户端超时控制

# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
  # 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  # 指的是建立俩进阶后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

5.6 日志打印

5.6.1 日志级别

级别 内容
NONE 默认的,不显示任何日志
BASIC 仅记录请求方法、URL、响应状态码及执行时间
HEADERS 除了BASIC中定义的信息之外,还有请求和响应的头信息
FULL 除了HEADERS 中定义的信息之外,还有请求和响应的正文及元数据

5.6.2 配置类

@Configuration
public class FeginConfig {
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

5.6.3 yml指定日志以什么级别监控哪个接口

logging:
  level:
    # feign 日志以什么级别监控哪个接口
    com.zyw.springcloud.service.PaymentFeginService: debug

SpringCloud Alibaba微服务分布式架构组件演变_第9张图片

6.Hystrix 断路器

6.1 问题:服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的**“扇出"**。

如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。

比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。

这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或

者叫雪崩。

6.2 概念

Hystrix是一个用于处理分布式系统的延迟容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等Hystrix能够保证在一个依赖出

问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应

(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布

式系统中的蔓延,乃至雪崩。

6.3 服务降级

6.3.1 概念:

服务不可用了,不让客户端等待,并立刻返回一个友好提示,fallback。

6.3.2 触发服务降级的情况:

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满

6.3.3 应用

6.3.3.1 依赖
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
        dependency>
6.3.3.2 解决的问题

超时导致服务器变慢(转圈)-》超时不再等待-》服务降级

出错(宕机或程序运行出错)-》出错要有兜底-》服务降级

生产者正常,消费者自己出现故障或有自我要求(自己的等待时间小于服务提供者响应时间)

6.3.3.3 生产者:

设置自身调用后超时时间的峰值,超过峰值做服务降级fallback

一旦调用服务方法失败并抛出错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法

系统运行报错也会走fallbackMethod标注的方法。

    /**
     * 超时
     *
     * @param id
     * @return
     */
    //@HystrixCommand 服务降级规则 响应超过3000毫秒,则执行paymentInfo_TimeOutHandler方法
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
    })
    @Override
    public String paymentInfo_TimeOut(Integer id) {
        int timeNumber = 5;
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~" + ",耗时" + timeNumber + "秒钟";
    }

    public String paymentInfo_TimeOutHandler(Integer id) {
        return "o(╥﹏╥)o\r\n调用支付接口超时或异常:\t" + "\t当前线程池名称" + Thread.currentThread().getName();
    }

主启动类添加@EnableCircuitBreaker注解

@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}
6.3.3.4 消费者:

配置文件中开启 feign 对 hystrix 的支持

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

主启动类添加@EnableHystrix注解

@EnableHystrix
@EnableFeignClients
@SpringBootApplication
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}

HystrixCommand:

    @GetMapping("/hystrix/TimeOut/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
    })
    @ApiOperation(value = "超时接口", response = String.class)
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    public String paymentTimeOutFallbackMethod(Integer id) {
        return "我是消费者80,对方支付系统繁忙,请10秒后重试\r\no(╥﹏╥)o";
    }

需要设置熔断器超时峰值,否则会报错

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            # 设置熔断器超时峰值
            timeoutInMilliseconds: 5000
6.3.3.5 配置全局fallback方法
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
@RequestMapping("consumer")
@Api(tags = {"整合Hystrix订单系统控制层"})
public class OrderHystirxController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/hystrix/OK/{id}")
    @HystrixCommand
    @ApiOperation(value = "正常接口", response = String.class)
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    /**
     * 全局fallback方法
     * @return
     */
    public String payment_Global_FallbackMethod(){
        return "payment系统繁忙,请联系客服处理\n" +
                "o(╥﹏╥)o";
    }

}

如果配置了@HystrixCommand中的fallbackMethod属性,则走专属配置的,没有则走全局的。

6.3.3.6 解耦合
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/OK/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/TimeOut/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id);

}
@Component
public class PaymentFallbackService implements PaymentHystrixService {

    @Override
    public String paymentInfo_OK(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_OK\r\no(╥﹏╥)o";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut\r\no(╥﹏╥)o";
    }

}

6.4 服务熔断

6.4.1 概念:

类似于保险丝达到最大服务访问量后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。

熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。

当检测到该节点微服务调用响应正常后,恢复调用链路。

在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,

当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。

6.4.2 注解

    //=====服务熔断=====
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败率达到多少后跳闸
    })
    @Override
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {

涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。

1: 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。

2∶请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。

3∶错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过
50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

6.4.3 应用

@Service
public class PaymentServiceImpl implements PaymentService {

    //=====服务熔断=====
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败率达到多少后跳闸
    })
    @Override
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("******id不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();
        return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id不能负数,请稍后再试,/(ToT)/~~id: " + id;
    }


}
@RestController
@Slf4j
@RequestMapping("/payment")
@Api(tags = {"整合hystrix支付系统控制层"})
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    //=====服务熔断=====
    @GetMapping("/hystrix/circuit/{id}")
    @ApiOperation(value = "服务熔断接口", response = String.class)
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("result:" + result);
        return result;
    }

}

6.4.4 原来的主逻辑要如何恢复呢?

对于这一问题,hystrix也为我们实现了自动恢复功能。

当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

6.5 服务限流

6.5.1 概念:

秒杀、高并发等操作,严禁其一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。

6.6 服务监控 hystrixDashboard

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。

Netfiix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

6.6.1 依赖

        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
        dependency>

6.6.2 主启动类添加注解@EnableHystrixDashboard

@SpringBootApplication
@EnableHystrixDashboard//开启图形化监控界面
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class,args);
    }
}

6.6.3 访问图形化界面

http://localhost:9001/hystrix

6.6.4 调整需要监控的服务主启动类

@EnableCircuitBreaker//开启熔断器
@EnableEurekaClient
@SpringBootApplication
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
    /**
     * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的玩
     * ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
     * 只要在自己的项目里配置上下面的servlet就可以了
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

6.6.5 输入监控的url

SpringCloud Alibaba微服务分布式架构组件演变_第10张图片

SpringCloud Alibaba微服务分布式架构组件演变_第11张图片

7、Gateway 网关

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。

Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统—的API路由管理方式。

SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

7.1 特性

  • 基于Spring Framework 5, Project Reactpr和Spring Boot 2.0进行构建;动态路由:能够匹配任何请求属性;
  • 可以对路由指定Predicate(断言)和Filter (过滤器);
  • 集成Hystrix的断路器功能;
  • 集成Spring Cloud服务发现功能;
  • 易于编写的 Predicate (断言)和Filter (过滤器);
  • 请求限流功能;
  • 支持路径重写。

7.2 Gateway与Zuul的区别

在SpringCloud Finchley正式版之前,Spring Cloud 推荐的网关是 Netflix提供的Zuul:

  • 1、Zuul 1.x,是一个基于阻塞IO的API Gateway
  • 2、Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket) Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Ngink用C+实现,Zuul用Java 实现,而JVM本身会有第—次加载较慢的情况,使得Zuul的性能相对较差。
  • 3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul.2 .x的性能较Zuul.1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
  • 4、Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
  • 5、Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

7.3 Gateway模型

传统的Web框架,比如说: struts2,springmvc等都是基于Servlet APl与Servlet容器基础之上运行的。

但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty, Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)

Spring WebFlux是 Spring 5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

7.4 三大基本概念

7.4.1 Route 路由

路由是构建网关的基本模块,它由ID,目标URl,一系列的断言和过滤器组成,如果断言为true则匹配该路由

7.4.2 Predicate 断言

参考的是Java8的java.util.functjon.Predicate

开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

7.4.3 Filter 过滤

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

7.5 应用

7.5.1 依赖

        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>

7.5.2 yml配置文件

server:
  port: 9527
eureka:
  instance:
    # eureka 服务端的实例名称
    hostname: cloud-gateway-service
  client:
    # false 表示自己端就是注册中心,我的职责就是维护服务实例,而不需要去检索服务
    fetch-registry: true
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7001.com:7001/eureka
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id : payment_routh               # 路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001       # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/getById/**     # 断言,路径相匹配的进行路由
        - id: payment_routh2
          uri: http://localhost:8001       # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**     # 断言,路径相匹配的进行路由

7.5.3 配置类方式配置网关路由

@Configuration
public class GateWayConfig {

    /**
     * 配置了一个id为route-name的路由规则
     * 当访问地址 https://localhost:9527/guoji 时会自动转发到地址:https://news.baidu.com/guonei
     * @param routeLocatorBuilder
     * @return
     */
    @Bean
    //路由定位器
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_route_zyw",
                r -> r.path("/guonei")
                        .uri("https://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

7.6 动态路由

默认情况下,GateWay会根据注册中心注册的服务列表,以注册中心上微服务名称为路径创建动态路由进行转发

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true                    # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id : payment_routh               # 路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-payment-service  # 匹配后提供微服务的路由地址
          predicates:
            - Path=/payment/getById/**     # 断言,路径相匹配的进行路由

        - id: payment_routh2
          uri: lb://cloud-payment-service  # 匹配后提供微服务的路由地址
          predicates:
            - Path=/payment/lb/**     # 断言,路径相匹配的进行路由

7.7 Predicate 断言

  • predicates.After : 配置启用时间
  • predicates.Before: 配置停用时间
  • predicates.Between: 配置可用时间范围
  • Cookie Route Predicate

需要两个参数,Cookie name和正则表达式

路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由。

  • Header Route Predicate

属性名+正则表达式

  • Method Route Predicate

规定请求方式

  • Query Route Predicate

规定必传参数

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true                    # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id : payment_routh               # 路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-payment-service  # 匹配后提供微服务的路由地址
          predicates:
            - Path=/payment/**             # 断言,路径相匹配的进行路由
            - After=2023-06-26T15:32:05.258+08:00[Asia/Shanghai]   # 配置启用时间
            - Before=2123-06-26T15:32:05.258+08:00[Asia/Shanghai]  # 配置停用时间
            - Between=2023-06-26T15:32:05.258+08:00[Asia/Shanghai],2123-06-28T15:32:05.258+08:00[Asia/Shanghai]  # 配置可用时间范围
            - Cookie=username,zyw
            - Header=X-Request-Id,\d+      # 请求头要有 X-Request-Id属性并且值为整数的正则表达式
            - Method=Get                   # 规定请求的方式
            - Query=idCard,\d+             # 参数名要有idCard且值必须为整数

时间格式生成工具类:

import java.time.ZonedDateTime;
public class DateTest {
    public static void main(String[] args) {
        ZonedDateTime zbj = ZonedDateTime.now();
        System.out.println("zbj = " + zbj);
    }
}

7.8 Filter 过滤器

生命周期:pre ==》 post

种类:GatewayFilter(单一的)、GlobalFilter(全局的)

7.8.1 自定义过滤器

8、Config 服务配置

SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置

SpringCloud Alibaba微服务分布式架构组件演变_第12张图片

分为服务端和客户端

服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口

客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容

8.1 作用:

  • 集中管理配置文件
  • 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
  • 运行期间动态调整配置,不再需要在萦个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  • 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
  • 将配置信息以REST接口的形式暴露,post、curl访问刷新均可

9、SpringCloud Alibaba

  • 服务限流降级:默认支持Servlet、Feign、RestTemplate、Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud服务注册与发现标准,默认集成了Ribbon的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于Spring Cloud Stream为微服务应用构建消息驱动力能力。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。
  • 网格任务支持海量子任务均匀分配到所有Worker (schedulerx-client)上执行。

组件:

  • Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
  • Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  • RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。Dubbo: Apache DubboTM是一款高性能Java RPC框架。
  • Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
  • Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。
  • Alibaba Cloud oSS:阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云提供的海星、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • Alibaba Cloud SchedulerX:阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。
  • Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

10、Nacos 服务注册和配置中心

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos = Eureka + Config + Bus

10.1 应用

10.1.1 依赖

父工程

            
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                2.1.0.RELEASE
                pom
                import
            

子工程

        <!-- SpringCloud alibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
10.1.2 配置文件
server:
  port: 9001
  servlet:
    context-path: /provider
spring:
  # 服务名称
  application:
    # 服务名称
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # 配置Nacos地址
management:
  endpoints:
    web:
      exposure:
        include: '*'

10.2 Nacos发现实例模型

SpringCloud Alibaba微服务分布式架构组件演变_第13张图片

10.3 注册中心对比

SpringCloud Alibaba微服务分布式架构组件演变_第14张图片

10.4 Nacos 支持AP和CP模式的切换

C :所有节点在同一时间看到的数据是一致的

A:所有请求都会收到响应

10.4.1 何时选择何种模式?

一般来说,
如果不需要存储服务级别的信息且服务实例是通过nacos-cient注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring cloud 和Dubo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。

如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

10.5 Nacos 服务配置

Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application

10.5.1 SpringCloud原生注解@RefreshScope

修饰Controller层可以支持Nacos的动态刷新功能

10.5.2 配置

bootstarp.yml

server:
  port: 3377
spring:
  # 服务名称
  application:
    # 订单服务
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # Nacos 服务注册中心地址
      config:
        server-addr: localhost:8848 # Nacos 配置中心地址
        file-extension: yaml # 指定yaml格式的配置
        group: TEST_GROUP
        namespace: 6536b558-4546-48a3-ba53-eaf9e264006d

applicaton.yml

spring:
  profiles:
    active: test # 表示测试环境
#    active: dev # 表示开发环境

SpringCloud Alibaba微服务分布式架构组件演变_第15张图片

最后公式 s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

10.5.3 分类设计思想

namespace用于区分部署环境

Group和DataID逻辑上区分两个目标对象

默认情况:Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT

SpringCloud Alibaba微服务分布式架构组件演变_第16张图片

  • Nacos默认的命名空间是public,Namespace主要用来实现隔离。

  • 比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

  • Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

  • Service就是微服务;一个Service可以包含多个Cluster (集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。

比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,

这时就可以给杭州机房的Service微服务起一个集群名称(HZ) ,

给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

最后是lnstance,就是微服务的实例。

10.6 Nacos 集群是持久化配置

默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。

为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。

10.6.1 Nacos支持三种部署模式
  • 单机模式-用于测试和单机试用。
  • 集群模式–用于生产环境,确保高可用。
  • 多集群模式–用于多数据中心场景。

11、Sentinel 熔断与限流

11.1 是什么?

分布式系统的流量防卫兵

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。

Sentinel以流星为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

11.2 特征

丰富的应用场景: Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

完备的实时监控: Sentinel同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况。

广泛的开源生态: Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud、Dubbo、gRPC的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。

完善的SPI扩展点: Sentinel提供简单易用、完善的SPI扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

11.3 特性

在这里插入图片描述

11.4 与Hystrix的区别

Hystrix Sentinel
需要手工搭建监控平台:DashBoard 单独一个组件独立出来
没有一套Web界面可以进行更加颗粒化的配置流量监控、速率控制、服务熔断、服务降级 支持界面化的细粒度统一配置

11.5 两个部分

核心库(Java客户端)不依赖任何框架/库,能够运行于所有Java运行时环境,同时对 Dubbo /Spring Cloud 等框架也有较好的支持。

控制台(Dashboard)基于Spring Boot 开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

启动命令:

java -jar sentinel-dashboard-1.7.1.jar

11.6 应用

11.6.1 依赖
        
        <dependency>
            <groupId>com.alibaba.cspgroupId>
            <artifactId>sentinel-datasource-nacosartifactId>
        dependency>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
        dependency>
11.6.2 配置文件
server:
  port: 8401
  servlet:
    context-path: /sentinel
spring:
  # 服务名称
  application:
    # 订单服务
    name: nacos-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # Nacos 服务注册中心地址
        namespace: 13e20987-6fcc-41d6-a358-e16b86bce522
      config:
        server-addr: localhost:8848 # Nacos 配置中心地址
        file-extension: yaml # 指定yaml格式的配置
        group: DEV_GROUP
        namespace: 13e20987-6fcc-41d6-a358-e16b86bce522
    sentinel:
      transport:
        # 配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,加入被占用会自动从8719开始依次+1扫描,知道找到未被占用的端口
        port: 8719
management:
  endpoints:
    web:
      exposure:
        include: "*"

11.7 流量配置规则

  • 资源名: 唯一名称,默认请求路径

  • 针对来源: Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)

  • 阈值类型/单机阈值:

    • QPS(每钟的请求数量): 当调用该api的QPS达到阈值的时候,进行限流。
    • 线程数: 当调用该api的线程数达到阈值的时候,进行限流
  • 是否集群: 不需要集群

  • 流控模式:

    • 直接: api达到限流条件时,直接限流
    • 关联: 当关联的资源达到阈值时,就限流自己
    • 链路: 只记录指定链路上的流星(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
  • 流控效果:

    • 快速失败: 直接失败,抛异常
    • Warm up: 根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待: 匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
11.7.1 直接(默认)

==》快速失败
在这里插入图片描述
在这里插入图片描述

11.7.2 关联

在这里插入图片描述

11.7.3 Warm Up 预热

Warm Up ( RuleConstant.CONTROL_BEHAVIOR_MARM_UuP)方式,即预热/冷启动方式。

当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

默认 coldFactor为3,即请求QPS从threshold / 3 开始,经预热时长逐渐升致设定的QPS阈值。

在这里插入图片描述

11.7.4 排队等待

匀速排队( RuleConstant.CONTROL_BEHAVIOR_RATE_LINITER )方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法

在这里插入图片描述

用于处理间隔性突发的流量,例如消息队列。

在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

在这里插入图片描述
在这里插入图片描述

11.8 熔断降级

11.8.1 概述

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException )。

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错谒。

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException) 。

RT(平均响应时间,秒级)
  • 平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
  • 窗口期过后关闭断路器
  • RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

平均响应时间(DEGRADE GRADE_RT ):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以ms为单位),那么在接下的时间窗口( DegradeRule中的timewindow,以s 为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。注意Sentinel默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。

在这里插入图片描述

在这里插入图片描述

每秒钟进来10个线程,我们Sentinel上配置的是希望200毫秒处理完本次任务,如果没有处理完,在未来1秒钟的时间窗口内,断路器打开,微服务不可用。

异常比列(秒级)

QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常比例( DEGRADE_GRADE_EXCEPTION_RATIO ):当资源的每秒请求量>= 5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的 count )之后,资源进入降级状态,即在接下的时间窗口( DegradeRule中的 timewindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0,1.0],代表0%- 100%。

在这里插入图片描述

异常数(分钟级)

异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常数( DE6RADE_GRADE_EXCEPTION_COUNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timewindow小于60s,则结束熔断状态后仍可能再进入熔断状态。

时间窗口一定要大于等于60S。

11.8.2 Sentinel断路器没有半开状态

半开状态:系统自动会去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。(Hystrix)

注意:异常降级仅针对业务异常,对Sentinel限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过Tracer.trace(ex)记录业务异常。

11.9 热点规则

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的Top K数据,并对其访问进行限制。

比如:

  • 商品ID为参数,统计—段时间内最常购买的商品ID并进行限制
  • 用户ID为参数,针对一段时间内频繁访问的用户ID进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel利用LRU策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

源码:

Entry entry = null;
try {
  entry = SphU.entry(resourceName,EntryType.IN1,paramA,paramB)// Your logic here.
}catch (BlockException ex) {
     //Handle request rejection.
}finally{
   if (entry != null) {
       entry.exit(1,paramA,paramB);
    }
}

测试案例:

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false)String p1,
                             @RequestParam(value = "p2",required = false)String p2){
        return "----testHotKey";
    }
    //兜底方法
    public String deal_testHotKey(String p1, String p2, BlockException exception){
        //Sentinel系统默认提示:Blocked by Sentinel (flow limiting)
        return "deal_testHotKey,o(╥﹏╥)o";
    }

在这里插入图片描述

​ @SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”)

第一个参数只要QPS超过每秒1次,马上降级处理,用自定义的deal_testHotKey兜底方法

11.9.1 参数例外项

在这里插入图片描述

11.9.2 运行时异常

​ @SentinelResource

处理的是Sentinel控制台配置的违规情况,有blockHandler 方法配置的兜底处理

主管配置出错,运行出错该走异常走异常

11.10 系统规则

Sentinel系统自适应限流从整体维度对应更入口流量进行控制,结合应用的Load、CPU使用率、总体平均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则支持以下的模式:

  • Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的maxQps * minRt估算得出。设定参考值一般是cPUcores * 2.5。
  • CPU usage (1.5.0+版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏。
  • 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒。·并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。

11.11 @SentinelResource

自定义异常兜底处理类

public class CustomerBlockFallback {
    public static CommonResult handlerFallback(@PathVariable("id") Long id,Throwable e){
        return CommonResult.BlockHandler("兜底异常--Fallback,exception内容:"+e.getMessage());
    }
}

自定义Sentinel控制台配置违规处理类

public class CustomerBlockHandler {
    public static CommonResult handlerblock(@PathVariable("id") Long id, BlockException exception) {
        return CommonResult.BlockHandler(exception.getMessage());
    }
}

业务层

@Service
public class PaymentServiceImpl {
    @Resource
    private PaymentService paymentService;
    @SentinelResource(value = "getPaymentById",
            fallbackClass = CustomerBlockFallback.class,
            fallback = "handlerFallback", //fallback只负责业务异常
            blockHandlerClass = CustomerBlockHandler.class,
            blockHandler = "handlerblock", //只负责Sentinel控制台配置违规
            exceptionsToIgnore = {IllegalArgumentException.class} //排除该异常的兜底方法
    )
    public CommonResult<Payment> getPaymentById(Long id) {
        if (id<0){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
        }
        CommonResult<Payment> paymentById = paymentService.getPaymentById(id);
        if (paymentById.getData()==null){
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return paymentById;
    }
}

11.12 熔断框架比较

Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比例、异常数 基于异常比例 基于异常比例、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持 Rate Limiter

11.13 Sentinel的规则持久化

解决方案:保存进Nacos(官方要求)、Mysql、Redis

11.13.1 依赖
        
        <dependency>
            <groupId>com.alibaba.cspgroupId>
            <artifactId>sentinel-datasource-nacosartifactId>
        dependency>

11.13.2 配置文件

spring:
  # 服务名称
  application:
    # 订单服务
    name: nacos-payment-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # Nacos 服务注册中心地址
        namespace: 13e20987-6fcc-41d6-a358-e16b86bce522
      config:
        server-addr: localhost:8848 # Nacos 配置中心地址
        file-extension: yaml # 指定yaml格式的配置
        group: DEV_GROUP
        namespace: 13e20987-6fcc-41d6-a358-e16b86bce522
    sentinel:
      transport:
        # 配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,加入被占用会自动从8719开始依次+1扫描,知道找到未被占用的端口
        port: 8719
      datasource:
        dsl:
          nacos:
            server-addr: localhost:8848
            namespace: 13e20987-6fcc-41d6-a358-e16b86bce522
            dataId: nacos-payment-consumer
            groupId: DEV_GROUP
            data-type: json
            rule-type: flow

在这里插入图片描述

resource: 资源名称;

limitApp: 来源应用;

grade: 阈值类型,0表示线程数,1表示QPS;

count: 单机阈值;

strategy: 流控模式,0表示直接,1表示关联,2表示链路;

controlBehavior: 流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;

clusterMode: 是否集群。

12、 Seata 处理分布式事务

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。

此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

一次业务操作需要跨多个数据源或者需要跨多个系统进行远程调用,就会产生分布式事务问题。

案例:

用户购买商品的业务逻辑:

  • 仓储服务:对给定的商品扣除仓储数量。
  • 订单服务:根据采购需求创建订单。
  • 账户服务:从用户账户中扣除余额。

SpringCloud Alibaba微服务分布式架构组件演变_第17张图片

12.1 Seata 简介2

概念:Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

分布式事务的处理过程:1 ID+ 3 组件模型

1 ID:全剧唯一的事务ID

术语3组件:

  • Tc-事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
  • TM-事务管理器:定义全局事务的范围︰开始全局事务、提交或回滚全局事务。
  • RM-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

SpringCloud Alibaba微服务分布式架构组件演变_第18张图片
SpringCloud Alibaba微服务分布式架构组件演变_第19张图片

12.2 Seata的安装

12.2.1 修改配置文件
#  Copyright 1999-2019 Seata.io Group.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

console:
  user:
    username: seata
    password: seata

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976
      group: SEATA_GROUP
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
      data-id: seataServer.yaml
      username: nacos
      password: nacos
 
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    # preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976
      cluster: default
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""


  store:
    # support: file 、 db 、 redis
    mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&serverTimezone=GMT
      user: root
      password: 123456
      min-conn: 5
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 100
      max-wait: 5000
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
12.2.2 在nacos上创建配置文件 seataServer.yaml
metrics:
  enabled: false
  exporterList: prometheus
  exporterPrometheusPort: 9898
  registryType: compact
server:
  maxCommitRetryTimeout: -1
  maxRollbackRetryTimeout: -1
  recovery:
    asynCommittingRetryPeriod: 3000
    committingRetryPeriod: 3000
    rollbackingRetryPeriod: 3000
    timeoutRetryPeriod: 3000
  rollbackRetryTimeoutUnlockEnable: false
  undo:
    logDeletePeriod: 86400000
    logSaveDays: 7
store:
  db:
    branchTable: branch_table
    datasource: druid
    dbType: mysql
    driverClassName: com.mysql.cj.jdbc.Driver
    globalTable: global_table
    lockTable: lock_table
    maxConn: 30
    maxWait: 5000
    minConn: 5
    password: root
    queryLimit: 100
    url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&serverTimezone=GMT
    user: root
  mode: db
transport:
  compressor: none
  serialization: seata

12.2.3 安装路径seata\seata-server-1.6.0\seata\script\config-center下有一个config.txt文件,修改后复制到seata路径下
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction routing rules configuration, only for the client
service.vgroupMapping.my_test_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=123456
store.redis.queryLimit=100

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false

#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

12.2.4 通过nacos-config.sh将config.txt文件的内容上传到nacos上

SpringCloud Alibaba微服务分布式架构组件演变_第20张图片

12.2.5 通过seata-server.bat启动

12.3 业务说明

  • 这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
  • 当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
  • 再通过远程调用账户服务来扣减用户账户里面的余额,
  • 最后在订单服务中修改订单状态为已完成。
  • 该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

12.4 应用

12.4.1 依赖
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-allartifactId>
                    <groupId>io.seatagroupId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>io.seatagroupId>
            <artifactId>seata-allartifactId>
            <version>1.6.0version>
        dependency>
12.4.2 配置文件
spring:
  # 服务名称
  application:
    # 订单服务
    name: seata-order-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # Nacos 服务注册中心地址
        namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976
      config:
        server-addr: localhost:8848 # Nacos 配置中心地址
        file-extension: yaml # 指定yaml格式的配置
        group: SEATA_GROUP
        namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976
    sentinel:
      transport:
        # 配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,加入被占用会自动从8719开始依次+1扫描,知道找到未被占用的端口
        port: 8719
    alibaba:
      seata:
        # 自定义事务组名称需要与seata-server中的对应
        tx-service-group: default_tx_group

# seata 配置, 代替file.conf和registry.conf配置
sfs:
  nacos:
    server-addr: 127.0.0.1:8848
    namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976
    group: SEATA_GROUP
    username: nacos
    password: nacos
seata:
  enabled: true
  application-id : ${spring.application.name}
  tx-service-group: default_tx_group
  use-jdk-proxy: true
  enable-auto-data-source-proxy: true
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: ${sfs.nacos.server-addr}
      namespace: ${sfs.nacos.namespace}
      group: ${sfs.nacos.group}
      username: ${sfs.nacos.username}
      password: ${sfs.nacos.username}
  config:
    type: nacos
    nacos:
      server-addr: ${sfs.nacos.server-addr}
      namespace: ${sfs.nacos.namespace}
      group: ${sfs.nacos.group}
      username: ${sfs.nacos.username}
      password: ${sfs.nacos.username}
  service:
    vgroupMapping:
      default_tx_group: default
12.4.3 订单服务主业务TOrderServiceImpl
@Service
@Slf4j
public class TOrderServiceImpl implements TOrderService {
    @Resource
    private TOrderDao tOrderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;

    /**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     * @param tOrder
     * @return
     */
    @Override
    public boolean create(TOrder tOrder) {

        //1.创建订单
        log.info("----->开始创建订单");
        tOrder.setStatus(0);
        tOrderDao.insert(tOrder);

        //2.扣减库存
        log.info("----->订单微服务开始调用库存,做扣减Count");
        storageService.decrease(tOrder.getProductId(),tOrder.getCount());
        log.info("----->订单微服务开始调用库存,做扣减end");

        //3.扣减账户
        log.info("----->订单微服务开始调用账户,做扣减Money");
        accountService.decrease(tOrder.getUserId(),tOrder.getMoney());
        log.info("----->订单微服务开始调用账户,做扣减end");

        //4.修改订单状态,0=>1
        log.info("----->修改订单状态开始");
        boolean flag = this.updateStatus(tOrder.getUserId(), 1);
        log.info("----->修改订单状态结束");

        log.info("----->下订单,结束了,O(∩_∩)O哈哈~");

        return flag;
    }

    @Override
    public boolean updateStatus(Long userId,Integer status) {
        QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        TOrder tOrder = new TOrder();
        tOrder.setStatus(status);
        return tOrderDao.update(tOrder, wrapper)>0;
    }
}
12.4.4 库存服务 TStorageServiceImpl
@Service
@Slf4j
public class TStorageServiceImpl implements TStorageService {

    @Resource
    private TStorageDao dao;

    /**
     * 扣减库存
     *
     * @param productId
     * @param count
     * @return
     */
    @Override
    public boolean decrease(Long productId, Integer count) {

        log.info("------>seata-storage-service中扣减库存开始");
        QueryWrapper<TStorage> wrapper = new QueryWrapper<>();
        wrapper.eq("product_id", productId);
        TStorage tStorage = dao.selectOne(wrapper);

        TStorage newTStorage = new TStorage();
        newTStorage.setUsed(tStorage.getUsed() + count);
        newTStorage.setResidue(tStorage.getResidue() - count);
        return dao.update(newTStorage, wrapper) > 0;
    }
}

12.4.5 账户服务 TStorageServiceImpl

@Service
@Slf4j
public class TAccountServiceImpl implements TAccountService {

    @Resource
    private TAccountDao dao;

    @Override
    public Boolean decrease(Long userId, BigDecimal money) {

        log.info("------>开始扣减账户");
        //模拟超时异常,全局事务回滚
        try {
            TimeUnit.SECONDS.sleep(20);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        QueryWrapper<TAccount> param = new QueryWrapper<>();
        param.eq("user_id",userId);
        TAccount tAccount = dao.selectOne(param);
        QueryWrapper<TAccount> wapper = new QueryWrapper<>();
        wapper.eq("user_id",userId);
        TAccount newTAccount = new TAccount();
        newTAccount.setUsed(tAccount.getUsed().add(money));
        newTAccount.setResidue(new BigDecimal(Double.toString(tAccount.getResidue().subtract(money).doubleValue())));
        log.info("------>结束扣减账户");
        return dao.update(newTAccount,wapper)>0;
    }
}
12.4.5 故障情况

当库存和账户扣减后,订单状态并没有改变,二期由于Feign的重试机制,账户余额还有可能重复扣减

12.4.6 使用Seata对数据源进行代理

MyBatis版

@Configuration
public class DataSourceMyBatisConfig {

    @Value("${mybatis-Plus.mapper-locations}")
    private String mapperLocations;

    @Bean
    @Primary//让MyBatis-Plus优先使用我们配置的数据源
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDatasource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

MyBatis-Plus版

/**
 * DataSourceProxyConfig :
 * 使用Seata对数据源进行代理
 *
 * @author zyw
 * @create 2023/7/10
 */
@Configuration
@MapperScan("com.zyw.springcloud.dao")
public class DataSourceMyBatisPlusConfig {

    @Value("${mybatis-Plus.mapper-locations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSource(DataSource druidDataSource){
        return new DataSourceProxy(druidDataSource);
    }


    /**
     * 配置mybatis-plus的分页
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//指定数据库
        return interceptor;
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(mapperLocations));
        // 配置spring的本地事务
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());

        // 配置mybatis-plus的log打印
        MybatisConfiguration cfg = new MybatisConfiguration();
        cfg.setJdbcTypeForNull(JdbcType.NULL);
        cfg.setMapUnderscoreToCamelCase(true);
        cfg.setCacheEnabled(false);
        cfg.setLogImpl(StdOutImpl.class);
        sqlSessionFactoryBean.setConfiguration(cfg);
        return sqlSessionFactoryBean.getObject();
    }
}

12.5 seata原理

12.5.1 业务执行流程
  • TM开启分布式事务(TM向TC注册全局事务记录);
  • 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态);
  • TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务);
  • TC汇总事务信息,决定分布式事务是提交还是回滚;
  • TC通知所有RM提交/回滚资源,事务二阶段结束。
12.5.2 AT模式

前提:

  • 给予支持本地ACID事务的关系型数据库。

  • Java应用,通过JDBC访问数据库。

整体机制:

  • 一阶段:业务数据和回归日志记录在同一个本地事务中提交,释放本地锁和连接资源

  • 二阶段:提交异步化,非常快速的完成;回滚通过一阶段的回滚日志进行反向补偿

一阶段加载

在一阶段,Seata会拦截“业务SQL” ,

1解析SQL语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,(前置镜像)

2执行“业务SQL”更新业务数据,在业务数据更新之后,

3其保存成“after image”,最后生成行锁。

以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性

SpringCloud Alibaba微服务分布式架构组件演变_第21张图片

二阶段提交

因为业务SQL在一阶段已经提交至数据库,所有Seata框架只需将一阶段保存的快照数据和行锁删除,完成数据清理即可。

SpringCloud Alibaba微服务分布式架构组件演变_第22张图片

二阶段回滚

二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的“业务SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
SpringCloud Alibaba微服务分布式架构组件演变_第23张图片

SpringCloud Alibaba微服务分布式架构组件演变_第24张图片

你可能感兴趣的:(Spring全家桶及相关框架,分布式\微服务,spring,cloud,spring,后端)