SpringCloud2020.mmap

微服务入门

SpringCloud=分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶;
对微服务的理解:
image.png
涉及的技术栈:
image.png
技术栈对应的架构模块
image.png

springBoot与springCloud

springBoot以字母划分版本,springCloud以英文字母划分版本;
springboot的github地址springcloud的github地址

springCloud命名规则

  • Spring Cloud采用了英国伦敦地铁站的名称来命名,并由地铁站名称字母A-Z依次类推的形式来发布迭代版本
  • SpringCloud是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了管理SpringCloud与各子项目的版本依赖关系,发布了一个清单,其中包括了某个SpringCloud版本对应的子项目版本。为了避免SpringCloud版本号与子项目版本号混淆,SpringCloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序。例如Angel是第一个版本, Brixton是第二个版本。
  • SpringCloud的发布内容积累到临界点或者一个重大BUG被解决后,会发布一个service releases版本,简称SRX版本,比如Greenwich.SR2就是SpringCloud发布的Greenwich版本的第2个SRX版本;

    boot与cloud版本

    boot与cloud版本必须对应:
    image.png
    ——>>boot与cloud版本详细对应信息
    本次版本选择:
    image.png

    Cloud组件停更说明-2020

    image.png
    —->>SpringCloud中文文档

    微服务架构编码构建

    预定>配置>编码

  • 预定:java编程规范,sql编程规范,sql编写大小写敏感程度….

  • 配置:分布式微服务架构的组件;

    构建父工程

    首先建一个maven项目作为父工程(父工程打包必须为pom),maven版本3.5以上,并且删掉src文件夹(父工程不需要);
    pom工程:maven的继承与依赖传递:

  • 继承:用在父级工程或聚合工程中,子项目是可以继承父项目中的依赖。用来做jar包的版本控制。

  • 依赖传递:在Maven中,依赖是可以传递的,就是说假设存在三个项目,分别是项目A,项目B以及项目C,假设C依赖于B,B依赖于A,那么我们可以根据Maven项目依赖的特征不难推出项目C也依赖于A。

父工程的集合、传递、依赖:

  • 聚合:便于批量操作,父工程操作后被聚合的子工程也进行同时进行同样操作。
  • 继承:子工程依赖父工程,我感觉主要是用来进行依赖版本控制操作,父工程可以定义版本,子工程不用自己定义版本,还有依赖的传递,父工程有的依赖,子工程也有。注意依赖的<scope>属性,有的属性下,依赖不传递。
  • 依赖:两个模块之间有依赖关系,比如controller层需要依赖service层中的类,则在controller的pom文件中依赖service的pom文件,依赖具有传递性,service依赖的jar包,controller默认也依赖。

    pom.xml

    1. <packaging>pom</packaging>
    2. <!--父工程打包必须为pom包-->
    3. ...
    4. <!-- 统一管理jar包版本 -->
    5. <properties>
    6. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    7. <maven.compiler.source>14</maven.compiler.source>
    8. <maven.compiler.target>14</maven.compiler.target>
    9. <!--以上两项写自己的java版本,必须1.8及以上-->
    10. <log4j.version>1.2.17</log4j.version>
    11. <lombok.version>1.18.22</lombok.version>
    12. <mysql.version>8.0.22</mysql.version>
    13. <druid.version>1.2.6</druid.version>
    14. <mybatis.spring.boot.version>2.1.4</mybatis.spring.boot.version>
    15. </properties>
    16. <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
    17. <dependencyManagement>
    18. <dependencies>
    19. <!--spring boot 2.2.2-->
    20. <dependency>
    21. <groupId>org.springframework.boot</groupId>
    22. <artifactId>spring-boot-dependencies</artifactId>
    23. <version>2.2.2.RELEASE</version>
    24. <type>pom</type>
    25. <scope>import</scope>
    26. </dependency>
    27. <!--spring cloud Hoxton.SR1-->
    28. <dependency>
    29. <groupId>org.springframework.cloud</groupId>
    30. <artifactId>spring-cloud-dependencies</artifactId>
    31. <version>Hoxton.SR1</version>
    32. <type>pom</type>
    33. <scope>import</scope>
    34. </dependency>
    35. <!--spring cloud alibaba 2.1.0.RELEASE-->
    36. <dependency>
    37. <groupId>com.alibaba.cloud</groupId>
    38. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    39. <version>2.1.0.RELEASE</version>
    40. <type>pom</type>
    41. <scope>import</scope>
    42. </dependency>
    43. <dependency>
    44. <groupId>mysql</groupId>
    45. <artifactId>mysql-connector-java</artifactId>
    46. <version>${mysql.version}</version>
    47. </dependency>
    48. <dependency>
    49. <groupId>com.alibaba</groupId>
    50. <artifactId>druid</artifactId>
    51. <version>${druid.version}</version>
    52. </dependency>
    53. <dependency>
    54. <groupId>org.mybatis.spring.boot</groupId>
    55. <artifactId>mybatis-spring-boot-starter</artifactId>
    56. <version>${mybatis.spring.boot.version}</version>
    57. </dependency>
    58. <dependency>
    59. <groupId>log4j</groupId>
    60. <artifactId>log4j</artifactId>
    61. <version>${log4j.version}</version>
    62. </dependency>
    63. <dependency>
    64. <groupId>org.projectlombok</groupId>
    65. <artifactId>lombok</artifactId>
    66. <version>${lombok.version}</version>
    67. <optional>true</optional>
    68. </dependency>
    69. </dependencies>
    70. </dependencyManagement>
    71. <build>
    72. <plugins>
    73. <plugin>
    74. <groupId>org.springframework.boot</groupId>
    75. <artifactId>spring-boot-maven-plugin</artifactId>
    76. <configuration>
    77. <fork>true</fork>
    78. <addResources>true</addResources>
    79. </configuration>
    80. </plugin>
    81. </plugins>
    82. </build>

    Maven工程落地细节

    <dependencyManagement><dependencies>

    <dependencyManagement>
  • Maven使用dependencyManagement元素来提供了一种管理依赖版本号的方式。通常会在一个组织或者项目的最顶层的父POM中看到dependencyManagement元素。

  • 使用pom.xml中的dependencyManagement元素能让所有在子项目中引用一个依赖而不用显式的列出版本号
  • Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个dependencyManagement元素中指定的版本号。
  • 这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改 ;另外如果某个子项目需要另外的一个版本,只需要声明<version>就可;
    • dependencyManagement里只是声明依赖,并不实现引入(所以导入依赖时先把<dependencyManagement>标签注掉),因此子项目需要显示的声明需要用的依赖。
    • 如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且<version><scope>都读取自父pom
    • 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

      跳过单元测试

      image.png

      支付模块构建

      支付模块为**cloud-provider-payment8001**
      image.png

      构建微服务模块步骤

  1. 建module;
  2. 改pom;
  3. 写yml;
  4. 主启动;
  5. 业务类;

    创建子模块

    在父工程里面创建一个Maven子模块cloud-provider-payment8001,继承父工程,创建成功后会发现在父工程的pom.xml文件中会自动生成如下:
    1. <modules>
    2. <module>cloud-provider-payment8001</module>
    3. </modules>

    pom.xml

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-web</artifactId>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.springframework.boot</groupId>
    8. <artifactId>spring-boot-starter-actuator</artifactId>
    9. </dependency>
    10. <dependency>
    11. <groupId>org.mybatis.spring.boot</groupId>
    12. <artifactId>mybatis-spring-boot-starter</artifactId>
    13. </dependency>
    14. <dependency>
    15. <groupId>com.alibaba</groupId>
    16. <artifactId>druid</artifactId>
    17. </dependency>
    18. <!--mysql-connector-java-->
    19. <dependency>
    20. <groupId>mysql</groupId>
    21. <artifactId>mysql-connector-java</artifactId>
    22. </dependency>
    23. <!--jdbc-->
    24. <dependency>
    25. <groupId>org.springframework.boot</groupId>
    26. <artifactId>spring-boot-starter-jdbc</artifactId>
    27. </dependency>
    28. <dependency>
    29. <groupId>org.springframework.boot</groupId>
    30. <artifactId>spring-boot-devtools</artifactId>
    31. <scope>runtime</scope>
    32. <optional>true</optional>
    33. </dependency>
    34. <dependency>
    35. <groupId>org.projectlombok</groupId>
    36. <artifactId>lombok</artifactId>
    37. <optional>true</optional>
    38. </dependency>
    39. <dependency>
    40. <groupId>org.springframework.boot</groupId>
    41. <artifactId>spring-boot-starter-test</artifactId>
    42. <scope>test</scope>
    43. </dependency>
    44. </dependencies>

    application.yaml

    1. server:
    2. port: 8001
    3. spring:
    4. application:
    5. name: cloud-payment-service
    6. datasource:
    7. type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型
    8. driver-class-name: com.mysql.cj.jdbc.Driver
    9. url: jdbc:mysql://124.70.84.192:3306/Cloud2020
    10. username: root
    11. password: 12345ssdlh
    12. mybatis:
    13. mapper-locations: classpath:mapper/*.xml
    14. type-aliases-package: com.atguigu.springcloud.entities #所有Entity别名类所在包

    主启动类

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

    业务类

    建表sql

    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
    

    实体类entities

    主实体Payment
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Payment implements Serializable {
     private Long id;
     private String serial;
    }
    
    Serializable
    这个接口的作用是实现序列化。
  • 序列化:对象的寿命通常随着生成该对象的程序的终止而终止,有时候需要把在内存中的各种对象的状态(也就是实例变量,不是方法)保存下来,并且可以在需要时再将对象恢复。虽然你可以用你自己的各种各样的方法来保存对象的状态,但是Java给你提供一种应该比你自己的好的保存对象状态的机制,那就是序列化。
  • 总结:Java 序列化技术可以使你将一个对象的状态写入一个Byte 流里(系列化),并且可以从其它地方把该Byte流里的数据读出来(反序列化)。
  • 序列化的用途:
    • 想把的内存中的对象状态保存到一个文件中或者数据库中时候;
    • 想把对象通过网络进行传播的时候;

只要一个类实现Serializable接口,那么这个类就可以序列化了;

Json封装体CommonResult

前后端分离项目,和前端沟通的时候不用和她讲具体业务,直接传一个CommonResult,让前端按照约定和规范去判断里面的编码是否成功,成功了再去做相关操作;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult<T>{
    private Integer code;
    private String message;
    private T data;
    public CommonResult(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

接口dao

mapper接口
@Mapper
public interface PaymentDao {
    public int create(Payment payment);
    public Payment getPaymentById(@Param("id")Long id);
}

mapper映射文件
<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
    <insert id="create" parameterType="com.atguigu.springcloud.entities.Payment" 
            useGeneratedKeys="true" keyProperty="id">
        insert into payment(serial) values (#{serial})
    </insert>
    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>
    <select id="getPaymentById" resultMap="BaseResultMap">
        select * from payment where id=#{id}
    </select>
</mapper>

service

接口PaymentService
public interface PaymentService {
    public int create(Payment payment);
    public Payment getPaymentById(Long id);
}

接口实现类PaymentServiceImpl
@Service
public class PaymentServiceImpl implements PaymentService {
    @Autowired
    private PaymentDao paymentDao;
    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }
    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }
}

controller

@Controller
@Slf4j
public class PaymentController {
    @Autowired
    private PaymentService paymentService;
    @ResponseBody
    @PostMapping(value = "/payment/create")
    public CommonResult<Integer> create(@RequestBody Payment payment){
        int i = paymentService.create(payment);
        log.info("*******插入结果:"+i);
        if(i>=1){
            return new CommonResult(200,"插入数据成功",i);
        }else {
            return new CommonResult(444,"插入数据失败");
        }
    }
    @ResponseBody
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
        Payment payment= paymentService.getPaymentById(id);
        log.info("*******查询结果:"+(payment!=null));
        if(payment!=null){
            return new CommonResult(200,"查询数据成功",payment);
        }else {
            return new CommonResult(444,"查不到数据id="+id);
        }
    }
}

小总结

模块构建步骤:

  1. 建module;
  2. 改pom;
  3. 写yml;
  4. 主启动;
  5. 业务类;

    开启devTools自动热部署

    1、子模块导入jar包

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

    2、父项目添加插件

    <build>
     <finalName>你自己的工程名字,可加可不加</finalName>
     <plugins>
         <plugin>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
             <configuration>
                 <fork>true</fork>
                 <addResources>true</addResources>
             </configuration>
         </plugin>
     </plugins>
    </build>
    

    3、IDEA开启编译的选项

    image.png

    4、设置

    image.png

    5、重启IDEA

    消费者订单模块

    pom.xml

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

    application.yaml

    server:
    port: 80
    

    主启动类

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

    业务类

    实体类entities

    Payment
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Payment implements Serializable {
     private Long id;
     private String serial;
    }
    
    CommonResult
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class CommonResult<T>{
     private Integer code;
     private String message;
     private T data;
     public CommonResult(Integer code, String message) {
         this.code = code;
         this.message = message;
     }
    }
    

    controller

    这里需要调用支付模块的部分,所以需要远程调用,就用到了RestTemplate;
    RestTemplate简介
  • RestTemplate提供了多种便捷访问远程Http服务的方法;
  • 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集;
  • 使用就如同JdbcTemplate一样先引入配置;

    RestTemplate使用

    创建一个配置类;

    @Configuration
    public class ApplicationContextConfig {
      @Bean
      public RestTemplate getRestTemplate(){
          return new RestTemplate();
      }
    }
    /*用注解的方式代替spring.xml配置文件方式*/
    

    OrderController
    @Controller
    @Slf4j
    public class OrderController {
      public static final String PAYMENT_URL="http://localhost:8001/";
      @Autowired
      private RestTemplate restTemplate;
      @ResponseBody
      @GetMapping(value = "/consumer/payment/create")
      public CommonResult<Payment> create(Payment payment){
          return restTemplate.postForObject(PAYMENT_URL+"payment/create",payment,CommonResult.class);
          //发送post请求,参数1为url。参数2为传入的参数对象。参数3为结果集对象。
      }
      @ResponseBody
      @GetMapping(value = "/consumer/payment/{id}")
      public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
          return restTemplate.getForObject(PAYMENT_URL+"payment/get/"+id,CommonResult.class);
          //发送get请求
      }
    }
    

    工程重构

  • 把支付模块与消费者模块共有实体类entities包单独提取出来,减少冗余度;

  • 单独创建一个模块**cloud-api-commons**,把公用的类,接口之类的放在这里面供调用;

    pom.xml

    <dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>cn.hutool</groupId>
          <artifactId>hutool-all</artifactId>
          <version>5.1.0</version>
      </dependency>
    </dependencies>
    

    之后将该引进的包和类进行引入,之后clean+install打包,其他项目进行引入这个自制的jar包即可;

    Eureka服务注册与发现

    介绍

    什么是服务治理

  • Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理;

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

    什么是服务注册与发现

    Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
    在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用。
    RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址));
    下图是Eureka系统框架(左)和Dubbo系统框架(右):
    image.png

    Eureka两大组件

    Eureka包含两个组件:Eureka Server和Eureka Client
    Eureka Server提供服务注册服务:

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

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

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

    单机Eureka构建

    Eureka server服务端

    【注册中心】

    pom.xml

    <dependencies>
      <!--eureka-server-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </dependency>
      <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
      <dependency>
          <groupId>com.atguigu.springcloud</groupId>
          <artifactId>cloud-api-commons</artifactId>
          <version>${project.version}</version>
      </dependency>
      <!--boot web actuator-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <!--一般通用配置-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    </dependencies>
    

    application.yaml

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

    主启动类

    @SpringBootApplication
    @EnableEurekaServer //表示当前是注册中心
    public class EurekaMain7001 {
      public static void main(String[] args) {
          SpringApplication.run(EurekaMain7001.class,args);
      }
    }
    

    启动

    image.png

    Eureka client客户端

    【服务提供者与消费者】

    pom.xml加Eureka-client依赖

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

    服务提供者

    application.yaml添加配置
    eureka:
    client:
      #表示是否将自己注册进EurekaServer默认为true。
      register-with-eureka: true
      #是否从EurekaServer抓取已有的注册信息,默认为true。
      #单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
      fetchRegistry: true
      service-url:
        defaultZone: http://localhost:7001/eureka
    
    主启动类
    添加注解**@EnableEurekaClient**
    测试
    启动Eureka注册中心,然后启动服务提供者,注册中心页面如下:
    image.png

    服务消费者

    application.yaml添加配置
    spring:
    application:
      name: cloud-order-service
    eureka:
    client:
      #表示是否将自己注册进EurekaServer默认为true。
      register-with-eureka: true
      #是否从EurekaServer抓取已有的注册信息,默认为true。
      #单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
      fetchRegistry: true
      service-url:
        defaultZone: http://localhost:7001/eureka
    
    主启动类
    添加注解@EnableEurekaClient
    测试
    image.png

    集群Eureka构建

    原理

    image.png
    微服务RPC远程服务调用最核心的是什么?高可用
    互相注册,相互守望

    服务端搭建

    首先构建一个和上面一摸一样(pom.xml配置文件导入依赖一样)的单机Eureka模块;
    然后为了一台机器上演示不重名,修改电脑hosts文件添加以下内容:

    127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com

application.yaml

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

测试

两个模块全部启动:
image.png

客户端搭建

application.yaml

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

测试

image.png

支付微服务集群搭建

构建一个和8001一样的(pom.xml引入依赖一样,application.yaml配置一样【记得改端口号】,业务类一样,主启动类一样)支付模块8002;
image.png

修改8001和8002的Controller

添加一下代码,用来标识执行操作的是哪台机器,便于观察,无实际作用;

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

修改消费者80连接

Controller修改连接地址

把原来的地址修改为服务名称
public static final String _PAYMENT_URL_="http://CLOUD-PAYMENT-SERVICE/";

配置类赋予RestTemplate负载均衡的能力
@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

默认是轮询:按照权重你一次我一次你…..

actuator微服务信息完善

简介

这是springboot程序的监控系统,可以实现健康检查,info信息等。在使用之前需要引入spring-boot-starter-actuator,并做简单的配置即可。
springboot2笔记里有记—->>springboot2笔记

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

服务名称修改

eureka:
  instance:
    instance-id: payment8001 #设置主机名称
    prefer-ip-address: true #访问路径可以显示IP地址

测试

image.png

服务发现Discovery

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息;

Controller

修改服务提供端8002机器的Controller;

@Autowired
private DiscoveryClient discoveryClient;
@ResponseBody
@GetMapping(value = "/payment/discovery")
public Object discovery(){
    List<String> services = discoveryClient.getServices();
    //获得服务清单列表(对外暴露的微服务名称)
    for (String service:services){
        log.info("*****element:"+service);
    }
    List<ServiceInstance> instances = 
        discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    //具体微服务的详细信息
    for(ServiceInstance instance:instances){
        log.info("主机id:"+instance.getServiceId());
        log.info("主机名称:"+instance.getHost());
        log.info("端口号:"+instance.getPort());
        log.info("URI地址:"+instance.getUri());
    }
    return discoveryClient;
}

主启动类

@EnableEurekaClient
@SpringBootApplication
@EnableDiscoveryClient//添加这个注解
public class PaymentMain8002 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8002.class,args);
    }
}

测试

image.png

Eureka自我保护

简介

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
image.png

  • 一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存;
  • 属于CAP里面的AP分支;

    产生原因

    为什么会产生Eureka自我保护机制?

    为了防止EurekaClient可以正常运行,但是与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除

    什么是自我保护模式?

    默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题:当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
    image.png
    在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着;
    综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

    禁用自我保护

    Eureka Server

    eureka:
    server:
      enable-self-preservation: false
      #关闭自我保护机制
      eviction-interval-timer-in-ms: 2000
      #修改最大心跳连接时间(默认90秒)
    

    Eureka Client

    eureka:
    instance:
      lease-renewal-interval-in-seconds: 1
      #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30秒)
      lease-expiration-duration-in-seconds: 2
      #Eureka服务端在收到最后一次心跳后等待时间上限,单位秒(默认90秒)超时剔除服务
    

    zookeeper服务注册与发现

  • zookeeper启动失败查看原因:使用./zkServer.sh start-foreground启动可以看到报错信息;

  • zookeeper启动时需要占用8080端口,可以通过在zoo.cfg配置文件中配置admin.serverPort=8888来解决zookeeper和Tomcat端口冲突问题;

    服务提供者

    pom.xml

    <dependencies>
      <!-- SpringBoot整合Web组件 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
      <dependency>
          <artifactId>cloud-api-commons</artifactId>
          <groupId>com.atguigu</groupId>
          <version>1.0-SNAPSHOT</version>
       </dependency>
      <!-- SpringBoot整合zookeeper客户端 -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    </dependencies>
    

    application.yaml

    #8004表示注册到zookeeper服务器的支付服务提供者端口号
    server:
    port: 8004
    #服务别名----注册zookeeper到注册中心名称
    spring:
    application:
      name: cloud-provider-payment
    cloud:
      zookeeper:
        connect-string: 124.70.84.192:2181
    

    主启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
    public class PaymentMain8004 {
      public static void main(String[] args) {
          SpringApplication.run(PaymentMain8004.class,args);
      }
    }
    

    注意

    假如启动时由于jar包冲突问题无法启动或连接失败:
    image.png
    可以使用jar包排除如下:

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

    测试

    启动主程序类之后,zookeeper客户端如下:
    image.png
    java连接zookeeper创建的服务节点是临时的,连接断开这个服务节点就会被移除,重新连接会创建一个新的服务节点:
    image.png

    服务消费者

    pom.xml

    <dependencies>
      <!-- SpringBoot整合Web组件 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- SpringBoot整合zookeeper客户端 -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
          <!--先排除自带的zookeeper-->
          <exclusions>
              <exclusion>
                  <groupId>org.apache.zookeeper</groupId>
                  <artifactId>zookeeper</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <!--添加zookeeper3.5.7版本-->
      <dependency>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
          <version>3.5.7</version>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    </dependencies>
    

    application.yaml

    server:
    port: 80
    spring:
    application:
      name: cloud-consumer-order
    cloud:
      #注册到zookeeper地址
      zookeeper:
        connect-string: 124.70.84.192:2181
    

    主启动类

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

    业务类

    controller

    @RestController
    public class ZkController {
      public static final String INVOKE_URL="http://cloud-provider-payment";
      @Autowired
      private RestTemplate restTemplate;
      @GetMapping(value = "/consumer/zk")
      public String paymentInfo(){
          return restTemplate.getForObject(INVOKE_URL+"/payment/zk",String.class);
          //"/payment/zk"为服务提供者的Controller层中设置的访问方法路径
          //这里测试服务消费者调用服务提供者的方法是否成功
      }
    }
    

    config

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

    测试

    image.png

    Consul服务注册与发现

    简介

    —->>官网

    介绍

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

  • 提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
  • 它具有很多优点。包括: 基于raft协议,比较简洁; 支持健康检查, 同时支持HTTP和DNS协议,支持跨数据中心的WAN集群,提供图形界面,跨平台,支持Linux、Mac、Windows;
  • —->>consul中文文档

    功能

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

    安装

    下载地址:https://www.consul.io/downloads
    官网安装说明:https://learn.hashicorp.com/consul/getting-started/install.html
    linux安装:

  6. Linux安装yum-utils:yum install -y yum-utils

  7. Linux配置consul的下载仓库:
    1. sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
    2. 必须要有此步,不然直接安装会因为在默认仓库找不到,导致安装失败;
  8. Linux yum安装consul:sudo yum -y install consul

安装完成后,consul检查版本号:consul -v

启动

命令:consul agent -dev -ui -node=consul-dev-14 -client=0.0.0.0

  • 0.0.0.0表示不绑定客户端IP地址,不然只能使用特定的IP访问;
  • -http-port 默认是8500
  • -client:客户端模式,http dns,默认127.0.0.1,回环令牌网址;
  • -data-dir:状态数据存储文件夹,所有的节点都需要。文件夹位置需要不收consul节点重启影响,必须能够使用操作系统文件锁,unix-based系统下,文件夹文件权限为0600,注意做好账户权限控制;
  • -dev:开发模式,去掉所有持久化选项,内存服务器模式。
  • -ui:内置web ui界面。

启动成功后可以用curl工具或者浏览器检测是否打开成功:

  • 命令:curl localhost:8500/v1/catalog/nodes
  • image.png

关闭consul命令:consul leave
查找consul安装包:

[root@ecs-mcr ~]# rpm -qa|grep consul consul-1.11.5-1.x86_64

查找安装包的安装路径:

[root@ecs-mcr ~]# rpm -ql consul-1.11.5-1.x86_64 /etc/consul.d/consul.env /etc/consul.d/consul.hcl /usr/bin/consul /usr/lib/systemd/system/consul.service

consul默认端口号:8500

服务提供者

pom.xml

<dependencies>
    <!--SpringCloud consul-server -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-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.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yaml

###consul服务端口号
server:
  port: 8006
spring:
  application:
    name: consul-provider-payment
  ####consul注册中心地址
  cloud:
    consul:
      host: 124.70.84.192
      port: 8500
      discovery:
        service-name: ${spring.application.name}
        heartbeat:
          enabled: true #开启心跳检测,不然会报连接不健康

主启动类

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

测试

image.png

服务消费者

pom.xml

<dependencies>
    <!--SpringCloud consul-server -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-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.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yaml

###consul服务端口号
server:
  port: 80
spring:
  application:
    name: cloud-consumer-order
  ####consul注册中心地址
  cloud:
    consul:
      host: 124.70.84.192
      port: 8500
      discovery:
        service-name: ${spring.application.name}
        heartbeat:
          enabled: true

主启动类

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

业务类

config

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

controller

@RestController
public class OrderConsulController {
    public static final String INVOKE_URL="http://consul-provider-payment";
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping(value = "/consumer/consul")
    public String paymentInfo(){
        return restTemplate.getForObject(INVOKE_URL+"/payment/consul",String.class);
        //"/payment/consul"为服务提供者的Controller层中设置的访问方法路径
        //这里测试服务消费者调用服务提供者的方法是否成功
    }
}

测试

image.png

三个注册中心异同点

image.png
C:强一致性;A:可用性;P:分区容错性;
CAP理论关注粒度是数据,而不是整体系统设计的策略;
最多只能同时较好的满足两个。

  • CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
    • CA-单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
    • CP-满足一致性,分区容忍必的系统,通常性能不是特别高。
    • AP-满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

AP架构(Eureka)

  • image.png
  • 结论:违背了一致性C的要求,只满足可用性和分区容错,即AP;

CP架构(Zookeeper/Consul)

  • image.png
  • 结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

    Ribbon负载均衡服务调用

    概述

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

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

    LB负载均衡是什么

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

  • Ribbon**=**负载均衡**+**RestTemplate调用

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

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

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

    集中式LB

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

    进程内LB

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

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

    Ribbon负载均衡演示

    架构说明

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

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

二说RestTemplate的使用

—>>官方文档

getForObject方法/getForEntity方法

  • getForObject:返回对象为响应体中数据转化成的对象,基本上可以理解为Json;
  • getForEntity:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等;
    @ResponseBody
    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
      return restTemplate.getForObject(PAYMENT_URL+"payment/get/"+id,CommonResult.class);
      //发送get请求
    }
    *****************上为getForObject*******************下为getForEntity*******************
    @ResponseBody
    @GetMapping(value = "/consumer/payment1/get/{id}")
    public CommonResult<Payment> getPayment1(@PathVariable("id")Long id){
      ResponseEntity<CommonResult> entity = 
         restTemplate.getForEntity(PAYMENT_URL + "payment/get/" + id, CommonResult.class);
      log.info("状态码是:"+entity.getStatusCode().toString());
      if(entity.getStatusCode().is2xxSuccessful()){
          return entity.getBody();
      }else {
          return new CommonResult<>(444,"操作失败");
      }
    }
    

    T getForObject(String url, Class responseType, Object… uriVariables); T getForObject(String url, Class responseType, Map uriVariables); T getForObject(URI url, Class responseType); ResponseEntity getForEntity(String url, Class responseType, Object… uriVariables);

postForObject/postForEntity

image.png

T postForObject(URI url, @Nullable Object request, Class responseType);

ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType, Object… uriVariables);

ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType, Map uriVariables);

ResponseEntity postForEntity(URI url, @Nullable Object request, Class responseType);

Ribbon核心组件IRule

IRule:根据特定算法中从服务列表中选取一个要访问的服务;
image.png
ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器;

替换负载规则

注意配置细节

官方文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
所以建包如下:
image.png

负载均衡配置包

在刚才所建的在主启动类所在包以外的包中;

package com.atguigu.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule(){
        return new RandomRule();//定义为随机
    }
}

主启动类

添加注解**@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)**

  • CLOUD-PAYMENT-SERVICE:服务提供者服务名

    Ribbon负载均衡算法

    负载均衡轮询算法:rest接口第几次请求数**%**服务器集群总数量**=**实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。例如下:

    List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”); List [0] instances = 127.0.0.1:8002 List [1] instances = 127.0.0.1:8001

OpenFeign服务接口调用

概述

—->>官网—->>GitHub源码地址
OpenFeign自带Ribbon

是什么

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

    能干吗

    Feign能干什么

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

    Feign集成了Ribbon

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

    Feign和OpenFeign两者区别

    Feign

  • Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端;

  • Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务;

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

    OpenFeign

  • OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。

  • OpenFeign的@FeignClient可以解析SpringMVC@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

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

    OpenFeign使用步骤

  • openfeign用在服务消费端;

    • image.png
  • 微服务调用接口+**@FeignClient**

    pom.xml

    <dependencies>
      <!--openfeign-->
      <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>
      <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
      <dependency>
          <artifactId>cloud-api-commons</artifactId>
          <groupId>com.atguigu</groupId>
          <version>1.0-SNAPSHOT</version>
      </dependency>
      <!--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>
      <!--一般基础通用配置-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    </dependencies>
    

    application.yaml

    server:
    port: 80
    eureka:
    client:
      register-with-eureka: false #这里就不注册进客户端了
      service-url:
        defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    

    主启动类

    @SpringBootApplication
    @EnableFeignClients //使用Feign激活并开启
    public class OrderFeignMain80 {
      public static void main(String[] args) {
          SpringApplication.run(OrderFeignMain80.class,args);
      }
    }
    

    业务类

    service

    写一个业务逻辑接口并添加上@FeignClient配置调用provider服务;

    @Service
    @FeignClient(value = "CLOUD-PAYMENT-SERVICE")//写微服务名称
    public interface PaymentFeignService {
      @GetMapping(value = "/payment/get/{id}")
      public CommonResult getByFeign(@PathVariable("id")Long id);
      //将形参id与上面{}中的id绑定
    }
    

    controller

    @Controller
    public class OrderFeignController {
      @Autowired
      private PaymentFeignService paymentFeignService;
      @ResponseBody
      @GetMapping(value = "/consumerFeign/payment/get/{id}")
      public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
          return paymentFeignService.getByFeign(id);
      }
    }
    

    总结

    image.png

    OpenFeign超时控制

    服务消费端使用openfeign,默认等待1s,而服务提供方处理业务需要3s,此时会报超时错误,如下:
    image.png

    配置openfeign的超时时间

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

    OpenFeign日志打印功能

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

    日志级别

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

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

    配置日志bean

    @Configuration
    public class FeignConfig {
      @Bean//import feign.Logger;
      Logger.Level feignLoggerLevel(){
          return Logger.Level.FULL;
      }//配置最高的FULL级别的日志
    }
    

    YML文件里需要开启日志的Feign客户端

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

    后台日志查看

    image.png

    Hystrix断路器

    概述

    分布式系统面临的问题

    复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
    image.png

    服务雪崩

    多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的扇出。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的雪崩效应。
    对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
    所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

    Hystrix是什么

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

    Hystrix能干吗

  • 服务降级;

  • 服务熔断;
  • 接近实时的监控;
  • —->>github官网
  • Hystrix官宣,停更进维

    Hystrix重要概念

    服务降级

    比如服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示(fallback);
    哪些情况会出发降级:

  • 程序运行异常;

  • 超时;
  • 服务熔断触发服务降级;
  • 线程池/信号量打满也会导致服务降级;

    服务熔断

    类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示;
    服务的降级->进而熔断->恢复调用链路;

    服务限流

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

    Hystrix案例

    服务提供者

    pom.xml

    <dependencies>
      <!--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>
      <!--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>
      <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
          <artifactId>cloud-api-commons</artifactId>
          <groupId>com.atguigu</groupId>
          <version>${project.version}</version>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    </dependencies>
    

    application.yaml

    server:
    port: 8001
    spring:
    application:
      name: cloud-provider-hystrix-payment
    eureka:
    client:
      register-with-eureka: true
      fetch-registry: true
      service-url:
        defaultZone: http://eureka7001.com:7001/eureka
        #使用单机版的eureka注册中心
    

    主启动类

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

    业务类

    service
    @Service
    public class PaymentService {
      //正常访问(一定没问题的方法)
      public String paymentInfo_OK(Integer id){
          return "*******************正常方法调用了*******************"+id;
      }
      //超时的方法
      public String paymentInfo_TimeOut(Integer id){
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          return "*******************超时方法调用了(耗时3s)*******************"+id;
      }
    }
    

    controller
    @RestController
    @Slf4j
    public class PaymentController {
      @Autowired
      private PaymentService paymentService;
      @Value("${server.port}")
      private String serverPort;
      @GetMapping("/payment/hystrix/ok/{id}")
      public String paymentInfo_OK(@PathVariable("id")Integer id){
          String s = paymentService.paymentInfo_OK(id);
          log.info("ok*************result:"+s);
          return s;
      }
      @GetMapping("/payment/hystrix/timeout/{id}")
      public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
          String s = paymentService.paymentInfo_TimeOut(id);
          log.info("timeout*************result:"+s);
          return s;
      }
    }
    

    Jmeter压力测试

    添加服务消费者80,压力测试下进行访问出现:卡顿,tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

    如何解决?解决的要求

    超市不再等待,出错要有兜底;

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级;

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

    服务降级

    服务提供者

    设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback;

    service业务层
    //超时的方法
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
          @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
                           value = "1000")//设定最大超时时间为1s,超过了就出错
    })
    //如果本方法出事了,paymentInfo_TimeOutHandler方法来兜底
    public String paymentInfo_TimeOut(Integer id){
      int age=10/0;    //程序出错
    //        try {        //超时异常
    //            Thread.sleep(3000);
    //
    //        } catch (Exception e) {
    //            e.printStackTrace();
    //        }
      return "*******************超时方法调用了(耗时3s)*******************"+id;
    }
    public String paymentInfo_TimeOutHandler(Integer id){
      return "paymentInfo_TimeOutHandler方法提醒您超时了┭┮﹏┭┮";
    }
    

    主启动类

    添加注解**@EnableCircuitBreaker**

    测试

    无论是超时还是程序报错,都可以弹出兜底方法;
    image.png

    服务消费者

    80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护;

    application.yaml
    server:
    port: 80
    eureka:
    client:
      register-with-eureka: false
      service-url:
        defaultZone: http://eureka7001.com:7001/eureka/
    feign:
    hystrix:
      enabled: true
    

    主启动类

    添加注解@EnableHystrix

    Controller
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "TimeOutHandler",commandProperties = {
          @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
                  value = "1000")//设定最大超时时间为1s,超过了就出错
    })//如果本方法出事了,TimeOutHandler方法来兜底
    public String TimeOut(@PathVariable("id")Integer id){
      return paymentHystrixService.TimeOut(id);
    }
    public String TimeOutHandler(@PathVariable("id")Integer id){
      return "消费者提醒您超时或运行错误";
    }
    

    service接口
    @Service
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
    public interface PaymentHystrixService {
      @GetMapping("/payment/hystrix/ok/{id}")
      public String OK(@PathVariable("id")Integer id);
      @GetMapping("/payment/hystrix/timeout/{id}")
      public String TimeOut(@PathVariable("id")Integer id);
    }
    

    测试

    image.png

    目前问题

    1、每个业务方法对应一个兜底方法,代码膨胀;

    解决代码膨胀
    @DefaultProperties(defaultFallback = "error01")
    //配置统一的兜底方法
    public class ServiceOrController{
      ...
      @HystrixCommand(fallbackMethod = "error02")
      //如果方法单独配了就用单独配的兜底方法  
      public String AAA(@PathVariable("id")Integer id){
         .....
      }
      public String error02(@PathVariable("id")Integer id){
         .....
      }
      ....
      @HystrixCommand
      //如果没配了就用统一的兜底方法
      public String BBB(@PathVariable("id")Integer id){
         .....
      }
      public String error01(){//全局服务降级方法必须空参
         .....
      }
      ....    
    }
    

    除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback="") 统一跳转到统一处理结果页面;
    2、代码和业务逻辑混在一块,代码耦合度高

    解决代码耦合度高

    未来我们要面对的异常:运行报错,超时,宕机;

    service接口实现类
    @Service//下面两个接口实现方法就是接口
    public class PaymentFallbackService implements PaymentHystrixService{    
      @Override
      public String OK(Integer id) {
          return "OK方法异常(运行报错,超时或者宕机)了";
      }
      @Override
      public String TimeOut(Integer id) {
          return "TimeOut方法异常(运行报错,超时或者宕机)了";
      }
    }
    

    原service接口
    @Service
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",
               fallback = PaymentFallbackService.class)
    //新增“fallback”属性:配置定义fallback方法的接口实现类
    public interface PaymentHystrixService {
      @GetMapping("/payment/hystrix/ok/{id}")
      public String OK(@PathVariable("id")Integer id);
      @GetMapping("/payment/hystrix/timeout/{id}")
      public String TimeOut(@PathVariable("id")Integer id);
    }
    

    application.yaml
    feign:
    hystrix:
      enabled: true #保证打开feign里面的hystrix功能
    

    测试

    服务消费端的Controller无需再使用_@HystrixCommand_注解或_@DefaultProperties_注解,无需再写服务降级方法。服务消费端调用服务生产者方法时,如果服务生产者里的业务方法出现程序错误,超时,宕机的情况时,服务消费端就会调用此服务降级方法,也就是重写的接口实现方法:
    image.png
    注意:此方法只针对服务生产者出现错误,如果是服务消费端出现错误则无法服务降级;

    总结

  • @HystrixCommand可以为某些方法单独配置服务降级方法,但是必须保证原方法与讲解方法形参完全一样,否则会报fallback方法找不到;

  • 如果直接配置@DefaultProperties(defaultFallback="")的话,全局服务降级方法必须是空参,不然会报fallback方法找不到错误;
  • 服务生产者用服务降级时,**yaml**配置文件无需开启hystrix,服务消费者使用服务降级时,需要开启hystrix,如下:

    feign:
    hystrix:
      enabled: true
    

    服务熔断

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

  • **Spring Cloud**框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand
  • —->>大神的服务熔断博客

    服务生产者

    service

    @HystrixProperty中的参数都在HystrixCommandProperties类中】

    /*==================服务熔断场景(参数不能为负数)====================*/
    @HystrixCommand(fallbackMethod="paymentCircuitBreaker_fallback",commandProperties={
          @HystrixProperty(name="circuitBreaker.enabled",value = "true"),//是否开启断路器
          @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="10"),
          //请求次数
          @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="60000"),
          //服务熔断的时间窗口期
          @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "60"),
          //失败率达到多少百分比跳闸
    /*这里指的是在窗口期60s内如果10次内有6次及以上请求失败时触发服务熔断,此时服务将被熔断,
    就算是正常的服务也不能正确响应,只有这60s的时间窗口期过后(熔断半开)访问正常的服务才会
    恢复调用链路,之后再次进入下一个窗口期*/})
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
      if(id < 0){
          throw new RuntimeException("******id不能负数");//抛一个“不能负数”异常
      }
      String serialNumber = IdUtil.simpleUUID();
      /*IdUtil.simpleUUID()等于UUID.randomUUID().toString()
       HUtool工具包的方法:生成的是不带-的字符串*/
      return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
    }
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
      return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " +id;
    }
    

    —->>hutool工具包官网

    测试

    在60秒内首先输入11次负数,显示id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id:xxx,之后输入正数仍然报错(服务熔断中),直到60秒过后(窗口期已过,恢复调用链路)输入正数显示hystrix-PaymentService-10 调用成功,流水号:xxx

    总结

    image.png

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

  • 熔断关闭:熔断关闭不会对服务进行熔断;
  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断;
    涉及断路器三个重要参数:
  1. 快照时间窗circuitBreaker.sleepWindowInMilliseconds断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的5秒。
  2. 请求总数阀值circuitBreaker.requestVolumeThreshold在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在5秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  3. 错误百分比阀值circuitBreaker.errorThresholdPercentage当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
    断路器开启或者关闭的条件
  • 当满足一定的阀值的时候(默认5秒内超过20个请求次数),当失败率达到一定的时候(默认5秒内超过50%的请求失败),到达以上阀值,断路器将会开启;
  • 当开启的时候,所有请求都不会进行转发;
  • 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。

    断路器打开之后

    1:再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
    2:原来的主逻辑要如何恢复呢?

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

  • 当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑;
  • 当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将闭合,主逻辑恢复。如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
    All配置
    //========================All
    @HystrixCommand(fallbackMethod = "str_fallbackMethod",
      groupKey = "strGroupCommand",
      commandKey = "strCommand",
      threadPoolKey = "strThreadPool",
      commandProperties = {
          // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
          @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
          // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
          @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", 
                           value = "10"),
          // 配置命令执行的超时时间
          @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", 
                           value = "10"),
          // 是否启用超时时间
          @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
          // 执行超时的时候是否中断
          @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", 
                           value = "true"),
          // 执行被取消的时候是否中断
          @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", 
                           value = "true"),
          // 允许回调方法执行的最大并发数
          @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", 
                           value = "10"),
          // 服务降级是否启用,是否执行回调函数
          @HystrixProperty(name = "fallback.enabled", value = "true"),
          // 是否启用断路器
          @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
          //该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为20的时候,
         //如果滚动时间窗(默认10秒)内仅收到了19个请求,即使这19个请求都失败了,断路器也不会打开。
          @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", 
                           value = "20"),
          /*该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
          circuitBreaker.requestVolumeThreshold的情况下,如果错误请求数的百分比超过50,
          就把断路器设置为“打开”状态,否则就设置为 "关闭" 状态。*/
          @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", 
                           value = "50"),
          /* 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
          会将断路器置为“半开”状态,尝试熔断的请求命令,如果依然失败就将断路器
          继续设置为“打开”状态,如果成功就设置为 "关闭" 状态。*/
          @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", 
                           value = "5000"),
          // 断路器强制打开
          @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
          // 断路器强制关闭
          @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
          // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
          @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", 
                           value = "10000"),
          /*该属性用来设置滚动时间窗统计指标信息时划分“桶”的数量,断路器在收集指标信息的时候
          会根据设置的时间窗长度拆分成多个“桶”来累计各度量值,每个“桶”记录了一段时间内的
          采集指标。比如10秒内拆分成10个“桶”收集这样,所以timeinMilliseconds必须能被
          numBuckets整除。否则会抛异常;
          */
          @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
          /*该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。
          如果设置为 false, 那么所有的概要统计都将返回-1。*/
          @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
          // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
          @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", 
                           value = "60000"),
          // 该属性用来设置百分位统计滚动窗口中使用“桶”的数量。
          @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", 
                           value = "60000"),
          /*该属性用来设置在执行过程中每个“桶”中保留的最大执行次数。如果在滚动时间窗内
          发生超过该设定值的执行次数,就从最初的位置开始重写。例如,将该值设置为100, 
          滚动窗口为10秒,若在10秒内一个“桶”中发生了500次执行,那么该“桶”中只保留最后的
          100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,
          并增加排序百分位数所需的计算时间。
          */
          @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
          // 该属性用来设置采集影响断路器状态的健康快照(请求的成功,错误百分比)的间隔等待时间。
          @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", 
                           value = "500"),
          // 是否开启请求缓存
          @HystrixProperty(name = "requestCache.enabled", value = "true"),
          // HystrixCommand的执行和事件是否打印日志到HystrixRequestLog中
          @HystrixProperty(name = "requestLog.enabled", value = "true"),
          },
      threadPoolProperties = {
          // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
          @HystrixProperty(name = "coreSize", value = "10"),
          /* 该参数用来设置线程池的最大队列大小。
          当设置为-1 时,线程池将使用SynchronousQueue实现的队列,
          否则将使用LinkedBlockingQueue实现的队列。
          */
          @HystrixProperty(name = "maxQueueSize", value = "-1"),
          // 该参数用来为队列设置拒绝阈值。通过该参数,即使队列没有达到最大值也能拒绝请求。
          // 该参数主要是对LinkedBlockingQueue队列的补充,因为LinkedBlockingQueue
          // 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
          @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
          }
    )
    public String strConsumer() {
      return "hello 2020";
    }
    public String str_fallbackMethod()
    {
      return "*****fall back str_fallbackMethod";
    }
    

    服务限流

    后面高级篇讲解alibaba的Sentinel说明;

    hystrix工作流程

    —->>官网介绍

    官网流程图

    image.png
    步骤说明:
    image.png

    服务监控hystrixDashboard

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

    创建可视化监控(仪表盘9001)

    pom.xml

    <dependencies>
      <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-actuator</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    </dependencies>
    

    application.yaml

    server:
    port: 9001
    

    主启动类

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

    注意

    所有被监控的微服务(8001/8002/8003)也都需要配置监控依赖:


    org.springframework.boot
    spring-boot-starter-actuator

监控地址http://ip:9001/hystrix,访问如下:
image.png

监控演示

修改被监控模块

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

监控

1、在监控平台输入被监控的网址:http://ip:8001/hystrix.stream
2、并在被监控网址输入几次请求;
3、可以在Hystrix Dashboard观察到:
image.png

看图

1圈:
  • 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
  • 该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。

    1线:

    曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。

    整图说明:

    image.png

    Gateway新一代网关

    简介

  • —->>上一代网关zuul

  • —->>当前网关Gateway

    介绍

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

  • Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等
  • SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty
  • Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标和限流。
  • 源码架构:image.png

    能做什么

    image.png
    反向代理、鉴权、流量控制、熔断、日志监控……

    GateWay模型

    Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。
    Spring Cloud Gateway具有如下特性:

  • 基于Spring Framework 5,Project ReactorSpring Boot 2.0进行构建;

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

    三大核心概念

  • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言过滤器组成,如果断言为true则匹配该路由;

  • Predicate(断言):开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

    总结

    image.png

  • web请求,通过一些匹配条件定位到真正的服务节点。并在这个转发过程的前后进行一些精细化控制。

  • predicate就是我们的匹配条件;
  • filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了;

    Gateway工作流程

    image.png
  1. 客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler
  2. Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。(过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前pre或之后post执行业务逻辑)
  3. Filter在pre类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
  4. post类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

核心逻辑:路由转发+执行过滤器链

入门配置

pom.xml

<dependencies>
    <!--gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--eureka-client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <!--一般基础配置类-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yaml

server:
  port: 9527
spring:
  application:
    name: cloud-gateway
eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

主启动类

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

只是个网关,看大门的,无需业务层;

做路由映射

我们目前不想暴露8001端口(服务提供者模块),希望在8001外面套一层9527;

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

测试

此时就可以用网关的端口访问支付模块的服务了,从而隐藏了支付模块的端口;
image.png

Gateway网关路由有2种配置方式:

在配置文件yml中配置

见上面;

代码中注入RouteLocator的Bean

config
@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("id_name",
                r->r.path("/guonei")
                    .uri("http://news.baidu.com/guonei")).build();
        //输入地址"http://localhost:9527/guonei"可以映射到"http://news.baidu.com/guonei"
        return routes.build();
    }
}

通过微服务名实现动态路由

  • 原来的地址写死了:uri: http://localhost:8001,这里要实现动态路由;
  • 原来是由eureka内嵌的Ribbon实现的负载均衡,这里要用我们创建的Gateway网关实现负载均衡;
  • 默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能;

    pom.xml

    集群中的多个服务提供者必须添加依赖:


    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client

application.yaml

网关的yaml配置文件进行以下更改:

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001     #匹配后提供服务的路由地址
          uri: lb://CLOUD-PAYMENT-SERVICE
          #lb就是一个标识表示需要负载均衡调用服务,底层解析判断uri前缀决定要不要负载均衡
          predicates:
            - Path=/payment/get/**        #断言,路径相匹配的进行路由
        - id: payment_routh2 
          #uri: http://localhost:8001        
          uri: lb://CLOUD-PAYMENT-SERVICE
          predicates:
            - Path=/payment/lb/**

Predicate的使用

介绍

  • Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
  • Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
  • Spring Cloud Gateway创建Route对象时, 使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给RouteSpring Cloud Gateway包含许多内置RoutePredicateFactories
  • 所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

    常用的Route Predicate

    所有Route Predicate均是此接口image.png的实现类;
    image.png

    1、After Route Predicate

    设置什么时间之后才能访问;

    ...
            predicates:
              - Path=/payment/get/**         #断言,路径相匹配的进行路由
              - After=2022-04-05T15:10:03.685+08:00[Asia/Shanghai] 
              #在这个时间之后我们的访问才有效果
    

    2、Before Route Predicate

    设置什么时间之前才能访问;

    ...
            predicates:
              - Path=/payment/get/**         #断言,路径相匹配的进行路由
              - Before=2022-04-25T15:10:03.685+08:00[Asia/Shanghai] 
              #在这个时间之前我们的访问才有效果
    

    3、Between Route Predicate

    设置哪个时间区间内才能访问;

    ...
            predicates:
              - Path=/payment/get/**         #断言,路径相匹配的进行路由
              - Between=2022-02-02T17:45:06.206+08:00[Asia/Shanghai],2023-03-25T18:59:06.206+08:00[Asia/Shanghai] 
              #在这个时间区间我们的访问才有效果
    

    4、Cookie Route Predicate

  • Cookie Route Predicate需要两个参数,一个是Cookie name ,一个是正则表达式。

  • 路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,没有匹配上不执行;
    ...
            predicates:
              - Path=/payment/get/**         #断言,路径相匹配的进行路由
              - Cookie=username,zzyy 
              #必须有一个cookie,且key是username,value是zzyy才能访问
    
    curl测试

    不加cookie C:\Users\Administrator>curl -I http://localhost:9527/payment/get/1 HTTP/1.1 404 Not Found transfer-encoding: chunked Content-Type: application/json Content-Length: 7230 加cookie且key和value符合规定 C:\Users\Administrator>curl -I http://localhost:9527/payment/get/1 —cookie “username=zzyy” HTTP/1.1 200 OK Content-Type: application/json Content-Length: 93 Date: Thu, 21 Apr 2022 10:17:30 GMT 加cookie但key和value不符合规定 C:\Users\Administrator>curl -I http://localhost:9527/payment/get/1 —cookie “username=yyds” HTTP/1.1 404 Not Found transfer-encoding: chunked Content-Type: application/json Content-Length: 7230

5、Header Route Predicate

两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。

...
          predicates:
            - Path=/payment/get/**         #断言,路径相匹配的进行路由
            - Header=Id, \d+
            # 请求头要有Id属性并且值符合为正整数的正则表达式

curl测试

C:\Users\Administrator>curl -I http://localhost:9527/payment/get/1?Id=1 -H “Id:1” HTTP/1.1 200 OK Content-Type: application/json Content-Length: 93 Date: Thu, 21 Apr 2022 10:27:08 GMT C:\Users\Administrator>curl -I http://localhost:9527/payment/get/1?Id=1 -H “Id:1.3” HTTP/1.1 404 Not Found transfer-encoding: chunked Content-Type: application/json Content-Length: 7235

6、Host Route Predicate

Host Route Predicate接收一组参数,一组匹配的域名列表,这个模板是一个ant分隔的模板,用**.**号作为分隔符。
它通过参数中的主机地址作为匹配规则。

...
          predicates:
            - Path=/payment/get/**         #断言,路径相匹配的进行路由
            - Host=localhost:9527 #请求标头的Host必须为localhost:9527才可访问

浏览器测试

image.png

7、Method Route Predicate

对请求方式进行限定(get``post)

...
          predicates:
            - Path=/payment/get/**         #断言,路径相匹配的进行路由
            - Method=GET #必须是get请求才可响应

8、Path Route Predicate

略,上面用的就是;

9、Query Route Predicate

支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式。

...
          predicates:
            - Path=/payment/get/**         #断言,路径相匹配的进行路由
            - Query=username, \d+  
            #要有参数username并且值还要是正整数才可访问

总结

路由配置的属性解析

  • id:我们自定义的路由 ID,保持唯一;
  • uri:目标服务地址;
  • predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非);

说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

Filter的使用

—->>官网介绍

介绍

  • 路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
  • Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。

    声明周期

  • pre:业务逻辑之前;

  • post:业务逻辑之后;

    种类

  • GatewayFilter:单一的,31种之多;

  • GlobalFilter:全局的,10个;
  • 具体见官网;

    yaml配置样例

    spring:
    application:
      name: cloud-gateway
    cloud:
      gateway:
        discovery:
          locator:
            enabled: true 
        routes:
          - id: payment_routh 
            uri: lb://CLOUD-PAYMENT-SERVICE
            filters:
              - AddRequestParameter=X-Request-Id,1024 
              #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
            predicates:
              - Path=/payment/get/**
    

    自定义过滤器

    自定义全局GlobalFilter

  • 实现两个接口GlobalFilter,Ordered

  • 可以做全局日志记录、统一网关鉴权等

    配置类
    @Configuration
    public class MyLogGateWayFilter implements GlobalFilter, Ordered {
      @Override
      public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
          System.out.println("现在时间:"+new Date());
          String uname = exchange.getRequest().getQueryParams().getFirst("uname");
          //获取参数uname的值
          if(uname==null){
              System.out.println("************非法用户********************");
              exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
              //回应一个不接受
              return exchange.getResponse().setComplete();
          }
          return chain.filter(exchange);
      }
    
      @Override//返回加载过滤器的顺序,数字越小优先级越高
      public int getOrder() {
          return 0;
      }
    }
    

    此时访问时必须带有“uname”参数才能访问成功;

    SpringCloud Config分布式配置中心

    —->>官网

    概述

    分布式系统面临的配置问题

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

  • SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理,/(ㄒoㄒ)/~~;

    是什么

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

    怎么用

    SpringCloud Config分为服务端和客户端两部分:

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

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

    能干嘛

  • 集中管理配置文件;

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

    与GitHub整合配置

    由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式;

    Config服务端配置与测试

    首先在自己的gitee{github太慢了}创建一个仓库(必须是开源的),库名为springcloud-config,并与本地库连接,在里面创建yaml配置文件;
    image.png
    之后创建一个模块cloud-config-center-3344用来做配置中心:

    pom.xml

    <dependencies>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-config-server</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    </dependencies>
    

    application.yaml

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

    主启动类

    @SpringBootApplication
    @EnableConfigServer//开启配置中心服务
    public class ConfigCenterMain3344 {
      public static void main(String[] args) {
          SpringApplication.run(ConfigCenterMain3344.class,args);
      }
    }
    

    测试

    之后在浏览器直接输入http://localhost:3344/master/config-dev.yaml就可以直接访问配置文件config-dev.yaml的内容了;

    配置读取规则

    1、/{label}/{application}-{profile}.yml(推荐)

  • master分支下:http://localhost:3344/master/config-dev.yml

  • dev分支下:http://localhost:3344/dev/config-dev.yml

    2、/{application}-{profile}.yml

    http://localhost:3344/config-dev.yml
    分支在yaml里面去配,默认是master分支

    3、/{application}/{profile}[/{label}]

    http://localhost:3344/config/dev/master
    此方法默认返回的是json串,还要自己解析

    小总结

  • label:分支(branch);

  • application:服务名;
  • profile:环境(dev/test/prod);

    Config客户端配置与测试

    pom.xml

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

    bootstrap.yaml

    是什么

  • applicaiton.yml是用户级的资源配置项;

  • bootstrap.yml是系统级的,优先级更加高;

Spring Cloud会创建一个Bootstrap Context,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment
Bootstrap属性有高优先级,默认情况下它们不会被本地配置覆盖。 Bootstrap ContextApplication Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap ContextApplication Context配置的分离(bootstrap.yml是比 application.yml先加载的。bootstrap.yml优先级高于application.yml;

内容

server:
  port: 3355
spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称
#上述3个综合:从http://config-3344.com:3344/master/config-dev.ymlmaster
#读取分支上config-dev.yml的配置文件
      uri: http://localhost:3344 #配置中心地址
#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

主启动类

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

业务类

@RestController
public class ConfigClientController {
    @Value("${id}")
    private String id;
    @GetMapping("/forid")
    public String id2(){return id;}
}

测试

image.png

Config客户端之动态刷新

避免每次更新配置都要重启客户端微服务3355;

动态刷新修改步骤

客户端pom.xml

客户端记得加上actuator健康监控;(差不多除了网管不加,其余微服务模块都要加)

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

客户端修改yml文件

修改YML,暴露监控端口;

#添加暴露监控端点配置
management:
  endpoints:
    web:
      exposure:
        include: "*"

客户端业务类

加上注解:@RefreshScope刷新注解标签

  • importorg.springframework.cloud.context.config.annotation.RefreshScope;

    测试

  1. 首先启动客户端之后修改gitee中该配置文件的内容;
  2. 发送post请求刷新这个客户端:curl -X POST "http://localhost:3355/actuator/refresh"
  3. 之后再次访问该客户端就得到了修改后的值;

手动刷新避免了客户端微服务重启

SpringCloud Bus消息总线

概述

上一讲解的加深和扩充,一言以蔽之,分布式自动刷新配置功能,**Spring Cloud Bus**配合**Spring Cloud Config**使用可以实现配置的动态刷新。

是什么

Bus支持两种消息代理:RabbitMQKafka,下图为第1种设计思想;
image.png
Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。

能干嘛

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道,下图为第2种设计思想。
image.png

为何被称为总线

什么是总线

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

基本原理

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

RabbitMQ环境配置

由于之前学的是activemq,这里简单记录一下rabbitmq的一些基本指令;

  • 开启rabbitmqrabbitmq-server -detached
  • 关闭rabbitmqrabbitmqctl stop
  • 查看rabbitmq默认的所有插件:rabbitmq-plugins list
  • 启用rabbitmq管理控制台插件:rabbitmq-plugins enable rabbitmq_management
  • web管理控制台的端口号:**15672**

默认rabbitmq给我们提供了一个guest的账户 ,密码也是guest,但是只能localhost本地主机访问:

  • image.png

我们可以使用命令再创建一个rabbitmq的管理员账户:

  • rabbitmqctl add_user root root

用户创建好之后,再给此root用户管理员的角色:

  • rabbitmqctl set_user_tags root administrator

将此用户root授予权限,以便下面进行消息总线的操作:

  • image.png

    SpringCloud Bus动态刷新全局广播

  1. 【首先创建客户端模块3366(以3355为模板),和3355一样(先把多个订阅者做出来,之后再进行配置);】

    设计思想

  2. 思想1:利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置;

  3. 思想2:利用消息总线触发一个服务端ConfigServer/bus/refresh端点,而刷新所有客户端的配置;

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

  • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
  • 破坏了微服务各节点的对等性。
  • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改。

    服务端3344添加消息总线支持

    pom.xml添加如下

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

    application.yaml

    server:
    port: 3344
    spring:
    application:
      name: cloud-config-center #注册进Eureka服务器的微服务名
    cloud:
      config:
        server:
          git:
            uri: https://gitee.com/machuran/springcloud-config.git
            #Gitee上面的git仓库名字
            ####搜索目录
            search-paths:
              - springcloud-config
            skip-ssl-validation: true
        ####读取分支
        label: master
    #rabbitmq相关配置
    rabbitmq:
      host: 124.70.84.192
      port: 5672 #客户端使用端口号
      username: root
      password: root
    #服务注册到eureka地址
    eureka:
    client:
      service-url:
        defaultZone: http://localhost:7001/eureka
    ##rabbitmq相关配置,暴露bus刷新配置的端点
    management:
    endpoints: #暴露bus刷新配置的端点
      web:
        exposure:
          include: "bus-refresh"
    

    客户端3355添加消息总线支持

    pom.xml添加如下

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

    bootstrap.yaml

    server:
    port: 3355
    spring:
    application:
      name: config-client
    cloud:
      #Config客户端配置
      config:
        label: master #分支名称
        name: config #配置文件名称
        profile: dev #读取后缀名称
        uri: http://localhost:3344 #配置中心地址
    #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
    rabbitmq:
      host: 124.70.84.192
      port: 5672
      username: root
      password: root
    #服务注册到eureka地址
    eureka:
    client:
      service-url:
        defaultZone: http://localhost:7001/eureka
    # 暴露监控端点
    management:
    endpoints:
      web:
        exposure:
          include: "*"
    

    客户端3366添加消息总线支持

    pom.xml添加如下

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

    bootstrap.yaml

    server:
    port: 3366
    spring:
    application:
      name: config-client
    cloud:
      #Config客户端配置
      config:
        label: master #分支名称
        name: config #配置文件名称
        profile: dev #读取后缀名称
        uri: http://localhost:3344 #配置中心地址
    #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
    rabbitmq:
      host: 124.70.84.192
      port: 5672
      username: root
      password: root
    #服务注册到eureka地址
    eureka:
    client:
      service-url:
        defaultZone: http://localhost:7001/eureka
    # 暴露监控端点
    management:
    endpoints:
      web:
        exposure:
          include: "*"
    

    测试

    在服务端和客户端都启动之后,运维人员修改gitee里面配置文件属性的值,之后运维人员向服务端ConfigServer3344发送post刷新请求**_curl -X POST "http://localhost:3344/actuator/bus-refresh"_**,之后客户端3355和3366无需微服务重启就可自动刷新到最新的配置属性值;
    观察rabbitmq的web控制台:
    image.png

    SpringCloud Bus动态刷新定点通知

    POST刷新请求curl -X POST "http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}"

  • 通过destination参数类指定需要更新配置的服务或实例;

此时我们不想全部通知,只想定点通知,我们只通知3355,不通知3366;
POST刷新请求curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"

  • 其中config-client是3355配置文件配置的application.name

    spring:
    application:
      name: config-client
    

    总结

    image.png

    SpringCloud Stream消息驱动

    消息驱动概述

    引出

    image.png

    是什么

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

  • —->>官网

  • —->>官方API
  • —->>Spring Cloud Stream中文指导手册

什么是SpringCloudStream?

  • 官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。
  • 应用程序通过inputs或者outputs来与Spring Cloud Streambinder对象交互。
  • 通过我们配置来binding(绑定) ,而Spring Cloud Streambinder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。
  • 通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
  • Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
  • 目前仅支持RabbitMQ、Kafka。

    设计思想

    标准MQ

    image.png
    没有引入spring cloud stream之前:

  • 生产者/消费者之间靠消息媒介传递信息内容,Message;

  • 消息必须走特定的通道,消息通道MessageChannel;
  • 消息通道里的消息如何被消费呢,谁负责收发处理,消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅;

    为什么用Cloud Stream?

    比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区;
    image.png
    这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。

    stream如何统一底层差异?
  • 在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性;

  • 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
  • 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程;

image.png
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。
Stream中的消息通信方式遵循了发布-订阅模式,Topic主题进行广播:在RabbitMQ就是Exchange,在Kakfa中就是Topic;

Spring Cloud Stream标准流程套路

image.png

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

    编码API和常用注解

    image.png

    案例

    创建三个子模块:

  • 8801作为生产者消息发送模块;

  • 8802,8803作为消息接收模块;

    消息生产者

    pom.xml

    <dependencies>
    <!--导入rabbit消息中间件的stream-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-rabbit</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.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--基础配置-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    </dependencies>
    

    application.yaml

    server:
    port: 8801
    spring:
    application:
      name: cloud-stream-provider
    cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息;
          defaultRabbit: # 表示定义的名称,用于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: 124.70.84.192
                  port: 5672
                  username: root
                  password: root
        bindings: # 服务的整合处理
          output: #这个名字是一个通道的名称,output标识消息生产者
            destination: studyExchange # 表示要使用的Exchange名称
            content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain”;
            binder: defaultRabbit #设置要绑定的消息服务的具体设置(这里爆红不用管)
    eureka:
    client: # 客户端进行Eureka注册的配置
      service-url:
        defaultZone: http://localhost:7001/eureka
    instance:
      lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
      lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
      instance-id: send-8801.com  # 在信息列表时显示主机名称
      prefer-ip-address: true     # 访问的路径变为IP地址
    

    主启动类

    最原始的只需一个@SpringBootApplication注解;

    业务类

    service
    /*********************接口***************************/
    public interface IMessageProvider{public String send();}
    /*******************接口实现类*******************/
    @EnableBinding(Source.class)//定义消息的推送管道
    //import org.springframework.cloud.stream.messaging.Source;
    //原来是标注@Service注解将方法注入到spring容器中,这里不再这样了
    public class IMessageProviderImpl implements IMessageProvider {
      //这里没有dao不需要操作数据库,这里操作的是消息中间件
      @Autowired
      private MessageChannel output;//消息发送管道
      @Override
      public String send() {
          String s = UUID.randomUUID().toString();
          output.send(MessageBuilder.withPayload(s).build());
          //import org.springframework.integration.support.MessageBuilder;
          System.out.println("+++++++++++++UUID:"+s);
          return null;
      }
    }
    
    controller
    @RestController
    public class SendMessageController {
      @Autowired
      private IMessageProvider messageProvider;
      @GetMapping(value = "/send")
      public String sendMess(){
          return messageProvider.send();
      }
    }
    

    测试

    image.png

    消息消费者

    pom.xml

    <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--基础配置-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    </dependencies>
    

    application.yaml

    server:
    port: 8802
    spring:
    application:
      name: cloud-stream-consumer
    cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息;
          defaultRabbit: # 表示定义的名称,用于于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: 124.70.84.192
                  port: 5672
                  username: root
                  password: root
        bindings: # 服务的整合处理
          input: # 这个名字是一个通道的名称
            destination: studyExchange # 表示要使用的Exchange名称定义
            content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain”
            binder: defaultRabbit # 设置要绑定的消息服务的具体设置
    eureka:
    client: # 客户端进行Eureka注册的配置
      service-url:
        defaultZone: http://localhost:7001/eureka
    instance:
      lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
      lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
      instance-id: receive-8802.com  # 在信息列表时显示主机名称
      prefer-ip-address: true     # 访问的路径变为IP地址
    

    主启动类

    和消息生产者一样;

    业务类

    @Component
    @EnableBinding(Sink.class)
    //import org.springframework.cloud.stream.messaging.Sink;
    public class MessageListener {
      @Value("${server.port}")
      private String port;
      @StreamListener(Sink.INPUT)
      public void input(Message<String> message){
          System.out.println("消费者"+port+":"+message.getPayload());
      }
    }
    

    测试

    image.png

    分组消费与持久化

    分组消费

    依照8802,clone出来一份运行8803,此时:
  1. 7001:服务注册;
  2. 8801:消息发送;
  3. 8802:消息消费;
  4. 8803:消息消费;

此时依次启动微服务,消息发送者发送消息,8802和8803都收到消息了,这就是消息重复消费问题

  • 比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。这时我们就可以使用Stream中的消息分组来解决;
  • 注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。不同组是可以全面消费的(重复消费),同一组内会发生竞争关系,只有其中一个可以消费。
  • image.png
    自定义配置分组(分成同一组)
    分到同一组atguigu
    ...
    spring:
    application:
      name: cloud-stream-consumer
    cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息
          defaultRabbit: # 表示定义的名称,用于于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: 124.70.84.192
                  port: 5672
                  username: root
                  password: root
        bindings: # 服务的整合处理
          input: # 这个名字是一个通道的名称
            destination: studyExchange # 表示要使用的Exchange名称定义
            content-type: application/json #设置消息类型,本次为对象json,如果是文本则设置“text/plain”
            binder: defaultRabbit # 设置要绑定的消息服务的具体设置
            group: atguigu #在这里配置组名<<<<<<<<<<<<<<<<<<这里
    ...
    
    image.png

    持久化

  1. 把消费者8802和8803都停掉,之后把8802配置的组名去掉
  2. 之后消息生产者8801发送消息;
  3. 之后再次启动8802和8803,发现8802消息丢失了,8803可以把刚才缺失的消息收到

如果不配置group属性,那么在默认分组时会生成一个随机的流水号作为组名,当这个消费者重启之后,由于是流水号组名会变,相当于是第一个订阅这个主题,那么就不可能收到之前的消息,这就造成了消息的丢失,所以说group属性可以保障消息的持久性

SpringCloud Sleuth分布式请求链路跟踪

概述

引入

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

是什么

  • —->>官网
  • **Spring Cloud Sleuth**提供了一套完整的服务跟踪的解决方案;
  • 在分布式系统中提供追踪解决方案并且兼容支持了**zipkin**

    搭建链路监控

    zipkin

    **SpringCloud**F版起已不需要自己构建Zipkin Server了,只需调用jar包即可;

  • —->>下载地址

  • image.png
  • 启动Zipkin Serverjava -jar zipkin-server-2.12.9-exec.jar启动成功如下:
    • image.png
  • 访问Zipkin Server控制台:http://124.70.84.192:9411/zipkin/:
  • image.png

    完整的调用链路

    请求链路:一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id关联起来;
    image.png
    整个链路的依赖关系如下:
    image.png

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

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

    服务提供者

    pom.xml

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

    application.yaml

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

    服务消费者

    pom.xml

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

    applicaion.yaml

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

    测试

    image.png