Skip to content

分布式思想与基本概念

高并发

  • Tomcat最多支持并发

    • Tomcat 默认配置的最大请求数是 150,可以将其改大。
    • Tomcat 默认的 HTTP 实现是采用阻塞式的 Socket 通信,每个请求都需要创建一个线程处理。这种模式下的并发量受到线程数的限制。
    • Tomcat 还可以配置 NIO 方式的 Socket 通信,在性能上高于阻塞式的,每个请求也不需要创建一个线程进行处理,并发能力比前者高。但没有阻塞式的成熟。
  • 操作系统对于进程中的线程数的限制:

    • Windows 每个进程中的线程数不允许超过 2000

    • Linux 每个进程中的线程数不允许超过 1000

  • 服务机器的性能限制

    • Java 中每开启一个线程需要耗用 1MB 的 JVM 内存空间用于作为线程栈之用。
    • 如果逻辑很复杂需要大量的计算,那并发能力势必会下降。如果每个请求都含有很多的数据库操作,那么对于数据库的性能也是非常高的。
  • 高并发衡量指标

    • 响应时间(RT) :请求做出响应的时间,即一个http请求返回所用的时间
    • 吞吐量:系统在单位时间内处理请求的数量
    • QPS(Query/Request Per Second)、 TPS(Transaction Per Second) :每秒查询(请求)数、每秒事务数。
    • 并发用户数:承载的正常使用系统功能的用户的数量

高可用

  • 服务集群部署
  • 数据库主从+双机热备
    • 双机热备可以被视为一种特殊的集群,它只有两个节点,一个主节点和一个备份节点。
    • 主-备方式(Active-Standby方式):主-备方式即指的是一台服务器处于某种业务的激活状态(即Active状态),另一台服务器处于该业务的备用状态(即Standby状态)。
    • 双主机方式(Active-Active方式):双主机方式即指两种不同业务分别在两台服务器上互为主备状态(即Active-Standby和Standby-Active状态)

注册中心

保存某个服务所在地址等信息,方便调用者实时获取其他服务信息

  • 服务注册:服务提供者

  • 服务发现:服务消费者

负载均衡

动态将请求派发给比较闲的服务器

策略:

  • 轮询(Round Robin):轮流发送请求
  • 加权轮询(Weighted Round Robin):轮流发送,权重多的多发送几次
  • 随机Random:随机发送
  • 哈希Hash:将请求发送给hash对应的服务器,确保同一个客户端发送的请求由同一个服务器处理
  • 最小连接数LC:发送给链接数最小的服务器。
  • 最短响应时间LRT:发送给响应时间最短的服务器。

服务雪崩

服务之间复杂调用,一个服务不可用,导致整个系统受影响不可用

熔断

某个服务频繁超时,直接将其短路,快速返回mock(模拟/虚拟)值

限流

限制某个服务每秒的调用本服务的频率

API网关

API网关要做很多工作,它作为一个系统的后端总入口,承载着所有服务的组合路由转换等工作,除此之外,我们一般也会把安全,限流,缓存,日志,监控,重试,熔断等放到 API 网关来做

服务追踪

追踪服务的调用链,记录整个系统执行请求过程。如:请求响应时间,判断链中的哪些服务属于慢服务(可能存在问题,需要改善)

弹性云

Elastic Compute Service(ECS)弹性计算服务

动态扩容,压榨服务器闲时能力

例如:双11,618,高峰时多配置些服务器,平时减少多余的服务器配置(用于其他服务应用),避免资源浪费。

SpringCloud

文档:https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md

概述

微服务架构

Breaker dashboard:断路器仪表板

Distributed Tracing分布式跟踪:(分布式处理程序链跟踪用于监视网络等待时间,并可视化通过微服务的请求流)

组件概述:

命名及版本选择:

  • SpringBoot2.3.6配合SpringCloud Hoxton.SR9版或者SpringCloud Alibaba 2.2.6

  • Spring Cloud 是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了管理SpringCloud与各子项目的版本依赖关系,发布了一个清单,其中包括了某个SpringCloud版本对应的子项目版本。为了避免SpringCloud版本号与子项目版本号混淆,SpringCloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来应对版本时间顺序。

构建项目

父工程:install

pom文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.fjut</groupId>
    <artifactId>SpringCloudPractice</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>Payment8001</module>
    </modules>

    <packaging>pom</packaging>

    <!-- 统一管理jar包版本 -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>8.0.18</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version  -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <!--配合热部署使用的插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

子工程:服务提供者

pom文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>Payment8001</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--用于热部署的jar包:spring-boot-devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
yml文件
yml
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud2021?serverTimezone=UTC&rewriteBatchedStatements=true
    username: root
    password: fujianz123

mybatis:
  mapperLocations: classpath:/mapper/*.xml
  type-aliases-package: com.fjut.springcloud.entities
热部署

子工程中添加热部署jar包,父工程中引入spring-boot-maven-plugin插件

Ctrl+Shift+Alt+/选择Registry…

  • compiler.automake.allow.when.app.running -> 自动编译

  • compile.document.save.trigger.delay -> 自动更新文件;它主要是针对静态文件如JS CSS的更新,将延迟时间减少后,直接按F5刷新页面就能看到效果!

子工程:服务使用者

pom文件

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Consumer8001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--监控健康组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
yml文件
yml
server:
  port: 80
spring:
  application:
    name: Consumer8001
RestTemplate

提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问Restful服务模板类,是Spring 提供的用于访问Rest服务的客户端模板工具集。(url,requestMap,ResponseBean.class)这三个参数分别代表REST请求地址、请求参数、Http响应转换被转换成的对象类型。

配置类
java
//创建一个RestTemplate对象
@SpringBootConfiguration
public class ApplicationContextConfig {
    @Bean
    //@LoadBalanced  开启负载均衡时再加上
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
Controller
java
@RestController//@Controller和@ResponseBody的组合
public class OrderController {

    //可以使用PAYMENT_URL =CLOUD-PAYMENT-SERVICE代替
    public static final String PAYMENT_URL = "http://localhost:8001";

    @Resource
    private RestTemplate restTemplate;

    @PostMapping("/consumer/payment/create")
    public CommonResult<Payment>   create(@RequestBody Payment payment){
        return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);  //写操作
    }

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

子工程:工具类及实体类

pom文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2021</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-api-commons</artifactId>

    <dependencies>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
           <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>
</project>
其他工程引入
xml
<dependency>
    <groupId>com.fjut</groupId>
    <artifactId>commons</artifactId>
    <version>${project.version}</version>
</dependency>

SpringCloud Netflix

Eureka注册中心

概述

  • SpringCloud封装了Netflix公司开发的Eureka模块来实现服务治理。在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂、所以需要进行服务治理,管理服务与服务之间依赖关联,以实现服务调用,负载均衡、容错,服务发现与注册

  • Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员可以通过Eureka Server来监控系统中各个微服务是否正常运行。

  • 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息,比如:服务通讯地址等以别名方式注册到注册中心上。另一方(消费者服务),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后,再实现本地RPC远程调用。

  • Eureka和Dubbo区别:

    • Dubbo是个微服务整体架构的框架,提供的功能包括服务注册发现,远程调用,监控等等。对标的项目大概是Feign+Ribbon+Hystrix。Dubbo的服务发现模块基于zookeeper实现,对应Eureka。
    • Eureka。是spring cloud之下一个专门负责微服务服务注册和发现的组件,Eureka就是为了服务发现而设计的。是Dubbo对应的概念的一个部分。

Eureka两组件

  • Eureka Server提供服务注册服务

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

  • Eureka Client通过注册中心进行访问

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

Eureka构建

子工程:serve端

pom文件

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Eureka</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Spring Boot 的一个启动器,它主要用于添加对生产级服务的监控和管理功能。-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>
</project>

yml文件

yml
server:
  port: 7001

eureka:
  instance:
    hostname: localhost #Eureka 服务实例的主机名,这里设置为 localhost,表示服务运行在本地。
    
  client:
    register-with-eureka: false #这个配置项决定是否将自己注册到 Eureka 服务注册中心
    fetchRegistry: false #这个配置项决定是否获取 Eureka 服务注册中心的注册信息
    service-url:
      defaultZone: http://localhost:7001/eureka # Eureka 服务注册中心的 URL

主启动

java
//添加上注释
@SpringBootApplication
@EnableEurekaServer// EurekaServer服务器端启动类,接受其它微服务注册进来
public class EurekaMain {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain.class, args);
    }
}

访问Eureka服务端页面:http://localhost:7001

子工程:client

pom文件

xml
<!--服务消费者和提供者pom文件中引入Eureka的client包-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

yml文件

yml
#eureka.instance下的hostname即主机名不配置的话默认为电脑名
#instanceID不配置的话默认值为主机名+服务名+端口
#prefer-ip-address表示猜测主机名(hostname)为ip形式,不配置的话默认为false

eureka:
  client:
    register-with-eureka: true #这个配置项决定是否将自己注册到 Eureka 服务注册中心
    fetchRegistry: true #这个配置项决定是否将自己注册到 Eureka 服务注册中心
    service-url:
      defaultZone: http://localhost:7001/eureka #Eureka服务注册中心的URL

启动类

java
//添加注解
@EnableEurekaClient

注册成功

Ribbon负载均衡

概述

  • Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。主要功能是提供客户端的软件负载均衡算法和服务调用。提供一系列完善的配置项,如:连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。

  • Ribbon的本地负载均衡VS Nginx服务端负载均衡

    • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后,由nginx实现转发请求。即负载均衡是由服务器端完成的。(分发客户端请求不同服务器)
    • Ribbon本地负载均衡,在调用微服务接口时候(消费者调用提供者),会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用。(分发消费者请求至不同服务者)
  • 集中式和进程内负载均衡

    • 集中式: 所有的负载均衡决策都由一个中心节点做出,即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;
    • 进程内:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
  • <p style="color:red">Eureka中自带Ribbon</p>

  • <p style="color:red">Ribbon=负载均衡+RestTemplate调用</p>

架构

第一步,先选择EurekaServer,它优先选择在同一个区域内负载较少的server。

第二步,再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略。比如:轮询、随机和根据响应时间加权。

总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

Pom文件

xml
<!--无需引入,Eureka中自带Ribbon-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

idea启动多个服务

-Dserve.port=* * * *

Ribbon核心组件IRule

IRule:根据特定算法从服务列表中选取一个要访问的服务

  1. com.netflix.loadbalancer.RoundRobinRule 轮询,默认策略。

  2. com.netflix.loadbalancer.RandomRule 随机

  3. com.netflix.loadbalancer.RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务

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

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

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

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

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

    当 Spring Boot 应用启动时,它会自动扫描主类所在的包以及子包下的所有组件,并将它们注册到 Spring 容器中。这个过程是由 @ComponentScan 注解控制的。
    
    如果你的 Ribbon 自定义配置类被放在了 @ComponentScan 所扫描的当前包下或子包下,那么这个配置类就会被 Spring Boot 自动注册到 Spring 容器中,成为一个全局的配置类。这就意味着这个配置类会被所有的 Ribbon 客户端共享,而不是只被你指定的那个 Ribbon 客户端使用。
    
    Ribbon 的默认负载均衡策略是轮询策略,如果你没有使用@RibbonClient指定配置类,但是在 Spring 容器中存在全局的Ribbon配置类,那么Ribbon客户端会使用这个全局的配置类。如果使用@RibbonClient指定配置类,将会覆盖其全局配置类。
  • 服务的名字由提供者端的yml配置文件中指定

java
//配置类,在消费者端创建规则类
@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule(){
        return new RandomRule();//定义为随机
    }
}

//主启动类,在消费者端创建:CLOUD-PAYMENT-SERVICE要调用的服务名,调用该服务采用MySelfRule规则
@EnableEurekaClient
@SpringBootApplication
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

//创建一个RestTemplate对象,消费者端
@SpringBootConfiguration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced//服务使用者需要加上负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

OpenFeign

概述

  • 使用Ribbon+RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。
  • 在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是DAO接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
  • Feign集成了 Ribbon:利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
  • OpenFeign 是对 Feign 进行了扩展和集成的版本

使用

微服务调用接口+@FeignClient

pom文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Consumer8001Feign</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
yml文件
yml
server:
  port: 80
spring:
  application:
    name: cloud-consumer-feign-order80
eureka:
  client:
register-with-eureka: true
fetch-registry: true
       service-url:
          defaultZone: http://localhost:7001/eureka
主启动类
java
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignMain {
    public static void main(String[] args) {
        SpringApplication.run(FeignMain.class, args);
    }
}

service层装配

java
@Service
@FeignClient(value="PAYMENT8001")//绑定的provider服务为CLOUD-PAYMENT-SERVICE
public interface FeignService {
    //以下为调用的provider的函数和注解,直接复制过来即可
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);
}

Controller层调用

java
@RestController
public class FeignController {
    @Resource
    private FeignService feignService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        return feignService.getPaymentById(id);
    }
}
负载均衡
java
//主启动类上直接添加
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)

超时控制

OpenFeign默认等待一秒钟,服务端处理需要超过1秒钟,超过后报错

  • 使用Feign调用接口分两层,ribbon的调用和hystrix的调用,所以ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间

  • 一般情况下 都是 ribbon 的超时时间(<)hystrix的超时时间(因为涉及到ribbon的重试机制)

  • ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制,Feign 中,重试机制是通过集成 Ribbon 来实现的

    java
    //Ribbon 和 Feign 的默认行为是不进行重试
    
    开启Feign的重试机制如下:(Feign默认重试五次 源码中有)
    @Bean
    Retryer feignRetryer() {
            return  new Retryer.Default();
    }
yml
#设置Feign客户端超时时间(openfeign默认支持ribbon)
ribbon:
  ReadTimeout:  3000  #读的超时时间,3秒(从目标服务接收响应的最大等待时间。)
  ConnectTimeout: 3000 	#链接的超时时间,3秒
  MaxAutoRetries: 1	 #同一台实例最大重试次数,不包括首次调用
  MaxAutoRetriesNextServer: 1 #请求失败时切换到下一个服务实例并进行重试的最大次数,不包括首次调用
  OkToRetryOnAllOperations: false # 是否对所有操作都进行重试(为 false 时,Ribbon 只会对幂等操作进行重试,即GET请求,对于POST,PUT,DELETE请求不重试)
  #(MaxAutoRetries+1)*(MaxAutoRetriesNextServer+1)=4次,一个实例重试两次,最多试两个实例、
  
#hystrix的超时时间
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true	#是否启用 Hystrix 超时机制
        isolation:
          thread:
            timeoutInMilliseconds: 9000 #Hystrix线程隔离的超时时间,单位为毫秒
  • 如果在重试期间,时间超过了hystrix的超时时间,便会立即执行熔断,fallback。
  • hystrix超时时间的计算:(1 + MaxAutoRetries )*(1+MaxAutoRetriesNextServer) *(ReadTimeout)
  • 当ribbon超时后且hystrix没有超时,便会采取重试机制。
  • 如果不配置ribbon的重试次数,默认会重试一次。默认情况下,GET方式请求无论是连接异常还是读取异常,都会进行重试。非GET方式请求,只有连接异常时,才会进行重试

OpenFeign日志打印

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节,对Feign接口的调用情况进行监控和输出。

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

配置类

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

yml文件

yml
logging:
	level:
		com.fjut.service.Payment: debug

Hystrix断路器

概述

  • 雪崩效应:多个微服务之间调用的时候,假如微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。如果扇出的链路上某个微服务的调用响应的时间过长或者不可用,对微服A的调用就会占用越来越多的系统资源,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。
  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix作用
  • 服务降级Fallback:服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示
    • 程序运行异常
    • 超时自动降级
    • 服务熔断触发服务降级
    • 线程池/信号量打满也会导致服务降级
    • 人工降级
  • 服务熔断Breaker
    • 类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
    • 服务的降级->进而熔断->恢复调用链路
  • 服务限流Flowlimit
    • 秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

构建provider

新建模块

pom文件

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Hystrix</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--新增hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

yml文件

yml
server:
  port: 8001

spring:
  application:
    name: cloud-hystrix-payment-service

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka/

主启动类

java
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}

Service层

java
public interface PaymentService {
    public String paymentInfo_OK(Integer id);
    public String payment_Timeout(Integer id);
}

@Service
public class PaymentServiceImpl implements PaymentService {

    //成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }

    //失败
    public String payment_Timeout(Integer id){
        int timeNumber = 3;
        try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
    }
}

Controller层

java
@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }
    
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentService.payment_Timeout(id);
        log.info("*******result:"+result);
        return result;
    }
}

Jmeter压测测试

Jmeter.bat点击运行

构建Consumer

pom文件

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>HystrixConsumer</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--新增hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

yml文件

yml
server:
  port: 80

spring:
  application:
    name: HystrixConsumer

eureka:
  client:
    register-with-eureka: true    #表示不向注册中心注册自己
    fetch-registry: true   #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://localhost:7001/eureka/

启动类

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

Service层

java
@FeignClient("Hystrix")
@Service
public interface HystrixService {
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);

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

Controller层

java
@RestController
@Slf4j
public class HystrixController {
    @Resource
    private HystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut( id);
        log.info("*******result:"+result);
        return result;
    }
}

服务降级

  • 设置消费者自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
  • 注意:服务降级可以在服务提供者侧,也可以在服务消费者侧。更多是在服务消费者侧。因为存在客户端去调用服务端,碰上服务端宕机或关闭。所以服务降级处理在客户端实现,与服务端无关,可实现解耦
在提供者端
java
//service层
@Service
public class PaymentServiceImpl implements PaymentService {

    @Override
    public String paymentInfo_OK(Integer id) {
        return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id="+id +" \t O(∩_∩)O哈哈~";
    }

    //超时降级演示
    @HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {
  
	//注意openFeign也有设置时间,如果超过其设置时间也会走兜底方法          
        
    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") //5秒钟以内就是正常的业务逻辑
    })
    @Override
    public String payment_Timeout(Integer id) {
        //int timeNumber = 3; //小于等于3秒算是正常情况
        int timeNumber = 15; //模拟非正常情况
        //int i = 1/0 ; //模拟非正常情况
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:"+Thread.currentThread().getName()+" payment_Timeout,id="+id+" \t o(╥﹏╥)o 耗时:"+timeNumber;
    }

    //兜底方法,上面方法出问题,我来处理,返回一个出错信息
    public String payment_TimeoutHandler(Integer id) {
        return "线程池:"+Thread.currentThread().getName()+" payment_TimeoutHandler,系统繁忙,请稍后再试\t o(╥﹏╥)o ";
    }
}

//主启动类添加@EnableHystrix,作用同@EnableCircuitBreaker,@EnableHystrix继承了@EnableCircuitBreaker
@SpringBootApplication
@EnableHystrix
@EnableEurekaClient
public class HystrixMain {
    public static void main(String[] args) {
        SpringApplication.run(HystrixMain.class, args);
    }
}
在消费者端
java
//yml文件
feign:
  hystrix:
    enabled: true //在 Feign中启用Hystrix功能,以增加对远程服务调用的熔断和容错能力

//主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class HystrixConsumerMain {
    public static void main(String[] args) {
        SpringApplication.run(HystrixConsumerMain.class, args);
    }
}


//兜底方法
@RestController
@Slf4j
public class HystrixController {
    @Resource
    private HystrixService paymentHystrixService; 
    
@HystrixCommand(fallbackMethod = "HandleException",commandProperties = {
    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut( id);
        log.info("*******result:"+result);
        return result;
    }

    //兜底方法的返回值和参数要和原方法一致
    public String HandleException(Integer id){
        return "系统繁忙…………请稍后再试";
    }
}
默认服务降级方法
java
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")  //全局的
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    //将会调用全局的兜底方法payment_Global_FallbackMethod
    @HystrixCommand//添加一个注解即可,不需要单独指定兜底方法
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    //下面是全局fallback方法
    public String payment_Global_FallbackMethod(){
        return "Global异常处理信息,请稍后再试,(┬_┬)";
    }
}

提取服务降级类

没有指定降级时间,则默认1秒降级

java
//实现feign远程调用的接口
@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
    }

    @Override
    public String payment_Timeout(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
    }
}

//开启feign的远程调用熔断
feign:
  hystrix:
    enabled: true
        
        
//指定远程调用的微服务名称,以及兜底方法类        
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
@Component
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);
}

服务熔断

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

熔断类型

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

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

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

断路器属性

  • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  • 请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。
断路器开启或者关闭的条件
  • 当满足一定阀值的时候(默认10秒内超过20个请求次数)

  • 当失败率达到一定的时候(默认10秒内超过50%请求失败)

  • 同时到达以上两个阀值,断路器将会开启,当开启的时候,所有请求都不会进行转发

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

断路器打开之后
  • 再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallbak。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

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

代码
  • @HystrixProperty(name = "circuitBreaker.enabled", value = "true") 是用于在方法级别配置断路器的属性,通过设置 circuitBreaker.enabled 属性为 true 来开启断路器功能。
  • @EnableCircuitBreaker 是用于在应用程序级别启用断路器功能,通过在配置类上添加该注解来启用断路器,并将其集成到应用程序中。
Java
//Threshold:门槛,阈值
//Volume:容量体积   

//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),  //是否开启断路器
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),   //用于设置断路器的请求阈值。当在一个统计窗口内的请求次数达到该阈值时,Hystrix 将会开始统计错误百分比来判断是否触发断路器,默认20个
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),  //断路多久以后开始尝试是否恢复,默认5s
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //出错百分比阈值,当达到此阈值后,开始短路。默认50%
})

public String paymentCircuitBreaker(Integer id){
    if (id < 0){
        throw new RuntimeException("*****id 不能负数");
    }
    String serialNumber = IdUtil.simpleUUID();//hutool.cn工具包

    return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
    return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
}
 //出错百分比阈值,当达到此阈值后,开始短路。默认50%
})
    
public String paymentCircuitBreaker(Integer id){
    if (id < 0){
        throw new RuntimeException("*****id 不能负数");
    }
    String serialNumber = IdUtil.simpleUUID();//hutool.cn工具包

    return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
    return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
}

服务限流

服务监控

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

仪表盘

pom文件

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2021</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>

    <dependencies>
        <!--新增hystrix dashboard-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <!--Spring Boot 提供的一个用于监控和管理应用程序的模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

yml文件

yml
server:
  port: 9001
hystrix:
  dashboard:
    proxy-stream-allow-list: "*" #允许访问Hystrix仪表板代理流的主机列表。设置为 "*",表示允许所有主机访问
    
#hystrix:
  #dashboard:
    #proxy-stream-allow-list: "localhost, 192.168.0.1" 
#只有localhost和192.168.0.1这两个主机可以访问Hystrix仪表板的代理流

主启动类

java
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class,args);
    }
}

provider

xml
<!--所有provider加入监控包-->

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

查看:http://localhost:9001/hystrix

断路器

将以下创建registrationBean方法放到要监控的主启动类内即可

java
//注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径

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

<p style="color:red">要在 Hystrix Dashboard 中监控方法,这些方法需要使用 @HystrixCommand 注解进行标记</p>

监控图

GateWay网关

https://docs.spring.io/spring-cloud-gateway/docs/2.2.6.RELEASE/reference/html/#the-addrequestheader-gatewayfilter-factory

核心概念

Predicate就是我们的匹配条件: 而Filter,就是可以理解为一个无所不能的拦截器。

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

过程

  • 客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由,将其发送到Gateway Web Handler.

  • Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。

  • 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑。

    • Filter在"pre“类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
    • Filter在”post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量控制

项目构建

pom文件

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>GateWay</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--新增gateway,不需要引入web和actuator模块-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

yml文件

不想暴露8001端口,希望在8001外面套一层9527

yml
server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
       - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
         uri: http://localhost:8001   #匹配后提供服务的路由地址(即要代理的路由地址)
         predicates:
           - Path=/payment/get/**   #断言,路径相匹配的进行路由
 
       - id: payment_routh2
         uri: http://localhost:8001
         predicates:
           - Path=/payment/lb/**   #断言,路径相匹配的进行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:7001/eureka

启动类

java
@SpringBootApplication
@EnableEurekaClient
public class GetwayMain {
    public static void main(String[] args) {
        SpringApplication.run(GetwayMain.class, args);
    }
}

测试

  • 启动7001:cloud-eureka-server7001
  • 启动8001:cloud-provider-payment8001
  • 启动9527网关:cloud-gateway-gateway9527

访问以下可以得到相同结果:

动态路由

修改yml文件

yml
server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator: #定位器
          enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          #uri的协议为lb,表示启用Gateway的负载均衡功能(轮询),cloud-payment-service 为微服务的名字,如果只有一个服务可以绑定IP和端口号,但是有多个服务只能使用微服务名。
          uri: lb://cloud-payment-service 
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:7001/eureka

Predicate

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

yml
routes:
    - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
    #uri: http://localhost:8001   #匹配后提供服务的路由地址
    uri: lb://cloud-payment-service
    predicates:
        - Path=/payment/lb/**   #断言,路径相匹配的进行路由
        - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] #在时间之后
        - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] #在时间之前
        - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] ,  2020-03-08T10:59:34.102+08:00[Asia/Shanghai] #在两时间之内 
        - Cookie=username,zhangshuai #携带Cookie,并且其是username=zhangshuai才能访问
        - Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
        - Host=**.atguigu.com #匹配请求的 Host(主机)是否符合指定的模式。
        - Method=GET #请求的方法是否为Get
        - Query=username, \d+ #查询参数中的 username 参数是否符合指定的正则表达式模式 \d+(正整数)。 

Filter

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,只能指定路由进行使用。

自定义过滤器

java
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("*********come in MyLogGateWayFilter: "+new Date());
        //获取参数
        String uname = exchange.getRequest().getQueryParams().getFirst("username");
        if(StringUtils.isEmpty(uname)){
            log.info("*****用户名为Null 非法用户,(┬_┬)");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    //Gateway 中可以配置多个过滤器,通过实现 Ordered 接口,可以指定一个特定的执行顺序。较小的值表示较高的优先级
    @Override
    public int getOrder() {
        return 0;
    }
}

SpringCloud Sleuth

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前端请求都会形成一个复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。Spring Cloud Sleuth(侦探)提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin(负责展现,以图形方式查看事务占用的时间量,并分解在调用中涉及的每个微服务所用的时间。)

步骤

  • 将Spring Cloud Sleuth和Zipkin JAR文件添加到捕获跟踪数据的服务中;
  • 在每个服务中配置Spring属性以指向收集跟踪数据的Zipkin服务器;
  • 安装和配置Zipkin服务器以收集数据;
  • 定义每个客户端所使用的采样策略,便于向Zipkin发送跟踪信息。

请求链路

一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来。

  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
  • span:表示调用链路来源,通俗的理解span就是一次请求信息

服务提供者

pom文件

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

yml文件

yml
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      #采样率值介于0~1之间,1表示全部采样
      probability: 1
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud2021?serverTimezone=UTC&rewriteBatchedStatements=true
    username: root
    password: fujianz123

mybatis:
  mapperLocations: classpath:/mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities

eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

服务消费者

pom文件

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

yml文件

yml
server:
  port: 80

spring:
    application:
        name: cloud-order-service
    zipkin:
      base-url: http://localhost:9411
    sleuth:
      sampler:
        probability: 1

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

使用

SpringCloud Alibaba

Spring Cloud Netflix项目进入维护模式,即不会再向模块添加新功能。仅修复block级别的bug以及安全问题,及考虑并审查社区的小型pull request。因此SpringCloud Alibaba出现。

官网:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

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

Nacos

  • 文档:https://nacos.io/zh-cn/docs/quick-start.html

  • Naming Configuration Service的缩写,动态服务发现,配置管理和服务管理中心,即注册中心+配置中心的组合,Nacos = Eureka(服务注册中心)+Config(服务配置中心)+Bus+Ribbon(负载均衡)

  • 开启:进入bin目录下,打开cmd窗口,执行startup.cmd -m standalone,默认:MODE="cluster"集群方式启动,如果单机启动需要设置-m standalone参数,否则,启动失败。

CAP原则

CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
  • 分区容忍性(P):表示系统能够在面对网络分区(节点之间无法相互通信)的情况下继续运行。这意味着即使系统中的某些节点无法通信,其他节点仍然可以继续处理请求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

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

curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

Nacos作为服务注册中心

服务提供者

pom文件

xml
<!--父工程-->
<!--spring cloud alibaba 2.2.6.RELEASE-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  <version>2.2.6.RELEASE</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>


<!--子工程-->
<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

yml文件

yml
server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

#用于配置 Spring Boot Actuator 管理端点的暴露规则,用于监控和管理 Spring Boot 应用程序的运行时状态和健康状况
management:
  endpoints:
    web:
      exposure:
        include: '*'  #默认只公开了/health和/info端点,要想暴露所有端点只需设置成星号

主启动类

java
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9001.class,args);
    }
}

业务类

java
@RestController
public class PaymentController{
    @Value("${server.port}")
    private String serverPort;
 
    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Long id) {
        return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
    }
}
服务消费者

pom文件

xml
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

yml文件

yml
server:
  port: 83

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者【可选】,注意:nacos-payment-provider含有IP和端口)
service-url:
  nacos-user-service: http://nacos-payment-provider

主启动类

java
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83{
    public static void main(String[] args){
        SpringApplication.run(OrderNacosMain83.class,args);
    }
}

配置类

java
@Configuration
public class ApplicationContextConfig{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

Controller层

java
@RestController
@Slf4j
public class OrderNacosController{
    @Resource
    private RestTemplate restTemplate;

    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @GetMapping(value = "/consumer/payment/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id){
        return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
    }
}
负载均衡

Nacos支持负载均衡,其原因是内部继承了ribbon框架

Nacos作为配置中心

工程创建

pom文件

xml
<dependencies>
    <!--nacos-config:配置中心依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!--nacos-discovery:注册中心依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--web + actuator-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--一般基础配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

yml文件

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

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

yml
#bootstrap.yml
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #服务注册中心地址
      config:
        server-addr: localhost:8848 #配置中心地址
        file-extension: yaml #指定yaml格式的配置(yml和yaml都可以)

#${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
#nacos-config-client-dev.yaml  (一定要与file-extension值保持一致)

#application.yml
spring:
  profiles:
    active: dev #表示开发环境

主启动类

java
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377{
    public static void main(String[] args) {
        SpringApplication.run(NacosConfigClientMain3377.class, args);
    }
}

业务类

java
@RestController
@RefreshScope   //通过SpringCould原生注解@RefreshScope实现配置自动更新
public class ConfigClientController{
    @Value("${config.info}") //对应nacos配置:nacos-config-client-dev.yaml
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
}

Nacos中添加配置信息

最后公式:

  • spring.application.name{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

  • nacos-config-client-dev.yaml

访问时可以读取出对应配置

分类概述

Nacos默认的命名空间是public,Namespace主要用来实现隔离,Group和DataID逻辑上区分两个目标对象。默认情况:Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT

  • Namespace:对于开发、测试、生产环境,我们就可以创建三个Namespace,不同的 Namespace之间是隔离的。

  • Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去。Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。比方说为了容灾,将Service微服务分别部署在了杭州机房广州机房,这时就可以给杭州机房的Service微服务起一个集群名称(HZ),给广州机房的Service微服务起一个集群名字(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

  • Instance,就是微服务的实例。

1) DataID方案

指定spring.profiles.active和DataID来使不同环境下读取不同的配置,默认空间+默认分组+新建dev和test两个DataID

  • 新建dev配置DataID

  • 新建test配置DataID

2) Group方案

通过Group实现环境区分,在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP

3) Namespace方案

配置中填写:

yml
# nacos配置
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: DEV_GROUP
        namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4

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

Nacos集群及持久化

默认Nacos使用嵌入式数据库实现数据的存储(Nacos默认自带数据库derby)。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。

Windows
  • nacos-server-1.4.2\nacos\conf目录下找到sql脚本,执行脚本nacos-mysql.sql在MySQL中创建数据库。

  • application.properties文件内添加

    properties
    spring.datasource.platform=mysql
    
    db.num=1
    db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC&rewriteBatchedStatements=true
    db.user=root
    db.password=fujianz123

注意:Nacos持久化之后,使用时一定要开启MySQL数据库,否则会报错

Linux
  • 创建/opt/nacoscluster目录,解压3个节点(Nacos需要三个及以上节点才能搭建集群)

  • Linux服务器上mysql数据库配置:找到nacos_mysql.sql文件,创建数据库并导入表结构

  • 三个节点conf/application.properties配置

    • 修改每一个节点下server.port,分别设置为:8848,8849,8850,并添加以下配置

      properties
      spring.datasource.platform=mysql
      
      db.num=1
      db.url.0=jdbc:mysql://localhost:3306/nacos_config? characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
      db.user=root
      db.password=fujianz123
  • 三个节点/conf下配置cluster.conf

    192.168.137.150:8848
    192.168.137.150:8849
    192.168.137.150:8850
  • 启动三个节点前,修改bin下startup.sh文件内存大小,否则,内存可能不够用。

  • 修改nginx的配置文件:vim /usr/local/nginx/conf/nginx.conf

    upstream nacoscluster{ 
        server localhost:8848;
        server localhost:8849;
        server localhost:8850;
    }
    
    server{               
        listen 1111;
        server_name localhost;
        location / {
             proxy_pass http://nacoscluster;                        
        }
    }

Sentinel

文档:https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html#_spring_cloud_alibaba_sentinel

运行: java -jar sentinel-dashboard-1.8.2.jar

  • java8环境OK
  • 8080端口不能被占用
  • 登录账号密码均为sentinel(哨兵)

演示工程

pom文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudPractice</artifactId>
        <groupId>com.fjut</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Sentinel</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

yml文件

yml
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719  #默认8719,应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用HttpServer

management:
  endpoints:
    web:
      exposure:
        include: '*'
主启动类
java
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelMain {
    public static void main(String[] args) {
        SpringApplication.run(SentinelMain.class, args);
    }
}
业务类
java
@RestController
public class SentinelController{
    @GetMapping("/testA")
    public String testA() {
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "------testB";
    }
}

Sentinel采用的懒加载说明,执行一次访问即可看到实时监控

流控规则

<img src="图片/image-20240106225847366.png" alt="image-20240106225847366" style="zoom:150%;" />

直接/默认模式

  • 资源名:访问的URL
  • 阈值范围QPS,单击阈值1:一秒钟一个查询,超出就直接-快速失败,报Blocked by Sentinel (flow limiting)
  • 并发线程,单击阈值1:只能有一个线程处理,超出就直接-快速失败,报Blocked by Sentinel (flow limiting)
  • 超时调用也会出现:Blocked by Sentinel (flow limiting)默认提示信息
关联

当关联资源/testB的QPS阀值超过1时,就限流/testA的REST访问地址,当关联资源到阀值后闲置配置的的资源名。

链路

只记录指定链路上的流量(指定资源从入口资源进来访问本资源的流量,如果达到阈值,就进行限流)(API级别的针对来源)

流控效果

快速失败

直接失败,抛出异常:Blocked by Sentinel (flow limiting)

warm up(预热)

公式:阈值除以coldFactor,经过预热时长后才会达到阈值.

默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。系统初始化的阈值为10/3约等于3,即阈值刚开始为3;然后过了5秒后阈值才慢慢升高,恢复到10

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

  • /testB每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。

熔断规则

慢比例调用

设定一个资源的慢调用比例阈值为50%,慢调用阈值为200ms,最小请求数为10个,单位统计时长为10s,熔断窗口期为10s。这意味着,如果在单位统计时长10s内,请求超过最小请求数10个,且该资源的调用中,响应时间超过200ms的调用比例超过了50%,那么接下来的10s内,对该资源的所有调用都会被直接降级,即不进行实际的业务逻辑处理,直接返回预设的降级结果。

异常比例

设定一个资源的异常比例阈值为20%,熔断窗口期为10s,最小请求书为10。这意味着,如果在统计时间窗口内,该资源的调用中,请求数大于10,且抛出异常的调用比例超过了20%,那么接下来的10s内,对该资源的所有调用都会被直接降级。

异常数

热点规则

代码
java
@GetMapping("/testHotKey")
//对于热点key为testHotKey的方法,违反规则将会调用兜底方法deal_testHotKey
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                         @RequestParam(value = "p2",required = false) String p2) {
    //int age = 10/0;
    return "------testHotKey";
}

//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
    return "------deal_testHotKey,o(╥﹏╥)o";
}

默认兜底方法

注意:@SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理。 java运行时报出的运行时异常,@SentinelResource不管

系统规则

@SentinelResource

概述

<img src="图片/image-20240107161509342.png" alt="image-20240107161509342" style="zoom:150%;" />

java
 	 /* @SentinelResource  与 Hystrix 组件中的@HystrixCommand注解作用类似的。
     *    value = "byResourceName"  用于设置资源名称,只有根据资源名称设置的规则,才能执行blockHandler所引用降级方法。
     *    如果按照映射路径进行规则配置,返回默认降级消息:Blocked by Sentinel (flow limiting)
     *    blockHandler 用于引用降级方法。
     *    blockHandlerClass 用于引用降级方法的处理器类。注意:降级方法必须是static的。否则,无法解析
     *    blockHandler + blockHandlerClass 只处理配置违规,进行降级处理。代码出现异常,不执行的。
     *
     *    blockHandler + fallback 同时存在,配置违规,代码也有异常,这时,走blockHandler配置文件降级处理
     *fallback:失败调用,若本接口出现未知异常,则调用fallback指定的接口。
	 * blockHandler:sentinel定义的失败调用或限制调用,如限流或服务降级,则调用blockHandler指定的接口。
     *    exceptionsToIgnore 设置特定异常不需要降级处理。
     */


    @RequestMapping("/fallback/{id}")
    @SentinelResource(value = "byFallbackName",blockHandler = "handleException3",
            blockHandlerClass = RateLimitControllerHandler.class,
            fallback = "handleException2",fallbackClass = RateLimitControllerHandler.class,
            exceptionsToIgnore=IllegalArgumentException.class
    )
    public CommonResult<Payment> fallback(@PathVariable("id") Long id) {
        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }

        if (id==-1) {
            CommonResult<Payment> result = new CommonResult<>(444,"数据不存在",null);
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }
        CommonResult<Payment> result = new CommonResult<>(200,"数据已经获取",new Payment(id,"test"+1));
        return result;
    }
yml文件
yml
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719  #默认8719,应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer

management:
  endpoints:
    web:
      exposure:
        include: '*'
业务类
java
@RestController
public class RateLimitController{
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource(){
        return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
//只有违反限流规则才会调用该方法,程序出异常不会调用,比原参数多一个BlockException参数
    public CommonResult handleException(BlockException exception){
        return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    }
}
主启动类
java
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401{
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }
}
流控效果

通过@SentinelResource指定,资源名与Value值相同,则会访问指定的降级方法

通过URL,则会返回系统默认的降级方法

统一自定义限流逻辑

创建兜底方法类

java
public class CustomerBlockHandler {
    //必须是公共的静态方法,且参数类型及返回值类型与调用兜底方法的方法一致,最后一个参数为BlockException
    public static CommonResult handleException(BlockException exception){
        return new CommonResult(2020,"自定义限流处理信息.... CustomerBlockHandler --- 1");
    }

    public static CommonResult handleException2(BlockException exception){
        return new CommonResult(2020,"自定义限流处理信息.... CustomerBlockHandler --- 2");
    }
}

控制类

java
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        //兜底方法类					  					兜底方法类中的方法
        blockHandlerClass =CustomerBlockHandler.class, blockHandler = "handleException2")
public CommonResult customerBlockHandler(){
    return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}

规则持久化

一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化。将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效

pom文件
xml
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
yml文件
yml
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'
添加Nacos配置
[
    {
         "resource": "/testA",
         "limitApp": "default",
         "grade": 1,
         "count": 1,
         "strategy": 0,
         "controlBehavior": 0,
         "clusterMode": false 
    }
]