SpringCloud

Spring Boot做文件上传时出现了The field file exceeds its maximum permitted size of 1048576 bytes.错误,显示文件的大小超出了允许的范围。查看了官方文档,原来Spring Boot工程嵌入的tomcat限制了请求的文件大小,这一点在Spring Boot的官方文档中有说明,原文如下

65.5 Handling Multipart File Uploads
Spring Boot embraces the Servlet 3 javax.servlet.http.Part API to support uploading files. By default Spring Boot configures Spring MVC with a maximum file of 1Mb per file and a maximum of 10Mb of file data in a single request. You may override these values, as well as the location to which intermediate data is stored (e.g., to the /tmp directory) and the threshold past which data is flushed to disk by using the properties exposed in the MultipartProperties class. If you want to specify that files be unlimited, for example, set the multipart.maxFileSize property to -1.The multipart support is helpful when you want to receive multipart encoded file data as a @RequestParam-annotated parameter of type MultipartFile in a Spring MVC controller handler method.

文档说明表示,每个文件的配置最大为1Mb,单次请求的文件的总数不能大于10Mb。要更改这个默认值需要在配置文件(如application.yml)中加入两个配置

Spring Boot2.0之后的版本配置修改为:


spring:
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 20MB

spring boot整合Mybatis plus的代码生成器

引入坐标

<!-- mybatis plus 代码生成器 -->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.2</version>
    </dependency>

    <!--代码生成器-->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-generator</artifactId>
      <version>3.4.1</version>
    </dependency>
    
    <!--模板-->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-generator</artifactId>
      <version>3.4.1</version>
    </dependency>

代码示例:

package com.yidong.untils;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

public class TestCode {
    public static void main(String[] args) {
        //代码生成器
        AutoGenerator generator =new AutoGenerator();

        //全局配置策略
       GlobalConfig gc = new GlobalConfig();
        //动态获取路径
        String path = System.getProperty("user.dir");
        gc.setFileOverride(false);//是否覆盖同名文件
        gc.setActiveRecord(true);//是否需要AR
        gc.setEnableCache(true);//是否需要二级缓存
        gc.setBaseResultMap(true);//默认的requestmap
        gc.setBaseColumnList(false);
        gc.setIdType(IdType.AUTO);//主键自增策略
        //输出的路径
        gc.setOutputDir(path+"/src/");


        //数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/mydb?useUnicode=true&useSSL=false&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");

        //包的配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.yidong")
                .setController("controller")
                .setMapper("mapper")
                .setEntity("pojo")
                .setXml("mapper")
                .setService("service");

        //策略配置
        StrategyConfig stConfig = new StrategyConfig();
        stConfig.setCapitalMode(true) //全局大写命名
                .setNaming(NamingStrategy.underline_to_camel) // 数据库表映射到实体的命名策略
                .setTablePrefix("sp_")
                .setInclude("sp_category"); // 生成的表,多个表继续传递即可,String类型的可变参数

        //将策略配置对象集成到代码生成器中
        generator.setGlobalConfig(gc);
        generator.setDataSource(dsc);
        generator.setPackageInfo(pc);
        generator.setStrategy(stConfig);

        //执行生成
        generator.execute();


    }

}

如果 runDashboard 控制台没有出来,右上角搜索 即可

运用spring cloud框架基于spring boot构建微服务,一般需要启动多个应用程序,在idea开发工具中,多个同时启动的应用

需要在RunDashboard运行仪表盘中可以更好的管理,但有时候idea中的RunDashboard窗口没有显示出来,也找不到直接的开启按钮

idea中打开Run Dashboard的方法如下

view > Tool Windows > Run Dashboard

父工程pom


<?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.yidong.springcloud</groupId>
  <artifactId>MyCloud</artifactId>
  <version>1.0-SNAPSHOT</version>
  <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.18.16</lombok.version>
    <mysql.version>5.1.47</mysql.version>
    <druid.verison>1.1.14</druid.verison>
    <mybatis.spring.boot.verison>2.1.4</mybatis.spring.boot.verison>
  </properties>

  <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>
      <!--spring cloud Hoxton.SR1-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.SR1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud alibaba 2.1.0.RELEASE-->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.0.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!-- MySql -->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
      </dependency>
      <!-- Druid -->
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid.verison}</version>
      </dependency>
      <!-- mybatis-springboot整合 -->
      <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.spring.boot.verison}</version>
      </dependency>
      <!--lombok-->
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
      </dependency>
      <!--junit-->
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
      </dependency>
      <!-- log4j -->
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <fork>true</fork>
          <addResources>true</addResources>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

服务发布者 pom

<?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>MyCloud</artifactId>
        <groupId>com.yidong.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloddproviderpayment8001</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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
      </dependency>

 </dependencies>
</project>

server:
  port: 8081

spring:
  application:
    name: cloud-payment-service

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2019?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
    username: root
    password: root

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.yidong.springcloud.entities  #所在Entry别名

公共的返回的json实体

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private Integer code;
    private String message;
    private T data;

    public CommonResult(Integer code,String message){
        this(code,message,null);
    }
}

服务消费者

消费方只有controller 、entities、配置RestTemplate的配置类

配置类

package com.yidong.springcloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;//网络客户端

@Configuration
public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
        /*
        RestTemplate提供了多种便捷访问远程http服务的方法,
        是一种简单便捷的访问restful服务模板类,是spring提供的用于rest服务的客户端模板工具集
        */
    }
}

控制类

package com.yidong.springcloud.controller;

import com.yidong.springcloud.entities.CommonResult;
import com.yidong.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class PaymentController {

    @Autowired
    private RestTemplate restTemplate;

    //远程调用的 地址
    public static final String PAYMENY_URL = "http://localhost:8001";

    @PostMapping("/payment/create")
    public CommonResult create(Payment payment){
      return restTemplate.postForObject(PAYMENY_URL,payment,CommonResult.class);
    }

    @GetMapping("/payment/get/{id}")
    public CommonResult create(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENY_URL+"/payment/get/"+id,CommonResult.class);
    }


}

一、Eureka

1.1服务治理

SpringCloud封装了Netflix公司开发的Eureka模块来实现服务治理

在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

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

在服务注册与发现中,有一个注册中心。当服务器启动时候,会把当前自己服务器的信息 比如服务地址 通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用。RPC远程调用框架核心设计思想:在于注册中心,因为便用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

1597291856582

Eureka 官方停更不停用,以后可能用的越来越少。

Eureka 是 Netflix 开发的,一个基于 REST 服务的,服务注册与发现的组件,以实现中间层服务器的负载平衡和故障转移。

Eureka 分为 Eureka Server 和 Eureka Client及服务端和客户端。Eureka Server为注册中心,是服务端,而服务提供者和消费者即为客户端,消费者也可以是服务者,服务者也可以是消费者。同时Eureka Server在启动时默认会注册自己,成为一个服务,所以Eureka Server也是一个客户端,这是搭建Eureka集群的基础。

Eureka Client:一个Java客户端,用于简化与 Eureka Server 的交互(通常就是微服务中的客户端和服务端)。通过注册中心进行访问。是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(roundrobin)负载算法去的负载均衡器

在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)
Eureka Server:提供服务注册服务,各个微服务节,通过配置启动后,会在Eureka Serverc中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点信息,服务节点的信息可以在界面中直观看到。
服务在Eureka上注册,然后每隔30秒发送心跳来更新它们的租约。如果客户端不能多次续订租约,那么它将在大约90秒内从服务器注册表中剔除。注册信息和更新被复制到集群中的所有eureka节点。来自任何区域的客户端都可以查找注册表信息(每30秒发生一次)来定位它们的服务(可能在任何区域)并进行远程调用

服务提供者向注册中心注册服务,并每隔30秒发送一次心跳,就如同人还活着存在的信号一样,如果Eureka在90秒后还未收到服务提供者发来的心跳时,那么它就会认定该服务已经死亡就会注销这个服务。这里注销并不是立即注销,而是会在60秒以后对在这个之间段内“死亡”的服务集中注销,如果立即注销,势必会对Eureka造成极大的负担。这些时间参数都可以人为配置。

Eureka还有自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,所以不会再接收心跳,也不会删除服务。

客户端消费者会向注册中心拉取服务列表,因为一个服务器的承载量是有限的,所以同一个服务会部署在多个服务器上,每个服务器上的服务都会去注册中心注册服务,他们会有相同的服务名称但有不同的实例id,所以拉取的是服务列表。我们最终通过负载均衡来获取一个服务,这样可以均衡各个服务器上的服务。

1.2单机版Eureka构建

消费者端口80,提供者端口8001。

Eureka端口7001

注册中心

pom文件添加


  <dependencies>
      <!--eureka  server-->
      <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>

      <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>

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>

  </dependencies>

配置文件:

server:
  port: 7001

eureka:
  instance:
    hostname: localhost #eureka服务器的实例名称
  client:
    #不注册自己
    register-with-eureka: false
    #自己是服务的注册中心 检索和发现其他服务
    fetch-registry: false
    service-url:
      # 设置与 Eureka Server 交互的地址,查询服务 和 注册服务都依赖这个地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类

开启Eureka

package com.yidong.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

// exclude :启动时不启用 DataSource的自动配置检查
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaServer   // 表示它是服务注册中心
public class EurekaMain7001 {

    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class,args);
    }


}

访问:http://localhost:7001/

注册服务

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

spring:
  application:
    name: cloud-consumer-order80

  devtools:
    livereload:
      enabled: true
eureka:
  client:
    # 注册进 Eureka 的服务中心
    register-with-eureka: true
    # 检索 服务中心 的其它服务
    fetch-registry: true
    service-url:
      # 设置与 Eureka Server 交互的地址
      defaultZone: http://localhost:7001/eureka/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kNTY2TwI-1630289944524)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629526432151.png)]

1.3集群版Eureka构建

  • 先启动eureka注册中心
  • 启动服务提供者payment支付服务
  • 支付服务启动后会把自身信息化 服务以别名方式注册进eureka
  • 消费者order服务在要调用接囗时,使用服务别名去注册中心取实际的RPC远程调用地址
  • 消费者获得调用地址后,底层实际是利用HttpClient技术实现远程调用
  • 消费者获得服务地址后会存jvm内存中,默认每间隔30s更新一次服务调用地址

Eureka Server在设计的时候就考虑了高可用设计,在Eureka服务治理设计中,所有节点既是服务的提供方,也是服务的消费方,服务注册中心也不例外。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NHnMeqbH-1630289944527)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629533219237.png)]

1.3.1 注册中心集群配置

7001配置文件

server:
  port: 7001

eureka:
  instance:
    hostname:  eureka7001.com  #eureka服务器的实例名称
  client:
    #不注册自己
    register-with-eureka: false
    #自己是服务的注册中心 检索和发现其他服务
    fetch-registry: false
    service-url:
      # 设置与 Eureka Server 交互的地址,查询服务 和 注册服务都依赖这个地址
      defaultZone: http://eureka7002.com:7002/eureka/
7002配置文件
server:
  port: 7002

eureka:
  instance:
    hostname:  eureka7002.com  #eureka服务器的实例名称
  client:
    #不注册自己
    register-with-eureka: false
    #自己是服务的注册中心 检索和发现其他服务
    fetch-registry: false
    service-url:
      # 设置与 Eureka Server 交互的地址,查询服务 和 注册服务都依赖这个地址
      defaultZone: http://eureka7001.com:7001/eureka/
  • 将交互地址换成其他机器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Naqd8CMs-1630289944529)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629534860440.png)]

注册服务修改
server:
  port: 80

spring:
  application:
    name: cloud-consumer-order80

  devtools:
    livereload:
      enabled: true
eureka:
  client:
    # 注册进 Eureka 的服务中心
    register-with-eureka: true
    # 检索 服务中心 的其它服务
    fetch-registry: true
    service-url:
      # 设置与 Eureka Server 交互的地址
      # 集群版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka


1.3.2 服务的集群化配置

将原有项目整个复制一份,修改端口号

服务消费者的访问地址不能写死成ip地址,修改为微服务的名称

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNsIU3v9-1630289944532)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629537115025.png)]

开启负载均衡


package com.yidong.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;//网络客户端

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced //开启负载均衡  默认的是轮询
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
        /*
        RestTemplate提供了多种便捷访问远程http服务的方法,
        是一种简单便捷的访问restful服务模板类,是spring提供的用于rest服务的客户端模板工具集
        */
    }
}

1.4 其他设置

主机名称的修改

访问ip有信息提示

eureka:
  client:
    # 注册进 Eureka 的服务中心
    register-with-eureka: true
    # 检索 服务中心 的其它服务
    fetch-registry: true
    service-url:
      # 设置与 Eureka Server 交互的地址
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  instance:
    # 主机名称的修改
    instance-id: payment8001
    # 地址显示
    prefer-ip-address: true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vP81TgxP-1630289944534)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629538063402.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-veOaRjXq-1630289944536)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629538365765.png)]

1.5 服务发现Discovery

1.启动类上

@EnableDiscoveryClient

2.服务提供者

 @GetMapping(value = "/payment/discovery")
    public Object discovery(){
        //哪些服务
        List<String> services = discoveryClient.getServices();
        for (String element:services) {
            System.out.println(element);
        }

        //某个服务的信息
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance instance:instances) {
            System.out.println(instance.getInstanceId()+""+instance.getHost());
        }
        return this.discoveryClient;
    }

1.6 Eureka 自我保护机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HkR5zgPK-1630289944537)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629539578240.png)]

  • 自我保护机制:默认情况下Eureka CIient定时向Eureka Server端发送心跳包。
  • 当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E1OE8jjo-1630289944539)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629539802355.png)]

关闭自我保护模式

注册中心
server:
#    关闭自我保护机制
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000
生产者
    # Eureka客户端像服务端发送心跳的时间间隔,单位s,默认30s
    least-renewal-interval-in-seconds: 1
    # Rureka服务端在收到最后一次心跳后等待时间上线,单位为s,默认90s,超时将剔除服务
    least-expiration-duration-in-seconds: 2

二、Zookeeper

springCloud 整合 zookeeper

  • zookeeper是一个分布式协调工具,可以实现注册中心功能
  • 关闭Linux服务器防火墙后动zookeeper服务器
  • zookeeper服务器取代Eureka服务器,zk作为服务注册中心
服务提供者

1.引入pom坐标

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>

2.启动类上添加注解

@EnableDiscoveryClient

3.配置文件

server:
  port: 8004

spring:
  application:
    name: cloud-provider-payment
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/cloud?characterEncoding=utf8&useSSL=false
    username: root
    password: root

    devtools:
      livereload:
        enabled: true #开启热部署
  cloud:
    zookeeper:
      connect-string: 192.168.43.215:2181


mybatis:
    mapper-locations: classpath:mapper/*.xml
    type-aliases-package: com.yidong.springcloud.entities  #所在Entry别名


启动后会在zookpper中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ouAEaHIu-1630289944540)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629558124651.png)]

服务消费者
  • pom、application.yml和提供者类似
  • 配置RestTemplate的时候注意要加上**@LoadBalanced**
package com.yidong.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }

}

package com.yidong.springcloud.controller;

import com.yidong.springcloud.entities.CommonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class CustomerZkController {

    public static final String INVOKE_URL= "http://cloud-provider-payment"; //和原来一样

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/customer/payment/zk")
    public String paymentInfo(){
        String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk",String.class);
        return result;
    }

    @GetMapping("/payment/get/{id}")
    public CommonResult create(@PathVariable("id") Long id){
        System.out.println(id);
        System.out.println(id);
        return restTemplate.getForObject(INVOKE_URL+"/payment/get/"+id,CommonResult.class);
    }

}

三、Ribbon

3.1主要介绍

SpringCloud Ribbon是基于NetfIixRibbon实现的一套客户端负载均衡的工具。

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

img

Ribbon在工作时分成两步:

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

3.2 负载均衡

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

Ribbon 负载均衡规则类型:

  • com.netflix.loadbalancer.RoundRobinRule:轮询
  • com.netflix.loadbalancer.RandomRule:随机
  • com.netfIix.IoadbaIancer.RetryRuIe:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
  • WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
  • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
  • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

img

3.3配置负载均衡规则

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

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

注意上面说的,而Springboot主启动类上的 @SpringBootApplication 注解,相当于加了@ComponentScan注解,会自动扫描当前包及子包,所以注意不要放在SpringBoot主启动类的包内。

(1)新建规则bean对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1waqZZCg-1630289944543)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629601773251.png)]

(2)启动类上添加注解指定自己的规则类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AFj7Aew7-1630289944545)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629602078501.png)]

3.4轮询算法原理

负载均衡轮询算法

rest接口第几次请求次数 % 服务器集群总数量 = 实际调用服务器位置下标

每次服务器重启后,rest接口计数从1开始。

img

四、openfeign

4.1 概述

这里和之前学的dubbo很像,例如消费者的controller 可以调用提供者的 service层方法,但是不一样,它貌似只能调用提供者的 controller,即写一个提供者项目的controller的接口,消费者来调用这个接口方法,就还是相当于是调用提供者的 controller ,和RestTemplate 没有本质区别

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

FeignOpenFeign
Feign是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端。Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务OpenFeign是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的下的接囗,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
org.springframework.cloud spring-cloud-starter-feignorg.springframework.cloud spring-cloud-starter-openfeign

4.2 消费端

  1. 新建cloud-consumer-feign-order80模块

pom文件添加坐标

<dependencies>
        <!-- Open Feign,他里面也有ribbon,所以有负载均衡 -->
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- eureka Client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.dkf.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

yaml文件配置eureka

server:
  port: 80
spring:
  devtools:
    livereload:
      enabled: true

  application:
    name: payment-service-consumer

eureka:
  client:
#    自己不注册
    register-with-eureka: false
#    搜索服务列表
    fetch-registry: true
#    服务的eurka列表
    service-url:
      defalutZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

启动类上添加开启openFeign的注解 @EnableFeignClients

package com.yidong.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

新建服务接口

package com.yidong.springcloud.service;

import com.yidong.springcloud.entities.CommonResult;
import com.yidong.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Component
@FeignClient("CLOUD-PAYMENT-SERVICE") //服务的名称
public interface PaymentFeignService {

    //和服务提供者的controller一样
    @GetMapping("/payment/get/{id}")
    public CommonResult<Payment> get(@PathVariable("id") Long id);


    @PostMapping("/payment/create")
    public CommonResult<Payment> create(@RequestBody Payment payment);


}

controller调用service

package com.yidong.springcloud.controller;

import com.yidong.springcloud.entities.CommonResult;
import com.yidong.springcloud.entities.Payment;
import com.yidong.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@Slf4j
public class CustomerFeignController {

    @Autowired
    private PaymentFeignService paymentFeignService;

    @GetMapping("/customer/feign/payment/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        System.out.println(id);
        return paymentFeignService.get(id);
    }

    @PostMapping("/payment/create")
    public CommonResult<Payment> create(Payment payment){
        System.out.println("aahhhh");
        return paymentFeignService.create(payment);
    }

}

4.3 超时控制

Openfeign默认超时等待为一秒,在消费者里面配置超时时间

消费者的配置文件

server:
  port: 80
spring:
  devtools:
    livereload:
      enabled: true

  application:
    name: payment-service-consumer

eureka:
  client:
#    自己不注册
    register-with-eureka: false
#    服务的eurka列表
    service-url:
      # 设置与 Eureka Server 交互的地址
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

4.4 开启打印日志

Feign共了日志打印功能,我们可以诵过配置来调整日志级别,从而了解Feign中Tttp请求的细节。

说白了就是对Feign接口的调用情况进行监控和输出。

日志级别:

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

(1)配置bean

package com.yidong.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ReignConfig {

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

(2)配置文件中配置

#日志
logging:
  level:
    com.yidong.springcloud.service.PaymentFeignService: debug

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AyClTsjd-1630289944547)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629634238629.png)]

五、Hystrix 断路器

5.1概述

官方地址:https://github.com/Netflix/Hystrix/wiki/How-To-Use

"断路器“本身是一种开关装置,当某个服务单元发生故障之后,涌过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

服务雪崩

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

A-->B,C
BC-->D
服务降级fallback服务器忙碌或者网络拥堵时,不让客户端等待并立刻返回一个友好提示,fallback。对方系统不可用了,你需要给我一个兜底的方法,不要耗死。向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务熔断break类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示 就是保险丝:服务的降级->进熔断->恢复调用链路
服务限流flowlimit秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟几个,有序进行

可见,上面的技术不论是消费者还是提供者,根据真实环境都是可以加入配置的。

5.2 服务降级

服务提供者

引入坐标

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

application添加配置

server:
  port: 80

spring:
  application:
    name: cloud-payment-service

  devtools:
    livereload:
      enabled: true

eureka:
  client:
    # 注册进 Eureka 的服务中心
    register-with-eureka: true
    # 检索 服务中心 的其它服务
    fetch-registry: true
    service-url:
      # 设置与 Eureka Server 交互的地址
      # 集群版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka



启动类

package com.yidong.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}


编写正常的controller和service

service

package com.yidong.springcloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    /* 可以正常访问的方法*/
    public String paymentinfo_Ok(Integer id){
        return "线程池:" + Thread.currentThread().getName() + "--paymentInfo_OK,id:" + id;
    }

    /* 超时访问的方法 */
    public String paymentinfo_Timeout(Integer id){
        int interTime = 3;
        try{
            TimeUnit.SECONDS.sleep(interTime);//模拟超时
        }catch (Exception e){
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + "--paymentInfo_Timeout,id:" + id +
                "耗时" + interTime + "秒钟--";
    }

}

controller

package com.yidong.springcloud.controller;

import com.yidong.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @GetMapping("/payment/hystrix/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id){
        log.info("paymentInfo_OKKKKOKKK");
        return paymentService.paymentinfo_Ok(id);
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_Timeout(@PathVariable("id")Integer id){
        log.info("paymentInfo_timeout");
        return paymentService.paymentinfo_Timeout(id);
    }
}

测试

服务消费者

新建消费者 cloud-customer-feign-hystrix-order80 模块:以feign为服务调用,eureka为服务中心的模块

pom

<?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>MyCloud</artifactId>
        <groupId>com.yidong.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-customer-feign-hystrix-order80</artifactId>

    <dependencies>
    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <!--eureka client-->
    <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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

</project>

yaml

<?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>MyCloud</artifactId>
        <groupId>com.yidong.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-customer-feign-hystrix-order80</artifactId>

    <dependencies>
    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <!--eureka client-->
    <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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

</project>

启动类

package com.yidong.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

服务降级的思路

  1. 对于服务提供者8001出现超时时候,调用者80不能一直卡死等待,需要有服务降级
  2. 对于服务提供者8001出现down机的时候,调用者80不能一直卡死等待,需要有服务降级
  3. 服务提供者正常,调用者自身出现问题必须要有服务降级

服务提供者降级

(1)service中添加**@HystrixCommand**

package com.yidong.springcloud.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    /* 可以正常访问的方法*/
    public String paymentinfo_Ok(Integer id){
        return "线程池:" + Thread.currentThread().getName() + "--paymentInfo_OK,id:" + id;
    }

    /* 超时访问的方法 */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                value="3000")
    })
    public String paymentinfo_Timeout(Integer id){
        int interTime = 5;
        try{
            TimeUnit.SECONDS.sleep(interTime);//模拟超时
        }catch (Exception e){
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + "--paymentInfo_Timeout,id:" + id +
                "耗时" + interTime + "秒钟--";
    }

    //兜底方法
    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池"+Thread.currentThread().getName()+"paymentInfo_TimeOutHandler,id"+id;
    }

}

(2)在启动类上添加注解@EnableCircuitBreaker
package com.yidong.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient
@EnableCircuitBreaker //开启断路器
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}


消费者进行降级

上面的案例是服务端降级,现在我们服务端处理3s,然后返回。但是消费端等1s就等不住了,这时候就需要消费端也有降级方法

80的降级。原理是一样的,上面的@HystrixCommand降级可以放在服务端,也可以放在消费端。但一般放在客户端。

(1) 在配置文件中开启hystrix
 feign:
  hystrix:
    enabled: true
(2) 启动类上添加@EnableHystrix
(3) 在消费端采用对controller添加@HytrixCommand进行降级
package com.yidong.springcloud.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.yidong.springcloud.sevice.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_Timeout(@PathVariable("id")Integer id){
//        int a= 10/0;
        return paymentHystrixService.paymentInfo_Timeout(id);
    }

    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试 或者 自己运行出错请检查自己,o(╥﹏╥)o";
    }

}


全局服务降级

目前问题:

  • 每个业务方法对应一个兜底的方法,代码膨胀
  • 同样和自定义分开

解决方法:

注解@DefaultProperties

在controller上添加**@DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”)**

自己指定了兜底方法的话就走自己的兜底方法,否则走全局的兜底方法*

package com.yidong.springcloud.controller;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.yidong.springcloud.sevice.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
//设置默认的兜底方法
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_Timeout(@PathVariable("id")Integer id){
//        int a= 10/0;
        return paymentHystrixService.paymentInfo_Timeout(id);
    }

    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试 或者 自己运行出错请检查自己,o(╥﹏╥)o";
    }

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

}

全局的降级服务FeignFallback(推荐:代码耦合性低)

服务降级,客户端去调用服务端,碰上服务端宕机或关闭。

本次案例降级处理是在客户端80实现完成的,与服务端8001没有关系。只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。

(1) 配置文件 开启Feign的Hystrix
feign:
  hystrix:
    enabled: true
(2) 新建一个类实现消费端的service
package com.yidong.springcloud.sevice;

import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
    }

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

(3) service上的@FeignClient上的fallback指定实现类
@Component
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {

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

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

}
(4) 设置超时时间

默认设置

hystrix.command.default.execution.timeout.enable=true    ## 默认值

## 为false则超时控制有ribbon控制,为true则hystrix超时和ribbon超时都是用,但是谁小谁生效,默认为true

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000  ## 默认值

## 熔断器的超时时长默认1秒,最常修改的参数
配置 Ribbon 的 超时时长

注意:hystrix 默认自带 ribbon包

ribbon:
	ReadTimeout: xxxx
	ConnectTimeout: xxx

5.3服务熔断

5.3.1 概述

  • 实际上服务熔断 和 服务降级 没有任何关系,就像 java 和 javaScript

  • 服务熔断,有点自我恢复的味道

  • 如同保险丝一样,达到最大的服务风问候,直接拒绝访问,拉闸限电,采用服务降级的方法进行友好的提示

  • 服务降级->进而熔断->恢复调用链路

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

    当检测到该节点微服务响应正常后恢复调用链路,在SpringCloud框架机制通过Hystrix实现,Hystrix会监控微服务见调用的状况,当失败的调用到一个阈值,默认是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand

5.3.2 服务熔断和降级的区别

  1. 调用失败会触发服务降级,降级会调用fallback方法,但是无论如何都会走正常的方法在调用fallbcak方法
  2. 单位时间内调用次数过多,降级次数过多,则触发熔断
  3. 熔断后会跳过正常方法直接调用fallback方法
  4. 熔断后服务不可用就是因为跳过正常的方法直接执行fallback
  5. 当检测到该节点的微服务调用正常后,恢复调用链路

5.3.3 熔断的状态

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

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

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

5.3.4 服务提供者添加熔断

启动类上添加   
@EnableCircuitBreaker

修改service添加注解@HystrixCommand

package com.yidong.springcloud.service;

import cn.hutool.core.util.IdUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    /* 可以正常访问的方法*/
    public String paymentinfo_Ok(Integer id){
        return "线程池:" + Thread.currentThread().getName() + "--paymentInfo_OK,id:" + id;
    }

    /* 熔断 */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="3000"),
    })
    public String paymentinfo_Timeout(Integer id){
        int interTime = 5;
        try{
            TimeUnit.SECONDS.sleep(interTime);//模拟超时
        }catch (Exception e){
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + "--paymentInfo_Timeout,id:" + id +
                "耗时" + interTime + "秒钟--";
    }

    //=====服务熔断
    @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"),// 失败率达到多少后跳闸
            }) // 在10s内10次请求有60%失败 // 先看次数,再看百分比
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        if(id < 0) {
            throw new RuntimeException("******id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();// 等价于UUID.randomUUID().toString(); //pom中有hutool-all

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
    }

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


    //兜底方法
    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池"+Thread.currentThread().getName()+"paymentInfo_TimeOutHandler,id"+id;
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EM376pCp-1630289944549)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629704972090.png)]

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

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

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

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

参数的查找在HystrixCommandProprerties

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mL6wVu1V-1630289944550)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629705457148.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxD1xYs0-1630289944551)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629706479972.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fe6n7tMp-1630289944552)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629706498096.png)]

例如上面提到的全局服务降级,并且是feign+hystrix整合,即 service 实现类的方式,如何做全局配置?

hystrix:
command:
default:
circuitBreaker:
  enabled: true
  requestVolumeThreshold: 10
  sleepWindowInMilliseconds: 10000
  errorThresholdPercentage: 60

5.3.5 工作流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6NxMXREM-1630289944553)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629706726116.png)]

5.4Hystrix DashBoard

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(HystrixDashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream实现了对以上指标的监控。SpringCloud也提供了HystrixDashboard的整合,对监控内容转化成可视化界面

(1)新建工程 并且引入pom文件引入

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

(2)添加**@EnableHystrixDashboard**注解

访问: http://localhost:9001/hystrix

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7zrLQtpm-1630289944554)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629710047736.png)]

(3)所有的微服务提供者都要在监控依赖配置
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

新版本的Hystrix需要在启动类上添加**@EnableCircuitBreaker**

@Bean // 注入豪猪的servlet // 该servlet与服务容错本身无关 // springboot默认路径不是/hustrix.stream,只要在自己的项目里自己配置servlet
public ServletRegistrationBean getServlet(){
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(streamServlet);
    servletRegistrationBean.setLoadOnStartup(1);
    servletRegistrationBean.addUrlMappings("/hystrix.stream");
    servletRegistrationBean.setName("HystrixMetricsStreamServlet");
    return servletRegistrationBean;
}

完整的微服务提供者的启动类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xw0cmocG-1630289944556)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629710361653.png)]

(4)启动9001端口监控8001

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oh8hLuht-1630289944557)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629710456284.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ei2cGcTd-1630289944558)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629710685125.png)]

六、服务网关

6.1 Gateway

简介

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

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

一方面因为Zuul 1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的是亲儿子产品,值得信赖。而且很多功能比zull用起来都简单便捷。

Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?

多方面综合考虑Gateway是很理想的网关选择。

img

SpringCloudGateway与Zuul的区别

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

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

Gateway是什么

Gateway特性
  • 基于SpringFramework5,ProjectReactor和SpringBoot 2.0进行构建;
  • 动态路由:能够匹任何请求属性;
  • 可以对路由指定Predicate(断言)和Filter(过滤器)
  • 集成Hystrix的断路器功能;
  • 集成SpringCloud服务发现功能;
  • 易于编写的Predicate(断言)和Filter(过滤器)
  • 请求限流功能;
  • 支持路径重写。
GateWay的三大核心概念

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

例子:通过断言虽然进来了,但老师要罚站10min(过滤器操作),然后才能正常坐下听课
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

6.2入门配置

现在实现,通过Gateway (网关) 来访问其它项目,这里选择之前8001项目,要求注册进Eureka Server 。其它没要求。

新建项目 cloud-gateway-gateway9527

(1) 添加pom依赖(gateway和spring web+actuator不能同时存在,即web相关jar包不能导入)
<dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client gateWay网关作为一种微服务,也要注册进服务中心。哪个注册中心都可以,如zk-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    <!-- gateway和spring web+actuator不能同时存在,即web相关jar包不能导入 -->
        <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>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.dkf.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

(2)主配置类
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain9527.class,args);
    }
}

(3)配置文件

server:
  port: 9527
spring:
  devtools:
    livereload:
      enabled: true
  application:
    name: cloud-gateway

  cloud:
    gateway:
      routes:
        - id: payment_routh                      # 路由id 必须唯一
             # 匹配后提供服务的路由地址 #uri+predicates 
             # 要访问这个路径得先经过9527处理
          uri: http://localhost:8001       
          predicates:
                # 断言,路径相匹配的进行路由
            - Path=/payment/get/**

        - id: payment_routh2
          uri: http://localhost:8001
          predicates:
            - Path=/payment/lb/**
            
  # 注册进 eureka Server # 网关他本身也是一个微服务,也要注册进注册主中心
eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

可见,当我们访问 http://localhost:9527/payment/get/1 时,即访问网关地址时,会给我们转发到 8001 项目的请求地址,以此作出响应。

加入网关前:http://localhost:8001/payment/get/1

加入网关后:http://localhost:9527/payment/get/1

6.3 动态配置

这里所谓的动态配置就是利用服务注册中心,来实现 负载均衡 的调用 多个微服务。

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

注意,这是GateWay 的负载均衡

对yml进行配置:让其先通过gateway,再通过gateway去注册中心找提供者

server:
  port: 9527
spring:
  devtools:
    livereload:
      enabled: true
  application:
    name: cloud-gateway

  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由

      routes:
        - id: payment_routh
          uri: lb://CLOUD-PAYMENT-SERVICE   # lb 属于GateWay 的关键字,代表是动态uri,即代表使用的是服务注册中心的微服务名,它默认开启使用负载均衡机制
          predicates:
            - Path=/payment/get/**

        - id: payment_routh2
          uri: lb://CLOUD-PAYMENT-SERVICE
          predicates:
            - Path=/payment/lb/**
eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  instance:
    hostname: cloud-gateway-service

6.4Gateway:Predicate

注意到上面yml配置中,有个predicates 属性值。

  • After Route Predicate

  • Before Route Predicate

  • Between Route Predicate

  • Cookie Route Predicate

  • Header Route Predicate

  • Host Route Predicate

  • Method Route Predicate

  • Path Route Predicate

  • Query Route Predicate

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmBT5L5h-1630289944560)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629775217186.png)]

具体使用

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

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
            #- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
            #- Cookie=username,zzyy
            #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式

参考:

https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#configuring-route-predicate-factories-and-gateway-filter-factories

6.5 Gateway:Filter

生命周期

pre:
post:

种类(参照官网)

GatewayFilter:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories
GlobalFilter:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#global-filters

常用的使用示例

server:
  port: 9527
spring:
  devtools:
    livereload:
      enabled: true
  application:
    name: cloud-gateway

  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由

      routes:
        - id: payment_routh
          uri: lb://CLOUD-PAYMENT-SERVICE   # lb 属于GateWay 的关键字,代表是动态uri,即代表使用的是服务注册中心的微服务名,它默认开启使用负载均衡机制
          //过滤器
          filters:
            - AddRequestHeader=X-Request-red, blue
          predicates:
            - Path=/payment/get/**

        - id: payment_routh2
          uri: lb://CLOUD-PAYMENT-SERVICE
          predicates:
            - Path=/payment/lb/**
eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  instance:
    hostname: cloud-gateway-service
自定义全局过滤器
package com.yidong.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

//全局过滤器
@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("uname");
        if (uname==null){
            log.info("*******用户名是nulL,非法用户");
            //设置状态
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        //下个过滤链路进行过滤
        return chain.filter(exchange);
    }

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

七、服务配置

1597213783265

7.1 概述

SpringCloud Config 分布式配置中心

微服务意味着要将单应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。

springCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理。比如数据库的信息,我们可以写到一个统一的地方。

常用的技术:

  • config+bus
  • alibaba nacos
  • 携程 阿波罗

7…2 SpringCloud Config

7.2.1简介

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

img

  • springcloud config分成客户端服务端
  • springcloud config也是一个独立的微服务应用,用来连接配置服务器并为客户端提供配置信息,加密/解密信息访问的接口
  • 服务端也是分布式配置中心
  • 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容

7.2.2 作用

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

7.3 SpringCloud config

7.3.1 config服务端配置

这个服务端指的是消费端与github之间的桥接

(1)github新建项目springcloud-config

(2 ) 拉取到本地

 git clone git@github.com:2441225890/springcloud-config.git

(3) 在git根目录下创建 yml配置文件

  • 开发环境:config-dev.yml
  • 生产环境:config-pro.yml
  • 测试环境:config-test.tml

本地上传到giethub上注意 与码云的区别默认分支是main 不是master

 # 添加origin 代替地址
 git remote add origin git@github.com:2441225890/springcloud-config.git
 
 git push origin main

(4)新建模块cloud-config-center3344

​ 引入pom文件:

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

        <!--eureka-client config Server也要注册进服务中心-->
        <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>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.dkf.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

yaml配置:

server:
  port: 3344
spring:
  application:
    name: cloud-config-center

  devtools:
    livereload:
      enabled: true

  cloud:
    config:
      server:
        git:
#          服务的地址
          uri: git@github.com:2441225890/springcloud-config.git
#          搜索的目录
          search-paths:
            - springcloud-config
#      读取的分支
      label: main


eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
        defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

主启动类添加注解**@EnableConfigServer**开启


@SpringBootApplication
@EnableConfigServer   //关键注解
public class ConfigCenterMain3344 { // 注意先去把Eureka启动起来
    public static void main(String[] args) {
        SpringApplication.run(ConfigCenterMain3344.class,args);
    }
}

(5)启动ConfigCenterMain3344

访问:http://127.0.0.1:3344/main/config-dev.yml 测试

报错原因:https://blog.csdn.net/keyue0459/article/details/105042063

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ahSzEHp-1630289944564)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629812064472.png)]

1597646186970

1597646308915

最后一个出的是json串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w7neAS1s-1630289944566)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629812907930.png)]

  • label:分支branch
  • name:服务名
  • profiles:环境dev/test/prod

7.3.2 config客户端配置

(1)新建cloud-config-client-3355模块

pom

	<dependencies>
        <!-- config Client 和 服务端的依赖不一样 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <!--eureka-client config Server也要注册进服务中心-->
        <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>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.dkf.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

启动类


@SpringBootApplication
@EnableEurekaClient //连接Euraka
public class ConfigClientMain3355 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientMain3355.class,args);
    }
}

要将Client模块下的application.yml文件改为bootstrap.yml,删掉application.yml这是很关键的

因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml

appllication.yml是用户级的资源配置项

bootstrap.ym1是系统级的,优先级更加高

SpringCloud会创建一个"Bootstrap Context"作为Spring应用的ApplicationContext的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment

Bootstrap 属性有高优先级,默认情况下,它们不会被本地配置覆盖。BootstrapContext和ApplicationContext、有着不同的约定,所以新增了一个bootstrap.yml文件,保证BootstrapContext和ApplicationContext配置的分离。

bootstrap.yml文件:

server:
  port: 3355

spring:
  application:
    name: config-client

  devtools:
    livereload:
      enabled: true

  cloud:
    config:
#      客户端的配置
      label: main   #分支名称
      name: config  #配置文件名称
      profile: dev  # 后缀名称
      uri: http://localhost:3344 #读取配置文件的地址(spring cloud config 的服务端)


eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

启动后发现远程github上的配置文件被加载执行

远程配置了端口号为3366,bootstrao.yml上的3355配置失效,当把远程的端口号去掉的时候,bootstrap.yml的配置端口才生效,必须要重新启动才可以生效(动态刷新)。

7.3.3 动态刷新

问题:

Linux运维修改GitHub上的配置文件内容做调整:比如修改config-dev.yml提交
刷新3344,发现ConfigServer服务端配置中心立刻响应,得到最新值了
刷新3355,发现ConfigClient客户端没有任何响应,拿到的还是旧值
客户端3355没有变化除非自己重启或者重新加载,才能拿到最新值

就是github上面配置更新了,config Server 项目上是动态更新的,但是,client端的项目中的配置,目前还是之前的,它不能动态更新,必须重启才行。

动态刷新问题的解决方案
手动版

(1) client必须要有actuator依赖

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

(2)bootstrap.yml中的配置

暴露监控端点

management:
  endpoints:
    web:
      exposure:
        include: "*"

完整配置文件:


server:
  port: 3355

spring:
  application:
    name: config-client

  devtools:
    livereload:
      enabled: true

  cloud:
    config:
#      客户端的配置
      label: main   #分支名称
      name: config  #配置文件名称
      profile: dev  # 后缀名称
      uri: http://localhost:3344 #读取配置文件的地址(spring cloud config 的服务端)


eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

management:
  endpoints:
    web:
      exposure:
        include: "*"

(3)在controller上添加注解@RefreshScope

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bka6TNwp-1630289944567)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629858592100.png)]

配置完成,但是还是不能在3355上获取最新值,还需要发送一个post请求

(4)向client发送post请求

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

两个必须:1.必须是 POST 请求,2.请求地址:http://localhost:3355/actuator/refresh

成功获得到最新值

方案的不足

但是又有一个问题,就是要向每个微服务客户端发送一次POST请求,当微服务数量庞大,又是一个新的问题。

能否广播,一次通知,处处生效?(还要求不要全广播,差异化管理,定点清除,20台只有18台更新)

就有下面的消息总线!

八、消息总线

1597213783265

8.1 概述

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让貝他连接在该主题上的实例都知道的消息。

作用:

  • 实现分布式自动刷新配置功能
  • spring cloud bus配合Spring Cloud Config使用可以实现配置的动态刷新

支持的两种消息代理:RabbitMQ和Kafka

实现原理

ConfigClient实例都监听MQ中同一个topic主题(默认是springCloud Bus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一topic的服务就能得到通知,然后去更新自身的配置

就是给其中一台发送我们的刷新POST,他刷新完后给Bus发消息,然后Bus通过消息中间件发送给BC进行更新

img

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

img

8.2 RabbitMQ

安装和配置

  1. 安装RabbitMQ的依赖环境 Erlang 下载地址: http://erlang.org/download/otp_win64_21.3.exe
  2. 安装RabbitMQ 下载地址: http://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe
  3. 进入 rabbitMQ安装目录的sbin目录下,打开cmd窗口,执行 【rabbitmq-plugins enable rabbitmq_management】
  4. 访问【http://localhost:15672/】,输入密码和账号:默认为guest

此处使用的是虚拟机上的的远程地址

常用的如下(sbin目录下):


#两种启动方式
 直接
 ./rabbitmq-server
 后台
 ./rabbitmq-server -detached

#开启服务
./rabbitmqctl start_app
#关闭服务
./rabbitmqctl stop_app

#打开web控制页面
./rabbitmq-plugins enable rabbitmq_management

8.3 设计思想

(1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置

img

(2)利用消息总线触发一个服务端ConfigServer的bus/refresh端点,而刷新所有的客户端

img

  • 图二的架构显然更加适合,图一不适合的原因如下

    • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责

    • 破坏了微服务各节点的对等性。

    • 有一定的局限性“例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的

8.4 动态刷新具体操作步骤

(1)服务端

(1)在spring cloud config 服务端添加消息总线支持

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

(2)服务端配置yaml


# rabbitMq的相关配置
rabbitmq:
  host: localhost
  port: 5672  # 这里没错,虽然rabbitMQ网页是 15672
  username: admin
  password: 123456
# rabbitmq 的相关配置2 暴露bus刷新配置的端点
management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'

(2)客户端

pom

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

yaml

rabbitmq:
  host: localhost
  port: 5672  # 这里没错,虽然rabbitMQ网页是 15672
  username: admin
  password: 123456

management:
  endpoints:
    web:
      exposure:
        include: "*"

完整的客户端

server:
  port: 3366

spring:
  application:
    name: config-client

  devtools:
    livereload:
      enabled: true

  cloud:
    config:
      uri: http://localhost:3344
      label: main
      profile: dev
      name: config

  rabbitmq:
    host: 192.168.43.215
    port: 5672
    username: admin
    password: 123456


eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

management:
  endpoints:
    web:
      exposure:
        include: "*"

完整的服务端

server:
  port: 3344
spring:
  application:
    name: cloud-config-center

  devtools:
    livereload:
      enabled: true

  cloud:
    config:
      server:
        git:
#          服务的地址
          uri: git@github.com:2441225890/springcloud-config.git
#          搜索的目录
          search-paths:
            - springcloud-config
#      读取的分支
      label: main


  rabbitmq:
    host: 192.168.43.215
    port: 5672
    username: admin
    password: 123456


eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
        defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'

(3)配置完成后发送post请求给config 的服务端

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

注意,之前是向config client 一个个发送请求,但是这次是向 config Server 发送请求,而所有的config client 的配置也都全部更新。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FtSDRQl4-1630289944574)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629886632974.png)]

8.5 动态刷新定点通知

新的需求:指定具体某一个实例(的参数)生效而不是全部,一些是最新值,一些是旧值

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

例子:curl -X POST "http//localhost:3344/actuator/bus-refresh/config-client:3355

即微服务名称+端囗号

/bus/refresh请求不再发送到具体的服务实例上,而是发给configserver并通过destination参数类指定需要更新配置的服务或实例

我们这里以刷新运行在3355端口上的config-client为例

只通知3355
不通知3366

九、消息驱动 SpringCloud Stream

9.1概述和理论知识

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

1597725567239

官方定义SpringCloud Stream是一个构建消息驱动微服务的框架。

官网: https://spring.io/projects/spring-cloud-stream#overview

应用程序通过inputs或者outputs来与SpringCloud Stream中binder对象(绑定器)交互

涌过我们配置来binding(绑定)而SpringCloud Stream的binder对象负责与消息中间件交互。

所以,我们只需要搞清楚如何与springCloudstrearn交互就可以方便使用消息驱动的方式。

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。

SpringCloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念

目前仅支持RabbitMQ、Kafka。

设计原理图

Spring Cloud Stream 应用模型

Stream标准流程套路:

binder:很方便的连接中间件,屏蔽差异
Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过channel对队列进行配置
Source(生产)和sink(消费):简单地可理解为参照对象是spring cloud stream自身,从stream发布消息就是输出,接收消息就是输入

img

常用注解:

组成说明
Middleware中间件,只支持RabbitMQ和Kafka
Binder消息中间件的封装,目前实行了KafKa和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,动态的改变消息的类型(对应Kafka的topic,RabbitMQ的exchange),可以通过配置文件来实现
@Input输入通道,通过该输入通道收到的消息进入应用程序
@Output注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener监听队列,用于消费者的队列的消息接收
@EnableBinding指信道channel和exchange绑定在一起

9.2实操部分

9.2.1消息生产者

新建三个模块

  • cloud-stream-rabbitmq-provide8801:作为生产者进行发消息模块
  • cloud-stream-rabbitmq-consumer8802:作为消息接收模块
  • cloud-stream-rabbitmq-consumer8802:作为消息接收模块

8801 pom


    <dependencies>
        <!-- stream-rabbit -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

        <!--eureka-client 目前,这个不是必须的-->
        <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>
        <dependency>
            <groupId>com.yidong.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

yaml

server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: #在此配置要绑定的RabbitMQ的服务信息
        defaultRabbit:  #定义的名称 用于和binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关环境配置
            spring:
              rabbitmq:
                host: 192.168.43.215
                port: 5672
                username: admin
                password: 123456


      #要绑定的rabbitMq的服务信息
      bindings: #服务的整合处理
        output: #表示生产者 向rabbitMQ发送消息
          destination: studyExchange  #表示要是用的Exchange名称
          content-type: application/json  #设置消息类型 本次是json 文本是“text/plain"
          binder: defaultRabbit #设置要绑定的消息服务的具体配置

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com: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 {
    public String send();
}

新建实现类

package com.yidong.springcloud.service.impl;

import com.yidong.springcloud.service.IMessageProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;

import java.util.UUID;

@EnableBinding(Source.class) //注意包的名称  定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {

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

    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload((serial)).build());
        System.out.println(serial);
        return null;
    }

}

9.2.2 消息消费者

新建模块 cloud-stream-rabbitmq-consumer8802

pom文件和生产者一样

yaml文件和生产者不同的是input和output

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: #在此配置要绑定的RabbitMQ的服务信息
        defaultRabbit:  #定义的名称 用于和binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关环境配置
            spring:
              rabbitmq:
                host: 192.168.43.215
                port: 5672
                username: admin
                password: 123456


      #要绑定的rabbitMq的服务信息
      bindings: #服务的整合处理
        input: #表示生产者 向rabbitMQ发送消息
          destination: studyExchange  #表示要是用的Exchange名称
          content-type: application/json  #设置消息类型 本次是json 文本是“text/plain"
          binder: defaultRabbit #设置要绑定的消息服务的具体配置

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com: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

新建controller 监听

package com.yidong.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

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

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

    @StreamListener(Sink.INPUT)
    public void  input(Message<String> message){
        System.out.println("消费者1号"+message.getPayload()+serverPort);
    }

}

克隆cloud-stream-rabbitmq-consumer8802产生cloud-stream-rabbitmq-consumer8803

9.2.3 遇到的问题(必须添加group分组才能解决重复消费和消息持久化)

重复消费

当运行时,会有两个问题。

第一个问题,两个消费者都接收到了消息,这属于重复消费。例如,消费者进行订单创建,这样就创建了两份订单,会造成系统错误。

注意在stream中处同一个group中的多个消费者是竞争关系,就能保证消息只会被其中一个应用消费一次。

1597731630685

解决方案:

(1)自定义配置分组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zi0XnbHV-1630289944578)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629909431702.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjPLzA45-1630289944579)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629909474218.png)]

(2) 自定义配置为一个组,解决重复消费

将两个yaml的group设置同一个值就是一个组

消息持久化

当服务宕机后重新启动后会将未消费的消息进行消费

加上group配置,就已经实现了消息的持久化。

十、分布式微服务链路追踪 Sleuth

10.1 概述

分布式请求链路跟踪,超大型系统。需要在微服务模块极其多的情况下,比如80调用8001的,8001调用8002的,这样就形成了一个链路,如果链路中某环节出现了故障,我们可以使用Sleuth进行链路跟踪,从而找到出现故障的环节。

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条杂的分布式服务调用链路,链路中每一环出现高延时或错误都会引起整个请求最后的失败。

sleuth 负责跟踪,而zipkin负责展示。

10.2具体使用

sleuth 负责跟踪,而zipkin负责展示。

zipkin 下载地址: http://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/2.12.9/zipkin-server-2.12.9-exec.jar

使用 【java -jar】 命令运行下载的jar包,访问地址:【 http://localhost:9411/zipkin/ 】

在消费者和80和生产者8001总添加pom和yaml

        <!-- 引入sleuth + zipkin -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

yaml

spring:
  zipkin:
    base-url: http://localhost:9411  # zipkin 地址
  sleuth:
    sampler:
      # 采样率值 介于0-1之间 ,1表示全部采集
      probability: 1

十一、SpringCloud Alibaba

11.1 概述

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

参考文档 请查看 WIKI

为 Spring Cloud Alibaba 贡献代码请参考 如何贡献

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

​ https://spring.io/projects/spring-cloud-alibaba

11.1.1 主要功能

  • 服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
  • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

基本使用:

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

11.1.2 组件

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

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

RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。

Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。

Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。

Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。

Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

更多组件请参考 Roadmap

11.2 Nacos(服务注册和配置中心)

Nacos = Eureka + Config + Bus

替代Eureka做服务注册中心

替代Config做服务配置中心

学习地址

github地址: https://github.com/alibaba/Nacos

Nacos 地址: https://nacos.io/zh-cn/

11.2.1 下载运行

下载地址:https://github.com/alibaba/nacos/releases/tag/1.1.4

解压后进入bin目录,执行startup.cmd

运行后可访问 : 【 http://localhost:8848/nacos/index.html】地址,默认账号密码都是nacos

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bR6VUsCE-1630289944581)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629919460875.png)]

12.2.2 Nacos作为服务的配置中心
nacos提供者

新建工程:cloudalibaba-provider-payment9001

pom

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

yaml

server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider

  devtools:
    livereload:
      enabled: true
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

management:
  endpoints:
    web:
      exposure:
        include: "*"

启动类

package com.yidong.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient //开启服务的注册与发现
public class NacosProviderDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(NacosProviderDemoApplication.class, args);
    }

}

新建cloudalibaba-provider-payment9002和上述9001一样的工程

Nacos 自带负载均衡机制

nacos消费者

新建消费者 模块: cloudalibaba-customer-nacos-order83

pom和生产者一样

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

yaml

server:
  port: 83
spring:
  application:
    name: nacos-order-consumer

  devtools:
    livereload:
      enabled: true

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

启动类

整合了openFeign和Ribbon

@SpringBootApplication
@EnableFeignClients
@RibbonClient(name = "nacos-payment-provider",configuration = MyRabbionRuler.class)
public class NacosConsumerDemoApplication83 {

    public static void main(String[] args) {
        SpringApplication.run(NacosConsumerDemoApplication83.class,args);
    }

}

controller

整合了两种调用方式一个是 RestTemplate 另外一个是openFeign

package com.yidong.springcloud.controller;

import com.yidong.springcloud.service.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class TestController {

    @Autowired
    private RestTemplate restTemplate;
    
    @Autowired
    private PaymentService paymentService;

    @Value("${service-url.nacos-user-service}")
    private String serverURL; //在yml里面写的提供者服务路径,  值为:http://nacos-provider


    @GetMapping(value = "/echo-rest/{id}")
    public String rest(@PathVariable Integer id) {
        return restTemplate.getForObject("http://nacos-payment-provider/payment/nacos/" + id, String.class);
    }


    @GetMapping(value = "/payment/nacos/{id}")
    public String feign(@PathVariable Integer id) {
        System.out.println("hhh");
        return paymentService.getPayment(id);
    }

}

service

@FeignClient(name = "nacos-payment-provider")//指定微服务的名称
public interface PaymentService {

    //和被调用方的的controller一样
    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id);


}

各种服务中心对比
服务注册与服务框架CAP模型控制台管理社区活跃度
EurekaAP支持低(2.x版本闭源)
ZookeeperCP支持
ConsulCP支持
NacosAP/CP支持
组件名语言CAP服务健康检查对外暴露接口SpringCloud集合
EurekajavaAP可配支持HTTP已集成
ConsulGoCP支持HTTP/DNS已集成
ZookeeperjavaCP支持客户端已集成

NACOS支持CP和AP切换

C要求一致性,A要求可用性 ,P是分区容错性。

11.3 nacos配置中心(自带动态刷新)

入门案例

新建cloudalibaba-config-nacos-client3377

pom

        <!-- 以 nacos 做服务配置中心的依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
     
        <!-- springcloud alibaba nacos 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

bootstrap.yaml

server:
  port: 3377

spring:
  devtools:
    livereload:
      enabled: true
  application:
    name: nacos-config-client

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml



application.yaml

spring:
  profiles:
    active: dev

必须要完整的配置

p r e f i x − {prefix}- prefix{spring.profiles.active}.${file-extension}

img

主启动类


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

测试controller

package com.yidong.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/config")
@RefreshScope  //保证动态刷新
public class ConfigController {

    @Value("${user.username}")
    private String username;

    @RequestMapping("/get")
    public String get() {
        return username;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ioVYImKW-1630289944585)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629969537399.png)]

nacos的优势

问题1:实际开发者,通常一个系统会准备dev/test/prod环境。如何保证环境启动时服务能正确读取nacos上相应环境的配置文件
用namespace区分环境

问题2:一个大型分布式微服务系统有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境。那怎么对微服务配置进行管理呢?
用group把不同的微服务划分到同一个分组里面去

1597808385154

Service就是微服务,一个service可以包含多个cluster集群,nacos默认cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。

比方说为了容灾,将service微服务分别部署在了杭州机房和广州机房,这是就可以给杭州机房的service微服务起一个集群名称HZ

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

最后instance就是微服务的实例

dgn方案(dataid+group+namespace)

  • dataid方案(就是nacos的文件名):

指定spring.profile.active和配置文件的dataID来使不同的环境下读取不同的配置
配置空间+配置分组+新建dev和test两个dataid:就是创建-后不同的两个文件名nacos-config-client-dev.yaml、nacos-config-client-test.yaml
通过IDEA里的spring.profile.active属性就能进行多环境下配置文件的读取

具体操作步骤:

(1)可视化界面上新增配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fzg5QfW4-1630289944589)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629972178469.png)]

(2)修改application.yaml

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QQWEk4FP-1630289944592)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629972222881.png)]

  • Group方案(默认DEFAULT_GROUP):

在nacos创建配置文件时,给文件指定分组。
在IDEA中该group内容
实现的功能:当修改开发环境时,只会从同一group中进行切换

具体步骤:

(1)新建配置文件添加分组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6wJLWOt6-1630289944594)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629972904798.png)]

(2)在bootstrap下添加group的配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLcp6Lhe-1630289944596)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629973016167.png)]

  • namespace方案(默认public):

这个是不允许删除的,可以创建一个新的命名空间,会自动给创建的命名空间一个流水号。
在nacos新建命名空间,自动出现7d8f0f5a-6a53-4785-9686-dd460158e5d4
在IDEA的yml中指定命名空间namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4
最后,dataid、group、namespace 三者关系如下:(不同的dataid,是相互独立的,不同的group是相互隔离的,不同的namespace也是相互独立的)

(1)新建命名空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lRbjalQD-1630289944597)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629973291521.png)]

(2)bootstrap.yaml

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0E5ltOCd-1630289944598)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629974439771.png)]

11.4 nacos集群/持久化(重要 )

网址:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tDxJEfbp-1630289944599)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1629974882977.png)]

deployDnsVipMode.jpg

VIP是虚拟IP,即nginx

nginx也该是集群

1597808678658

具体步骤(伪分布):

(1)下载nacos的Linux版本,上传并且解压

tar -xzvf  nacos-server-1.4.2.tar.gz 

注意单机版启动需要加上:

 ./startup.sh -m standalone
 tail -n 10 /usr/local/nacos/logs/start.out 

(2)初始化mysql数据库,数据库初始化文件:nacos/conf/nacos-mysql.sql。创建个database数据库nacos_devtest

(3)修改nacos/conf/application.properties文件(切换数据库),增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。

# 切换数据库
spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://192.168.43.215:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=root

(4)Linux上配置cluster.conf

复制cluster.conf.example到cluster.conf

cp cluster.conf.example cluster.conf

配置cluster.conf

192.168.43.215:3333
192.168.43.215:4444
192.168.43.215:5555

(6)修改startup.sh使他能接受不同的启动端口

nacos/bin下的startup.sh

1597812799242

1597813020494

(7)修改启动文件的配置(解决了占用系统资源过大导致只能启动一个的问题)

修改前:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uTq6CA3R-1630289944602)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630042966049.png)]

修改后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h1ovjOim-1630289944603)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630042923166.png)]

(8)将nocas复制三份 分别启动

sh /usr/local/nacos/bin/start.sh  - p 3333
sh /usr/local/nacos2/bin/start.sh - p 4444
sh /usr/local/nacos3/bin/start.sh - p 5555

(9)nginx的配置

   //负载均衡
    upstream cluster{
      server 127.0.0.1:3333;
      server 127.0.0.1:4444;
      server 127.0.0.1:5555;
    }

    server {
        listen      1111;
        server_name localhost;
        location / {
           proxy_pass http://cluster;
       }
    }

(10) 启动nginx

nginx/sbin./nginx -c /usr/local/nginx/conf/nginx.conf

十二、Sentinel

12.1 概述

sentinel在 springcloud Alibaba 中的作用是实现熔断限流。类似于Hystrix豪猪

下载地址:https://github.com/alibaba/Sentinel/releases/download/1.7.1/sentinel-dashboard-1.7.1.jar

官方文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

主要的作用

Sentinel-features-overview

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

下载后执行 java -jar(Java8以上的环境和8080端口不能被占用)

 java -jar D:\Users\Ocean\Desktop\大三下\笔记\sentinel.jar

访问:http://localhost:8080/#/login 用户名密码都是sentinel

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HgAITlpl-1630289944605)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630051009662.png)]

12.1 入门案例

先启动nacos

新建模块 cloudalibaba-sentinel-service8401 ,使用nacos作为服务注册中心

Sentinel可以对service进行监控、熔断、降级

没访问时再sentinel里是看不到监控的应用的,因为是懒加载,需要访问一次

pom 文件

	<dependencies>
        <!-- 后续做Sentinel的持久化会用到的依赖 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!-- sentinel  -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- springcloud alibaba nacos 依赖,Nacos Server 服务注册中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        
        <!-- springboot整合Web组件 -->
        <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>

        <!-- 日常通用jar包 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</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>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.dkf.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

yaml 的配置

server:
  port: 8401
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        # 服务注册中心 # sentinel注册进nacos
        server-addr: localhost:8848
    sentinel:
      transport:
        # 配置 Sentinel Dashboard 的地址
        dashboard: localhost:8080
        # 默认8719 ,如果端口被占用,端口号会自动 +1,直到找到未被占用的端口,提供给 sentinel 的监控端口
        port: 8719
        
management:
  endpoints:
    web:
      exposure:
        include: '*'

启动类省略

@EnableDiscoveryClient
@SpringBootApplication

测试controller

package com.yidong.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class FlowLimitController {
    @GetMapping("/testA")
    public String testA() {
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB() {
        log.info(Thread.currentThread().getName() + "\t" + "...testB");
        return "------testB";
    }

}

12.2 流量控制

流控规则

  • 资源名:唯一名称,默认请求路径
  • ​ 针对来源:sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)

阈值类型/单机值

  • ​ QPS(每秒钟的请求数量):当调用该api就QPS达到阈值的时候,进行限流
  • ​ 线程数.当调用该api的线程数达到阈值的时候,进行限流

是否集群:不需要集群

流控模式

  • ​ 直接:api达到限流条件时,直接限流。分为QPS和线程数

  • ​ 关联:当关联的资源到阈值时,就限流自己。别人惹事,自己买单

  • ​ 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级

    ​ 别的针对来源】

流控效果

  • ​ 快速失败:直接抛异常

  • ​ warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设

    ​ 置的QPS阈值

    关联:当testA访问过大的时候testB会被限流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SlJcjlZg-1630289944606)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630061927509.png)]

QPS直接失败(每秒钟请求数量大于1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HG0hP14d-1630289944607)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630062306275.png)]

线程数(没有流控模式):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XfSqtuMk-1630289944608)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630062497314.png)]

允许进入,但是多个请求的处理线程数是有限的,后来进入的请求没有线程处理的请求会报错

流控效果-预热

1597820534168

12.3 熔断降级

异常比例(秒级)

降级策略–RT

平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级

窗口期过后关闭断路器

RT最大4900(更大的需要通过-Dcsp.Sentinel.statistic.max.rt=XXXX才能生效)

必须满足1s内大于或者等于5个的请求,请求数不够服务可用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BckJLSgb-1630289944610)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630069723082.png)]

异常比例(秒级)

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

异常数

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

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

时间窗口结束后关闭降级

localhost:8401/testE , 第一次访问绝对报错,因为除数不能为零,
我们看到error窗口,但是达到5次报错后,进入熔断后降级。

12.4 热点Key限流

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

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

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

代码示例:controller

//热点参数
    @GetMapping("/testhotkey")
    @SentinelResource(value = "testhotkey",blockHandler = "deal_testhotkey")
    public String testHotKey(
            @RequestParam(value="p1", required = false) String p1,
            @RequestParam(value = "p2", required = false) String p2
    ){
        return "testHotKey__success";
    }


    //类似Hystrix 的兜底方法
    public String deal_testhotkey(String p1, String p2, BlockException e){
        return "testhotkey__fail";
    }

注解:https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rX61iaxq-1630289944611)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630074447405.png)]

配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyX5smSt-1630289944612)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630126204337.png)]

当带有第一个参数时候的阈值超过1的时候就会限流,调用blockHandlerClass

当第一个参数为wang的时候设定特殊的限流阈值

blockHandler只对违反自定义的限流规则进行兜底,方法错误不兜底

12.5 系统规则

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-amDppC0J-1630289944613)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630143076352.png)]

12.6 整合 openfeign 服务降级

sentinel整合ribbon+openFeign+fallback

新建两个工程 9003,9004(生产者)

相同pom

<dependencies>
        <!-- springcloud alibaba nacos 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- springboot整合Web组件 -->
        <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>

        <!-- 日常通用jar包 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</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>
        <dependency>
            <groupId>com.yidong.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>


yaml只是端口号不同

server:
  port: 9003

spring:

  application:
    name: nacos-payment-provider

  devtools:
    livereload:
      enabled: true

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

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

新建消费者 84

pom

	<dependencies>
        <!-- 后续做Sentinel的持久化会用到的依赖 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!-- sentinel  -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- springcloud alibaba nacos 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- springboot整合Web组件 -->
        <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>

        <!-- 日常通用jar包 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</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>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.dkf.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

yaml

server:
  port: 84
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
  application:
    name: nacos-order-consumer

controller

@RestController
public class OrderController {

    private static final String PAYMENT_URL="http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consutomer/payment/get/{id}")
    public CommonResult getPayment(@PathVariable("id")Long id){
        if(id >= 4){
            throw new IllegalArgumentException("非法参数异常...");
        }else {
            return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
        }
    }
    
    @Resource
    private PaymentService paymentService;
    
    @GetMapping("/payment/get/{id}")
    @SentinelResource(value = "fallback")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        if (id>4){
            throw  new IllegalArgumentException("非法参数异常");
        }
        return paymentService.paymentSql(id);
    }
}

引入pom

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

启动类开启对feign的支持 @EnableFeignClients

yaml

# feign对访问sentinel的支持
feign:
  sentinel:
    enabled: true

service:

@Component
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {

    @GetMapping("/payment/get/{id}")
    public CommonResult paymentSql(@PathVariable("id")Long id);


}

对paymentservice的降级处理

@Component
public class PaymentFallbackService implements PaymentService {

    @Override
    public CommonResult paymentSql(Long id) {
        return new CommonResult(500,"出错了,降级处理");
    }

}

12.持久化

目前的sentinel 当重启以后,数据都会丢失,和 nacos 类似原理。需要持久化。它可以被持久化到 nacos 的数据库中。

pom

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

yaml

datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
              dataId: ${spring.application.name}
              group: DEFAULT_GROUP
              data-type: json
              rule-type: flow

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-78y8Vdq1-1630289944614)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630163967530.png)]

启动时候报错:https://blog.csdn.net/csdn370139800/article/details/105892522

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENk2XWF0-1630289944615)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630166388044.png)]

十三、Seate

13.1 概述

Seate 处理分布式事务。

微服务模块,连接多个数据库,多个数据源,而数据库之间的数据一致性需要被保证。

官网: http://seata.io/zh-cn/

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

img

1.TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;

2,XID在微服务调用链路的上下文中传播;

3,RM向TC汪册分支事务,将其纳入XID对应全局事务的管辖;

4,TM向TC发起针对XID的全局提交或回滚决议;

5,TC调度XID下管辖的全部分支事务完成提交或回请求。

13.2 下载安装:

1.修改配置文件

(1)下载后修改conf/file.conf下的默认测试分组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJ75Qeko-1630289944617)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630174265977.png)]

(2)修改log模式是db,并且配置数据源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1hXPv81-1630289944618)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630174357666.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7pMQlTLB-1630289944619)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630174386742.png)]

2.创建名和 file.conf 指定一致的数据库,并且执行自带的数据库脚本。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pGATD1Je-1630289944620)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630174495773.png)]

3.修改 conf/registry.conf 文件内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J8f6NFnJ-1630289944621)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630174645507.png)]

先启动nacos然后启动seate

13.3 案例

img

这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
再通过远程调用账户服务来扣减用户账户里面的余额,
最后在订单服务中修改订单状态为已完成。

该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

创建三个数据库: seata_account、seata_order、seata_storage

新建模块 cloudalibaba-seata-order2001

pom

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

        <!-- seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>
        <!-- seata-->
        <!--feign-->
        <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.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>

yaml

server:
  port: 2002

spring:
  application:
    name: seata-storage-service

  cloud:

    nacos:
      discovery:
        server-addr: localhost:8848  #服务注册发现地址

    alibaba:
      seata:
        tx-service-group: fsp_test_tx_group #要和file.conf中的一样

  #   数据源
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root

mybatis:
  mapper-locations: classpath:mapper/*.xml


logging:
  level:
    io:
      seata: info

配置让seata管理数据源

package com.yidong.springcloud.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

//让seata对数据源进行代理
@Configuration
public class DataSourceProxyConfig {
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

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

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

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


mybatis配置


@Configuration
@MapperScan("com.yidong.springcloud.dao")
public class MybatisConfig {

}

复制seata的conf下的file.conf和registry.conf到resources下

启动类:

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataAccountMain2003 {

    public static void main(String[] args) {
        SpringApplication.run(SeataAccountMain2003.class,args);
    }

}

加上注解就能生效

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njK7rDUm-1630289944624)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630230389487.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3nQoJhJT-1630289944625)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630230465880.png)]

运行原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TiOn4eLl-1630289944626)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630230890182.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0C1NBTvd-1630289944627)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630230903598.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jOVSWR1F-1630289944628)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630230935202.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XPzUczYD-1630289944630)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630230955643.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qw5I0SsO-1630289944631)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630230963976.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sAxewuW3-1630289944632)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630231105928.png)]

十四、雪花算法

高可用、低延时、高QPS的全局ID

14.1 UUID

uuid :只有唯一性,趋势递增

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FsOxwRWX-1630289944633)(C:\Users\Ocean\AppData\Roaming\Typora\typora-user-images\1630232233532.png)]

14.2 数据库自增主键

唯一性、递增

自增原理: 数据库自增id和mysql数据库的replace info实现的。

create table t_test(

 Id BIGINT(20) UNSIGNED not null AUTO_INCREMENTPRIMARY KEY,
 stub CHAR(1) NOT NULL DEFAULT '',
 UNIQUE KEY stub (stub)
);

REPLACE into t_test (stub) VALUES('b');
select LAST_INSERT_ID();

缺点:防止单点故障必须要设置集群,设置集群以后要给每台机器进行设置生成id的步长,扩容性不好

14.3 redis生成的全局策略

redis6.0支持多线程

原理:redis天生就是单线程的保证原子性,可以使用原子操作INCR和INCRBY来实现

集群分布式:需要和mysql一样设置增长步长,redis默认永不过期

假设一个集群中有5台Redis,可以初始化每台Redis的值时1,2,3,4,5,然后步长是5

每个Redis是产生的ID为:

A 1,6

B 2,7

c 3 8 。。。。。

14.4 Twitter的分布式自增ID算法snowflake

Twitter的雪花算法SnowFlake,使用Java语言实现。

SnowFlake算法产生的ID是一个64位的整型,结构如下(每一部分用“-”符号分隔):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

1位标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以为0;

41位时间戳部分,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L 60 60 24 365) = 69年;

10位节点部分,Twitter实现中使用前5位作为数据中心标识,后5位作为机器标识,可以部署1024个节点;

12位序列号部分,支持同一毫秒内同一个节点可以生成4096个ID;

SnowFlake算法生成的ID大致上是按照时间递增的,用在分布式系统中时,需要注意数据中心标识和机器标识必须唯一,这样就能保证每个节点生成的ID都是唯一的。或许我们不一定都需要像上面那样使用5位作为数据中心标识,5位作为机器标识,可以根据我们业务的需要,灵活分配节点部分,如:若不需要数据中心,完全可以使用全部10位作为机器标识;若数据中心不多,也可以只使用3位作为数据中心,7位作为机器标识。

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。据说:snowflake每秒能够产生26万个ID。

学习案例:

public class IdWorker {
 
	//因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
 
	//机器ID  2进制5位  32位减掉1位 31个
	private long workerId;
	//机房ID 2进制5位  32位减掉1位 31个
	private long datacenterId;
	//代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个
	private long sequence;
	//设置一个时间初始值    2^41 - 1   差不多可以用69年
	private long twepoch = 1585644268888L;
	//5位的机器id
	private long workerIdBits = 5L;
	//5位的机房id
	private long datacenterIdBits = 5L;
	//每毫秒内产生的id数 2 的 12次方
	private long sequenceBits = 12L;
	// 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
	private long maxWorkerId = -1L ^ (-1L << workerIdBits);
	// 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32以内
	private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 
	private long workerIdShift = sequenceBits;
	private long datacenterIdShift = sequenceBits + workerIdBits;
	private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
	private long sequenceMask = -1L ^ (-1L << sequenceBits);
	//记录产生时间毫秒数,判断是否是同1毫秒
	private long lastTimestamp = -1L;
	public long getWorkerId(){
		return workerId;
	}
	public long getDatacenterId() {
		return datacenterId;
	}
	public long getTimestamp() {
		return System.currentTimeMillis();
	}
 
 
 
	public IdWorker(long workerId, long datacenterId, long sequence) {
 
		// 检查机房id和机器id是否超过31 不能小于0
		if (workerId > maxWorkerId || workerId < 0) {
			throw new IllegalArgumentException(
					String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
		}
 
		if (datacenterId > maxDatacenterId || datacenterId < 0) {
 
			throw new IllegalArgumentException(
					String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
		}
		this.workerId = workerId;
		this.datacenterId = datacenterId;
		this.sequence = sequence;
	}
 
	// 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id
	public synchronized long nextId() {
		// 这儿就是获取当前时间戳,单位是毫秒
		long timestamp = timeGen();
		if (timestamp < lastTimestamp) {
 
			System.err.printf(
					"clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
			throw new RuntimeException(
					String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
							lastTimestamp - timestamp));
		}
 
		// 下面是说假设在同一个毫秒内,又发送了一个请求生成一个id
		// 这个时候就得把seqence序号给递增1,最多就是4096
		if (lastTimestamp == timestamp) {
 
			// 这个意思是说一个毫秒内最多只能有4096个数字,无论你传递多少进来,
			//这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
			sequence = (sequence + 1) & sequenceMask;
			//当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
			if (sequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}
 
		} else {
			sequence = 0;
		}
		// 这儿记录一下最近一次生成id的时间戳,单位是毫秒
		lastTimestamp = timestamp;
		// 这儿就是最核心的二进制位运算操作,生成一个64bit的id
		// 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
		// 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
		return ((timestamp - twepoch) << timestampLeftShift) |
				(datacenterId << datacenterIdShift) |
				(workerId << workerIdShift) | sequence;
	}
 
	/**
	 * 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
	 * @param lastTimestamp
	 * @return
	 */
	private long tilNextMillis(long lastTimestamp) {
 
		long timestamp = timeGen();
 
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}
	//获取当前时间戳
	private long timeGen(){
		return System.currentTimeMillis();
	}
 
	/**
	 *  main 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		System.out.println(1&4596);
		System.out.println(2&4596);
		System.out.println(6&4596);
		System.out.println(6&4596);
		System.out.println(6&4596);
		System.out.println(6&4596);
//		IdWorker worker = new IdWorker(1,1,1);
//		for (int i = 0; i < 22; i++) {
//			System.out.println(worker.nextId());
//		}
	}
}

SnowFlake算法的优点:

(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。

(2)容量大:每秒中能生成数百万的自增ID,比较灵活。

(3)ID自增:时间戳在高位,存入数据库中,索引效率高。

SnowFlake算法的缺点:

依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。

时钟问题:

  • 百度开源的分布式唯一ID生成器UidGenertor
  • Leaf—美团点评分布式ID生成器

14.5 实际使用

  • 糊涂工具包 https://www.hutool.cn/
  • springboot整合雪花算法

引入坐标

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.10</version>
</dependency>

工具类

package com.yidong.springcloud.until;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
@Slf4j
public class IdGeneratorSnowflake {
    private  long workerId = 0;
    private  long datacenterId = 1 ;
    private Snowflake snowflake = IdUtil.createSnowflake(workerId,datacenterId);

    @PostConstruct
    public void  init(){
        try {
            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
            log.info("当前机器的workerId:{}",workerId);
        }catch (Exception e){
             e.printStackTrace();
             log.warn("当亲机器的workerId获取失败",e);
             workerId = NetUtil.getLocalhostStr().hashCode();
        }
    }

    public synchronized  long snowflakeId(){
        return snowflake.nextId();
    }

    //都是0-31
    public synchronized  long snowflakeId(long workerId,long datacenterId){
        Snowflake snowflake = IdUtil.createSnowflake(workerId,datacenterId);
        return snowflake.nextId();
    }

}

差不多可以用69年
private long twepoch = 1585644268888L;
//5位的机器id
private long workerIdBits = 5L;
//5位的机房id
private long datacenterIdBits = 5L;
//每毫秒内产生的id数 2 的 12次方
private long sequenceBits = 12L;
// 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32以内
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private long sequenceMask = -1L ^ (-1L << sequenceBits);
//记录产生时间毫秒数,判断是否是同1毫秒
private long lastTimestamp = -1L;
public long getWorkerId(){
	return workerId;
}
public long getDatacenterId() {
	return datacenterId;
}
public long getTimestamp() {
	return System.currentTimeMillis();
}



public IdWorker(long workerId, long datacenterId, long sequence) {

	// 检查机房id和机器id是否超过31 不能小于0
	if (workerId > maxWorkerId || workerId < 0) {
		throw new IllegalArgumentException(
				String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
	}

	if (datacenterId > maxDatacenterId || datacenterId < 0) {

		throw new IllegalArgumentException(
				String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
	}
	this.workerId = workerId;
	this.datacenterId = datacenterId;
	this.sequence = sequence;
}

// 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id
public synchronized long nextId() {
	// 这儿就是获取当前时间戳,单位是毫秒
	long timestamp = timeGen();
	if (timestamp < lastTimestamp) {

		System.err.printf(
				"clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
		throw new RuntimeException(
				String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
						lastTimestamp - timestamp));
	}

	// 下面是说假设在同一个毫秒内,又发送了一个请求生成一个id
	// 这个时候就得把seqence序号给递增1,最多就是4096
	if (lastTimestamp == timestamp) {

		// 这个意思是说一个毫秒内最多只能有4096个数字,无论你传递多少进来,
		//这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
		sequence = (sequence + 1) & sequenceMask;
		//当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
		if (sequence == 0) {
			timestamp = tilNextMillis(lastTimestamp);
		}

	} else {
		sequence = 0;
	}
	// 这儿记录一下最近一次生成id的时间戳,单位是毫秒
	lastTimestamp = timestamp;
	// 这儿就是最核心的二进制位运算操作,生成一个64bit的id
	// 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
	// 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
	return ((timestamp - twepoch) << timestampLeftShift) |
			(datacenterId << datacenterIdShift) |
			(workerId << workerIdShift) | sequence;
}

/**
 * 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
 * @param lastTimestamp
 * @return
 */
private long tilNextMillis(long lastTimestamp) {

	long timestamp = timeGen();

	while (timestamp <= lastTimestamp) {
		timestamp = timeGen();
	}
	return timestamp;
}
//获取当前时间戳
private long timeGen(){
	return System.currentTimeMillis();
}

/**
 *  main 测试类
 * @param args
 */
public static void main(String[] args) {
	System.out.println(1&4596);
	System.out.println(2&4596);
	System.out.println(6&4596);
	System.out.println(6&4596);
	System.out.println(6&4596);
	System.out.println(6&4596);

// IdWorker worker = new IdWorker(1,1,1);
// for (int i = 0; i < 22; i++) {
// System.out.println(worker.nextId());
// }
}
}


SnowFlake算法的优点:


(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。

(2)容量大:每秒中能生成数百万的自增ID,比较灵活。

(3)ID自增:时间戳在高位,存入数据库中,索引效率高。

 

SnowFlake算法的缺点:

依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。



时钟问题:

-  百度开源的分布式唯一ID生成器UidGenertor
- Leaf---美团点评分布式ID生成器



## **14.5 实际使用**

- 糊涂工具包 https://www.hutool.cn/
- springboot整合雪花算法

引入坐标

~~~xml
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.10</version>
</dependency>

工具类

package com.yidong.springcloud.until;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
@Slf4j
public class IdGeneratorSnowflake {
    private  long workerId = 0;
    private  long datacenterId = 1 ;
    private Snowflake snowflake = IdUtil.createSnowflake(workerId,datacenterId);

    @PostConstruct
    public void  init(){
        try {
            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
            log.info("当前机器的workerId:{}",workerId);
        }catch (Exception e){
             e.printStackTrace();
             log.warn("当亲机器的workerId获取失败",e);
             workerId = NetUtil.getLocalhostStr().hashCode();
        }
    }

    public synchronized  long snowflakeId(){
        return snowflake.nextId();
    }

    //都是0-31
    public synchronized  long snowflakeId(long workerId,long datacenterId){
        Snowflake snowflake = IdUtil.createSnowflake(workerId,datacenterId);
        return snowflake.nextId();
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud是一个用于构建分布式系统的开发工具集合。它提供了一些常用的组件和框架,包括服务注册和发现、负载均衡、断路器、分布式配置等等。在使用Spring Cloud时,有一些常见的错误和注意事项需要注意。 首先,关于Spring BootSpring Cloud版本对应错误。在使用Spring Cloud时,需要确保Spring BootSpring Cloud的版本兼容。不同版本之间可能存在依赖冲突或不兼容的情况,因此需要根据官方文档或者相关文档来选择合适的版本。 另外,Spring Cloud Config是一个用于集中管理和动态获取配置的工具。它支持从Git、SVN或本地文件系统中获取配置文件,并提供了服务器和客户端支持。你可以通过官方使用说明文档了解更多关于Spring Cloud Config的详细信息。 此外,关于选择使用Nacos还是Eureka作为服务注册和发现组件的问题。Nacos是一个功能更强大的服务注册和发现组件,它整合了Spring Cloud Eureka、Spring Cloud Config和Spring Cloud Bus的功能。使用Nacos可以实现配置的中心动态刷新,而不需要为配置中心新增集群或使用消息队列。另一方面,Eureka是Spring Cloud原生全家桶的一部分,相对来说更加稳定一些。选择使用哪个组件需要根据具体的需求和项目特点来决定。 综上所述,Spring Cloud是一个用于构建分布式系统的开发工具集合,它提供了一些常用的组件和框架。在使用Spring Cloud时,需要注意Spring BootSpring Cloud版本的兼容性,并可以使用Spring Cloud Config来动态获取配置。同时,可以选择使用Nacos或Eureka作为服务注册和发现组件,具体选择需要根据项目需求来决定。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值