SpringCloud(H版以及Alibaba版本)学习笔记(一)

本笔记学习自B站尚硅谷Springcloud时所记录

笔记内容包括了:Springcloud的H版以及Alibaba版本

  • H版具体内容包括:Eureka、Zookeeper、Consul、Ribbon、OpenFeign、Hystrix、Gateway、Config、Bus、Stream、Sleuth等技术的使用;
  • Alibaba版本包括:Nacos、Sentinel、Seata等技术的使用

点击直达

目录

一、SpringCloud的理论学习

SpringCloud+SpringCloud Alibaba

1、分布式架构会遇到的四个核心问题是什么?

  • 这么多的服务,客户端该如何去访问?
  • 这么多服务,服务之间该怎么去通信?
  • 这么多服务,如何治理呢?
  • 服务挂了之后该怎么办?

2、分布式架构问题的解决方案

SpringCloud是一套生态,就是用来解决以上分布式架构的四个问题
想使用SpringCloud就必须要掌握SpringBoot,因为SpringCloud是基于SpringBoot的;

  • 1、一站式解决方案:
    SpringCloud NetFlix出来了一套解决方案,一站式解决方案:

    • API网关,zuul组件
    • Feign —> HttpClient —> Http的通信方式,同步并阻塞
    • 服务的注册与发现,Eureka
    • 熔断机制,Hystrix

    2018年低,NetFlix宣布无限期停止维护,生态不再维护就会脱节。

  • 2、第二套半自动解决方案
    Apache Dubbo Zookeeper,第二套解决方案,半自动:

    • API:没有,要么找第三方组件,要么自己实现
    • Dubbo是一个高性能的基于JAVA实现的RPC通信框架 2.6.x
    • 服务的注册与发现:Zookeeper(动物管理员)(Hadoop,Hive)
    • 熔断机制:没有,借助了Hystrix

    不完善,Dubbo停止更新太久。

  • 3、SpringCloud Alibaba ,一站式解决方案

  • 4、未来方案:

    • 服务网格:下一代微服务标准,Server ,Mesh
    • 代表解决方案:istio
  • 5、总结
    万变不离其宗,一通百通,主要的还是解决以下问题:

    • API网关,服务路由
    • HTTP,RPC框架,异步调用
    • 服务注册与发现,高可用
    • 熔断机制,服务降级

基于以上这四个问题,开发一套解决方案,也叫SpringCloud

3、微服务架构理论

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

4、微服务技术栈有哪些?

在这里插入图片描述

  • 服务开发: SpringBoot、Spring、SpringMVC

  • 服务配置与管理: Netflix公司的Archaius、阿里的Diamond等

  • 服务注册与发现: Euraka、Consul、Zookeeper等

  • 服务调用: Rest、RPC、gRPC

  • 服务熔断器: HyStrix、Envoy等

  • 负载均衡: Ribbon、Nginx等

  • 服务接口调用: Feign等 (客户端调用服务的简化工具)

  • 消息队列: Kafka、RabbirMQ、ActiveMQ等

  • 服务配置中心管理:SpringCloudConfig、Chef等

  • 服务路由(API网关):Zuul等

  • 服务监控: Zabbix、Nagios、Metrics、Specatator等

  • 全链路追踪: Zipkin、Brave、Dapper等

  • 服务部署: Doker、OpenStack、Kubernetes等

  • 数据流操作开发包:SprinfCloud Stream(封装了与Redis。Rabbit、Kafka等发送接收消息)

  • 时间消息总线: SpringCloud Bus
    在这里插入图片描述
    在这里插入图片描述

5、SpringBoot和SpringCloud版本选型

在这里插入图片描述

  • Spring Boot 与 Spring Cloud 兼容性查看

  • 此次开发用到的组件版本

    • Cloud - Hoxton.SR1
    • Boot - 2.2.2.RELEASE
    • Cloud Alibaba - 2.1.0.RELEASE
    • Java - Java 8
    • Maven - 3.5及以上
    • MySQL - 5.7及以上

特别注意:如果boot和cloud的版本不一致,可能会导致环境问题,所以推荐使用官网建议的boot和cloud的版本。

6、SpringCloud组件停更/升级/技术替代

(1)停更引发的“升级惨案”问题

  • 停更不停用
  • 被动修复bugs
  • 不再接受合并请求
  • 不再发布新版本

(2)Cloud升级
在这里插入图片描述

  • 服务注册中心

    • × Eureka
    • √ Zookeeper
    • √ Consul
    • √ Nacos(重点)
  • 服务调用

    • √ Ribbon
    • √ LoadBalancer
  • 服务调用2

    • × Feign
    • √ OpenFeign
  • 服务降级

    • × Hystrix
    • √ resilience4j
    • √ sentienl(推荐)
  • 服务网关

    • × Zuul
    • ! Zuul2
    • √ gateway(推荐)
  • 服务配置

    • × Config
    • √ Nacos (推荐)
  • 服务总线

    • × Bus
    • √ Nacos(推荐)

二、微服务架构编码构建

  • 目标:使用SpringCloud的不同组件,搭建订单-支付模块微服务。
  • 要求:约定 > 配置 > 编码
  • Rest微服务工程构建

1、微服务父工程的构建

  • (1)New Project
    在这里插入图片描述

  • (2)聚合总父工程名字
    在这里插入图片描述

  • (3)Maven选版本
    在这里插入图片描述

  • (4)工程名字
    在这里插入图片描述
    在这里插入图片描述
    接下来整环境。

  • (5)字符编码
    打开Setting----->Editor ------>File Encoding----->设置为UTF-8
    在这里插入图片描述

  • (6)注解生效激活
    在这里插入图片描述

  • (7)Java编译版本选8
    在这里插入图片描述

  • (8)File Type过滤
    在这里插入图片描述

2、父工程的POM文件

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

  <groupId>com.oldou.springcloud</groupId>
  <artifactId>cloud2021</artifactId>
  <version>1.0-SNAPSHOT</version>

  <!-- 由于这是父工程,这里添加pom,注意不是jar或war -->
  <packaging>pom</packaging>

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

  <!-- 子模块继承之后,提供作用:
      锁定版本+子modlue不用写groupId和version -->
  <dependencyManagement>
    <dependencies>
      <!--spring boot 2.2.2-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.2.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud Hoxton.SR1-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.SR1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud alibaba 2.1.0.RELEASE-->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.1.0.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
      </dependency>
      <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.spring.boot.version}</version>
      </dependency>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
      </dependency>
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
      </dependency>
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
      </dependency>
    </dependencies>
  </dependencyManagement>

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

小知识

3、Maven的DependencyManagement和Dependencies

  • Maven使用dependencyManagement元素来提供了一种管理依赖版本号的方式。
  • 通常会在一个组织或者项目的最顶层的父POM中看到dependencyManagement元素。
  • 使用pom.xml中的dependencyManagement元素能让所有在子项目中引用个依赖而不用显式的列出版本量。
  • Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个dependencyManagement元素中指定的版本号。

例如:在父工程中

<dependencyManagement>
    <dependencies>
        <dependency>
        <groupId>mysq1</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.2</version>
        </dependency>
    <dependencies>
</dependencyManagement>

然后在子项目里就可以添加mysql-connector时可以不指定版本号,例如:

<dependencies>
    <dependency>
    <groupId>mysq1</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

这样做的好处就是

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

  • 另外如果某个子项目需要另外的一个版本,只需要声明version就可以了

  • dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖

  • 如果不在子项目中声明依赖,是不会从父项目中继承下来的只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom

  • 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

注意

  • 父工程创建完成执行mvn : install将父工程发布到仓库方便子工程继承。
    在这里插入图片描述

  • IDEA右侧旁的Maven插件有Toggle ’ Skip Tests’ Mode按钮,这样maven可以跳过单元测试
    在这里插入图片描述

4、支付模块的构建

  • 步骤:

    • (1)建Module
      选中父工程,new一个module,maven,选中JDK1.8,然后下一步。
      在这里插入图片描述
    • (2)改子工程的POM文件
    <dependencies>
            <!--包含了sleuth+zipkin-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-zipkin</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.springcloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>${project.version}</version>
            </dependency>
            -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
            <!--mysql-connector-java-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--jdbc-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-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>
    
    • (3)写YML文件
    server:
      port: 8001
    spring:
      application:
        name: cloud-payment-service
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
        driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包
        url: jdbc:mysql://localhost:3306/cloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: root
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
      type-aliases-package: com.oldou.springcloud.entities    # 所有Entity别名类所在包
    
    • (4)主启动
      在这里插入图片描述
    • (5)建表
    CREATE TABLE `payment` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `serial` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '流水号',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    

在这里插入图片描述

  • (6)Entities

    • 实体类Payment:
       /**
        * @auther oldou
        */
       @Data
       @AllArgsConstructor
       @NoArgsConstructor
       public class Payment implements Serializable {
       
           private Long id;
       
           private String serial;
       
       }
    
    • JSON封装体CommonResult
    /**
     * @auther oldo
     * JSON封装体
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class CommonResult<T>{
        private Integer code;
        private String message;
        private T data;
    
        public CommonResult(Integer code, String message){
            this(code, message, null);
        }
    }
    
  • (7)Dao层

    • 接口PaymentDao:
    /**
     * @auther oldou
     */
    @Mapper
    public interface PaymentDao {
    
        public int create(Payment payment);
    
        public Payment getPaymentById(@Param("id") Long id);
    
    }
    
    • PaymentMapper.xml:
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    
    <mapper namespace="com.oldou.springcloud.dao.PaymentDao">
    
        <insert id="create" parameterType="com.oldou.springcloud.entities.Payment" useGeneratedKeys="true" keyProperty="id">
            insert into payment(serial)  values(#{serial});
        </insert>
    
        <resultMap id="BaseResultMap" type="com.oldou.springcloud.entities.Payment">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <id column="serial" property="serial" jdbcType="VARCHAR"/>
        </resultMap>
    
        <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
            select * from payment where id=#{id};
        </select>
    
    </mapper>
    
    
  • (8)Service层

    • 接口PaymentService:
    public interface PaymentService {
    
        public int create(Payment payment);
    
        public Payment getPaymentById(@Param("id") Long id);
    
    }
    
    • 接口PaymentServiceImpl:
    @Service
    public class PaymentServiceImpl implements PaymentService {
        @Resource
        private PaymentDao paymentDao;
    
        public int create(Payment payment) {
            return paymentDao.create(payment);
        }
    
        public Payment getPaymentById(Long id) {
            return paymentDao.getPaymentById(id);
        }
    }
    
  • (9)Controller层

    • PaymentController:
@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

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


    @GetMapping(value = "/pay/get/{id}")
    public CommonResult getById(@PathVariable("id") Long id){
        Payment result = paymentService.getPaymentById(id);
        log.info("**************查询结果:"+result);
        if(result != null){
            return new CommonResult(200,"查询成功",result);
        }
        return new CommonResult(404,"找不到ID为:"+id+"的记录",null);
    }
}

5、支付模块的测试

  • (1)测试
    我们首先在数据库表中手动插入一条测试数据,
    然后找到支付模块的启动类PaymentMain8001,启动项目,
    在浏览器中输入:http://localhost:8001/pay/get/1
    在这里插入图片描述
    然后使用POSTMAN工具发送新增请求:
    在这里插入图片描述

6、热部署Devtools

我们每次改动项目中的代码时,都需要将项目重启,有没有什么办法可以改善呢,那么热部署可以帮助我们。

  • 第一步:添加devtools的pom依赖到项目中
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
  • 第二步:添加macen插件到父工程中
<build>
   <plugins>
     <plugin>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-maven-plugin</artifactId>
       <version>2.3.5.RELEASE</version>
       <configuration>
         <fork>true</fork>
         <addResources>true</addResources>
       </configuration>
     </plugin>
   </plugins>
 </build>
  • 第三步:打开以下界面,配置
    在这里插入图片描述
  • 第四步:在IDEA中 按下【 Ctrl+Shift + Alt + /】,选择第一个。
    在这里插入图片描述
  • 第五步:重启IDEA就可以了

7、消费者订单模块

在这里插入图片描述

(1)建Module
名称为:cloud-consumer-order80 的模块

(2)改POM

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

    <artifactId>cloud-consumer-order80</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

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

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

</project>

(3)写YML

 server:
  port: 80

(4)主启动

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


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

(5)业务类

  • 实体类
    和上面两个一样,Payment 和 CommonResult

  • 控制层Controller

import com.oldou.springcloud.entities.CommonResult;
import com.oldou.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@Slf4j
@RestController
public class OrderController {

    public static final String PAYMENT_URL = "http://localhost:8001";

    @Resource
    private RestTemplate restTemplate;

    @PostMapping("/consumer/pay/create")
    public CommonResult<Payment> create(Payment payment){

        return restTemplate.postForObject(PAYMENT_URL+"/payment/create", payment, CommonResult.class);
    }

    @GetMapping("/consumer/pay/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, CommonResult.class);
    }
}
  • 配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

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

RestTemplate的说明

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

官网地址

使用:

  • 使用restTemplate访问restful接口非常的简单粗暴无脑。
  • (url, requestMap, ResponseBean.class)这三个参数分别代表。
  • REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。

(6)测试
首先启动消费者订单服务,然后再启动支付服务

  • 再浏览器中先输入:http://localhost:8001/pay/get/1
    在这里插入图片描述

  • 然后再输入:http://localhost/consumer/pay/get/4
    在这里插入图片描述

8、如何开启Run DashBoard【课外】

  • 第一步:打开工程路径下的.idea文件夹的workspace.xml

  • 第二步:在中修改或添加以下代码:

<option name="configurationTypes">
	<set>
		<option value="SpringBootApplicationConfigurationType"/>
    </set>
</option>
  • 第三步:重启IDEA
  • 可能名称有不一样的,有的命名是Services
    在这里插入图片描述

9、工程重构

(1)问题
我们对比订单服务和支付服务,发现两个服务的entities中的内容都是一致的,这样会造成代码的冗余,为了减少这样的情况,我们将工程中相同的代码提取出来,成为一个公共的工程,以便于其他服务调用。

(2)新建一个新的工程【cloud-api-commons】

(3)改POM文件

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

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

(4)代码移动

  • 第一步:在cloud-api-commons中新建com.oldou.springcloud.entities包;
  • 第二步:将cloud-consumer-order80或者是cloud-provider-payment8001的entities中两个实体类复制到cloud-api-commons新建的com.oldou.springcloud.entities包中;
  • 第三步:将cloud-api-commons项目进行maven install打包;
  • 第四步:删除cloud-consumer-order80和cloud-provider-payment8001两个项目中的entities;
  • 第五步:分别在cloud-consumer-order80和cloud-provider-payment8001两个项目的pom文件中引入cloud-api-commons的依赖。
<dependency>
   <groupId>com.lun.springcloud</groupId>
   <artifactId>cloud-api-commons</artifactId>
   <version>${project.version}</version>
</dependency>

(5)测试
启动两个项目,重新调用接口测试一下,发现是OK的。

三、微服务组件之服务注册与发现

1、Eureka服务注册与发现

1.1、Eureka基础知识

什么是服务治理?

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

什么是服务注册与发现?

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

什么是Eureka?

  • Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册中心,比如Zookeeper。
  • Netflix 在设计 Eureka 时,遵循的就是AP原则(CAP文章下面有介绍)。

Eureka的两个组件介绍

  • Eureka包含两个组件: Eureka Server和Eureka Client。

  • Eureka Server提供服务注册服务,各个节点启动后,会在EurekaServer中进行注册,这样Eureka Server中的服务注册表将会注册所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到;

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

1.2、单机Eureka的构建

(1)IDEA生成eurekaServer端服务注册中心,类似物业公司

  • 第一步:创建名为cloud-eureka-server7001的Maven工程

  • 第二步:修改pom.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud2021</artifactId>
            <groupId>com.oldou.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-eureka-server7001</artifactId>
        <dependencies>
            <!--eureka-server-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
            <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <dependency>
                <groupId>com.oldou.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>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  • 第三步:添加application.yml

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

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @EnableEurekaServer //开启EurekaServer
    public class EurekaMain7001 {
        public static void main(String[] args) {
            SpringApplication.run(EurekaMain7001.class,args);
        }
    }
    
  • 第五步:启动测试:localhost:7001
    在这里插入图片描述

1.3、支付服务8001入驻EurekaServer

我们访问Eureka的界面时发现现在是没有任何服务入驻的,接下来我们将支付服务8001入驻Eureka。
在这里插入图片描述
目的我们将EurekaClient端cloud-provider-payment8001注册进EurekaServer 7001成为服务提供者provider。
在这里插入图片描述

  • 第一步:修改cloud-provider-payment8001服务的pom文件,添加以下依赖:
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 第二步:修改cloud-provider-payment8001服务的YML,添加以下配置:
    eureka:
      client:
        #表示是否将自己注册进Eurekaserver默认为true。
        register-with-eureka: true
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        fetchRegistry: true
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  • 第三步:修改cloud-provider-payment8001服务的主启动类,添加以下注解:
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    @SpringBootApplication
    @EnableEurekaClient      //添加此注解,EurekaClient
    public class PaymentMain001 {
    
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain001.class, args);
        }
    }
    
  • 第四步:启动cloud-provider-payment8001服务,在Eureka网页中刷新发现服务注册进来了。
    在这里插入图片描述
    而上图中,我们可以发现注册进来的Application显示的名称正是cloud-provider-payment8001服务的配置文件application.yml设置的应用名cloud-payment-service。
    在这里插入图片描述

1.4、Eureka的自我保护机制

  • 出现的问题:
    在这里插入图片描述
    我们在Eureke服务的界面中看到以上红色提示,这就是Eureka的自我保护机制,那么这是什么意思呢?

  • 概述:
    保护模式主要是用于一组客户端和Eureka Server之间存在网络分区场景下的保护,一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不会删除服务注册表中的数据,也就是不会注销任何微服务

一句话总结:某时刻某一个微服务不可以用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存!

  • 默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka之间无法正常通行,以上行为可能变得非常危险了,因为微服务本身其实是健康的,此时本不应该注销这个服务。Eureka通过自我保护机制来解决这个问题当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该EurekaServer节点会自动退出自我保护模式。

  • 在自我保护模式中,EurekaServer会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数(5s检测一次)重新恢复到阈值以上时,该EurekaServer节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话:好死不如赖活着

  • +综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮和稳定

怎么禁止Eureka的自我保护机制?

在SpringCloud中,可以在yml文件中使用eureka.server.enable-self-preservation = false禁用自我保护模式【不推荐关闭自我保护机制】

默认:
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90

# 举例说明
eureka:
  ...
  instance:
    instance-id: payment8001
    prefer-ip-address: true
    #心跳检测与续约时间
    #开发时没置小些,保证服务关闭后注册中心能即使剔除服务
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

1.5、订单服务80入驻EurekaServer

目的我们将EurekaClient端cloud-consumer-order80注册进EurekaServer 7001成为服务消费者consumer。

  • 第一步:修改cloud-consumer-order80服务的pom文件,添加以下依赖:
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 第二步:修改cloud-consumer-order80服务的YML,添加以下配置:
    server:
      port: 80
    
    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
    
  • 第三步:修改cloud-consumer-order80服务的主启动类,添加以下注解:
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    
    @SpringBootApplication
    @EnableEurekaClient
    public class OrderMain80 {
        public static void main( String[] args ){
            SpringApplication.run(OrderMain80.class, args);
        }
    }
    
  • 第四步:启动cloud-consumer-order80服务,在Eureka网页中刷新发现服务注册进来了。
    在这里插入图片描述

1.6、Eureka集群的构建

Eureka集群原理说明:
在这里插入图片描述
问题:微服务RPC远程服务调用最核心的问题是什么?
答:高可用,如果注册中心只有一个,当它出问题的时候就会导致整个微服务环境不可用,也就是常见的单点故障,因此,要想解决这样的问题,就需要搭建Eureka注册中心集群,实现负载均衡+故障容错。

Eureka集群的搭建:
在这里插入图片描述

  • 第一步: 创建cloud-eureka-server7002工程,将7001的全部内容(包括pom、yml、启动类)复制到7002,yml文件中的端口需要修改成7002。

  • 第二步: 找到C:\Windows\System32\drivers\etc路径下的hosts文件,修改映射配置添加进hosts文件

    127.0.0.1  eureka7001.com
    127.0.0.1  eureka7002.com
    

由于注册中心需要相互注册、相互守望,因此第三、四步需要修改配置文件

  • 第三步: 修改cloud-eureka-server7001配置文件

    server:
      port: 7001
    
    eureka:
      instance:
        hostname: eureka7001.com#eureka服务端的实例名称
      client:
        register-with-eureka: false  #false表示不向注册中心注册自己。
        fetch-registry: false  #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
        service-url:
          #集群时需要指向其它eureka地址
          defaultZone: http://eureka7002.com:7002/eureka/
    
          #设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。 单机时就是自己
          # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
  • 第四步: 修改cloud-eureka-server7002配置文件

    server:
      port: 7002
    
    eureka:
      instance:
        hostname: eureka7002.com #eureka服务端的实例名称
      client:
        register-with-eureka: false  #false表示不向注册中心注册自己。
        fetch-registry: false  #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
        service-url:
          #集群时需要指向其它eureka地址
          defaultZone: http://eureka7001.com:7001/eureka/
    
          #设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。 单机时就是自己
          # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
  • 第五步: 访问测试,访问 http://eureka7001.com:7001 或者 localhost:7001
    在这里插入图片描述
    发现两个注册中心能够相互守望了,测试成功。

1.7、配置订单支付两个服务注册进Eureka集群

  • 目的: 将支付服务8001微服务,订单服务80微服务发布到上面2台Eureka集群配置中。

  • 修改: 将支付服务8001,订单服务80的yml文件中的eureka.client.service-url.defaultZone进行修改

  # Eureka的配置
eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      # 单机版
      #defaultZone: http://localhost:7001/eureka
      #集群版
      defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
  • 测试: 启动两个注册中心的同时,将8001服务以及80服务启动,分别访问两个注册中心,可以看到以下:
    在这里插入图片描述
    在这里插入图片描述

1.8、支付服务集群的配置

  • 准备: 新建一个Module名为:cloud-provicer-payment8002,将8001中所有的内容(pom依赖、yml文件、启动类、业务类)都复制一份到8002中,配置文件中的端口改成8002,启动类记得改名。

  • 修改: cloud-provicer-payment8001和cloud-provicer-payment8002的controller中的代码做以下修改,添加serverPort:

import com.oldou.springcloud.entities.CommonResult;
import com.oldou.springcloud.entities.Payment;
import com.oldou.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

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

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


    @GetMapping(value = "/pay/get/{id}")
    public CommonResult getById(@PathVariable("id") Long id){
        Payment result = paymentService.getPaymentById(id);
        log.info("**************查询结果:"+result);
        if(result != null){
            return new CommonResult(200,"查询成功,serverPort:"+serverPort,result);
        }
        return new CommonResult(404,"找不到ID为:"+id+"的记录,serverPort:"+serverPort,null);
    }
}
  • 访问测试: 访问 http://eureka7001.com:7001/得到以下界面:
    在这里插入图片描述
    我们可以发现,同名称的有两个服务(8001和8002),这是因为8001和8002服务在yml文件中设置的名称是一致的。
    • 我们访问:http://localhost:8002/pay/get/1或者 http://localhost:8001/pay/get/1 都能看到对应的端口号。
      在这里插入图片描述

负载均衡的配置:

  • 第一步: 我们还需要去cloud-consumer-order80订单服务中去修改访问地址,因为我们现在的支付服务有两个,因此访问地址不能写死,需要将访问地址改成服务的名称。

@Slf4j
@RestController
public class OrderController {
 	// 单机版是配置的固定访问地址
    // public static final String PAYMENT_URL = "http://localhost:8001";

    // 集群就需要配置成服务名称,去Eureka上通过注册的服务名去查找
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
    
    ...
}
  • 第二步: 修改完以上之后,我们还需要去ApplicationContextConfig配置类中为RestTemplate添加@LoadBalanced注解开启负载均衡,让服务知道同一名称下的多个服务,到底是分配哪一个。
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced //使用此注解赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • 第三步: 测试,我们访问 http://localhost/consumer/pay/get/1 ,进行测试,每刷新一次端口号都会改变,这是负载均衡中的轮询策略,因此就会出现8001/8002交替出现。

1.9、actuator的修改

我们在Eureka服务的界面中发现,注册的服务的ip地址都暴露出来了,本部分主要是关于如何修改主机名称以及隐藏ip。
在这里插入图片描述
主机名称:服务名称修改

  • 第一步: 找到8001以及8002的yml文件打开
  • 第二步: 作以下修改:
eureka:
 client:
   #表示是否将自己注册进Eurekaserver默认为true。
   register-with-eureka: true
   #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
   fetchRegistry: true
   service-url:
     # 单机版
     #defaultZone: http://localhost:7001/eureka
     #集群版
     defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
 instance:
   instance-id: payment8001 #添加这个配置,8002就该成8002
  • 第三步: 刷新Eureka服务的界面
    在这里插入图片描述
    访问8001服务的健康检查:http://localhost:8001/actuator/health
    在这里插入图片描述

显示IP的修改:
我们访问信息有IP信息提示(就是将鼠标指针移至payment8001,payment8002名下,左下角会有IP地址提示)
将8001和8002的yml文件中添加以下配置:

eureka:
...
  instance:
    instance-id: payment8001
    prefer-ip-address: true   #添加此配置

在这里插入图片描述

1.10、Eureka的服务发现Discovery

对注册进eureka里面的微服务,我们可以通过其他服务发现并获得该服务的信息(主机名称、端口号等等)。

  • 第一步: 修改cloud-provider-payment8001/8002中的PaymentController
    @RestController
    @Slf4j
    public class PaymentController {
    	...
        // 注意这里导包是:org.springframework.cloud.client.discovery.DiscoveryClient;
        @Resource
        private DiscoveryClient discoveryClient; // 服务发现
    
       ...
    
        @GetMapping(value = "/pay/discovery")
        public Object discovery(){
            // 这是获取全部注册的服务
            List<String> services = discoveryClient.getServices();
            services.forEach(
                    e->{
                        log.info("******element--->"+ e);
                    }
            );
    
            //这里是通过已经暴露的服务名称进行获取
            List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
            for (ServiceInstance instance : instances) {
                log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
            }
            return this.discoveryClient;
        }
    }
    
  • 第二步: 在cloud-provider-payment8001/8002的主启动类上添加@EnableDiscoveryClient注解
    /**
     * @auther oldou
     */
    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient  //添加此注解
    public class PaymentMain8001 {
        public static void main( String[] args ) {
            SpringApplication.run(PaymentMain8001.class, args);
        }
    }
    
  • 第三步: 访问测试,访问http://localhost:8001/pay/discovery同时查看控制台。
    在这里插入图片描述
    控制台输出:
    在这里插入图片描述
    我们可以发现,通过此方法我们可以拿到注册到Eureka中的服务信息。

1.11、Eureka的停更说明

官网地址:https://github.com/Netflix/eureka/wiki

Eureka 2.0 (Discontinued)
The existing open source work on eureka 2.0 is discontinued. The code base and artifacts that were released as part of the existing repository of work on the 2.x branch is considered use at your own risk.
Eureka 1.x is a core part of Netflix’s service discovery system and is still an active project.
现有的eureka 2.0的开源工作已经停止。代码库和工件作为2上现有工作存储库的一部分发布。X分支机构的使用风险由您自行承担。
Eureka 。x是Netflix服务发现系统的核心部分,目前仍是一个活跃的项目。

那么接下来,我们尝试使用zookeeper代替Eureka实现服务注册与发现。

2、使用Zookeeper实现服务注册与发现

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

因此,这里需要准备一个Linux服务器。

2.1、支付服务注册进zookeeper

前期准备:

  • 一台Linux服务器,我这里是centos 7
  • 查看以下服务器的ip:192.168.15.131
  • 关闭服务器的防火墙 systemctl stop firewalld
  • 安装一个zookeeper,教程在这里
  • 安装的位置:/usr/local/zookeeper
  • 启动zk的命令:
    启动zookeeper:
    ./zkServer.sh start
    
    关闭zookeeper:
    ./zkServer.sh stop
    
    查看zookeeper状态:
    ./zkServer.sh status
    
    链接zookeeper
    ./zkCli.sh -server localhost:2181
    

先启动zookeeper,然后再链接zk。
在这里插入图片描述
zookeeper服务器取代Eureka服务器,zk作为服务注册中心

开始构建

  • 第一步:新建一个Module,名为cloud-provider-payment8004的Maven工程

  • 第二步:改pom文件,zk的版本要一致

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud2021</artifactId>
            <groupId>com.oldou.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-provider-payment8004</artifactId>
    
        <dependencies>
            <!-- SpringBoot整合Web组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
                <groupId>com.oldou.springcloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>${project.version}</version>
            </dependency>
            <!-- SpringBoot整合zookeeper客户端 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
                <!--先排除自带的zookeeper3.5.3 防止与3.6.0起冲突-->
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.zookeeper</groupId>
                        <artifactId>zookeeper</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!--添加zookeeper3.6.0版本-->
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>3.6.0</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>
    
    </project>
    
  • 第三步:yml文件

    #8004表示注册到zookeeper服务器的支付服务提供者端口号
    server:
      port: 8004
    
    #服务别名----注册zookeeper到注册中心名称
    spring:
      application:
        name: cloud-provider-payment-zk
      cloud:
        zookeeper:
          connect-string: 192.168.15.131:2181 # zookeeper安装的Linux ip地址
    
  • 第四步:主启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient//该注解用于向使用consul或者zookeeper作为注册中心时注册服务
    public class PaymentMain8004 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8004.class, args);
        }
    }
    
  • 第五步:controller

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.UUID;
    
    @RestController
    @Slf4j
    public class PaymentController {
        @Value("${server.port}")
        private String serverPort;
    
        @RequestMapping(value = "/pay/zk")
        public String payzk(){
            return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
        }
    }
    
  • 第六步:启动测试,我们启动8004,然后访问:http://localhost:8004/paym/zk
    在这里插入图片描述
    接下来我们去zookeeper中查看发现,多出了一个东西:
    在这里插入图片描述
    说明我们的8004服务已经成功注册到zookeeper服务了,我们继续深挖:
    在这里插入图片描述
    我们发现,注册到zookeeper服务的是一串JSON字符串,我们复制到网页JSON解析工具查看一下:

    {
      "name": "cloud-provider-payment-zk",
      "id": "df6f19ab-d2a6-4ed6-b5ff-0e966c936dbf",
      "address": "192.168.56.1",
      "port": 8004,
      "sslPort": null,
      "payload": {
        "@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
        "id": "application-1",
        "name": "cloud-provider-payment-zk",
        "metadata": {}
      },
      "registrationTimeUTC": 1626084216494,
      "serviceType": "DYNAMIC",
      "uriSpec": {
        "parts": [
          {
            "value": "scheme",
            "variable": true
          },
          {
            "value": "://",
            "variable": false
          },
          {
            "value": "address",
            "variable": true
          },
          {
            "value": ":",
            "variable": false
          },
          {
            "value": "port",
            "variable": true
          }
        ]
      }
    }
    

这就是服务注册的信息。

提出问题: 服务注册到zookeeper中,是作为临时节点还是持久性节点存在呢?
答:临时节点。当8004服务停掉以后,zookeeper会有一个心跳监测时间,一段时间后就会将停掉的服务节点进行清除。

2.2、订单服务注册进zookeeper

  • 新建Module: 新建一个名为cloud-consumerzk-order80 的maven项目。
  • 改pom文件:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud2021</artifactId>
            <groupId>com.oldou.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-consumerzk-order80</artifactId>
        <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.6.0版本-->
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>3.6.0</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>
    </project>
    
  • 新建application.yml文件
    server:
      port: 80
    
    #服务别名----注册zookeeper到注册中心名称
    spring:
      application:
        name: cloud-consumer-order-zk
      cloud:
        zookeeper:
          connect-string: 192.168.15.131:2181  # zookeeper安装的Linux ip地址
    
  • 启动类:
    @SpringBootApplication
    @EnableDiscoveryClient
    public class OrderZKMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderZKMain80.class, args);
        }
    }
    
  • 业务类:
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderZKController {
    public static final String INVOKE_URL = "http://cloud-provider-payment-zk";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/pay/zk")
    public String paymentInfo() {
        String result = restTemplate.getForObject(INVOKE_URL+"/pay/zk",String.class);
        return result;
    }
}
  • 启动测试:
    运行ZooKeeper服务端,启动服务cloud-consumerzk-order80和cloud-provider-payment8004。

打开ZooKeeper客户端去查看:
在这里插入图片描述
浏览器访问:http://localhost/consumer/pay/zk
在这里插入图片描述

3、Consul实现服务注册与发现

3.1、Consul的简介

Consul是什么?

官网解释: Consul是一个服务网格解决方案,它提供了一个功能齐全的控制平面,具有服务发现、配置和分段功能。这些特性中的每一个都可以根据需要单独使用,也可以一起用于构建全服务网格。Consul需要一个数据平面,并支持代理和本机集成模型。Consul船与一个简单的内置代理,使一切工作的开箱即用,但也支持第三方代理集成,如Envoy。

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

Consul能够干什么?

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

3.2、Consul的安装与运行

这里我为了方便,下载了一个Windows版本的Consul,windows版解压缩后,得consul.exe,按住WIN+R打开cmd,切换到Consul所在位置,或者是如图所示:

  • 查看版本:consul -v
    在这里插入图片描述
  • 开发模式启动:consul agent -dev
  • 浏览器输入 - http://localhost:8500/- 打开Consul控制首页。
    在这里插入图片描述

3.3、服务提供者注册进Consul

  • 新建一个Module,名称为:cloud-provider-payment8006

  • 修改pom文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud2021</artifactId>
            <groupId>com.oldou.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-provider-payment8006</artifactId>
        <dependencies>
            <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <dependency>
                <groupId>com.oldou.springcloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>${project.version}</version>
            </dependency>
            <!--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>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>RELEASE</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>RELEASE</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    </project>
    
  • 新增并修改application.yml文件

    ###consul服务端口号
    server:
      port: 8006
    
    spring:
      application:
        name: consul-provider-payment
      ####consul注册中心地址
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            #hostname: 127.0.0.1
            service-name: ${spring.application.name}
    
  • 主启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    public class PaymentMain8006 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8006.class, args);
        }
    }
    
  • Controller

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.UUID;
    
    @RestController
    @Slf4j
    public class PaymentController {
        @Value("${server.port}")
        private String serverPort;
    
        @RequestMapping(value = "/pay/consul")
        public String paymentConsul() {
            return "springcloud with consul: "+serverPort+"\t   "+ UUID.randomUUID().toString();
        }
    }
    
  • 启动服务,访问测试
    访问:http://localhost:8006/pay/consul
    在这里插入图片描述
    访问:http://localhost:8500 - 会显示provider8006的服务名称
    在这里插入图片描述
    说明服务已经注册到Consul服务中了。

3.4、服务消费者注册进Consul

  • 新建一个Module,名称为:cloud-consumerconsul-order80

  • 修改pom文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud2021</artifactId>
            <groupId>com.oldou.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-consumerconsul-order80</artifactId>
    
        <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>
    
    </project>
    
  • 新增并修改application.yml文件

###consul服务端口号
server:
  port: 80

spring:
  application:
    name: consul-consumer-order
  ####consul注册中心地址
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        #hostname: 127.0.0.1
        service-name: ${spring.application.name}
  • 启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient  //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
    public class OrderConsulMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderConsulMain80.class,args);
        }
    }
    
  • 配置类

    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestTemplate;
    
    @Configuration
    public class ApplicationContextConfig {
    
        @Bean
        @LoadBalanced
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }
    
  • Controller

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import javax.annotation.Resource;
    
    @RestController
    @Slf4j
    public class OrderConsulController {
    
       public static final String INVOKE_URL = "http://consul-provider-payment";
    
       @Resource
       private RestTemplate restTemplate;
    
       @GetMapping(value = "/consumer/pay/consul")
       public String paymentInfo() {
           String result = restTemplate.getForObject(INVOKE_URL+"/pay/consul",String.class);
           return result;
       }
    }
    
  • 访问测试
    保证consul运行正常,启动cloud-providerconsul-payment8006,cloud-consumerconsul-order80两个服务。

访问:http://localhost/consumer/pay/consul
在这里插入图片描述

  • 访问:http://localhost:8500/ui/dc1/services 可以看见consul和consul-consumer-order、consul-provider-payment三个服务
    在这里插入图片描述

4、Eureka、ZK、Consul三者的异同

组件名语言CAP服务健康检查对外暴露接口Spring Cloud集成
EurekaJavaAP可配支持HTTP
ConsulGoCP支持HTTP/DNS
ZookeeperJavaCP支持客户端已集成

4.1、CAP的介绍

什么是CAP?

  • CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)

  • 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)

  • 分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

  • CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求。

RDBMS(Mysql、Oracle、sqlServer)关系型数据库-------->ACID
NoSQL(Redis、Mongodb)非关系型数据库---------->CAP

因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

  • CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
  • CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

CAP理论关注粒度的数据,而不是整体系统设计的策略。

4.2、三种对应的CAP


AP架构(Eureka)

  • 当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。

结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
在这里插入图片描述


CP架构(ZooKeeper/Consul)

  • 当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。

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

在这里插入图片描述
CP 与 AP 对立同一的矛盾关系。

4.3、Eureka和ZK的对比

4.3.1、作为注册中心,Eureka比Zookeeper好在哪里?

  • 由于分区容错性P在分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡。
  • Zookeeper保证的是CP;
  • Eureka保证的是AP。

4.3.2、Zookeeper保证的是CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接宕机不可用,也就是说,服务注册功能对可用性的要求要高于一致性。但是Zookeeper会出现这样一种情况,当Master节点因为网络故障与其他节点失去联系的时候,剩余节点就会重新进行Leader的选举,问题在于选举Leader的时间太长(30-120s),且选举期间整合Zookeeper是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络原因使得ZK集群失去Master节点是较大概念会发生的事件,虽然服务最终能够回复,但是漫长的选举事件导致注册长期不可用是不能容忍的。

4.3.3、Eureka保证的是AP

Eureka看明白了这个问题,因此在设计的时候就优先保证可用性。Eureka的各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务,而Eureka的客户端在向某个Eureka注册的时候,如果发现连接失败,则会自动切换到其他节点,只要由一台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的数据,除此之外,Eureka还有一种自我保护机制,如果15分钟内超过85%的节点都没有正常的心跳(每隔5秒检测一下),那么Eureka就会认为客户端与注册中心出现了网络故障,此时就会出现以下几种情况:
(1)Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务;
(2)Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用);
(3)当网络稳定时,当前实例新的注册信息会被同步到其他节点中。

因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样使整个注册服务瘫痪。

4.3.4、Zookeeper与Eureka区别总结【重点】:

  • zookeeper保证cp(一致性);
  • eureka保证ap(可用性);
  • zookeeper在选举Leader的期间注册服务瘫痪,期间不可用;
  • eureka的各个节点平等关系,只要有一台就可保证服务可用,而查询到的数据可能不是最新的,可以很好应对网络故障导致部分节点失联情况;
  • zookeeper有leader、follower、observer三种角色,而eureka各个节点平等;
  • zookeeper采用半数存活原则(避免脑裂),eureka采用自我保护机制来解决分区问题;
  • eureka本质是个工程,zookeeper只是一个进程 ZooKeeper基于CP,不保证高可用,如果zookeeper正在选主,或者Zookeeper集群中半数以上机器不可用,那么将无法获得数据。Eureka基于AP,能保证高可用,即使所有机器都挂了,也能拿到本地缓存的数据。作为注册中心,其实配置是不经常变动的,只有发版(发布新的版本)和机器出故障时会变。对于不经常变动的配置来说,CP是不合适的,而AP在遇到问题时可以用牺牲一致性来保证可用性,既返回旧数据,缓存数据。所以理论上Eureka是更适合做注册中心。而现实环境中大部分项目可能会使用ZooKeeper,那是因为集群不够大,并且基本不会遇到用做注册中心的机器一半以上都挂了的情况。所以实际上也没什么大问题。

四、微服务组件之服务调用

1、Ribbon负载均衡服务调用

1.1、Ribbon的简介

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

  • 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。

  • 简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

Ribbon目前也进入维护模式。
Ribbon未来可能被Spring Cloud LoadBalacer替代。

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

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

1.1.2、集中式LB

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

1.1.3、进程内LB

  • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
  • Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

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

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

总结:负载均衡 + RestTemplate调用

1.2、RestTemplate的使用介绍

说明: Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合,这里和Eureka结合只是其中的一个实例。
在这里插入图片描述
Ribbon在工作时分成以下两步:

  • 第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server。
  • 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种策略:比如轮询(默认)随机根据响应时间加权重

POM说明
我们在【三、微服务组件之服务注册与发现的1.8支付集群的配置】讲解了80通过轮询负载均衡访问8001/8002,而且这里没有引入spring-cloud-starter-ribbon的依赖也可以使用ribbon,同时也能够配置负载均衡,这是为什么呢?

<dependency>
    <groupld>org.springframework.cloud</groupld>
    <artifactld>spring-cloud-starter-netflix-ribbon</artifactid>
</dependency>

这是因为spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用。
在这里插入图片描述

RestTemplate 的使用

getForObject()getForEntity() - GET请求方法

  • getForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json。
  • getForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。

这里我们需要启动cloud-provider-payment8001cloud-provider-payment8002cloud-eureka-server7001cloud-eureka-server7001,同时在cloud-consumer-order80服务的OrderController中添加以下方法启动测试:

@GetMapping("/consumer/pay/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/pay/get/" + id, CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){ //如果成功
            return entity.getBody();
        }
        return new CommonResult<>(444,"操作失败---->");
    }

postForObject() 和 postForEntity() - POST请求方法

  • postForObject() :通过将给定对象POS到URL中创建新资源,并返回响应中找到的表示。
  • postForEntity() :通过将给定对象POS到URL中创建新资源,并将响应返回为响应要点。

1.3、Ribbon核心组件IRule

  • lRule:根据特定算法中从服务列表中选取一个要访问的服务,IRule有很多实现类,具体见下图:
    在这里插入图片描述
    根据图中有以下七种负载均衡方式:
  • com.netflix.loadbalancer.RoundRobinRule:轮询,默认的负载均衡方式;
  • com.netflix.loadbalancer.RandomRule:随机;
  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失 败则在指定时间内会进行重;
  • WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;
  • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
  • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
  • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器;

1、如何替换负载均衡算法
2、假设不够用了,如何做一下扩展,有没有自己手写过一个负载均衡算法。

1.4、Ribbon负载规则替换

第一步:修改cloud-consumer-order80服务

第二步:注意配置细节

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

  • 也就是说不要将Ribbon配置类与主启动类同包
    在这里插入图片描述

第三步:新建package - com.oldou.myrule

第四步:在com.oldou.myrule下新建MySelfRule规则类

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

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main( String[] args ){
        SpringApplication.run(OrderMain80.class, args);
    }
}

第六步:启动测试
启动以下五个服务:
在这里插入图片描述
访问:http://localhost/consumer/pay/get/1,然后不断的刷新,发现访问8001/8002是随机的。

1.5、Ribbon默认负载轮询算法原理

  • 默认负载轮训算法: rest接口第几次请求数(N) % 服务器集群总数量(Num) = 实际调用服务器位置下标(Index)
  • 每次服务重启动后rest接口计数从1开始。

我们在代码中,有下面根据服务名称获取服务器实例集合的步骤:
List<Servicelnstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
在这里插入图片描述

例如这个集合中有以下两个服务实例:

List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001

8001+ 8002组合成为集群,它们共计2台机器,集群总数(Num)为2,按照轮询算法原理:

  • 当总请求数(N)为1时:1%2=1,对应下标位置(Index)为 1,则获得服务地址为List [1]:127.0.0.1:8001
  • 当总请求数(N)为2时:2%2=0,对应下标位置(Index)为 0,则获得服务地址为List [0]:127.0.0.1:8002
  • 当总请求数(N)为3时:3%2=1,对应下标位置(Index)为 1,则获得服务地址为List [1]:127.0.0.1:8001
  • 当总请求数(N)为4时:4%2=0,对应下标位置(Index)为 0,则获得服务地址为List [0]:127.0.0.1:8002
  • 如此类推…
  • 当服务重启以后,请求总数会重置。

1.6、RoundRobinRule(轮询)源码分析

在这里插入图片描述首先,我们根据以上图关系,找到IRule的源码:

public interface IRule{
    /*
     * choose one alive server from lb.allServers or
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none
     *  server is available 
     */
     // 选择,从服务集合中选择哪个服务做这件事
    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

之后找到AbstractLoadBalancerRule,再找到RoundRobinRule,源码为:

public class RoundRobinRule extends AbstractLoadBalancerRule {

    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }
	
	// ------>这里是重点 
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
        	// 选出活着/健康的服务机器
            List<Server> reachableServers = lb.getReachableServers();
            // 获取集群所有的服务机器,前面会有一个根据服务名称拿到服务集群的
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();//这个就相当于集群中服务器总数Num
			
			//如果活着的服务一个都没有,那么就会报错
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
			
			// 拿到服务器的下标,也就是Index
            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex); //根据下标拿到对应服务

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) { //这里不是死循环,是JUC中的知识--自旋锁
            int current = nextServerCyclicCounter.get(); //首先得到0
            int next = (current + 1) % modulo; //求余
            //比较并交换 CAS
            if (nextServerCyclicCounter.compareAndSet(current, next)) 
                return next;
        }
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

源码里面其实就是【Ribbon默认负载轮询算法原理】这里描述的公式,底层运用了CAS+自旋锁。

1.7、Ribbon之手写轮询算法

目的: 手写一个RoundRobinRule类似的本地负载均衡算法

准备:

  • 需要开启EurekaMain7001/7002集群
  • 完成对PaymentMain8001/8002服务的改造- controller
@RestController
@Slf4j
public class PaymentController {

    @Value("${server.port}")
    private String serverPort;
	
	........
	
    @GetMapping(value = "/pay/lb")
    public String getPayLB(){
        return serverPort;
    }

}

完成对OrderMain80服务的改造

  • 第一步:ApplicationContextConfig去掉注解@LoadBalanced,OrderMain80去掉注解@RibbonClient
    在这里插入图片描述
    在这里插入图片描述

  • 第二步:新建com.oldou.springcloud.lb,创建LoadBalancer接口

    package com.oldou.springcloud.lb;
    
    import org.springframework.cloud.client.ServiceInstance;
    import java.util.List;
    
    public interface LoadBalancer {
        // 这个接口需要收集现在服务器集群上有多少台机器,然后自己轮询返回哪个机器干活
        ServiceInstance instances(List<ServiceInstance> serviceInstances);
    }
    
  • 第三步:创建实现LoadBalancer的实现类–MyLB

    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    @Component
    public class MyLB implements LoadBalancer {
    
        private AtomicInteger atomicInteger = new AtomicInteger(0);
    
        public final int getAndIncrement(){
            int current;
            int next;
            do{
                current = this.atomicInteger.get();
                // Integer.MAX_VALUE = 2147483647
                next = current >= 2147483647 ? 0 : current + 1;
            }while (!this.atomicInteger.compareAndSet(current,next));
            System.out.println("------------>第几次访问,次数next:"+next);
            return next;
        }
    	
    	//rest接口第几次请求数(N) % 服务器集群总数量(Num) = 实际调用服务器位置下标(Index)
        @Override
        public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
            int index = getAndIncrement() % serviceInstances.size();
            return serviceInstances.get(index);
        }
    }
    
    
  • 第四步:修改OrderMain80服务的controller

@Slf4j
@RestController
public class OrderController {
    .....

    @Resource
    private LoadBalancer loadBalancer;
    @Resource
    private DiscoveryClient discoveryClient;

	.....

    @GetMapping(value = "/consumer/pay/lb")
    public String getPayLB(){
        // 根据服务名获取服务集合 8001/8002
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

        if(instances == null || instances.size() <= 0){
            return null;
        }

        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();

        return restTemplate.getForObject(uri+"/pay/lb",String.class);
    }

}
  • 第五步:启动测试,启动cloud-consumer-order80服务,访问:http://localhost/consumer/pay/lb,然后不断刷新,可以发现网页上的端口号按照轮询的方式变更,同时控制台输出如下:
    在这里插入图片描述

2、OpenFeign服务接口调用

2.1、OpenFeign的概念

2.1.1、Feign是什么?

官网介绍:Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign. link

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

总结:Feign是一个声明式的Web服务客户端,让编写Web服务客户端更加容易,只需要创建一个接口并在接口上添加注解即可

2.1.2、Feign能干啥?

  • Feign旨在使编写Java Http客户端变得更容易。

  • 我们前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。

  • 在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

2.1.3、Feign集成了Ribbon

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

2.1.4、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是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@Feignclient可以解析SpringMVc的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    

2.2、OpenFeign实现服务调用

  • 如何实现?---->接口+注解,微服务调用接口+@FeignClient
  • 注意:Feign是用在消费端的

第一步: 新建一个名为:cloud-consumer-feign-order80的Maven工程

第二步: 修改pom文件

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

    <artifactId>cloud-consumer-feign-order80</artifactId>

    <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>
            <groupId>com.oldou.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</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>
</project>

第三步: 新增application.yml文件

server:
  port: 80

spring:
  application:
    name: cloud-order-service-feign

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

第四步: 启动类

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

@SpringBootApplication
@EnableFeignClients //这里一定要添加这个
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class, args);
    }
}

第五步: 业务类

  • service层,新建PaymentFeignService接口并新增注解@FeignClient

    import com.oldou.springcloud.entities.CommonResult;
    import com.oldou.springcloud.entities.Payment;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    /**
     * 业务逻辑接口+@FeignClient配置调用provider服务
     */
    @Component
    @FeignClient(value = "CLOUD-PAYMENT-SERVICE")
    public interface PaymentFeignService {
    	// 这里是远程调用8001/8002服务的查询接口
        @GetMapping(value = "/pay/get/{id}")
        public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
    }
    
  • controller层

    import com.oldou.springcloud.entities.CommonResult;
    import com.oldou.springcloud.entities.Payment;
    import com.oldou.springcloud.service.PaymentFeignService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    import javax.annotation.Resource;
    
    @RestController
    @Slf4j
    public class OrderFeignController {
       @Resource
       private PaymentFeignService paymentFeignService;
    
       @GetMapping(value = "/consumer/pay/get/{id}")
       public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
           return paymentFeignService.getPaymentById(id);
       }
    }
    

第六步: 测试

  • 启动cloud-provider-payment8001/8002、cloud-eureka-server7001/7002、cloud-consumer-feign-order80服务

  • 访问:http://localhost/consumer/pay/get/1
    在这里插入图片描述

  • 我们刷新网页重复调用这个接口,发现返回的是以轮询的负载均衡方式展现给我们,这是因为Feign集成了Ribbon,Feign调用自带负载均衡配置项

  • 调用说明:
    在这里插入图片描述

2.3、OpenFeign超时控制

OpenFeign默认只等待1秒钟,如果调用时间超过1秒就会报错;

测试: 模拟超时设置,故意设置调用超时,演示OpenFeign调用超时出错情况。

  • 第一步: 需要服务提供方8001/8002的controller中写暂停接口

    @RestController
    @Slf4j
    public class PaymentController {
    	.....
    	@Value("${server.port}")
        private String serverPort;
    	
    	....
    	
    	@GetMapping(value = "/pay/feign/timeout")
        public String payFeignTimeout() {
            // 业务逻辑处理正确,但是需要耗费3秒钟
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return serverPort;
        }
    	
    }
    
  • 第二步: 在cloud-consumer-feign-order80的PaymentFeignService中做修改

    @Component
    @FeignClient(value = "CLOUD-PAYMENT-SERVICE")
    public interface PaymentFeignService {
    
        .....
    
        @GetMapping(value = "/pay/feign/timeout")
        public String payFeignTimeout();
    
    }
    
  • 第三步: 在cloud-consumer-feign-order80的controller中做修改

    @RestController
    @Slf4j
    public class OrderFeignController {
    
    	.....
    
        @GetMapping(value = "/consumer/pay/feign/timeout")
        public String payFeignTimeout(){
            // OpenFeign客户端一般默认等待1秒钟
            return paymentFeignService.payFeignTimeout();
        }
    
    }
    
  • 第四步: 测试,启动cloud-provider-payment8001/8002、cloud-eureka-server7001/7002、cloud-consumer-feign-order80服务,访问:http://localhost/consumer/pay/feign/timeout
    在这里插入图片描述
    如上图所示:feign.RetryableException:Read timed out executing GET http://CLOUD-PAYMENT-SERVCE/payment/feign/timeout。报Feign的超时异常。

  • 这是因为OpenFeign默认只会等待1秒钟,超过后报错,这样就会导致Feign客户端不想等待了,直接返回报错,为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制;

  • 解决办法为:YML文件里需要开启OpenFeign客户端超时控制

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

2.4、OpenFeign日志增强

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

OpenFeign的日志级别

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

如何配置日志?

  • 第一步: 打开cloud-consumer-feign-order80服务,新建一个com.oldou.springcloud.config包,
  • 第二步: 新建一个FeignConfig类,
    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class FeignConfig {
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL; //配置全日志
        }
    }
    
  • 第三步: 在yml文件中配置Feign客户端开启日志
logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.lun.springcloud.service.PaymentFeignService: debug
  • 第四步: 测试,调用:http://localhost/consumer/pay/get/1
    然后后台查看日志输出。

五、微服务组件之Hystrix实现服务降级

1、Hystrix的基本概述

分布式系统面临的问题

  • 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败
    在这里插入图片描述

服务雪崩

  • 多个微服务之间调用的时候,假设微服务A调用微服务B微服务C微服务B微服务C又调用其它的微服务,这就是所谓的**“扇出”**。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.

  • 对于高流量的应用来说,单一的后避依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

  • 所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

Hystrix是什么?

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

Hystrix能干什么?

  • 服务降级
  • 服务熔断
  • 接近实时的监控
  • 限流、隔离等等

2、Hystrix的服务降级熔断限流概念

2.1、服务降级

概念: 服务器忙,请稍后重试,不让客户端等待并立即返回一个友好提示,fallback。

哪些情况下会发生降级?

  • 程序运行异常
  • 超时
  • 服务熔断出发服务降级
  • 线程池/信号量打满也会导致服务降级

2.2、服务熔断

概念: 类似于保险丝,当达到最大服务访问量后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。

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

2.3、服务限流

概念: 秒杀高并发等操作,严禁一窝蜂的过来拥挤,让大家都排队,一秒钟只允许通过N个,有序进行。

3、Hystrix支付微服务的构建

准备: 首先将cloud-eureka-server7001改配置还原成单机版

第一步: 新建一个Module,名为:cloud-provider-hystrix-payment8001

第二步: 改pom文件

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

    <artifactId>cloud-provider-hystrix-payment8001</artifactId>

    <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 -->
            <groupId>com.oldou.springcloud</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>
</project>

第三步: 改yml文件

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,http://eureka7002.com:7002/eureka
      defaultZone: http://eureka7001.com:7001/eureka

第四步: 主启动类

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

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {

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

}

第五步: 业务类

  • service
    import org.springframework.stereotype.Service;
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class PaymentService {
       // 正常访问 OK的方法
       public String paymentInfo_OK(Integer id) {
           return "线程池:  "+Thread.currentThread().getName()+"  paymentInfo_OK,id:  "+id+"\t"+"O(∩_∩)O哈哈~";
       }
       //超时错误的
       public String paymentInfo_TimeOut(Integer id) {
           try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
           return "线程池:  "+Thread.currentThread().getName()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
       }
    }
    
  • controller
import com.oldou.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {
   @Resource
   private PaymentService paymentService;

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

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

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

第六步: 正常测试

  • 启动cloud-eureka-server7001
  • 启动cloud-provider-hystrix-payment8001
  • 访问成功的方法:localhost:8001/pay/hystrix/ok/1
  • 访问超时的方法:localhost:8001/pay/hystrix/timeout/1

两个都能正常访问,接下来我们要以上面的Module为基础,从正确–>错误–>降级熔断–>恢复。

4、Jmeter高并发压测后卡顿

4.1、Jmeter的下载以及安装

配置的话在网上都有教程,下面介绍一下我安装的教程:

  • 电脑桌面---->“计算机”图标---->鼠标右键选择“属性”---->点击高级系统设置—>环境变量页面

在这里插入图片描述

  • 开始配置环境变量。在系统变量框,点击“新建”,建立一个变量:JMETER_HOME,值为你解压的jmeter安装路径。我的安装路径是在D盘,这个路径根据自己实际安装路径进行填写。然后点击确定保存即可
    在这里插入图片描述
    在这里插入图片描述
  • 配置classpath变量,没有的话也要按照上面步骤进行新建,有的话直接进行选中,点击编辑即可。变量值固定为:%JMETER_HOME%\lib\ext\ApacheJMeter_core.jar;%JMETER_HOME%\lib\jorphan.jar;%JMETER_HOME%\lib\logkit-2.0.jar;
    做完之后一定要保存,不确定的话可以直接点击确定按钮直到退到我的电脑页面
    在这里插入图片描述
  • 基本配置完成,然后验证一下是否配置正确,是否可用。首先进到你的jmeter安装路径,找到bin文件夹,点击进去,找到 jmeter.bat,鼠标右键用管理员方式运行,或者直接双击打开,此时会弹出2个界面:1.个是命令窗口,使用jmeter的时候此命令窗口不能关,你缩小到电脑任务栏即可。2.还有一个界面是jmeter工作页面,你可以在里面进行相关的操作,具体如图
    在这里插入图片描述
  • 双击 jmeter.bat之后打开下图所示两个窗口,黑窗口不能关闭。我们可以发送这个到桌面快捷方式,每次打开就不用进入到这个位置来开启了。
    在这里插入图片描述

4.2、Jmeter对支付服务进行压测

目的: 开启Jmeter,开20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut接口。

  • 测试计划中右键添加-》线程-》线程组
    在这里插入图片描述
  • (线程组202107,线程数:200,循环次数:100,其他参数默认),然后保存;
    在这里插入图片描述
  • 保存之后,右键线程组->添加->取样器->Http请求,填写好基本信息之后,保存
    在这里插入图片描述
    在这里插入图片描述
  • 启动之后,我们在网页访问:http://localhost:8001/pay/hystrix/ok/1 时发现,访问卡顿了,之前都不会卡顿的,现在访问请求一多,卡顿了。
  • 上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖慢。

结论: 访问拖慢,原因:tomcat的默认的工作线程数(10个)被打满了,没有多余的线程来分解压力和处理,因此我们需要Hystrix这样的容错限流框架来对我们的系统进行保护。

5、订单微服务调用支付服务出现卡顿

目的: 添加80服务加入。

第一步: 新建一个Module,名为:cloud-consumer-feign-hystrix-order80

第二步: 改pom文件

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

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

    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.oldou.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</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>
</project>

第三步: 改yml文件

server:
  port: 80

spring:
    application:
      name: cloud-order-service-feign-hystrix

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

第四步: 主启动类

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

@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {

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

第五步: 业务类

  • service:远程调用cloud-provider-hystrix-payment8001服务的接口
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {

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

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

}
  • controller
@RestController
@Slf4j
public class OrderHystirxController {

   @Resource
   private PaymentHystrixService paymentHystrixService;

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

   @GetMapping("/consumer/pay/hystrix/timeout/{id}")
   public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
       String result = paymentHystrixService.paymentInfo_TimeOut(id);
       return result;
   }
}

第六步: 测试,启动cloud-consumer-feign-hystrix-order80服务

  • 正常访问:http://localhost/consumer/pay/hystrix/ok/1,而且出现得很快,没有任何停顿
    在这里插入图片描述
  • 高并发测试访问,开启2W个线程压测cloud-provider-hystrix-payment8001的Ok微服务
    在这里插入图片描述
  • 然后再去网页上访问80微服务:http://localhost/consumer/pay/hystrix/ok/1,然后我们发现,消费者80被拖慢了,出现明显的卡顿。

原因: 8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕。

正因为有上述故障或不佳表现才有我们的降级/容错/限流等技术诞生。

6、降级容错解决的维度要求

出现的问题:

  • 超时导致服务器变慢(转圈) - 如果超时,那么不再等待

  • 出错(宕机或程序运行出错) - 出错要有兜底

解决方法:

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

7、Hystrix之服务降级支付侧fallback

  • 服务降级的配置,添加@HystrixCommand注解

测试情况一: 先从cloud-provider-hystrix-payment8001服务自身找问题

  • 目的:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处埋,作服务降级fallback。(先保证自己自测通过,再给别人调用)

  • 第一步: 找到cloud-provider-hystrix-payment8001服务,打开业务类:@HystrixCommand报异常后如何处理,—旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法

    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
    import org.springframework.stereotype.Service;
    
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class PaymentService {
    
    	.....
    
        @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler"/*指定善后方法名*/,commandProperties = {
                @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")/*规定3秒钟以内是正常业务*/
        })
        public String paymentInfo_TimeOut(Integer id) {  //超时错误的
            //int age = 10/0;
            try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
            return "线程池:  "+Thread.currentThread().getName()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
        }
    
        //被指定善后的方法
        public String paymentInfo_TimeOutHandler(Integer id){
            return "线程池:  "+Thread.currentThread().getName()+"  8001系统繁忙或者运行报错,请稍后再试,id:  "+id+"\t"+"o(╥﹏╥)o";
        }
    
    }
    
    
  • 第二步: cloud-provider-hystrix-payment8001服务的启动类添加@EnableCircuitBreaker

    @SpringBootApplication
    @EnableEurekaClient
    @EnableCircuitBreaker //添加此注解
    public class PaymentHystrixMain8001 {
    
        public static void main(String[] args) {
            SpringApplication.run(PaymentHystrixMain8001.class, args);
        }
    
    }
    
  • 第三步: 启动cloud-provider-hystrix-payment8001服务,访问:http://localhost:8001/pay/hystrix/timeout/1,观察超时以后会不会调用善后的方法
    在这里插入图片描述

  • 第四步: 同样的,当我们将测试int age = 10/0;时,也会调用善后的方法。以此实现了服务降级。

8、Hystrix之服务降级订单侧fallback

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

注意: 我们自己配置过的热部署方式对Java代码的改动明显,但是对@HystrixCommand内属性的修改还是建议重启微服务。
在这里插入图片描述

那么,如何对订单服务进行降级保护呢?一般服务降级可以放在客户端。

第一步: 修改cloud-consumer-feign-hystrix-order80服务的yml文件

server:
  port: 80

spring:
    application:
      name: cloud-order-service-feign-hystrix

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
feign:
  hystrix:
    enabled: true  # 开启

第二步: cloud-consumer-feign-hystrix-order80服务的主启动类上加@EnableHystrix注解

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

/**
 */
@SpringBootApplication
@EnableFeignClients
@EnableHystrix //添加此注解
public class OrderHystrixMain80 {

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

}

第三步: 修改cloud-consumer-feign-hystrix-order80服务的业务类

@RestController
@Slf4j
public class OrderHystirxController {

	....

    @GetMapping("/consumer/pay/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
    }) //1.5秒内返回就正常,如果超过1.5秒,那么我就调用我善后的方法进行服务的降级
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        //int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

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

第四步: 修改cloud-provider-hystrix-payment8001服务的业务类的时间,保证服务调用是正常的
在这里插入图片描述
第五步: 重启80和8001服务,访问:http://localhost/consumer/pay/hystrix/timeout/1时,由于80服务的调用时间是1.5秒内有效,而8001服务的调用时间是3秒,因此会调用80服务的降级方法。
在这里插入图片描述
第六步: 测试,将int age = 10/0;的代码注释掉,当80服务自身报异常后,同样的也会调用降级方法。

9、Hystrix之全局服务降级DefaultProperties

目前的问题:

  • (1)每个业务方法都需要有一个兜底善后的方法,造成了系统的代码膨胀;
  • (2)善后的代码和业务逻辑写在一块,造成了代码混乱;

解决方法:

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

  • 我们打开cloud-consumer-feign-hystrix-order80服务的业务类,做以下修改:

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //这里需要添加这个注解,定义全局的
public class OrderHystirxController {

....


   /* @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
    }) //1.5秒内返回就正常,如果超过1.5秒,那么我就调用我善后的方法进行服务的降级*/
    @HystrixCommand //所有加了这个注解的方法都会去调用全局的fallback方法
    @GetMapping("/consumer/pay/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        //int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

....

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

}

总的来说,第一:需要定义一个全局的fallback方法,然后在controller上添加一个@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")去指定对应的fallback方法,同时,在需要进行降级的业务方法上添加@HystrixCommand

这样的方式解决了代码膨胀的问题

10、Hystrix之通配服务降级FeignFallback

上面我们讲述了怎么解决代码膨胀的问题,这一小节介绍怎么解决代码混乱的问题

服务降级,客户端去调用服务端,碰上服务端宕机或者关闭
注意: 本次案例服务降级出来是在客户端80实现完成的,与服务端8001没有任何关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。

未来我们可能遇到的三个问题: 运行异常、超时、宕机,本次我们使用宕机来做实验。

通配服务降级FeignFallback的步骤:

  • 第一步: 打开cloud-consumer-feign-hystrix-order80服务,新建一个PaymentFallbackService,去实现PaymentHystrixService接口
import org.springframework.stereotype.Component;

/**
 * 服务降级的fallback方法
 */
@Component
public class PaymentFallbackService implements PaymentHystrixService {

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

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
    }
}
  • 第二步: 修改cloud-consumer-feign-hystrix-order80服务的PaymentHystrixService,在Feign注解中添加服务降级的fallback处理类。
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class) //指定PaymentFallbackService类作为fallback
public interface PaymentHystrixService {

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

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


}
  • 第三步: 改yml文件,配置中要开启hystrix
#开启
feign:
  hystrix:
    enabled: true
  • 第四步: 启动cloud-consumer-feign-hystrix-order80cloud-provider-hystrix-payment8001EurekaMain7001 [devtools] :7001/服务;
    • 先访问:http://localhost/consumer/pay/hystrix/ok/1,访问正常
      在这里插入图片描述
    • 停掉8001服务模拟宕机,再去访问:http://localhost/consumer/pay/hystrix/ok/1
      在这里插入图片描述

总结:

  • 客户端自己调用提示 - 此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
  • 这样的处理,就不用在controller中写配置和fallback方法了,解决了代码混乱的问题。

11、哪些情况会造成服务降级?

  • 程序运行异常
  • 超时访问
  • 服务熔断出发服务降级
  • 线程池/信号量打满也会导致服务降级

六、微服务组件之Hystrix实现服务熔断

1、服务熔断机制理论

  • 服务熔断就相当于断路器,相当于保险丝。

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

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

2、Hystrix之服务熔断案例(上)

  • 这里用到了一个名为:Hutool国产工具类

  • 修改cloud-provider-hystrix-payment8001服务的service

    @Service
    public class PaymentService {
    
       //-----------服务熔断的
    
       // 表示:10000ms(10秒)以内的10次请求中,有 60%(也就是6次) 都是失败的话,那么这个断路器就会起作用
       // 参数可以去查看:HystrixCommandProperties
       @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
               @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
               @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
               @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
               @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
       })
       public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
           if(id < 0) {
               throw new RuntimeException("******id 不能负数");
           }
           String serialNumber = IdUtil.simpleUUID();
    
           return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
       }
       public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
           return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " +id;
       }
    }
    
  • 官网给出的熔断解释:

The precise way that the circuit opening and closing occurs is as follows:
Assuming the volume across a circuit meets a certain threshold : HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
电路的准确分合方式如下:
假设电路中的音量达到某一阈值

And assuming that the error percentage, as defined above exceeds the error percentage defined in : HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
并假设上述定义的误差百分比超过

Then the circuit-breaker transitions from CLOSED to OPEN.
然后断路器从闭合变为开路。

While it is open, it short-circuits all requests made against that circuit-breaker.
然后断路器从闭合变为开路。
当它是开着的,它会短路所有针对那个断路器的请求。

After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()),the next request is let through. If it fails, the command stays OPEN for the sleep window. If it succeeds, it transitions to CLOSED and the logic in 1) takes over again.
一段时间后,下一个请求将被允许通过。 如果失败,命令将在休眠窗口中保持OPEN状态。 如果成功,它将转换为CLOSED,并且1)中的逻辑将再次接管。

  • HystrixCommandProperties配置类可自行去查看

3、Hystrix之服务熔断案例(下)

修改cloud-provider-hystrix-payment8001服务的controller

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

 	......
 
    //***************服务熔断
    @GetMapping("/pay/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id)
    {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("****result: "+result);
        return result;
    }
}

启动测试:

修改完毕以后,我们重启cloud-provider-hystrix-payment8001服务,完成以下测试:

  • 首先,访问:http://localhost:8001/pay/circuit/1 ,访问正常
    在这里插入图片描述
  • 然后我们将1改成负数,由于我们在service中做了判断,为负数的话会进行服务的降级,调用fallback方法
    在这里插入图片描述
  • 之后我们多次访问错误的请求(也就是为负数的请求),然后再访问正确的请求,就会发现,id为正数也会报错。
    在这里插入图片描述
  • 一段时间后,我们再次刷新正确的请求,发现有可以正常访问了。

总结分析:

当我们访问错误时,首先会进行服务的降级,调用service中写的fallback,当多次访问错误之后(也就是我们在service中配置的10次的60%,超过6次访问失败后),就会进服务的熔断(跳闸),这个时候尽管是正确的请求也会返回fallback中的信息,当有正常的请求之后,调用链路就会慢慢的恢复。
服务降级—>进而熔断---->恢复调用链路
在这里插入图片描述

4、Hystrix之服务熔断总结

在这里插入图片描述

4.1、服务熔断的类型

  • 熔断打开(Open):请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态。
  • 熔断关闭(Closed):熔断关闭不会对服务进行熔断。
  • 熔断半开(Half Open):部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断。

官网断路器工作流程图
在这里插入图片描述

官网给出的步骤:地址

The precise way that the circuit opening and closing occurs is as follows:

1、Assuming the volume across a circuit meets a certain threshold : HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()

2、And assuming that the error percentage, as defined above exceeds the error percentage defined in : HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()

3、Then the circuit-breaker transitions from CLOSED to OPEN.

4、While it is open, it short-circuits all requests made against that circuit-breaker.

5、After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), the next request is let through. If it fails, the command stays OPEN for the sleep window. If it succeeds, it transitions to CLOSED and the logic in 1) takes over again.

4.2、断路器在什么情况下开始起作用?

在这里插入图片描述
涉及到断路器是否启动的三个重要参数:

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

4.3、断路器开启或者关闭的条件是什么?

  • 到达以下阀值,断路器将会开启:

    • 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
    • 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
  • 当开启的时候,所有请求都不会进行转发

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

4.4、当断路器打开之后有什么影响?

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

4.5、原来的主逻辑要如何恢复呢?

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

4.6、服务熔断的所有配置

@HystrixCommand(fallbackMethod = "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 doSomething() {
	...
}

5、Hystrix工作流程最后总结

  • 官网介绍
  • 官方步骤:
    在这里插入图片描述
    步骤说明:
  • 1、 创建HystrixCommand (用在依赖的服务返回单个操作结果的时候)或HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象。
  • 2、命令执行。
  • 3、其中 HystrixCommand实现了下面前两种执行方式
    • 3.1、execute():同步执行,从依赖的服务返回一个单一的结果对象或是在发生错误的时候抛出异常。
    • 3.2、queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。
  • 4、而 HystrixObservableCommand实现了后两种执行方式:
    • 4.1、obseve():返回Observable对象,它代表了操作的多个统
      果,它是一个Hot Observable (不论“事件源”是否有“订阅者”,都会在创建后对事件进行发布,所以对于Hot Observable的每一个“订阅者”都有可能是从“事件源”的中途开始的,并可能只是看到了整个操作的局部过程)。
    • 4.2、toObservable():同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有“订间者”的时候并不会发布事件,而是进行等待,直到有“订阅者"之后才发布事件,所以对于Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
  • 5、若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
  • 6、检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。
  • 7、线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。
  • 8、Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
    • 8.1、HystrixCommand.run():返回一个单一的结果,或者抛出异常。
    • 8.2、HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。
  • 9、Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"。
  • 10、当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。而能够引起服务降级处理的情况有下面几种:
    • 10.1、第4步∶当前命令处于“熔断/短路”状态,断路器是打开的时候。
    • 10.2、第5步∶当前命令的钱程池、请求队列或者信号量被占满的时候。
    • 10.3、第6步∶HystrixObsevableCommand.construct()或HytrixCommand.run()抛出异常的时候。
  • 11、当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回。

tips: 如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常,Hystrix依然会运回一个Obsevable对象,但是它不会发射任结果数惯,而是通过onError方法通知命令立即中断请求,并通过onError方法将引起命令失败的异常发送给调用者。

6、Hystrix服务监控仪表盘Dashboard

6.1、Hystrix图形化Dashboard搭建

概述:

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

  • Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。本次介绍Dashboard的搭建。

搭建步骤:

  • 第一步: 新建一个Module,名为:cloud-consumer-hystrix-dashboard9001

  • 第二步: 改pom文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud2021</artifactId>
            <groupId>com.oldou.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>
        <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>
    </project>
    
  • 第三步: 改yml文件

    server:
      port: 9001
    
  • 第四步: 主启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    
    @SpringBootApplication
    @EnableHystrixDashboard
    public class HystrixDashboardMain9001 {
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardMain9001.class, args);
        }
    }
    
  • 第五步: 确定所有的Provider微服务(cloud-provider-payment8001、cloud-provider-payment8002、cloud-provider-payment8004、cloud-provider-payment8006、cloud-provider-hystrix-payment8001)中,都需要有健康监控的依赖

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

第六步: 启动测试,启动cloud-consumer-hystrix-dashboard9001服务,访问:http://localhost:9001/hystrix
在这里插入图片描述

6.2、Hystrix图形化Dashboard监控实战

目标: 我们使用搭建的9001来监控cloud-provider-hystrix-payment8001服务

第一步: 由于新版本Hystrix需要在cloud-provider-hystrix-payment8001服务的主启动类PaymentHystrixMain8001中指定监控路径,因此需要添加以下代码:

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //添加此注解
public class PaymentHystrixMain8001 {

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

    /**
     *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
     *只要在自己的项目里配置上下面的servlet就可以了
     *否则,Unable to connect to Command Metric Stream 404
     */
    @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;
    }
    
}

第二步: 检查cloud-provider-hystrix-payment8001服务的pom文件是否包含以下依赖:

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

第三步: 启动cloud-eureka-server7001cloud-provider-hystrix-payment8001
在这里插入图片描述
第四步: 在Dashboard上配置监控8001,8001的地址为:http://localhost:8001/hystrix.stream,这里的地址来源于主启动类中添加的那个方法。
在这里插入图片描述

第五步: 测试访问

  • 正确的:http://localhost:8001/pay/circuit/1
  • 错误的:http://localhost:8001/pay/circuit/-1
  • 我们先访问正确的,多刷新几次,然后去Dashboard的页面点击监控
    在这里插入图片描述
  • 接下来我们开始访问错误的,多访问几次,然后再看仪表盘
    在这里插入图片描述

6.3、怎么看仪表盘的数据?

七色: 如下图所示:
在这里插入图片描述
右上角的七种状态分别对应七种颜色,同时也与左上角的七种颜色一一对应。

1圈
在这里插入图片描述

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

1线
在这里插入图片描述

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

整图说明
在这里插入图片描述
在这里插入图片描述

缺点: 使用Hystrix实现服务的监控时,还需要自己搞一个服务,这样太麻烦了,因此我们后面会学到alibaba的Sentinel。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值