Live2D

SpringCloud微服务框架

Springcloud微服务框架二


Springcloud微服务框架三



1.父工程的创建

创建一个普通的maven项目,依赖如下

<?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.wl.springcloud</groupId>
  <artifactId>SpringCloud</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <name>Maven</name>
  <!-- FIXME change it to the project's website -->
  <url>http://maven.apache.org/</url>
  <inceptionYear>2001</inceptionYear>

  <distributionManagement>
    <site>
      <id>website</id>
      <url>scp://webhost.company.com/www/website</url>
    </site>
  </distributionManagement>

  <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.12</druid.version>
    <mybatis.spring.boot.version>2.1.4</mybatis.spring.boot.version>
  </properties>
  <!--子模块继承后,提供的作用:锁定版本+子模块不用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>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
      </dependency>
    </dependencies>
  </dependencyManagement>


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


</project>

2. maven中 dependencyManagement 的作用

maven中 dependencyManagement 可以统一版本号,如果有多个子工程继承此工程时候,不需要再指定版本号。

3.子模块的构建-支付模块的提供者的构建

依赖如下

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

    <artifactId>Payment8001</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <!--mysql-connector-java-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <artifactId>cloud-api-public</artifactId>
            <groupId>com.wl.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

yml配置如下

server:
  port: 8001
  servlet:
    context-path: /provider

spring:
  application:
    name: cloud-provider-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource      #当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver        #mysql驱动包
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding-utr-8&useSSL=false
    username: root
    password: qq2315290571

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.wl.springcloud.entities

controller层代码如下

package com.wl.springcloud.controller;

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

import javax.annotation.Resource;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:54
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    //只传给前端CommonResult,不需要前端了解其他的组件
    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody  Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入结果:"+result);
        if(result > 0){
            return new CommonResult(200,"插入数据成功",result);
        }else{
            return new CommonResult(444,"插入数据失败",null);
        }
    }
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****插入结果:"+payment);
        if(payment != null){
            return new CommonResult(200,"查询成功",payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }
}

实体类代码如下

package com.wl.springcloud.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:46
 * @Version 1.0
 * @Describe
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {  //泛型:如果装的payment 返回payment,装的order 返回order

    private Integer code;
    private String message;
    private T data;

    public CommonResult(Integer code,String message){
        this(code,message,null);
    }
}
package com.wl.springcloud.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:45
 * @Version 1.0
 * @Describe
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    private Long id;
    private String serial;
}

service业务逻辑层

package com.wl.springcloud.service;

import com.wl.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:52
 * @Version 1.0
 * @Describe
 */

public interface PaymentService {
    public int create(Payment payment);

    public Payment getPaymentById(@Param("id") Long id);
}

业务实现层

package com.wl.springcloud.service;

import com.wl.springcloud.dao.PaymentDao;
import com.wl.springcloud.entities.Payment;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:53
 * @Version 1.0
 * @Describe
 */
@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);
    }
}

Mybatis配置文件

<?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.wl.springcloud.dao.PaymentDao">
    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        insert into payment(serial) values(#{serial})
    </insert>

    <resultMap id="BaseResultMap" type="com.wl.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>
    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
        select * from payment where id=#{id};
    </select>
</mapper>

主启动类

package com.wl.springcloud;

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

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:34
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}

4.sql语句

DROP TABLE IF EXISTS `payment`;
CREATE TABLE `payment`  (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `serial` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

5.消费者模块的构建

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

    <artifactId>Payment80</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.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>
RestTemplate
RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集

官网地址:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

使用:

  • 使用restTemplate访问restful接口非常的简单粗暴无脑。

  • (url, requestMap, ResponseBean.class)这三个参数分别代表。

  • REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。

配置类
package com.wl.springcloud.config;

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

/**
 * @Autor: wl
 * @Date: 2021-10-24     17:07
 * @Version 1.0
 * @Describe
 */
@Configuration
public class ApplicationContextConfig {
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

因为消费者是请求服务的,所以只需要一个controller
package com.wl.springcloud.controller;

import com.wl.springcloud.entities.CommonResult;
import com.wl.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;

/**
 * @Autor: wl
 * @Date: 2021-10-24     17:08
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class OrderController {
    private static final String PAYMENT_URL="http://localhost:8001";
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment){
        return restTemplate.postForObject(PAYMENT_URL+"/provider/payment/create",payment,CommonResult.class);
    }

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

}
yml里只需配置一个端口号80就行了

6.Eureka

1.服务治理

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

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

2.服务注册与发现

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

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

3.Eureka的两个个组件
Eureka Server提供服务注册服务

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

EurekaClient通过注册中心进行访问

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

3.Eureka的使用
先创建一个maven工程,端口号为7001,并创建yml文件,内容如下
server:
  port: 7001

eureka:
  instance:
    hostname: locathost #eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己。
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
pom内容如下
并在启动类中加上 @EnableEurekaServer 注解
package com.wl.springcloud;

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

/**
 * @Autor: wl
 * @Date: 2021-10-24     20:30
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
@EnableEurekaServer // 这个注解代表Eureka是注册中心 服务端
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class,args);
    }
}

将80端口和8001端口 的工程 分别引入 eureka的客户端的依赖
<!--引入Eureka客户端的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
并在启动类中加入以下注解 @EnableEurekaClient,并在yml里面添加以下内容,将工程注册进eureka服务端
eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
5.Eureka集群原理说明

image

6.Eureka集群的搭建
新建一个工程 端口号为7002,参考7001的创建,找到C:\Windows\System32\drivers\etc路径下的hosts文件,修改映射配置添加进hosts文件
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
修改端口号为7001的工程yml文件
server:
  port: 7001

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

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己。
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://eureka7001.com:7001/eureka/
7.Eureka集群配置
修改8001/8002 端口号工程的Controller,添加serverPort
package com.wl.springcloud.controller;

import com.wl.springcloud.entities.CommonResult;
import com.wl.springcloud.entities.Payment;
import com.wl.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;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:54
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class PaymentController {


    @Resource
    private PaymentService paymentService;

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


    //只传给前端CommonResult,不需要前端了解其他的组件
    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody  Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入结果:"+result);
        if(result > 0){
            return new CommonResult(200,"插入数据成功 serverPort: "+serverPort,result);
        }else{
            return new CommonResult(444,"插入数据失败",null);
        }
    }
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****插入结果:"+payment);
        if(payment != null){
            return new CommonResult(200,"查询成功 serverPort: "+serverPort,payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }
}

负载均衡

订单服务地址不能写死,需要修改80端口的代码

image
这里对应着 eureka的这里
image

import javax.annotation.Resource;

/**
 * @Autor: wl
 * @Date: 2021-10-24     17:08
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class OrderController {
//    private static final String PAYMENT_URL="http://localhost:8001";
    private static final String PAYMENT_URL="http://cloud-provider-service";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment){
        return restTemplate.postForObject(PAYMENT_URL+"/provider/payment/create",payment,CommonResult.class);
    }

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

}

使用@LoadBalanced注解赋予RestTemplate负载均衡的能力

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//使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}

8.actuator微服务信息完善

主机名称:服务名称修改(也就是将IP地址,换成可读性高的名字),在8001 端口和 8002 端口的yml分别 加上

eureka:
  ...
  instance:
    instance-id: payment8001 #添加此处
    prefer-ip-address: true #添加此处
eureka:
  ...
  instance:
    instance-id: payment8002 #添加此处
    prefer-ip-address: true #添加此处
9.服务发现Discovery

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

在Payment8001的Controller 添加以下内容
    @Resource
    private DiscoveryClient discoveryClient;
	
   @GetMapping(value = "/payment/discovery")
    public Object discovery() {
        // 发现服务
        List<String> services = discoveryClient.getServices();
        services.forEach(
                // 打印eureka中的服务名称
                s -> log.info(s)
        );

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-SERVICE");
        instances.forEach(
                // 打印服务信息  ip地址  端口号  路径
                instance->log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri())
        );
        return this.discoveryClient;
    }
并在8001主启动类中添加以下注解
@EnableDiscoveryClient//添加该注解
10. Eureka的自我保护机制

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

如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THANTHRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUSTTO BE SAFE
自我保护机制的导致原因

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

使用eureka.server.enable-self-preservation = false可以禁用自我保护模式

7.Zookeeper

zookeeper是一个分布式协调工具,可以实现注册中心功能

关闭Linux服务器防火墙后,启动zookeeper服务器
下面使用zookeeper作为注册中心代替Eureka

1.创建Maven工程名为Payment8003

依赖如下:

  <dependencies>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.lun.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.4.9起冲突-->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--添加zookeeper3.4.9版本-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</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>
2.创建yml文件,内容如下:
server:
  port: 8003


spring:
  application:
    name: cloud-provider-service
  cloud:
    zookeeper:
      connect-string: http://www.wlstudy.top:2181 # zookeeper远程地址
3.创建controller层测试:
package com.wl.springcloud.controller;

/**
 * @Autor: wl
 * @Date: 2021-10-29     9:22
 * @Version 1.0
 * @Describe
 */
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 = "/payment/zk")
    public String paymentzk()
    {
        return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
    }
}

出现以下内容说明Payment8003工程入驻进zookeeper了

image

4.创建一个Maven工程名为Consumer8013

依赖如下

    <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.4.9版本-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</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>

主启动类如下:

package com.wl.springcloud;

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

/**
 * @Autor: wl
 * @Date: 2021-10-31     15:58
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
@EnableDiscoveryClient
public class Consumer8013 {
    public static void main(String[] args) {
        SpringApplication.run(Consumer8013.class,args);
    }
}

5.yml内容如下:
server:
  port: 8013

spring:
  application:
    name: cloud-consumer-service
  cloud:
    zookeeper:
      connect-string: http://www.wlstudy.top:2181

6.配置类和controller如下:
package com.wl.springcloud.config;

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

/**
 * @Autor: wl
 * @Date: 2021-10-31     16:03
 * @Version 1.0
 * @Describe
 */
@Configuration
public class ApplicationContextConfig {

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

package com.wl.springcloud.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;

/**
 * @Autor: wl
 * @Date: 2021-10-31     16:00
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class Consumer8013Controller {

    @Resource
    private RestTemplate restTemplate;

    public static final String INVOKE_URL = "http://cloud-provider-service";

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

}

6.验证测试

运行ZooKeeper服务端,cloud-consumerzk-order80,cloud-provider-payment8004。

打开ZooKeeper客户端:

[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-consumer-order, cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 2]
7.访问测试地址 - http://localhost:8013/consumer/payment/zk

8.Ribbon

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

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

1.**Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别**

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

2.**Ribbon默认自带的负载规则**

img

  • RoundRobinRule 轮询

  • RandomRule 随机

  • RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重

  • WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越

  • 容易被选择

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

  • 量最小的服务

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

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

3.Ribbon默认负载轮询算法原理
默认负载轮训算法: rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后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台机器,集群总数为2,按照轮询算法原理:

    当总请求数为1时:1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
    当总请求数位2时:2%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
    当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
    当总请求数位4时:4%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
    如此类推…

9.OpenFeign

OpenFeign是什么?

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

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

OpenFeign的特点

OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@Feignclient可以解析SpringMVc的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

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

OpenFeign的服务调用

接口+注解:微服务调用接口+@FeignClient

1.新建工程cloud-consumer-feign-order8011,依赖如下
 <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>
        <!--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>
2.yml文件内容和启动类
server:
  port: 8011

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

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

/**
 * @Autor: wl
 * @Date: 2021-11-04     15:50
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain8011 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain8011.class,args);
    }
}
3.controller层
package com.wl.springcloud.controller;

import com.wl.springcloud.entities.CommonResult;
import com.wl.springcloud.entities.Payment;
import com.wl.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;

/**
 * @Autor: wl
 * @Date: 2021-11-04     15:57
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentId(@PathVariable Long id){
        return paymentFeignService.getPaymentById(id);
    }
}

4. 业务逻辑接口+@FeignClient配置调用provider服务 新建PaymentFeignService接口并新增注解@FeignClient
package com.wl.springcloud.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:46
 * @Version 1.0
 * @Describe
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {  //泛型:如果装的payment 返回payment,装的order 返回order

    private Integer code;
    private String message;
    private T data;

    public CommonResult(Integer code,String message){
        this(code,message,null);
    }
}
package com.wl.springcloud.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Autor: wl
 * @Date: 2021-10-24     16:45
 * @Version 1.0
 * @Describe
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    private Long id;
    private String serial;
}
package com.wl.springcloud.service;

import com.wl.springcloud.entities.CommonResult;
import com.wl.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Autor: wl
 * @Date: 2021-11-04     15:53
 * @Version 1.0
 * @Describe
 */
@Service
@FeignClient("cloud-provider-service")
public interface PaymentFeignService {

    @GetMapping("provider/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
OpenFeign的超时控制
我们在微服务提供者8001 controller层里面让设置超时3秒,OpenFeign默认等待1秒,超时后会报错,报错如下:

image

1.8001controller层代码如下
// 编写超时请求
@GetMapping("/payment/getTimeout")
public String FeignTimeout(){
    // 设置延迟三秒
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return serverPort;
}
2.在cloud-consumer-feign-order8011工程下的controller层和service层代码如下
  // Feign的超时
    @GetMapping("/consumer/payment/getTimeout")
    public String Timeout(){
     return paymentFeignService.Timeout();
    }
@GetMapping("provider/payment/getTimeout")
String Timeout();
3.可以在yml文件中设置超时时间
#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

OpenFeign日志增强
1.日志打印功能

Feign提供了日志打印功能,我们可以通过配置来调整日恙级别,从而了解Feign 中 Http请求的细节。

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

2.日志级别
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
3.配置日志bean
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

10.Hystrix

1.概述
分布式系统面临的问题

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

服务雪崩

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

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

Hystrix是什么

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

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

2.Hystrix的服务降级熔断限流概念初讲
服务降级

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback

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

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

服务限流

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

11.Hystrix支付8010微服务构建

1.新建cloud-provider-hygtrix-payment8010工程
pom依赖如下
 <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>
            <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>
2.yml内容
server:
  port: 8010

spring:
  application:
    name: cloud-provider-hystrix-payment
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  instance:
    instance-id: cloud-provider-hystrix-payment
    prefer-ip-address: true


3.控制层和业务层
package com.wl.springcloud.controller;

import com.wl.springcloud.service.PaymentHystrixService;
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;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:08
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class PaymentHystrix8010Controller {

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

    @Resource
    private PaymentHystrixService hystrixService;

    /**
     * 正常返回ok
     * @param id
     * @return
     */
    @GetMapping("/payment/hystrix/ok/{id}")
    public String Payment_inFo(@PathVariable ("id") Integer id){
        String result = hystrixService.PaymentOk(id);
        log.info("*****result: "+result);
        return result;
    }

    /**
     * 超时返回
     * @param id
     * @return
     */
    @GetMapping("/payment/hystrix/TimeOut/{id}")
    public String Payment_timeOut(@PathVariable ("id") Integer id){
        String result = hystrixService.PaymentTimeOut(id);
        log.info("*****result: "+result);
        return result;
    }

}

package com.wl.springcloud.service;

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

import java.util.concurrent.TimeUnit;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:12
 * @Version 1.0
 * @Describe
 */
@Service
public class PaymentHystrixService {

    /**
     * 正常访问
     * @param id
     * @return
     */
    public String PaymentOk(Integer id) {
        return "线程池:  "+Thread.currentThread().getName()+"  paymentInfo_OK,id:  "+id+"\t"+"O(∩_∩)O哈哈~";
    }

    /**
     * 超时访问
     * @param id
     * @return
     */
    })
    public String PaymentTimeOut(Integer id){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:  "+Thread.currentThread().getName()+"  paymentTimeOut,id:  "+id+"\t"+"┭┮﹏┭┮";
    }

}

4.主启动类
package com.wl.springcloud;

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

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:03
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
@EnableEurekaClient // 注册进eureka
public class HystrixMain8010 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixMain8010.class,args);
    }
}

5.正常测试

启动eureka7001

启动cloud-provider-hystrix-payment8010

访问

success的方法 - http://localhost:8010/payment/hystrix/ok/1
每次调用耗费5秒钟 - http://localhost:8010/payment/hystrix/timeout/1

上述module均OK

以上述为根基平台,从正确 -> 错误 -> 降级熔断 -> 恢复。

12.JMeter高并发压测后卡顿

开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务

1.测试计划中右键添加-》线程-》线程组(线程组202102,线程数:200,线程数:100,其他参数默认)

2.刚刚新建线程组202102,右键它-》添加-》取样器-》Http请求-》基本 输入http://localhost:8010/payment/hystrix/ok/1

3.点击绿色三角形图标启动。

看演示结果:拖慢,原因:tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

Jmeter压测结论

上面还是服务提供者8010自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端不满意,服务端8010直接被拖慢。

13.订单微服务调用支付服务出现卡顿

看热闹不嫌弃事大,消费订单项目8115新建加入

新建工程ConsumerHystrix8115
pom依赖如下
    <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>
        <!--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>
2.yml内容
server:
  port: 8115
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000
3.主启动
package com.wl.springcloud;

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

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:53
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
@EnableFeignClients
public class ConsumerHystrix8115 {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerHystrix8115.class,args);
    }
}

4.业务类和控制类
package com.wl.springcloud.controller;

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

import javax.annotation.Resource;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:57
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class OrderHystrix8115Controller {
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/hystrix/TimeOut/{id}")
    public String timeOut(@PathVariable Integer id){
        return  paymentHystrixService.Payment_timeOut(id);
    }

}

package com.wl.springcloud.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:58
 * @Version 1.0
 * @Describe
 */
@Service
@FeignClient("cloud-provider-hystrix-payment")
public interface PaymentHystrixService {

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

    @GetMapping("/payment/hystrix/TimeOut/{id}")
    String Payment_timeOut(@PathVariable ("id") Integer id);

}

5.高并发测试

2W个线程压8001

消费端8115微服务再去访问正常的Ok微服务8010地址

http://localhost:8115/consumer/hystrix/ok/32

消费者80被拖慢

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

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

14._降级容错解决的维度要求

超时导致服务器变慢(转圈) - 超时不再等待

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

解决:

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

15.Hystrix之服务降级支付侧fallback

降级配置 - @HystrixCommand

8010先从自身找问题

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

—旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法

package com.wl.springcloud.service;

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

import java.util.concurrent.TimeUnit;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:12
 * @Version 1.0
 * @Describe
 */
@Service
public class PaymentHystrixService {

    /**
     * 正常访问
     * @param id
     * @return
     */
    public String PaymentOk(Integer id) {
        return "线程池:  "+Thread.currentThread().getName()+"  paymentInfo_OK,id:  "+id+"\t"+"O(∩_∩)O哈哈~";
    }

    /**
     * 超时访问
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "Payment_timeOutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000")
    })
    public String PaymentTimeOut(Integer id){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:  "+Thread.currentThread().getName()+"  paymentTimeOut,id:  "+id+"\t"+"┭┮﹏┭┮";
    }

    public String Payment_timeOutHandler(Integer id){
        return "线程池:  "+Thread.currentThread().getName()+"  8010系统繁忙或者运行报错,请稍后再试,id:  "+id+"\t"+"o(╥﹏╥)o";
    }
}

当前服务不可用了,做服务降级,兜底的方案都是Payment_timeOutHandler

当前服务不可用了,做服务降级,兜底的方案都是Payment_timeOutHandl

主启动类激活

添加新注解@EnableCircuitBreaker

@SpringBootApplication
@EnableEurekaClient // 注册进eureka
@EnableCircuitBreaker // 开启服务降级
public class HystrixMain8010 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixMain8010.class,args);
    }
}

16.Hystrix之服务降级订单侧fallback

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

修改yml文件开启服务降级

server:
  port: 8115
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

#开启服务降级
feign:
  hystrix:
    enabled: true

并在主启动类中添加@@EnableHystrix注解

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

控制层

package com.wl.springcloud.controller;

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

import javax.annotation.Resource;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:57
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class OrderHystrix8115Controller {
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/hystrix/TimeOut/{id}")
    @HystrixCommand(fallbackMethod = "ConsumerTimeOut",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    public String timeOut(@PathVariable Integer id){
        return  paymentHystrixService.Payment_timeOut(id);
    }


    public String ConsumerTimeOut(@PathVariable Integer id){
        return "我是消费者8115,提供者有错误或者自己有错误";
    }
}

当前服务不可用了,做服务降级,兜底的方案都是Payment_timeOutHandl

<h6>主启动类激活</h6>

添加新注解@EnableCircuitBreaker

```java
@SpringBootApplication
@EnableEurekaClient // 注册进eureka
@EnableCircuitBreaker // 开启服务降级
public class HystrixMain8010 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixMain8010.class,args);
    }
}

16.Hystrix之服务降级订单侧fallback

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

修改yml文件开启服务降级

server:
  port: 8115
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

#开启服务降级
feign:
  hystrix:
    enabled: true

并在主启动类中添加@@EnableHystrix注解

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

控制层

package com.wl.springcloud.controller;

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

import javax.annotation.Resource;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:57
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
public class OrderHystrix8115Controller {
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/hystrix/TimeOut/{id}")
    @HystrixCommand(fallbackMethod = "ConsumerTimeOut",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    public String timeOut(@PathVariable Integer id){
        return  paymentHystrixService.Payment_timeOut(id);
    }


    public String ConsumerTimeOut(@PathVariable Integer id){
        return "我是消费者8115,提供者有错误或者自己有错误";
    }
}

17.Hystrix之全局服务降级DefaultProperties

目前问题1 每个业务方法对应一个兜底的方法,代码膨胀

解决方法

1:1每个方法配置一个服务降级方法,技术上可以,但是不聪明

1:N除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = “”)统一跳转到统一处理结果页面

通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量

package com.wl.springcloud.controller;

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

import javax.annotation.Resource;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:57
 * @Version 1.0
 * @Describe
 */
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod",commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
public class OrderHystrix8115Controller {
    @Resource
    private PaymentHystrixService paymentHystrixService;


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

    @GetMapping("/consumer/hystrix/TimeOut/{id}")
//    @HystrixCommand(fallbackMethod = "ConsumerTimeOut",commandProperties = {
//            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
//    })
    @HystrixCommand// 使用全局的方法
    public String timeOut(@PathVariable Integer id){
        return  paymentHystrixService.Payment_timeOut(id);
    }

    public String ConsumerTimeOut(@PathVariable Integer id){
        return "我是消费者8115,提供者有错误或者自己有错误";
    }
    // 全局方法
    public String payment_Global_FallbackMethod(){
        return "Global异常信息处理,请稍后重试~~~~~";
    }
}

18.Hystrix之通配服务降级FeignFallback

目前问题2 统一和自定义的分开,代码混乱

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

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

未来我们要面对的异常

  • 运行

  • 超时

  • 宕机

修改

根据ConsumerHystrix8115已经有的PaymentHystrixService接口,
重新新建一个类(paymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

PaymentFallbackService类实现PaymentHystrixService接口

package com.wl.springcloud.service;

import org.springframework.stereotype.Component;

/**
 * @Autor: wl
 * @Date: 2021-11-08     22:05
 * @Version 1.0
 * @Describe
 */
@Component
public class PaymentFallbackService implements PaymentHystrixService{
    @Override
    public String Payment_inFo(Integer id) {
        return "对方服务器宕机了,请稍后重试";
    }

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

在@FeignClient中添加fallback属性,代码如下

package com.wl.springcloud.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:58
 * @Version 1.0
 * @Describe
 */
@Service
@FeignClient(value = "cloud-provider-hystrix-payment",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {

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

    @GetMapping("/payment/hystrix/TimeOut/{id}")
    String Payment_timeOut(@PathVariable ("id") Integer id);

}

测试

正常访问测试 - http://localhost:8115/consumer/hystrix/ok/31

故意关闭微服务8010

客户端自己调用提示 - 对方服务器宕机了,请稍后重试,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。

19.Hystrix之服务熔断理论

服务熔断相当于断路器,类似于保险丝

1.熔断机制概述

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

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

服务熔断的示例图如下

img

2.服务熔断实际操作
修改cloud-provider-hygtrix-payment8010 controller层 添加以下代码
    /**
     * 服务熔断
     */
    //====服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id)
    {
        String result = hystrixService.paymentCircuitBreaker(id);
        log.info("****result: "+result);
        return result;
    }
Service层代码如下
    @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(Integer id) {
        if(id < 0) {
            throw new RuntimeException("******id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
    }
    public String paymentCircuitBreaker_fallback(Integer id) {
        return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " +id;
    }
3.测试代码

正确-http://localhost:8010/payment/circuit/9

错误-http://localhost:8010/payment/circuit/-9

在浏览器中多次点击错误的请求,抛出异常,当错误率达到60以上,会开启服务熔断(断路器),此时再请求正确的请求,也会走的是降级的方法。直到错误率逐渐减少,才会恢复正常情况

image

20.Hystrix图形化Dashboard

1.搭建工程cloud-consumer-hystrix-dashboard9001
pom依赖如下
   <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>

2.yml中添加端口号
server:
  port: 9001
3.在主启动类中添加@EnableHystrixDashboard注解
package com.wl.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

/**
 * @Autor: wl
 * @Date: 2021-11-09     8:38
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard9001Main {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboard9001Main.class,args);
    }
}

启动9001工程,并进入图形化用户界面,浏览器输入http://localhost:9001/hystrix

2.监控8010工程,修改cloud-provider-hystrix-payment8010

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

package com.wl.springcloud;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

/**
 * @Autor: wl
 * @Date: 2021-11-06     17:03
 * @Version 1.0
 * @Describe
 */
@SpringBootApplication
@EnableEurekaClient // 注册进eureka
@EnableCircuitBreaker // 开启服务熔断
public class HystrixMain8010 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixMain8010.class,args);
    }
    @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;
    }
}

在Hystrix监控器的图形化用户界面中输入8010的监控地址:http://localhost:8010/hystrix.stream

跳转到 监控页面

image

当请求8010中正确的请求 http://localhost:8010/payment/circuit/1,可以看到点击次数和压力
image

当请求8010中错误的请求 http://localhost:8010/payment/circuit/1,可以看到服务熔断(保险丝)开启了

image

21.Hystrix之服务熔断总结

img

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

img

断路器在什么情况下开始起作用
@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"),// 失败率达到多少后跳闸
})
涉及到断路器的三个重要参数:
  • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  • 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次7,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
断路器开启或者关闭的条件
  • 到达以下阀值,断路器将会开启:

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

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

步骤说明
  1. 创建HystrixCommand (用在依赖的服务返回单个操作结果的时候)或HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象

  2. 命令执行。

  3. 其中 HystrixCommand实现了下面前两种执行方式

    1.execute():同步执行,从依赖的服务返回一个单一的结果对象或是在发生错误的时候抛出异常。

    2.queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。

4.而 HystrixObservableCommand实现了后两种执行方式:

    1.obseve():返回Observable对象,它代表了操作的多个统果,它是一个Hot Observable (不论“事件源”是否有“订阅者”,都会在创      		建后对事件进行发布,所以对于Hot Observable的每一个“订阅者”都有可能是从“事件源”的中途开始的,并可能只是看到了整个操作		的局部过程)。

	2.toObservable():同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有“订间者”的		时候并不会发布事件,而是进行等待,直到有“订阅者"之后才发布事件,所以对于Cold Observable 的订阅者,它可以保证从一开始		看到整个操作的全部过程)。

5.若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
6.检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。

7.线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。

8.Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
1.HystrixCommand.run():返回一个单一的结果,或者抛出异常。
2.HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。
9.Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"。

10.当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。而能够引起服务降级处理的情况有下面几种:
第4步∶当前命令处于“熔断/短路”状态,断洛器是打开的时候。
第5步∶当前命令的钱程池、请求队列或者信号量被占满的时候。
第6步∶HystrixObsevableCommand.construct()或HytrixCommand.run()抛出异常的时候。
11.当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回。





posted @ 2021-10-23 16:21  没有梦想的java菜鸟  阅读(72)  评论(0编辑  收藏  举报