springCloud-学习笔记

单词

discoveryClient-发现客户端、loadBalance-负载均衡、Rule-规则、current-当前的、fallback-回退(降级)、break-中断(熔断)、flowlimit-限流、circuit-电路、dashboard-仪表盘、refresh-刷新、actuator-执行器

SpringCloud是一系列框架的有序集合,是一揽子框架的解决方案

约定 > 配置 > 编码

微服务架构

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建

分布式架构

或者说是对分布式架构的理解

  • 服务注册与发现
  • 服务调用
  • 服务熔断
  • 负载均衡
  • 服务降级
  • 服务消息队列
  • 配置中心管理
  • 服务网关
  • 服务监控
  • 全链路追踪
  • 自动化构建部署
  • 服务定时任务调度操作

一些架构需要学习掌握的技术

image-20210915215045613

开始搭建项目

  • dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。
  <!--dependencyManagement: 子模块继承之后,提供作用:锁定版本+子module不用写groupId和version  -->
	<dependencyManagement>
        <dependencies>
            <!--spring boot 2.2.2-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            ......

这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改﹔另外如果某个子项目需要另外的一个版本,只需要声明version就可。

跳过maven的test单元测试,项目可以跑的更快

image-20210916202236605

一个微服务模块搭建的基本流程

1、建module

2、改POM

3、写YML

4、主启动

5、业务类

RestTemplate

RestTemplate提供了多种便捷访问远程Http服务的方法,
是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集

1、创建一个父类总工程

pom.xml

<dependencyManagement>: 子模块继承之后,提供作用:锁定版本+子module不用写groupId和version
    </dependencyManagement>

2、编写服务的生产者

1、建module

​ cloud_provider_payment8001

2、改POM


需要的依赖
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-actuator</artifactId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>mysql-connector-java</artifactId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<artifactId>spring-boot-devtools</artifactId>
<artifactId>lombok</artifactId>
<artifactId>spring-boot-starter-test</artifactId>

3、写YML

改端口号8001,mysql jdbc 驱动 数据源 ,mybatis扫描

4、主启动

编写主启动类

5、业务类

​ 5.1、编写Payment.java 的bean

​ 5.2、编写CommontResult.java 的bean 面相前端开放的一个bean,前端只需要调用这个bean的结果即可

​ 5.3、Dao、DaoImpl、mapper.xml

​ 5.4、service、serviceImpl

​ 5.5、controller

​ 消费者调用该写入数据库的业务时,会给生产者传回一个json字符串,造成写入数据库错误,写入一个null值,所以需要在形参中加上==@RequestBody== 注解 将传回来的字符串转换成对象

@Autowired
    private PaymentService paymentService;

    @PostMapping(value="/payment/create")
    public CommonResult create(@RequestBody Payment payment){
        int result = paymentService.create(payment);
        log.info("=====插入结果====="+payment);
        if(result > 0){
            return new CommonResult(200,"插入成功",payment);
        }
        return new CommonResult(444,"插入失败",payment);
    }

3、编写服务的消费者

1、建module

​ cloud_consumer_order80

2、改POM

<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-actuator</artifactId>
<artifactId>spring-boot-devtools</artifactId>
<artifactId>lombok</artifactId>
<artifactId>spring-boot-starter-test</artifactId>

3、写YML

​ 改端口号80 默认不用写就是80端口,所以消费者只用写ip地址即可访问服务

4、主启动

​ 主启动类

5、业务类

​ 5.1、编写Payment.java 的bean

​ 5.2、 编写CommonResult.java 的 bean

​ 5.3 编写config配置类 ApplicationContextConfig.java

// 配置类
@Configuration
public class ApplicationContextConfig {
    // 可以复用的restTemplate方法
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

​ 5.4、编写controller层

​ 使用到了RestTemplate , 写操作使用postForObject() , 读操作使用getForObject()

@RestController
@Slf4j
public class OrderController {
    @Resource
    private RestTemplate restTemplate;
    public static final String PAYMENT_URL = "http://localhost:8001";
    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        // 用于访问Rest服务的客户端模板工具集  ( URL访问地址,传参类型,返回类型 )
        return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
    }

    @GetMapping("/consumer/payment/getPayment/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/getPayment/"+id,CommonResult.class);
    }
}

创建一个抽取公用API的模块

1、pom.xml

新加入了一个hutool工具包
<artifactId>hutool-all</artifactId>

2、将entities实体类包抽取出来

3、使用maven的生命周期的方法

​ 3.1、clean清空

​ 3.2、install安装打包一下

4、在需要使用到该实体类的模块中引入依赖

<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.study</groupId>
            <artifactId>cloud_api_commons</artifactId>
            <version>${project.version}</version>
</dependency>

Eureka

( 已停止更新 )

Eureka包含两个组件 : Eureka ServerEureka Client

Eureka Server提供服务注册服务

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

EurekaClient通过注册中心进行访问

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

服务注册中心

需要先启动服务注册中心,再服务的提供者。

创建服务注册中心模块

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>

服务注册中心的yaml

server:
  port: 7001

eureka:
  instance:
    hostname: localhost # eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己
    register-with-eureka: false
    #false 表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

服务注册中心的主启动类

@SpringBootApplication
@EnableEurekaServer // 表示自己是Eureka服务注册中心
public class EurekaMain7001 {...}

服务提供者

依赖

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

服务提供者的yaml

eureka:
  client:
    #表示是否将自己注册进EurekaServer 默认为true
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      # 向哪个注册中心注册服务
      defaultZone: http://localhost:7001/eureka

服务提供者的主启动类

@SpringBootApplication
@EnableEurekaClient // 服务提供者,让注册中心发现、并扫描到该服务
public class PaymentMainAPP {...}

集群版Eureka

1、新建一个服务的注册中心 eureka7002

2、在windows的hosts文件中加上本机的ip地址,服务注册中心的服务名( 相当于域名 )

127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

3、修改服务注册中心的yaml文件

​ 3.1、修改eureka服务名 eureka7001.com 在浏览器地址栏中输入,可以映射到127.0.0.1

127.0.01 localhost 192.168.177.1(127.0.0.1 = 本机ip地址 ,localhost = 本机的域名,192.168.177.1 = 本机网卡对外的ip))

​ 3.2、修改 defaultZone :ip地址:port/eureka/

​ ,之前是单机版本,只需要把自己的地址注册上即可,现在是集群,所以要写上其他服务注册中心的地址

​ 多台服务注册中心需要 相互注册互相关联相互守望 , 如果有多台可以用 ==,==逗号隔开

server:
  port: 7001
eureka:
  instance:
    hostname: eureka7001.com # eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己
    register-with-eureka: false
    #false 表示自己段就是注册中心,我的职责就是我维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
#      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      #使用集群的方式
      defaultZone: http://eureka7002.com:7002/eureka/

Client向集群注册中心注册服务

修改yaml文件

eureka:
  client:
    #表示是否将自己注册进EurekaServer 默认为true
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      # 向哪个注册中心注册服务
#      defaultZone: http://localhost:7001/eureka
      #向集群注册中心注册服务
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

Client端集群(提供者)

不仅要做服务注册中心的集群,还要做服务提供者的集群,这样可用性更高

1、再创建一个服务提供者的模块 cloud_provider_payment8002

​ 1.1、改 POM

​ 1.2、写YAML

​ yaml的端口号改成8002,(多个服务提供者的服务名需要保持一致,让消费者可以通过一个服务名找到多个提供者)

​ 1.3 、主启动类

​ 1.4、业务类

​ 可选: 可以在controller层加上 以便以端口号识别的参数

@Value("${server.port}") // 从配置文件中读取端口号
private String serverPort ;
....
    return new CommonResult(200,"查询成功 + " + serverPort,paymentById);

2、在消费者 (80端口)修改controller层的URL访问地址

//    public static final String PAYMENT_URL = "http://localhost:8001";
// URL访问地址从服务提供者的ip改成服务提供者集群对外暴露的服务名(需要在RestTemplate的配置上加上@LoadBalanced 在可以生效)
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT";

​ 2.1、给RestTemplate加上注解@LoadBalance 赋予它负载均衡的能力 (默认轮询的方式来负载均衡)

// 配置类
@Configuration
public class ApplicationContextConfig {
    // 可以复用的restTemplate方法
    @Bean
    @LoadBalanced // 赋予RestTemplate 负载均衡的能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();}}

完善微服务的配置信息(可选)

前台监控页面查看时可以显示更详细的信息
instance:
  instance-id: payment8001 #显示服务的主机名
  prefer-ip-address: true  #访问路径可以显示服务的ip地址

服务发现Discover

使用参数DisCoverClient

@Resource//DiscoverClient服务发现
private DiscoveryClient discoveryClient;

写方法获取实例信息

@GetMapping(value = "/payment/discover")
public Object discover(){
    // Eureka注册的微服务有哪些,服务列表清单
    List<String> services = discoveryClient.getServices();
    for (String element:services) {
        log.info("services--->"+element);
    }
    ------------------结果-----------------------------
        services--->cloud-order-com.study.cloud.service
		services--->cloud-payment-service
    ------------------------------------------------
    //根据微服务的名称,获取微服务具体的信息
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    for (ServiceInstance instance : instances) {
        // 可以使用根据服务名实例获取到的内容来get该服务名的详细的信息
        log.info("instance--->"+instance.getInstanceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
    }
    return this.discoveryClient;
}
------------------结果---------------------------
  	instance--->payment8002	192.168.1.6	8002	http://192.168.1.6:8002
	instance--->payment8001	192.168.1.6	8001	http://192.168.1.6:8001

主启动类上加上@EnableDiscoveryClient注解

Euraka的自我保护

某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存

自我保护机制∶

​ 默认情况下EurekaClient定时向EurekaServer端发送心跳包
​ 如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除 该服务,但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eurekaserver会开启自我保护机制,不会剔 除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时 间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题, 而保护机制是为了解决此问题而产生的)

关闭自我保护机制

enable-self-preservation:false

Euraka客户端向服务端发送信条的时间间隔,单位为秒(默认30)秒

lease-renewal-interval-in-seconds:1

Zookeeper

在linux中启动zookeeper和它的客户端

引入zookeeper的依赖

<!-- spring整合zookeeper客户端       -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>

不过这样直接引用可能会造成jar包和本地安装的zookeeper冲突,因为版本不一致

所以要排除掉spring给的ZK版本,来自己手动指定ZK的版本

<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <!--先排除自带的zookeeper3.5.3-->
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--添加zookeeper3.6.3版本-->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.3</version>
</dependency>

可以再ZK客户端用 ls /查看所有的服务

spring启动完成之后,把服务注册进zookeeper

ZK客户端多出一个services服务

使用ls /services 可以查看该服务,本次显示的是8004的服务名

[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-payment-service]
[zk: localhost:2181(CONNECTED) 2] 

获取该服务具体信息,get 获取到的服务名

get cloud-payment-service

zookeeper的节点是临时节点,服务关闭,一段时间后,zookeeper没有收到服务的心跳,就会把这个服务的节点踢掉,当服务再次上线后,重新获取节点,但是这次的节点是一个全新的节点,不是之前的老节点

zookeeper消费端

@RestController
public class PaymentZKController {
	// 调用提供者URL地址(地址为提供者注册到zookeeper的服务名)
    private static final String INVOKE_URL="http://cloud-payment-service";
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/payment/zk")
    public String payment(){
        String forObject = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
        return forObject;
    }
}

Consul

  • 能干嘛?
    • 服务发现
      • 提供HTTP和DNS两种发现方式
    • 健康监测
      • 支持多种方式,HTTP、TCP、Docker、Shell脚本定制化
    • KV存储
      • key、value存储
    • 多数据中心
      • Consul支持多数据中心
    • 可视化Web界面

代码基本与Euraka,zookeeper注册中心一致,就是需要添加一个Consul的依赖,YAML中添加Consul的地址 默认端口:8500

CAP的意思

C : Consistency (强一致性)

A : Avaliability (可用性)

P : Partition tolerance (分区容错性)


Euraka ----AP

Zookeeper ---- CP

Consul ---- CP

Ribbon

Ribbon是什么?

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

​ 简单的说,Ribbon是Netflx发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。


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

  • LB(负载均衡)
    • 集中式LB ( Nginx服务端的负载均衡就是集中式的LB )
    • 进程内LB ( Ribbon本地的负载均衡就是进程内的LB )

​ 简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件Nginx,

LVS,硬件F5等。

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

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

Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到VM本地,从而在

本地实现RPC远程服务调用技术。

简单来说Ribbon就是 RestTemplate + 负载均衡的调用

这个Eureka 依赖 自带 Ribbon

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Ribbon负载均衡和Rest调用

@GetMapping("/consumer/payment/getForEntities/{id}")
public CommonResult<Payment> getPayment1(@PathVariable("id") long id){

    ResponseEntity<CommonResult> forEntity = restTemplate.getForEntity(PAYMENT_URL + "/payment/getPayment/" + id, CommonResult.class);
    if(forEntity.getStatusCode().is2xxSuccessful()){
        return forEntity.getBody();
    }
    return new CommonResult(444,"请求失败");
}

返回对象为ResponseEntity对象,包含了响应中的一些 重要信息,比如响应头、响应状态码、响应体等

image-20210923193907278

Ribbon核心组件IRule

根据特定算法中从服务列表中选取一个要访问的服务,一共有7中负载均衡方式,(默认轮询)

image-20210923195109691

如何替换IRule?

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

​ 这个自定义配置类不能放在@ComponentScan所扫描的包下以及子包下 (主程序的@SpringbootApplication注解就包含了@ComponentScan注解)

​ 否则我们自定的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

自定义配置类

在主程序的包外新建一个包myrule ,再创建一个自定义的配置类MySelfRule

@Configuration
public class MySelfRule {
    // 随机轮询的方式
    @Bean
    public IRule MyRule(){
        return new RandomRule();
    }
}

加载自定义配置类,让配置生效

@SpringBootApplication
@EnableEurekaClient
// RibbonClient 组件加载自定义的Ribbon配置类 (使用哪个服务,使用自定的轮询方式)
@RibbonClient(name= "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }

}

负载均衡算法

​ rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。

image-20210923202034722

Feign

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

简单来讲Feign就是使用方法上定义一个服务接口然后在上面添加注解(@FeignClient),Feign是在消费端使用的

Feign自带负载均衡,自带Ribbon的依赖

消费者80

主程序类中开启Feign功能

@SpringBootApplication
@EnableFeignClients // 开启Feign功能
public class FeignMain80 {

将需要调用的服务提取出来

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")//调用的服务名
public interface PaymentService { // 写成一个接口,让消费者的controller去调用这个接口
    //调用服务下的哪个方法(从服务提供者的controller层调用)
    @GetMapping(value = "/payment/getPayment/{id}")
    CommonResult getPaymentById(@PathVariable("id") long id);
}

80消费端controller调用service , service去调用服务端controller

@RestController
public class FeignController {
    @Resource
    private PaymentService paymentService;
    @GetMapping(value = "/consumer/payment/getPayment/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") long id){
        return paymentService.getPaymentById(id);
    }
}

OpenFeign超时控制

openFeign 调用服务默认等待1秒钟,超出时间后报错

测试超出时间报错

服务提供者设置睡眠时间

@GetMapping("/payment/timeout")
public String feignTimeOut(){ //Feign超时时间,让调用该服务的时间超出默认的1秒钟
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return serverPort;
}

调用服务

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")//调用的服务名
.....
@GetMapping("/payment/timeout")
public String feignTimeOut();
@GetMapping("/consumer/payment/timeout")
    public String feignTimeOut(){
        return paymentService.feignTimeOut();
    }

Ribbon超时时间

不希望超出时间报错,想多等一会

可以再yaml中设置ribbon的连接时间(Feign中自带Ribbon)

   # 请求连接的超时时间
ribbon: 
	ConnectTimeout: 5000
# 请求处理的超时时间
	ReadTimeout: 5000

OpenFeign日志增强

日志级别:

​ NONE:默认的,不显示任何日志;

​ BASIC:仅记录请求方法、URL、响应状态码及执行时间;

​ HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;

​ FULL:除了HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

自定义一个配置类,定义打印的日志级别

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

日志打印的类型级别

logging:
  level:
    #feign日志以什么级别监控哪个接口
    com.study.cloud.service.PaymentService: debug
[PaymentService#getPaymentById] <--- HTTP/1.1 200 (57ms)
[PaymentService#getPaymentById] connection: keep-alive
[PaymentService#getPaymentById] content-type: application/json
[PaymentService#getPaymentById] date: Fri, 24 Sep 2021 14:53:34 GMT
[PaymentService#getPaymentById] keep-alive: timeout=60
[PaymentService#getPaymentById] transfer-encoding: chunked
[PaymentService#getPaymentById] 
[PaymentService#getPaymentById] {"code":200,"massage":"查询成功 + 8001","data":{"id":1,"serial":"abcdefg"}}
[PaymentService#getPaymentById] <--- END HTTP (79-byte body)
Flipping property: CLOUD-PAYMENT-SERVICE.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

Hystrix

以停止更新、维护

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等, Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控〔类似熔断保险丝),向调用方返回一个符合顶期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会波长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

能干嘛?

  • 服务降级
  • 服务熔断
  • 接近实时的监控

fallback-降级、break-熔断、flowlimit-限流

d

服务熔断和限流是保证或提高系统服务可用的一种手段,而服务降级是体现熔断和限流的方法(个人理解)

  • 服务降级( fallback )
    • 服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
    • 哪些情况会触发降级?
      • 程序运行异常
      • 超时
      • 服务熔断触发服务降级
      • 线程池/信号量打满也会导致服务降级
  • 服务熔断 ( break )
    • 类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示( 就像保险丝 )
    • 服务的降级–>进而熔断–>恢复调用链路
  • 服务限流 ( flowlimit )
    • 秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

故障现象和导致原因

  • 8001同一层次的其他接口服务被困死,因为tomc1at线程池里面的工作线程已经被挤占完毕-

  • 80此时调用8001,客户端访问响应缓慢,转圈圈

出现问题如何解决?

  • 超时导致服务器变慢(转圈)————超时不在等待
  • 出错(宕机或程序运行出错)————出错要有兜底

解决

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须要有服务降级
  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

容错方法、兜底方法(fallback)

 // 如果该方法出现错误,(调用兜底方法(Dubbo的容错机制), commandProperties设置超时时间,如果超时就启动兜底方法)
    @HystrixCommand(fallbackMethod = "paymentInfoHandler",commandProperties = { 
    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000")})
    public String paymentInfoTimeout(Integer id){
        try {
            int i = 10/0 ; // 这行代码会立即触发兜底方法,不会等待超时
            int timeout = 3;
            TimeUnit.SECONDS.sleep(timeout);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Thread.currentThread().getName()+"paymentInfoTimeout-id="+id;
    }

    //兜底方法
    public String paymentInfoHandler(Integer id){
        return "程序出错请重试"+id;
    }

主程序类上加注解

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class HystrixMain8001 {
@EnableHystrix 自带 @EnableCircuitBreaker

80controller调用feignHystrixService,feignHystrixService调用8001controller,测试超时时间

@HystrixCommand(fallbackMethod = "timeoutFallback" ,commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000")})
@GetMapping("/consumer/payment/timeout/{id}")
public String paymentInfoTimeout(@PathVariable("id") Integer id){
    return feignHystrixService.paymentInfoTimeout(id);
}
public String timeoutFallback(@PathVariable("id") Integer id){
    return "程序异常,请重试"+id;
}

80 yaml

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

#feign:
#  hystrix:
#    # true 开启feign对hystrix的支持
#    enabled: true   
# 暂时注释掉,不知什么原因开启支持会报错,注释掉可以正常启动
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

ribbon:
  #  指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 5000
  #  指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ConnectTimeout: 5000
controller中超时时间配置不生效原因:
  关键在于feign:hystrix:enabled: true的作用,官网解释“Feign将使用断路器包装所有方法”,也就是将@FeignClient标记的那个service接口下所有的方法进行了hystrix包装(类似于在这些方法上加了一个@HystrixCommand),这些方法会应用一个默认的超时时间为1s,所以你的service方法也有一个1s的超时时间,service1s就会报异常,controller立马进入备用方法,controller上那个3秒那超时时间就没有效果了。
改变这个默认超时时间方法:
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
然后ribbon的超时时间也需加上
ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000


全局fallback方法@DufaultProperties(defaultFallback="")

@DefaultProperties(defaultFallback = “”)
一对一每个方法配置一个服务降级方法,技术上可以,但实际上是不明智的选择,代码很膨胀
一对N除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = ")统一跳转到统一处理结果页面
通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量,

类上加上注解@DefaultProperties()设置统一降级的方法

@RestController
@DefaultProperties(defaultFallback = "globalFallback")
public class FeignHystrixOderController {

方法上加上@HystrixCommand注解 不指定特定的兜底方法

//    @HystrixCommand(fallbackMethod = "timeoutFallback" ,commandProperties = {
//            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000")})
    @HystrixCommand // 不指定特定的兜底方法
    @GetMapping("/consumer/payment/timeout/{id}")
    public String paymentInfoTimeout(@PathVariable("id") Integer id){
        int i = 10/0;// 制造错误
        return feignHystrixService.paymentInfoTimeout(id);
    }

------------统一 --------------
 public String globalFallback(){
        return "全局降级方法";
    }

统配服务降级FeignFallback

service 去调用LOUD-PROVIDER-HYSTRIX-PAYMENT这个微服务下的方法,如果出现错误 (超时、运行时异常、宕机…)

就会启动兜底方法,现给这个service接口下的所有方法统配一个兜底方法的实现类

@FeignClient()注解中添加内容 fallback=xxx.class

// 调用哪个服务,如果出现错误,采用哪个兜底方法,在消费端自处理
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = FeignHystrixServiceImpl.class)
public interface FeignHystrixService {

FeignHystrixFallback.class

@Component
public class FeignHystrixFallback implements FeignHystrixService {
    @Override
    public String paymentInfoOk(Integer id) {
        return "消费端自处理异常";
    }
    @Override
    public String paymentInfoTimeout(Integer id) {
        return "消费端自处理异常";
    } }

现在去调用正常运行的ok方法,方法上没有加@HystrixCommand,运行过程中,关闭8001提供者,造成宕机,消费端会自动启用自己的兜底方法

@GetMapping("/consumer/payment/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id){
    return feignHystrixService.paymentInfoOk(id);
}

熔断机制

熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,
当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。

1、调用失败会触发降级而降级会调用fal lback方法。

2、但无论如何降级的流程一定会先调用正常方法再调用fallback方法

3、假如单位时间内调用失败次数过多,也就是降级次数过多,则触发熔断

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

4、熔断以后就会跳过正常方法直接调用fallback方法

5、所谓“熔断后服务不可用”就是因为跳过了正常方法直接执行fallback

8001提供者服务熔断

如果出现错误,降级次数过多,就会触发熔断,熔断后会直接跳过正常方法,直接执行fallback,一段时间后才会再次执行正常方法

以下注解参数的含义,开启断路器,在10秒内 10次请求,错误率达到60% 就会触发熔断

//服务熔断
@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"),// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id")Integer id){
    if(id < 0){
        throw new RuntimeException("id 不能小于0");
    }
    String serialNumber = IdUtil.simpleUUID();
    return "调用 "+Thread.currentThread().getName()+"成功"+"流水号为:"+serialNumber;
}
// 服务熔断fallback方法
public String paymentCircuitBreaker_fallback(@PathVariable("id")Integer id)
{
    return "id:"+id+" 不能小于0";
}

熔断三种类型

  • 熔断打开

    • 请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
  • 熔断关闭

    • 熔断关闭不会对服务进行熔断
  • 熔断半开

    • 部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢某正常,关闭熔断

断路器什么时候开始起作用?

image-20210926203503508

断路器打开或者关闭的条件

当满足一定的阀值的时候(默认10秒内超过20个请求次数)

当失败率达到一定的时候(默认10秒内超过50%的请求失败)到达以上阀值,断路器将会开启

当开启的时候,所有请求都不会进行转发

一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5

DashBoard

服务监控

新的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

新的注解

@EnableHystrixDashboard
public class DashBoardMain9001 {...}

搭建完成后

http://localhost:9001/hystrix` 微服务监控页面


9001 监控 8001

被监控的服务一定要有actuator依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

被监控的服务主程序类上还要加上

/**
 *此配置是为了服务监控而配置,与服务容错本身无关,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;
}

服务监控地址

地址后面的 /hystrix.stream 是固定的

image-20210926225513465

网关

主流网关 : zuul zuul2 Gatway

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

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

能干嘛?

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控

微购物架构的网关在哪里?

image-20210926232513026

GatWay 三大核心概念

  • Route ( 路由 )
    • 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • Predicate ( 断言 )
    • 参考的是Java8的java.util.function.Predicate
      开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter ( 过滤 )
    • 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

断言是判断具体哪个路由过滤器是找到路由然后做一些前置操作

新建项目

新的依赖

<!--    getway 不需要添加web 和  actuator 依赖 添加会报错   -->
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

配置路由的方式一

gatway 的 yaml

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
          # 断言 8001外面套了一个9527 通过9527可以访问到8001 判断8001下是否有以下的路径,如果为true访问成功,false失败
            # 后面的 ** 通配符,匹配地址栏中输入的{id}
            - Path=/payment/getPayment/**         # 断言,路径相匹配的进行路由 
# 可以添加多个路由

#        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001          #匹配后提供服务的路由地址
#          predicates:
#            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由

eureka:
  client: #服务提供者provider注册进eureka服务列表内
    register-with-eureka: true
    fetch-registry: true
    defaultZone: http://eureka7001.com:7001/euraka

这样配置完成之后,相当于在配置的8001 的服务前面挡了一层,来做路由转发,执行过滤链等

访问的话既可以用8001端口访问,也可以用9527访问

配置路由的方式二

硬编码的方式

@Configuration
public class RouteConfig {
    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        // ( id名字随意,路由(http://localhost:9527/guonei),映射的地址 )
        routes.route("routeLocator",r->r.path("guonei").uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

动态路由

默认情况下Gateway会根据注册中心注册的服务列表,
以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。

routes:
  - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
    #uri: http://localhost:8001          #匹配后提供服务的路由地址
    uri: lb://cloud-payment-service     #使用负载均衡的方式,匹配提供服务的路由地址
    predicates:
      # 断言 8001外面套了一个9527 通过9527可以访问到8001 判断8001下是否有以下的路径,如果为true访问成功,false失败
      # 后面的 ** 通配符,匹配地址栏中输入的{id}
      - Path=/payment/getPayment/**         # 断言,路径相匹配的进行路由

常用的 route Predicate

1、After Route Predicate

​ 地址匹配成功后,还需要时间匹配,到了设定的时间之后,才可以正常访问路径(类似商城秒杀开始时间)

ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区 获取当前时区时间

...
 	  - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
      # uri: http://localhost:8001          #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service #匹配后提供服务的路由地址
      predicates:
   	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
         - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]     # 断言,路径相匹配的进行路由

2、Before Route Predicate

​ 设定时间之前才可以访问路径

3、Between Route Predic

​ 设定时间之间才可以访问路径

4、Cookie Route Predicate

​ 带cookie才可以访问,不带cookie才可以访问,带什么cookie才可以访问,

...
  - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
  # uri: http://localhost:8001          #匹配后提供服务的路由地址
  uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  predicates:
- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
	- Cookie= username,hhqq   # kv键值对 路径访问时,需要携带这个kv的cookie才可以正常访问

​ 可以用cmd的 curl进行测试

C:\Users\guoqi>curl http://localhost:9527/payment/port --cookie "username=hhqq"
8001
C:\Users\guoqi>curl http://localhost:9527/payment/port --cookie "username=hhqq"
8002

5、Header Route Predicate

6、Host Route Predicate
7、Method Route Predicate

8、Path Route Predicate
9、Query Route Predicate

自定义全局GlobalFilter

能干嘛?

  • 全局日志记录
  • 统一网关鉴权
@Component
@Slf4j
// 自定义全局过滤
public class MyLogGetWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");// 请求路径中需要包含uname参数
        if(uname == null){
            log.info("用户错误,不可接受");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Config配置

总控中心搭建

首先在git上创建一个远程库 ,并拉取到本地,再提交三个配置文件上去,以模拟不同的生产环境,可以做到任意切换配置文件

image-20210929224530438

配置文件中,正常的yaml格式的键值对即可,以dev配置文件为例

config:
    info: dev branch,springloud-config/config-dev/yml  version=1

创建配置中心模块 cloud-config-center

新的pom依赖模块后缀名带有server 说明这是config的服务端,客户端不带server

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

修改yaml文件

server:
  port: 3344

spring:
  application:
    name:  cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/gitgqH/cloud-study.git #Gitee上面的git仓库名字
          ####搜索目录
          search-paths:
            -  cloud-study
      ####读取分支
      label: master
#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

主程序类加上新的注解@EnableConfigServer

配置读取规则

  • /{label}/{application}-{profile}.yml

    • /分支/配置-文件名.yaml ( 配置文件名字中心需要加 - 减号 )
      • http://localhost:3344/master/config-test.yaml
  • /{application}-{profile}.yml

    • 路径取消掉分支 默认是master
  • /{application}/{profile}[/{label}]

    • 。。。。

Config 配置中心客户端的配置

新建模块cloud-config-center-3355 yaml不再是applicaton.yaml了,而是bootstrap.yaml

因为bootstrap.yml (系统级别)是比application.yml (用户级别)先加载的。bootstrap.yml优先级高于application.yml

config 客户端配置的内容会自己进行拼接 减号 和 .yaml 会自动拼接起来

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址k

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

image-20210929233235967

编写业务类

编写业务类,去拿到配置中心的配置文件的config.info 的内容

@RestController
public class ConfigController {

    @Value("${config.info}")
    private String configInfo;
    @GetMapping(value = "/configInfo")
    public String getInfo(){
        return configInfo;
    }
}

正确拿到之后,会出现一个问题

如果运维工程师在git中修改了配置中心的配置文件

3344 可以刷新一下获取最新的配置文件,但是3355却不行(因为3344 是直连git配置中心的,3355 是拿到的java硬编码,死的),只有重启服务才可以拿到,这样会很浪费时间

所以需要动态刷新配置文件

手动版动态刷新配置文件

修改添加yaml,暴露监控模块

# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

引入pom.xml

如果发生改变会被监控到

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

在业务类上添加新的注解@RefreshScope 来动态获取并刷新配置

@RestController
@RefreshScope
public class ConfigController {

这样配置完成之后,发现3355还是获取不到最新的配置文件

因为需要运维人员发送Post请求刷新3355才可以

可以使用curl命令 post 请求 ,也可以使用postman…

curl -X POST “http://localhost:3355/actuator/refresh”

SpringBus消息总线

是什么?

Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。

Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,
它整合了Java的事件处理机制和消息中间件的功能。

Spring Clud Bus目前支持RabbitMQ和Kafka。

能干嘛?

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。

配置

1、安装windows版本的erlang、rabbitmq

​ 进入rabbitmq的sbin目录 cmd 输入命令

rabbitmq-plugins enable rabbitmq_management安装可视化插件( 前台监控页面 )

​ 检查是否成功http://localhost:15672/

2、创建cloud-center-3366 模块,此模块也是像3355一样 客户端的配置

​ 创建过程与3355一致

3、需要给三个配置中心的新添加一个依赖

<!--添加消息总线RabbitMQ支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
		

​ 添加yaml的rabbitmq的相关配置

#rabbitmq相关配置
rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

4、3344配置暴露bus刷新的端点

##rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints: #暴露bus刷新配置的端点
    web:
      exposure:
        include: 'bus-refresh'

这样只用请求更新服务端配置中心3344配置中心即可,一次发送,处处更新

curl -X POST http://localhost:3344/actuator/bus-refesh

springBud动态刷新定点通知

只想通知消息总线中的一部分服务配置刷新

请求刷新连接后面加上 需要刷新的服务名加端口号

公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}

curl -X POST http://localhost:3344/actuator/bus-refesh/config-client:3366

springStream 消息驱动

是什么?

​ 屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型

stream凭什么可以统一底层差异?

​ 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

​ bander有input、output

​ 其中 INPUT 对应于消费者

​ OUTPUT对应于生产者

Spring Cloud Stream标准流程套路

image-20211002190934414image-20211002190747057
  • Binder
    • 很方便的连接中间件,屏蔽差异
  • Channel
    • 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
  • Source和Sink
    • 简单的可理解为参照对象是Spring Cloud Stream自身,
      从Stream发布消息就是输出,接受消息就是输入。

生产者 8801

新建一个cloud-stream-rabbitmq-provider8801 模块

yaml

server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        output: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

service

public interface IMessageProvider {
     String sent();
}

impl

这里的impl调用的不再是dao了,而是创建一个消息发送的管道

@EnableBinding(Source.class) MessageChannel

@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class MessageProviderImpl implements IMessageProvider {

    @Resource
    private MessageChannel output; // 消息的发送管道

    @Override
    public String sent() {
        String msg = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(msg).build()); // 创建并发送消息
        System.out.println("***消息发送***"+msg);
        return msg;//返回什么无所谓
    }
}

最后controller调用

消费者8802

消费者的yaml 中 发生改变的地方

output变成input 这个是重点,区分生产者和消费者的地方

instance-id: provider-8801.com变成instance-id: receive-8802.com 这个无所谓只是显示的名称问题

bindings: # 服务的整合处理
  input: # 这个名字是一个通道的名称
    destination: studyExchange # 表示要使用的Exchange名称定义
    content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
    binder: defaultRabbit # 设置要绑定的消息服务的具体设置
    
    .....
    instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8802.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

业务类

@EnableBinding(Sink.class) @StreamListener(Sink.INPUT)

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageController {

    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT) //监听
    public void receive(Message<String>message){// 发送用的String类型,接收就用String
        System.out.println("接收到的消息--->" + message.getPayload()+",端口号--->"+serverPort);
    } }

重复消费问题

发生原因

注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
不同组是可以全面消费的(重复消费),
同一组内会发生竞争关系,只有其中一个可以消费。

所以把不同的消费者都改为同一个组,让它们造成竞争关系,即可避免重复消费

可以理解为发送给同一个queue,会用轮询的方式进行消费


在绑定关系的配置下,给不同的消费者添加相同的组名即可

bindings: # 服务的整合处理
  input: # 这个名字是一个通道的名称
    destination: studyExchange # 表示要使用的Exchange名称定义
    content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
    binder: defaultRabbit # 设置要绑定的消息服务的具体设置
    group: gq1

持久化的问题

基本mq的理论

8802 8803 两个消费者都关闭服务,并且8802把group组名去掉了。

8801提供者发送消息。

重新启动8802 8803

这是只有8803可以消费到关闭服务时 ,提供者8801 发送的消息,8802是接收不到的。

原因

个人理解是,由于8802 没有绑定group组名,所以它默认归属的是随机的组名,

假设 :服务关闭时,提供者分别给 axx1 (对应着8802)和 gq1(对应着8803) 两个组发送消息 , 由于gq1绑定的是固定的组,所以再次启动也可以接收到消息。

8802是随机的组名 再次启动服务,就会被分配到另一个组名上,发送给axx1的消息自然接收不到

Sleuth

Sleuth分布式请求链路跟踪

完整的调用链路

image-20211002232056755

简易版

image-20211002232117430

Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识

span:表示调用链路来源,通俗的理解span就是一次请求信息

链路,简单来讲就是可以查看服务调用和被调用之间的关系,和相关依赖等等

安装zipkin 的jar包 (和sleuth是一个东西,你中有我我中有你,sleuth收购了zipkin)

cmd运行 java -jar zipkin-server-2.19.2-exec.jar

打开前台监控页面,默认端口号9411

提供者和消费者都要加上zipkin的依赖

<!--包含了sleuth+zipkin-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

新增yml

spring:
  application:
    name: cloud-payment-service 
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      #采样率值介于 0 到 1 之间,1 则表示全部采集
     probability: 1

写一个简单的业务类,让消费者调用

测试使用的是 80调用 8001 测试时去掉了80@loadbalance负载均衡的注解,因为只启动了8001一个提供者,开启负载均衡会报错

此时就可以在 zipkin 9411 前台查看到调用和依赖的关系

image-20211002234205416

springCloud Alibaba

nacos服务注册

在nacos的bin目录下运行 startup.cmd 命令即可启动nacos

http://localhost:8848/nacos 访问nacos页面 ,服务注册中心的管理页面

默认账户、密码 nacos

springcloud父类引入依赖alibaba-dependencies

<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.1.0.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

本类引入依赖alibaba-nacos-discovery

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

yaml 配置nacos

server:
  port: 9001

spring:
  application:
    name: nacos-provider-service

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # 配置nacos地址

# 监控端点
management:
  endpoints:
    web:
      exposure:
        include: '*'

nacos整合了ribbon,自带负载均衡

nacos配置中心

新建项目cloud-nacos-cconfig-center3377

导入新的依赖

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

新建两个配置文件 bootstrap.yaml application.yaml ,预先加载bootstap.yaml

bootstap.yaml

配置文件读取规则:

​ prefix 默认为 spring.application.name 的值

spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置。

file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置

nacosConfig

server:
  port: 3377
spring:
  application:
    name: nacos-config-center
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848
        file-extension: yaml #指定yaml格式的配置

# 配置文件读取规则${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

application.yaml

spring:
  profiles:
    active: dev

在nacos页面新建配置文件

image-20211004160505511

最后业务类加上注解@RefreshScope,在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。

配置文件的三种加载方案

1、GroupID 的方式

​ 通过配置文件的名字来区分

​ 默认空间+默认分组+新建dev和test两个DataID

application.yaml配置

spring:
  profiles:
#    active: info
    active: dev

2、Group的方式

通过group来实现环境的区分

image-20211004170418289

image-20211004170854528-group

bootstrap.yaml中加上group的配置信息

config:
  server-addr: localhost:8848
  file-extension: yaml #指定yaml格式的配置
  group: TEST_GROUP

3、namespace的方式

新建命名空间

image-20211004170627304

再根据命名空间分类,来获取不同组的配置文件

image-20211004170710049

bootstrap.yaml加上namespace的配置信息

config:
  server-addr: localhost:8848
  file-extension: yaml #指定yaml格式的配置
  group: TEST_GROUP
  namespace: 061a51bd-e7ed-41fb-9877-a479a931fbd4

配置文件的名字后缀都统一在spring.profiles.active中配置


简单来讲三种方案就是分组再分组

持久化

nacos默认自带一个嵌入式数据库derby

集群中的每一个服务都会把数据存到这里,会造成数据不一致的问题

所以需要一个统一的数据源,nacos官方建议使用mysql


将nacos/conf文件夹给的nacos-config.sql文件夹来建表

nacos/conf 文件夹中的application.properties新增内容

#使用的数据源
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=3333

此时nacos的数据源已经切换到了mysql

linux系统nacos的使用

解压nacos安装包

在bin目录下使用命令启动

使用mysql数据源

在linux下安装mysql( mysql笔记下有记录 )

与windows一样

使用nacos给的sql脚本,建库建表

修改并备份一份 bin/application.properties

修改添加使用的数据源信息,与windows一致

集群

进入nacos的conf目录

再拷贝一份cluster.conf.example 并重命名为cluster.conf

在里面添加集群主机的ip地址和端口号

192.168.177.130:3333
192.168.177.130:4444
192.168.177.130:5555

编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口

image-20211007230723426

image-20211007230732404

nginx的集群配置

 upstream cluster{
        server  192.168.177.130:3333;
        server  192.168.177.130:4444;
        server  192.168.177.130:5555;
    }
    server {
        listen       1111;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location / {
           # root   html;
           # index  index.html index.htm;
           proxy_pass http://cluster;
        }

启动nacos集群

使用bin目录的命令 ./startip.sh -p 3333 后面跟上配置好的nacos集群端口号 3333、4444、5555

可以使用ps -ef | grep nacos| grep -v grep | wc -l 查看成功的启动了几台nacos

启动nginx

使用命令./nginx -c /usr/local/nginx/conf/nginx.conf -c 指定配置文件

测试

访问http://192.168.177.130:1111/nacos 因为修改了nginx的配置文件 , 监听的是1111端口

进入到nacos的页面, 添加配置

查看linux的MySQL 的nacos_info 表是否有新增的记录。

最后测试将微服务 payment9002 注册进Linux的nacos

Slibaba Sentinel

类似hystirx 可以做服务监控 服务降级 服务限流…

下载安装

访问GitHub的sintinel页面下载sentinel的jar包

使用java命令运行java -jar sentinel.xxxx

前台默认端口8080 用户名&密码 都是sentinel

使用

创建cloudalibaba-sentinel-service8401 微服务

添加新的依赖

 <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

yaml配置

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

写一个简单的业务类。

启动微服务,访问sentinel的控制台,发现并没有监控到服务

因为sentinel使用的懒加载,需要客户端和服务端相互连通( 服务端可以访问到客户端,客户端也要执行一次访问(controller业务类的访问即可)连接一下服务端 )

Sentinel流控

  • 资源名:唯一名称,默认请求路径
  • 针对来源: Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阈值:
    • QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
    • 线程数:当调用该api的线程数达到阈值的时候,进行限流
  • 是否集群:不需要集群
  • 流控模式:
    • 直接:api达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
  • 流控效果:
    • 快速失败:直接失败,抛异常
    • Warm Up:根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

QPS(每秒钟的请求数量)∶当调用该api的QPS达到阈值的时候,进行限流

设置QPS 单机阈值为 1 : 每秒请求/testa 超过1次 ,则进行限流,会在前台展示限流信息

image-20211009151115450

线程数限流:每秒超出设定的线程数量访问,则进行限流

关联流控模式

image-20211009154832064

/testa 关联资源 /testb 如果B 的每秒请求数量超出阈值,则会限流A

Warm UP

image-20211009155916783

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

案例,阀值为10+预热时长设置5秒。
系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10

( 10/3 =3 一开始的每秒访问量为3,过5秒后,恢复10的访问量 )

排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。

设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。

image-20211009161357481

熔断降级

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

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

Sentinel的断路器是没有半开状态的

​ 半开的状态系统自动去检测是否请求有异常,
​ 没有异常就关闭断路器恢复使用,
​ 有异常则继续打开断路器不可用。具体可以参考Hystrix

RT

( 平均响应时间,秒级 )

image-20211009220327397

请求 总体响应的平均时间 > 设定的阈值 则会触发降级(默认上限4900ms),才会触发降级

image-20211009215945066

测试

RT 每秒的请求数量 5 个

设定200ms 每个请求需要在200ms内完成。

也就是 5 个请求 总体响应的平均时间 > 设定的阈值 则会触发降级

时间窗口:降级时间间隔,设定10秒的时间窗口,每过10秒就会进行监控是否满足降级条件

image-20211009215655599

image-20211009215709923

按照上述配置,

永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,
如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了

后续停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK

异常比例

image-20211009223610667

同时满足两点,触发降级

异常数

出现的异常超过设定的次数,触发降级

image-20211009230705732

image-20211009230723608

热点限流

何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

新的注解@SentinelResource 类似 @HystrixCommand

@GetMapping(value = "/hotKey")
//热点,value: 监控的资源名,自定义的,命名规范是与请求地址一致,去掉/ ,  blockHandler: 兜底方法
@SentinelResource(value = "hotKey",blockHandler = "dieHandler")
public String hotKey(@RequestParam(value = "p1",required = false)String p1,//required 请求地址的参数可加可不加
                     @RequestParam(value = "p2",required = false)String p2){
    return "---hotKey---";
}
//兜底方法,需要携带相同的参数,并且需要加上参数BlockException
public String dieHandler(String p1, String p2, BlockException blockException){
    return  "---hotkey  die---";
}

配置

资源名:SentinelResource 的value 不是请求地址

参数索引:代码形参中第几个参数进行热点监控,现在设定第0个,只有请求地址中携带了这个参数才会被限流

image-20211009233950739

@SentinelResource 处理的仅仅是Sentinel控制台配置的违规情况,如果程序本身出现了错误,不会执行兜底方法

如果希望程序出现异常也执行兜底方法可以加参数fallback

参数例外项

我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样,类似一些网站的VIP用户,普通用户无法访问,但是VIP用户可以。

第0个参数正常阈值为 1 ,但是当它携带参数值 5 时,阈值只变成了200image-20211010000529404

image-20211010000249917

热点参数的注意点,参数必须是基本类型或者String

URL限流

​ 上面使用的是资源名(hotkey)限流,也可以使用URL 请求地址(/hotkey)进行限流。

​ 设置限流后,如果 @SentinelResource 参数中不加 blockHandler 兜底方法,那么就会返回sentinel自带的限流结果

    // 通过URL进行限流,没有写blockHandler 默认使用sentinel自带的限流结果
    @GetMapping(value = "/byurl")
//    @SentinelResource("byurl") // 可写可不写,因为是通过URL限流
    public String byURL(){
        return "通过URL限流";
    }

全局兜底方法

跟Hystrix一样,为了防止出现大量的兜底方法,使得代码冗余肿胀,而采用的方式。

@GetMapping(value = "/globalA")
// 使用全局兜底方法,blockHandlerClass:使用哪个类,blockHandler:哪个方法
@SentinelResource(value = "globalA",
                  blockHandlerClass = CommonResource.class,
                  blockHandler = "globalA")
public CommonResult testGlobalA(){
    return new CommonResult(200,"200用户自定义 流量正常",new Payment(20,"正常"));
}

兜底方法配置类

public class CommonResource {

    // 全局兜底方法,需要是静态的,返回值要一致
    public static CommonResult globalA (BlockException blockException){
        return new CommonResult(404404,"A限流A,采用全局兜底方法");
    }
	// 一个A 一个B  可以任意选择使用哪个兜底方法
    public static CommonResult globalB (BlockException blockException){
        return new CommonResult(404404,"B限流B,采用全局兜底方法");
    }

}

日后整合ribbon,sentinel,openfeign,fallback

想要达到的目的是,blockHandler管理配置的违规问题,fallback管理运行异常

image-20211010181752757

两个同时都配,都会起作用,运行中,符合哪个异常标准就走哪个兜底方法

exceptionToIngNore:排除某个异常,不走兜底方法,正常报错

规则配置持久化

一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台
的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效

pom.xml 新增的依赖

<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
 

新增的yaml配置信息

在sentinel 下面的配置

sentinel:   
...
datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service   # nacos新建配置的DataID
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

image-20211011151603366

[
    {
        "resource": "/retaLimit/byUrl", # sentinel资源名 监控的Urlf
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
****************内容解析*****************
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。

这样配置就可以让sentine读取到nacos里保存的服务配置信息

Seata

  • TC 事务协调者

    • 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
  • TM 事务管理器

    • 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
  • RM 资源管理器

    • 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚

blockHandler 兜底方法,那么就会返回sentinel自带的限流结果

    // 通过URL进行限流,没有写blockHandler 默认使用sentinel自带的限流结果
    @GetMapping(value = "/byurl")
//    @SentinelResource("byurl") // 可写可不写,因为是通过URL限流
    public String byURL(){
        return "通过URL限流";
    }

全局兜底方法

跟Hystrix一样,为了防止出现大量的兜底方法,使得代码冗余肿胀,而采用的方式。

@GetMapping(value = "/globalA")
// 使用全局兜底方法,blockHandlerClass:使用哪个类,blockHandler:哪个方法
@SentinelResource(value = "globalA",
                  blockHandlerClass = CommonResource.class,
                  blockHandler = "globalA")
public CommonResult testGlobalA(){
    return new CommonResult(200,"200用户自定义 流量正常",new Payment(20,"正常"));
}

兜底方法配置类

public class CommonResource {

    // 全局兜底方法,需要是静态的,返回值要一致
    public static CommonResult globalA (BlockException blockException){
        return new CommonResult(404404,"A限流A,采用全局兜底方法");
    }
	// 一个A 一个B  可以任意选择使用哪个兜底方法
    public static CommonResult globalB (BlockException blockException){
        return new CommonResult(404404,"B限流B,采用全局兜底方法");
    }

}

日后整合ribbon,sentinel,openfeign,fallback

想要达到的目的是,blockHandler管理配置的违规问题,fallback管理运行异常

[外链图片转存中…(img-P0naaSh5-1646986384132)]

两个同时都配,都会起作用,运行中,符合哪个异常标准就走哪个兜底方法

exceptionToIngNore:排除某个异常,不走兜底方法,正常报错

规则配置持久化

一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台
的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效

pom.xml 新增的依赖

<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
 

新增的yaml配置信息

在sentinel 下面的配置

sentinel:   
...
datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service   # nacos新建配置的DataID
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

[外链图片转存中…(img-cREFq3WT-1646986384136)]

[
    {
        "resource": "/retaLimit/byUrl", # sentinel资源名 监控的Urlf
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
****************内容解析*****************
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。

这样配置就可以让sentine读取到nacos里保存的服务配置信息

Seata

  • TC 事务协调者

    • 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
  • TM 事务管理器

    • 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
  • RM 资源管理器

    • 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚

版权声明:本文为weixin_47135811原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。