不要急,不要急,马上好^_^...

SpringCloud学习之路


SpringCloud

1.了解SpringCloud

版本问题

  1. 首先Spring cloud的版本号是以英文来区分的,而Springboot是以数字来区分的

  2. Spring Cloud的版本用英文来区分的原因是因为根据火车站名来定的

  3. 它们俩之间的版本约束

    image-20210306105531571

    • 这里需要注意的是:并不是都用最新版的就是最好的,而是有一定的约束条件,根据上下图来选择最佳版本搭配使用

      image-20210306105840939

    • image-20210306110441367

    • image-20210306110508237

    • 这里也有我们版本的最佳选择

  4. 升级引发的惨案

    image-20210306113137764

2.配置运行环境

  1. 首先搭建一个父工程的maven项目

  2. 然后只留一个pom.xml文件,并且引入以下信息

    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>SpringCloudStudy</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>
            <lombok.version>1.18.18</lombok.version>
            <log4j.version>1.2.17</log4j.version>
            <mysql.version>8.0.18</mysql.version>
            <druid.version>1.1.16</druid.version>
            <mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version>
        </properties>
    
        <!--子模块继承之后,提供作用:锁定版本+子module不用谢groupId和version-->
        <dependencyManagement>
            <dependencies>
                <!--spring boot 2.2.2-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>2.2.2.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!--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 阿里巴巴-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>2.1.0.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!--mysql-->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>${mysql.version}</version>
                </dependency>
                <!-- druid-->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid</artifactId>
                    <version>${druid.version}</version>
                </dependency>
                <!--mybatis-->
                <dependency>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                    <version>${mybatis.spring.boot.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>
    
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>${lombok.version}</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <!--bulid是这样的用springboot默认的build方式-->
        <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>
    
  3. 然后创建子工程的maven项目

    1. 子工程的pom.xml文件引入以下内容

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <parent>
              <artifactId>SpringCloudStudy</artifactId>
              <groupId>org.example</groupId>
              <version>1.0-SNAPSHOT</version>
          </parent>
          <modelVersion>4.0.0</modelVersion>
      
          <artifactId>payment</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.2.5</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>
      
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-test</artifactId>
              </dependency>
          </dependencies>
      
      </project>
    2. 子工程的resource目录下创建一个application.yml文件

      server:
        port: 8001
      
      spring:
        application:
          name: payment
        datasource:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: org.gjt.mm.mysql.Driver
          url: jdbc:mysql://localhost:3306/user?serverTimezone=UTC
          username: root
          password: root
      
      mybatis:
        mapper-locations: classpath:mapper/*.xml
        type-aliases-package: springcloud.hwm.entity
    3. 建立数据库表

      CREATE TABLE `payment`
      (
      	`id` BIGINT(20) not NULL auto_increment COMMENT 'ID',
      	`serial` VARCHAR(200) DEFAULT'',
      	primary key(`id`)
      )ENGINE=INNODB auto_increment=1 DEFAULT CHARSET=utf8;
    4. 编写service-dao-controller的一系列代码

3.Eureka

  1. 基础知识

    1. 什么是服务治理

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

    2. 什么是服务注册与发现

      1. Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务使用Eureka客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
      2. 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把自己服务器的信息注册到注册中心上,然后消费者、服务提供者就可以以别名的方式获取实际的服务通讯地址,然后再实现本地RPC调用远程RPC框架的思想:远程RPC框架中都会有一个注册中心存放服务地址相关信息
    3. Eureka包含两个组件

      1. Eureka Server:各个微服务节点通过配置启动后都再Eureka Server中进行注册
      2. EurekaClient:是一个Java客户端,用于简化Eureka Server的交互。在应用启动后,会向Eureka Server发送心跳,Eureka Server通过心跳对其进行管理,如果在某个周期没有收到心跳,那么它就会将服务注册表中的这个服务节点移除
  2. Eureka自我保护机制:

    1. 在某一时刻微服务不可用的时候(微服务本身是健康的,但是可能由于网络延迟等其他原因导致没能及时发送心跳数据包),Eureka不会立刻清理该微服务,而是对这个微服务的信息进行保存。
    2. 属于CAP里面的AP分支

Eureka的使用

Eureka Server端的创建

  1. 首先得有得创建一个maven工程,这个工程作为我们的Eureka Server端

  2. 在pom.xml中添加下面的依赖

    <dependencies>
           <dependency>
               <!-这里就是导入Eureka Server的依赖 -->
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
               <version>3.0.1</version>
           </dependency>
       
           <dependency>
               <groupId>org.example</groupId>
               <artifactId>Common</artifactId>
               <version>1.0-SNAPSHOT</version>
               <scope>compile</scope>
           </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>
           </dependency>
       
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-test</artifactId>
           </dependency>
       
           <dependency>
               <groupId>junit</groupId>
               <artifactId>junit</artifactId>
           </dependency>
       
       </dependencies>
  3. 编写application.yml文件

    # 设定端口号
    server:
      port: 7001
    eureka:
      instance:
      	#设定服务端的主机名
        hostname: eureka7001.com
      client:
      	#是否注册自己
        register-with-eureka: false 
        fetch-registry: false
        #服务的URL
        service-url:
          defaultZone: http://eureka7003.com:7003/eureka/
    
      
    
  4. 编写主启动类

    @SpringBootApplication
    //开启Eureka Server的注解
    @EnableEurekaServer 
    public class EurekaMain {
        public static void main(String[] args) {
            SpringApplication.run(EurekaMain.class,args);
        }
    }
    
  5. 如果服务端创建多个,那么在yml文件中,最后的URL就是映射到其他服务端,有几个,就映射几个

Eureka Client端创建

  1. 在pom文件中添加下面依赖

    <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
          </dependency>
  2. 编写yam文件

    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
      instance:
      	# 显示的是自定义的id地址
        instance-id: payment8001
        prefer-ip-address: true
        
        # 这里因为服务端不是一个,所以url不是一个
  3. 主启动类

    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient
    public class PaymentMain8001 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8001.class,args);
        }
    }
    
  4. 编写业务代码

  5. 这里注意:如果编写的是消费者的客户端,那么我们可以在自定义配置类中加下面得Bean对象

    @Configuration
    public class SpringConfig {
        @Bean
        @LoadBalanced //实现负载均衡
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }

    然后在Controller中使用RestTemplate进行转发请求。此时,我们不需要知道请求的路径,因为此时我们的路径映射为

    spring:
      application:
        name: order

    请求直接写成http://order

4.Zookeeper的使用

  1. 首先要在Linux系统中,启动Zookeeper注册中心

  2. 在项目的pom文件中多添加一个依赖image-20210310120300775

  3. 然后编写yml文件image-20210310120437519

  4. 主启动类

    image-20210310120523019

  5. 在启动的时候,会报错,是因为Zookeeper依赖里面含有Zookeeper的旧版本,使之与我们在Linux系统安装的Zookeeper版本不匹配导致报错

    解决办法:image-20210310120713683

  6. 思考:Zookeeper的服务节点是临时节点还是持久节点

    ​ 答:是临时节点,它相比较于eureka,没有保护机制,而是在某一段时间内,如果客户端没有发送心跳数据包就直接删除。

5.Consul

简介:

​ Consul是一套开源的分布式服务发现和配置管理系统,又HashiCorp公司用Go语言开发

​ 提供了微服务系统中的服务治理、配置中心、控制总线、等功能。功能可单独使用,也可以一起使用构建服务网络,总之Consul提供了一种完整的服务网络解决风格

​ 优点:基于raft协议,简洁;支持健康检查,同时支持HTTP和DNS协议,支持跨数据中心的WAN集群 提供图形界面 跨平台支持Linux、Mac、Windows

功能

  1. 服务发现:提供HTTP和DNS两种发现方式
  2. 健康检测:支持多种方式:HTTP、TCP、Docker、Shell脚本定制化
  3. KV存储:KEY Value的存储方式
  4. 多数据中心: Consul支持多数据中心
  5. 可视化Web界面

下载安装

  1. 先去Consul官网下载
  2. 完成之后打开压缩包,看到Consul.exe文件,在此打开cmd窗口
  3. 输入命令:consul agent -dev,运行成功
  4. 访问http://localhost:8500

使用

  1. pom文件中加入以下依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
  2. 编写yml文件

    server:
      port: 80
    
    spring:
      application:
        name: consul-order
    
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            service-name: ${spring.application.name}
  3. 主启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    public class Consul_order80 {
        public static void main(String[] args) {
            SpringApplication.run(Consul_order80.class,args);
        }
    }
  4. 编写业务代码,验证注册是否成功,以及是否能访问数据。

6.三大注册中心的异同点

image-20210310133646113

image-20210310133708206

image-20210310133719084

7.Ribbon

简介:

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

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

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

负载均衡

  • 负载均衡是什么?

    • 简单的说就是将用户的请求平摊的分配到多个服务器上,从而达到系统HA(高可用)
    • 常见的负载均衡又软件Nginx、LVS、硬件F5
  • Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡

    • Nginx是服务器负载均衡,客户端所有的请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务器实现的。
    • Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
  • 负载均衡分为两种:集中式LB、进程内LB

    • 集中式LB:在服务的消费方和提供方之间使用独立的LB设施,由该设施负责把访问请求通过某种策略转发至服务的提供方

    • 进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器

  • Ribbon属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取服务器提供方的地址。

  • Ribbon在工作时分为两步

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

Ribbon的使用

  1. 首先我们如果在项目中添加了

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

    那么我们就不需要再添加Ribbon的依赖,因为新版的Eureka依赖包中包含了Ribbon依赖,所以我们可以直接使用

  2. 实现负载均衡肯定是在集群的环境下进行的,所以我们在80消费者端口的Configuration的Rest Template上面加上@LoadBalancer注解

  3. 访问的地址直接填写spring application name 即可

Ribbon的负载均衡详解

  • 负载均衡分为哪几种方式

image-20210310212521311

  • 负载均衡的算法:

image-20210310212616061

  • 负载均衡的接口与类实现

    image-20210310213007243

  • 查看源码,首先进入到IRul接口中,然后CTRL+Alt+b显示其所有的实现类

image-20210310213120384

  • RoundRobinRule就是轮询负载均衡的类

    //这里是对服务地址的选择
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) { //如果lb是空,会提示没有负载均衡
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;
    
            while(true) {
                if (server == null && count++ < 10) {
                    List<Server> reachableServers = lb.getReachableServers();
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();//这里是第几次请求数
                    int serverCount = allServers.size();//这里是服务总集群数
                    if (upCount != 0 && serverCount != 0) {
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }
    
                            server = null;
                        }
                        continue;
                    }
    
                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }
    
                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }
    
                return server;
            }
        }
    }
    
    private int incrementAndGetModulo(int modulo) {
        int current;//当前的服务端口号
        int next;//下一个服务端口号
        do {
            current = this.nextServerCyclicCounter.get();
            next = (current + 1) % modulo;
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));
    
        return next;
    }

手写Ribbon轮询负载均衡算法并使用

过程图:

image-20210310222003535

  1. 首先去掉在order工程,下面的Configuration类,里面自动注入RestTemplate的@LoadBalanced注解

  2. 编写LoadBalancer接口

    @Component
    public interface LoadBalancer {
        //这里主要是获取所有的微服务端
        ServiceInstance instances(List<ServiceInstance> serviceInstances);
    }
  3. 编写MyLB

    package springcloud.hwm.LB;
    
    @Component
    public class MyLB implements LoadBalancer {
        //必须有一个原子型整数,这里设置的初始值为0
        private AtomicInteger atomicInteger = new AtomicInteger(0);
    
      
        public final int getAndIncreament() {
            int current;//当前的访问次数
            int next;//下一次访问次数
            do {
                current = this.atomicInteger.get();//当前的访问次数先从这个原子型整数这里获取;
                //2147483647这个数是整型的最大值,如果大于这个数就从0记起,如果不大于就等于当前访问次数+1
                next = current >= 2147483647 ? 0 : current + 1;
            }while (!this.atomicInteger.compareAndSet(current,next));
            //这里的compareAndSet,使用的是CAS原理,CAS相比Synchronized,避免了锁的使用,总体性能比Synchronized高很多.
            //这里我们分析一下CAS的方法是如何进行使用的:
            //在这个循环条件下,假设有多个线程在同时执行这段代码,即使有一个线程在产生了新值之后,
            // 它还需要让它自己产生的新值与旧值比较之后才能决定要不要这个新值。也就是说,如果oldValue是10,
            // 而产生的新值是20,然后程序还没到while的时候,有其他线程修改了newValue值,那当这个线程到while判断的时候,
            // 会出现comparAndSet方法的预期值不跟实际值一样,导致方法返回false,直到没有其他线程干扰。这时就确定了新产生的值。
    
            //那么根据以上的原理,只有当预期值和实际值相同的时候,方法返回的是True,那么这个时候while循环内是false,就会跳出循环 并且的到我们的预期值
            return next;
        }
    
        @Override
        //这个就是获取服务端的所有信息,根据信息我们来返回我们想要的值
        public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
    
            //这里就是轮询算法的关键之处,也就是我们所说的   访问次数 % 服务集群数  = 实际调用服务的下标
            int index = getAndIncreament() % serviceInstances.size();
            //通过下标选择返回哪一个微服务端
            return serviceInstances.get(index);
        }
    }
  4. 编写OrderController类

    @GetMapping("/customer/payment/lb")
    public String getLB(){
    
        List<ServiceInstance> instances = discoveryClient.getInstances("payment");
        if (instances==null&&instances.size()<=0){
            return null;
        }
        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();
        log.info("当前的URL为+"+uri+"/payment/lb");
        return restTemplate.getForObject(uri+"/payment/lb",String.class);
    }

8.OpenFeign

简介

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

Feign能干什么

​ Feign旨在使编写javaHttp客户端变得更容易

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

​ Feign集成了Ribbon

​ 利用Ribbon维护了payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用

Feign与OpenFeign的区别

image-20210311101020998

Feign的使用

  1. 创建一个消费者项目

  2. 编写pom文件

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>Common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
  3. 编写yam文件

    server:
      port: 80
    
    eureka:
      client:
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
  4. 主启动类

    @SpringBootApplication
    @EnableFeignClients  //一定要添加这个注解
    public class OrderFeignMain {
        public static void main(String[] args) {
            SpringApplication.run(OrderFeignMain.class,args);
        }
    }
  5. 编写service接口

    @Component
    @FeignClient("payment")
    public interface FeignService {
    
        @GetMapping("/payment/findById/{id}")
        public CommonResult<payment> findById(@PathVariable("id") long id);
    }
  6. 编写controller

    @RestController
    public class FeignController {
    
        @Resource
        private FeignService service;
    
        @GetMapping("/customer/findById/{id}")
        public CommonResult<payment> findById(@PathVariable("id") long id){
            return service.findById(id);
        }
    }

注意:当报404错误的时候,一定要看好自己的地址是否正确的编写,细心检查(报错点)

Feign的超时控制

​ 项目启动,Feign客户端默认只等待1s,但是服务端需要超过1s,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

​ 案例:

​ 先假设超时:

@GetMapping("/TimeOut")
public String paymentTimeOut(){
    //这里设置延迟的时间为3秒
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return serverPort;
}

​ 然后通过消费者去调用:会报错

​ 这时候我需要通过yml进行配置

依然报错:

​ 原因:因为在消费者的pom文件中,导入了Eureka和Feign两个依赖包,他们下面都包含Ribbon依赖包,导致包冲突,并且不能直接通过Ribbon来设置成功

# 设置feign客户端超时时间
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeOut: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeOut: 5000
  
  #这个是不成功的,即使启动也会报错

​ 正确的修改方式:

feign:
  client:
    config:
      default:
        #简历连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
        ConnectTimeOut: 5000
        #指建立连接后从服务端读取到可用资源所用的时间
        ReadTimeOut: 10000

日志增强

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

​ 使用:

  1. 开启日志增强功能:

    @Configuration
    public class Config {
        //开启详细日志
        @Bean
        Logger.Level Log(){
            return Logger.Level.FULL;
        }
    }
  2. 配置yml文件

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

日志级别:

image-20210311131546538

9.Hystrix

概念

  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
  • “断路器”本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似于熔断保险丝),向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方法的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
  • 服务降级:
    • 服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
    • 哪些情况会发出降级:
      • 程序运行异常
      • 超时
      • 服务熔断触发服务降级
      • 线程池、信号量打满也会导致服务降级
  • 服务熔断:
    • 类比保险丝,达到最大服务访问后直接拒绝访问,拉闸限电,然后调用服务降级方法并返回友好提示
    • 服务的降级->进而熔断->恢复调用链路
  • 服务限流:
    • 秒杀高并发等操作,严禁一窝蜂的过来拥挤,排队有序进行

Hystrix的服务降级使用

  1. 首先创建一个消费者客户端(一般都是使用在消费者客户端)

  2. pom文件添加以下依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
  3. yml文件的编写

    server:
      port: 80
    
    eureka:
      client:
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
    
    # 这里设置之后,后面的超时测试老是报错
    #原因:关键在于feign:hystrix:enabled: true的作用,官网解释“Feign将使用断路器包装所有方法”,也就是将@FeignClient标记的那个service接口下所有的方法进行了hystrix包装(类似于在这些方法上加了一个@HystrixCommand),这些方法会应用一个默认的超时时间为1s,所以你的service方法也有一个1s的超时时间,service1s就会报异常,controller立马进入备用方法,controller上那个3秒那超时时间就没有效果了。
    feign:
      hystrix:
        enabled: true
    
    #解决:
    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 3000
    #然后ribbon的超时时间也需加上
    ribbon:
      ReadTimeout: 5000
      ConnectTimeout: 5000
        
  4. 主启动类的编写

    @SpringBootApplication
    @EnableFeignClients
    @EnableHystrix
    public class FeignHystrixOrderMain {
        public static void main(String[] args) {
            SpringApplication.run(FeignHystrixOrderMain.class,args);
        }
    }
  5. service层的编写

    @Service
    //这里的fallback就是利用一个实现类,完成所有方法的降级处理,这样可以程序耦合度降低,而不会把所有的降级方法都写在Servcie或者Controller类里面,造成代码膨胀且杂乱
    @FeignClient(value = "PAYMENT-HYSTRIX",fallback = FeignHystrixServiceImpl.class)
    public interface FeignHystrixService {
    
        @GetMapping("/payment/hystrix/ok/{id}")
        public String payment_ok(@PathVariable("id")Integer id);
    
        @GetMapping("/payment/hystrix/TimeOut/{id}")
        public String payment_TimeOut(@PathVariable("id")Integer id);
    }
  6. Controller类的编写

    @RestController
    @Slf4j
    
    //这个是定义全局的降级方法,前提是加了@HystrixCommand注解。
    @DefaultProperties(defaultFallback = "globolMethod")
    public class FeignHystrixOrder80Controller {
    
        @Resource
        private FeignHystrixService feignHystrixService;
    
        @HystrixCommand
        @GetMapping("/customer/payment/hystrix/ok/{id}")
        public String payment_ok(@PathVariable("id")Integer id){
            int a= 10/0;
            return feignHystrixService.payment_ok(id);
        }
    
    
        @GetMapping("/customer/payment/hystrix/TimeOut/{id}")
        //这个是特定的降级方法
        @HystrixCommand(fallbackMethod = "TimeOutHandler",commandProperties = {
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
        })
        public String payment_TimeOut(@PathVariable("id")Integer id){
            return feignHystrixService.payment_TimeOut(id);
        }
    
        public String TimeOutHandler(Integer id){
            return "系统繁忙,请稍后再试!!!!"+id;
        }
    
        public String globolMethod(){
            return "系统出错";
        }

服务熔断

​ 熔断机制概述:

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

​ 在Spring Cloud框架中,熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5s内20次调用失败,就会启动熔断机制。熔断机制的注解@HystrixCommand。

  • 熔断过程中的三大状态:

image-20210311205815632

  • 熔断的三个重要参数:

image-20210311205852006

  • 熔断过程:

image-20210311210022578

  • 熔断最后的恢复机制:

image-20210311210119799

Hystrix图形化监控

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

​ 如何使用:

  1. 首先,添加依赖

    <!--        导入图形监控的依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</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>
  2. yml文件

    server:
      port: 9001
  3. 主启动类

    @SpringBootApplication
    @EnableHystrixDashboard
    public class DashboardMain {
        public static void main(String[] args) {
            SpringApplication.run(DashboardMain.class,args);
        }
    
    }
  4. 这里在客户端的启动类里面添加

    如果不添加网页会报:Unable to connect to Command Metric Stream.

    /**
     * 此配置是为了服务监控而配置的,与服务容错本身无关,spring cloud升级后的坑
     * Servlet RegistrationBean 因为springboot的默认路径不是/hystrix.stream,
     * 只要在自己的项目里配置下面得servlet就可以了
     */
    
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

10.GateWay

简介:

  1. SpringCloud GateWay是Spring Cloud的一个全新项目,基于Spring5.0+SpringBoot2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的同意的API路由管理方式
  2. SpringCloud GateWay作为SpringCloud生态系统中的网关,目标是替代Zuul,在SpringCloud2.0以上版本中,没有对新版本的Zuul2.0以上最新高性能版本进行集成,仍然还是使用Zuul1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于web Flux框架实现的,而web Flux框架底层则使用了高性能的Reactor模式通信框架Netty。
  3. SpringCloud GateWay的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能

为什么选择GateWay

  1. Netflix不太靠谱,zuul2.0一直跳票,迟迟不发布
    1. 一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能比起Zuul是非常简单便捷的
    2. GatWay是基于异步非阻塞模型上进行开发的,性能方面不需要担心,虽然Netflix早就发布了最新的Zuul2.x,但Spring cloud貌似没有整合计划。而且Netflix相关组件都宣布维护期,不知前景如何
  2. SpringCloud GateWay具有如下特性
    1. 基于Spring5.0+SpringBoot2.0和Project Reactor进行构建
    2. 动态路由:能够匹配任何请求属性
    3. 可以对路由指定Predicate(断言)和Filter(过滤器)
    4. 集成Hystrix的断路器功能
    5. 集成Spring cloud服务发现功能
    6. 易于编写的Predicate和Filter
    7. 请求限流功能
    8. 支持路径重写
  3. SpringCloud GateWay于Zuul的区别
    1. Zuul1.x是一个基于阻塞IO的API Gateway
    2. Zuul 1.x基于Servlet2.5使用阻塞架构它不支持任何长连接(如web Socket)Zuul的设计模式和Nginx较像,每次IO操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差
    3. Zuul2.x理念更先进,想基于Netty非阻塞和支持长连接,但Spring cloud目前还没有整合。Zuul2.x的性能较Zuul1.x有较大提升。在性能方面,根据官方提供的基准测试,SpringCloud GateWay的RPS(每秒请求数)是Zuul的1.6倍
    4. SpringCloud GateWay建立在Spring5.0+SpringBoot2.0和Project Reactor之上,使用非阻塞API
    5. SpringCloud GateWay还支持web Socket,并且与Spring紧密集成拥有更好的开发体验

Zuul模型

  • Spring cloud中所集成的zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型

  • servlet由servlet container进行生命周期管理

    • container启动时构建servlet对象并调用servlet init进行初始化
    • container运行时,接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用Service
    • container关闭时调用destory销毁

    image-20210314144009513

    上述模型的缺点

    ​ servlet是一个简单的网络IO模型,当请求进入servlet container时,就会为其绑定一个线程,在并发不高的场景下这种模型是适用的,但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上下文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或者几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势

    ​ 所以zuul1.x是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet并由该servlet阻塞式处理,所以Zuul无法摆脱servlet模型的弊端

Gateway的三个核心概念

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

image-20210314153602944

image-20210314153702611

客户端向Spring cloudGateWay发出请求,然后再Mapping中找到与请求相匹配的路由,将其发送到GateWayWebHandler

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

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

Filter在”pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

在”post”类型的过滤器中可以做响应内容、响应头的修改、日志的输出,流量监控等有着非常重要的作用

Gateway的使用

  1. 创建一个项目

  2. pom文件导入

    <dependencies>
    
        <!-- 首先得说明以下,必须把web和actuator这两个依赖除去--->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-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>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>Common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
  3. yml文件

    server:
      port: 9527
    
    spring:
      application:
        name: gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由
          routes:
          # 这下面不仅可以配置断言,还可以配置filter
            - id: payment_route #路由的id,没有固定规则但要求唯一,建议配合服务名
    #          uri: http://localhost:8001
              uri: lb://PAYMENT
              predicates:
                - Path=/payment/findById/** #断言,路径相匹配的进行路由
            - id: payment_route2
              uri: lb://PAYMENT # 匹配后提供服务的路由
              predicates:
                - Path=/payment/lb/**
    
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
      instance:
        hostname: gateway-service
  4. 主启动类

    @SpringBootApplication
    @EnableEurekaClient
    public class GatwayMain {
        public static void main(String[] args) {
            SpringApplication.run(GatwayMain.class,args);
        }
    }
  5. 配置gateway路由的第二种方法

    @Configuration
    public class GatewayConfig {
        @Bean
        public RouteLocator customerRouteLocator2(RouteLocatorBuilder builder){
            RouteLocatorBuilder.Builder routes =builder.routes();
            routes.route("route_payment3",r->r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
            return routes.build();
        }
    }
  6. 配置全局Filter

    @Component
    @Slf4j
    public class MyFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("我的过滤器"+ 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;
        }
    }

11.spring cloud Config 配置总控中心

是什么?

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

怎么用?

​ spring cloud config分为 服务端和客户端两部分

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

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

能干嘛?

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

image-20210315091817638

操作使用

  1. 新建两个工程,一个是config服务端,一个是config客户端

    <!-这里是服务端导入的依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    
    <!-这里是客户端导入的依赖 -->
     <dependency>
     		<groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
     </dependency>
  2. yml文件编写

    #这里是服务端需要编写的yml文件
    server:
      port: 3344
    
    spring:
      application:
        name: config-center
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/hou-wenming/springcloud-config.git #上面的git仓库名字
              search-paths:
                - springcloud-config
    #          skip-ssl-validation: true
    #          username: 156542114@qq.com
    #          password: hwm2000916..
              default-label: master
    
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
      instance:
        hostname: config-center
    #这里是客户端编写的yml文件
    #命名必须是bootstrap.yml 否则无法获取中心仓库的内容
    server:
      port: 3355
    
    spring:
      application:
        name: config-client
        #config客户端配置
      cloud:
        config:
          label: master # 分支名称
          name: config # 配置文件名称
          profile: dev # 读取后缀名称
          uri: http://localhost:3344 # 配置中心地址
          # discovery:
            # enabled: true
    
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
      instance:
        hostname: config-client
    
    # 暴漏监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"
  3. 主启动类

    //服务端
    @SpringBootApplication
    @EnableConfigServer
    public class ConfigCenterMain {
        public static void main(String[] args) {
            SpringApplication.run(ConfigCenterMain.class,args);
        }
    }
    //客户端
    @SpringBootApplication
    @EnableEurekaClient
    public class ConfigClientMain {
        public static void main(String[] args) {
            SpringApplication.run(ConfigClientMain.class,args);
        }
    }
  4. controller类

    @RestController
    @RefreshScope//这个是解决配置中心内容修改,不需要重启的问题
    public class ConfigClientController {
    
        @Value("${server.info}")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfig(){
            return configInfo;
        }
    }
  5. 当我们在配置中心修改了文件的时候,这时候服务端的数据可以实时更新,但是客户端不行,只能重启获取数据(如果服务端庞大,重启将会崩毁)

    解决办法:暴漏监控端点

    # 暴漏监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    //在Controller类上面加上这个注解
    @RefreshScope

    最后在cmd命令行中发送一个POST请求

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

12.bus总线

前提:

​ bug总线解决了上面我们的spring cloud config最后因为在远程配置中心修改文件的时候,我们的客户端不能实时的更新配置,而是需要重启才行,然后解决方法是发送一个post请求解决问题,但是我们每次更新都需要发送一个请求未免太麻烦了,这时候bus总线就体现了它的使用价值

简介:

​ spring cloud bus 配合 spring cloud config 使用可以实现配置的动态刷新

​ spring cloud bus是用来将分布式系统的节点与轻量级消息系统连接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。spring cloud bus目前支持RabbitMQ和Kafka

image-20210316170332385

这个图是bus通过向一个客户端进行发送,然后这个客户端再向其他客户端进行转发

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

image-20210316170855107

为什么被称为操作总线

什么是总线

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

基本原理

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

实际操作使用

  1. 配置pom文件

    //使用总线,必须在客户端和服务端添加这个依赖
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
  2. 配置yam文件

    #这个是配置中心,需要用mq的支持
    
    # rabbitmq的相关配置
    rabbitmq:
      host: localhost
      port: 5672
      username: guest
      password: guest
      
      
    management:
      endpoints:
        web:
          exposure:
            include: 'bus-refresh'
  3. 后面的配置和前面的配置中心一样

  4. 如何去操作:

    1. 首先我们得启动我们的eureka注册中心,以及配置中心,和配置客户端
    2. 然后修改一下我们的中心配置文件
    3. 在cmd中发送这个请求:curl -X POST “http://localhost:3344/actuator/bus-refresh"
    4. 登陆rabbit MQ进行广播
    5. 成功!
    6. 也可以针对性的进行广播 在cmd中发送这个请求:curl -X POST “http://localhost:3344/actuator/bus-refresh:config-client:3355"

13.springcloud stream消息中间件

简介:

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

​ 官方定义springcloud stream是一个构建消息驱动微服务的框架

​ 应用程序通过inputs或者outputs来与springcloud stream对象交互,通过配置来bingding(绑定),而springcloud stream的binder对象负责与消息中间件交互,所以我们只需要弄清楚如何与springcloud stream交互就可以方便使用消息驱动的方式

​ 通过spring integration来连接消息代理中间件以实现消息事件驱动,springcloud stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布订阅、消费组、分区的三个核心概念

Stream凭什么可以统一底层差异

​ 通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各个不同的消息中间件实现

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

设计思想

  • 标准MQ
    • 生产者/消费者之间靠消息媒介传递信息内容 Message
    • 消息必须走特定的通道 消息通道Message Channel
    • 消息通道里的消息如何被消费呢,谁负责收发处理 消息通道MessageChannel的子接口SubscribleChannel,由MessageHandler消息处理器所订阅

image-20210317095910865

  • 为什么用cloudStream?
    • 这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候spring cloud stream 给我们提供了一种解耦合的方式
  • Stream中的消息通信方式遵循了发布-订阅模式
    • Binder:很方便的连接中间件,屏蔽差异
    • Channel:通道,是队列的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过channel对队列进行配置
    • Source和SInk:简单的可理解为参照对象是SpringCloudStream自身,从Stream发布消息就是输出,接收消息就是输入

SpringCloudStream标准流程套路

image-20210317101033342

image-20210317101042001

如何使用?

  1. 创建stream流的服务端和客户端

  2. 导入pom文件

    <dependencies>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>Common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    
        <!--导入stream rabbit依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
    </dependencies>
  3. 编写yml文件

    server:
      port: 9902
    
    
    spring:
      application:
        name: stream-customer
      cloud:
        stream:
          binders: # 在此处配置要绑定的rabbitmq的服务信息
            defaultRabbit: # 表示定义的名称,用于bingding整合
              type: rabbit #消息组件类型
              environment: #设置rabbitmq的相关的环境配置
                spring:
                  rabbitmq:
                    host: localhost
                    port: 5672
                    username: guest
                    password: guest
          bindings: #服务的整合处理
            input: #这个名字是一个通道的名称
              destination: studyExchange #表示要使用的Exchange名称定义
              content-type: application/json #设置消息类型,本次为json,文本则设置text/plain
              defaultbinder: defaultRabbit #设置要绑定的消息服务的具体设置
              group: A
    
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
      instance:
        lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30s)
        lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔(默认是90s)
  4. 主启动类

  5. 提供方的业务类代码

    //接口
    public interface streamrabbitmqService {
        public String send();
    }
    
    //实现类
    @EnableBinding(Source.class)
    public class StreamrabbitmqServiceImpl implements streamrabbitmqService {
        @Resource
        private MessageChannel output;
    
        @Override
        public String send() {
            String serial = UUID.randomUUID().toString();
            output.send(MessageBuilder.withPayload(serial).build());
            System.out.println("*********"+serial);
            return null;
        }
    }
    
    //controller
    @RestController
    public class streamrabbitmqController {
        @Resource
        private streamrabbitmqService service;
    
        @GetMapping("/sendMessage")
        public String sendMessage(){
            return service.send();
        }
    }
    
  6. 消费方的业务代码

    @Component
    @EnableBinding(Sink.class)
    public class StreamMQCustomerController {
        @Value("${server.port}")
        private String serverPort;
    
        @StreamListener(Sink.INPUT)
        public void input(Message<String> message){
            System.out.println("消费者9902----->接受到的消息:"+message.getPayload()+"\t   +port:"+serverPort);
        }
    }
  7. 以上的全部完成之后,启动Eureka注册中心,以及消费者和提供者,还有RabbitMQ消息转发中心

  8. 然后进行测试,测试地址:http://localhost:9901/sendMessage

  9. 当我们访问的时候,提供方会进行消息的发送,然后消费方会收到信息,并在控制台显示,这时候我们的RabbitMQ显示台就会显示我们的信息发送的频率

  10. 最后如果我们的消费方有多个,那么就会发生重复消费的问题,如何解决?

    1. 我们可以在配置文件中加入 grop(组)的概念
    2. 这样如果不同的组会继续重复消费,而相同的组则会竞争轮询的进行消费
    3. 但是如果我们消费方这时候断线了,并且其中一个消费者移除了grop这个概念,那么再上线的时候,移除了grop的会收不到未接受的信息,而没有移除的则会收到未接受的信息

    image-20210317144749751

14.sleuth +Zipkin

image-20210317182456061

image-20210317182502828

15.Cloud Alibaba

有哪些功能:

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

1. Nacos

简介:

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

​ 就是注册中心+配置中心的组合(相当于前面所讲的Eureka+ribbon+bus所继承为一体的管理平台)

使用:

1. 首先启动nacos
2.编写服务提供项目
  1. pom

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

    #服务端口
    server:
      port: 9001
    #服务名
    spring:
      application:
        name: nacos-payment
    # 注册进nacos的服务地址
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #配置nacos地址
    #暴露端口
    management:
      endpoints:
        web:
          exposure:
            include: "*"
  3. 主启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    public class AlibabaPaymentMain9001 {
        public static void main(String[] args) {
            SpringApplication.run(AlibabaPaymentMain9001.class,args);
        }
    }
  4. 业务逻辑

    @RestController
    public class PaymentController {
    
        @Value("${server.port}")
        private String serverport;
        @GetMapping("/nacos/{id}")
        public String getPayment(@PathVariable("id") Integer id){
            return "hello nacos,serverport:"+serverport+"    id:"+id;
        }
    }
  5. 运行

3. 编写配置中心
  1. pom

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

    server:
      port: 3377
    
    spring:
      application:
        name: nacos-config-client
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
          config:
            server-addr: localhost:8848
            file-extension: yaml
    #        group: test   这里是分组的名字
    #        namespace: 123456  这里是命名空间的名字
    spring:
      profiles:
        active: dev   #这里是测试开发环境
    #    active: test
    1. 主启动类

      @SpringBootApplication
      @EnableDiscoveryClient
      public class configAlibabaMain {
          public static void main(String[] args) {
              SpringApplication.run(configAlibabaMain.class,args);
          }
      }
  3. 服务类

    @RestController
    @RefreshScope //动态刷新功能
    public class ConfigController {
        @Value("${config.info}")
        private String Info;
        @GetMapping("/config/Info")
        public String getInfo(){
            return Info;
        }
    
    }
  4. 启动运行

4. 编写客户端
  1. pom文件

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  2. yml文件

    server:
      port: 83
    
    spring:
      application:
        name: nacos-order
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
    
    server-url:
      nacos-user-service: http://nacos-payment
  3. 主启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    public class OrderMian83 {
        public static void main(String[] args) {
            SpringApplication.run(OrderMian83.class,args);
        }
    }
  4. 配置类

    @Configuration
    public class Config {
        @Bean
        @LoadBalanced
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }
  5. 服务类

    @RestController
    public class OrderController {
        @Resource
        private RestTemplate restTemplate;
    
        @Value("${server-url.nacos-user-service}")
        private String serviceURL;
    
        @GetMapping("/customer/nacos/{id}")
        public String AlibabaNacos(@PathVariable("id") String id){
            return restTemplate.getForObject(serviceURL+"/nacos/"+id,String.class);
        }
    }
5. 实现集群配置以及持久化(重点)
  1. 总体架构实现 1 nginx+3 nacos + 1 mysql
  2. 首先在虚拟机上部署我们的nginx服务器,然后配置nacos服务,最后将nacos自带的嵌入式数据库换成mysql
  3. 在本机运行,实现多个注册中心同时运行

2.Sentinel

image-20210324214058584

Sentinel持久化
  1. 首先在客户端添加以下的配置文件

image-20210326203526258

  1. 然后在nacos中配置中心配置以下信息

    image-20210326203602222

image-20210326203613771

3. Seata

简介:

  1. 什么是Seata:

    Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAG和XA事务模式,为用户打造一站式的分布式解决方案。

  2. Seata术语

    1. XID

    2. TC-事务协调者:

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

    3. TM-事务管理器、

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

    4. RM-资源管理器

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

    image-20210326210229190

    1. TM向TC申请开启一个全局事务,全部事务创建成功并生成一个全局唯一的XID
    2. XID在微服务调用链路的上下文中传播
    3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
    4. TM向TC发起针对XID的全局提交或者回滚决议
    5. TC调度XID下管辖的全部分支事务完成提交或回滚请求

详细过程:

  1. TM开启分布式事务(TM向TC注册全局事务记录)
  2. 按业务场景、编排数据库、服务等事务内资源(RM向TC汇报资源准备状态)
  3. TM结束分布式事务,事务一阶段能结束(TM通知TC提交/回滚分布式事务)
  4. TC汇总事务信息,决定分布式事务是提交还是回滚
  5. TC通知所有RM提交/回滚资源,事务二阶段结束
其中的事务阶段细节
  1. 在一阶段,Seata会拦截业务SQL
    1. 解析SQL语义,找到业务SQL要更新的业务数据,在业务数据被更新前,将其保存成before image
    2. 执行业务SQL更新业务数据,在业务数据更新之后
    3. 将其保存成after image,最后生成行锁
    4. 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性

image-20210330114218900

  1. 二阶段回滚:
    1. 二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的业务SQL,还原业务数据
    2. 回滚方式就是用before image还原业务数据;但是还原之前要首先校验脏写,对比数据库当前业务数据和after image
    3. 如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理

image-20210330114702918

image-20210330121819368


文章作者: Mr Hou
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mr Hou !
希望得到你的建议哦
  目录