分布式系统面临的问题

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

Hystrix是什么?

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

断路器(Hystrix)是什么?

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

Hystrix能做什么?

  1. 服务降级
  2. 服务熔断
  3. 服务限流
  4. 接近实时的监控
  5. 。。。。。

官网资料

服务熔断:

熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,<mark>进而熔断该节点微服务的调用,快速返回“错误”的响应信息</mark>。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定的阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。

Hystrix用法:

服务熔断用法:

首先在父工程下创建一个工程microservicecloud-provider-dept-hystrix-8001,
其次把8001工程的src/main/java 路径下的包和src/java/resources路径下的包copy到Hystrix8001工程相应的下面进行修改。

  1. 首先pom文件需要添加Hystrix的依赖:
	<!-- 引入服务熔断Hystrix依赖===start -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix</artifactId>
		</dependency>
		<!-- 引入服务熔断Hystrix依赖===end -->

pom 文件是在8001工程基础上添加了上面的依赖,完整的pom文件如下:

<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>
	<parent>
		<groupId>com.lyj.springcloud</groupId>
		<artifactId>microservicecloud</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>microservicecloud-provider-dept-hystrix-8001</artifactId>

	<dependencies>
		<!-- 引入服务熔断Hystrix依赖===start -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix</artifactId>
		</dependency>
		<!-- 引入服务熔断Hystrix依赖===end -->

		<!-- 引入自定义的api通用包,可以使用Dept部门的Entity -->
		<dependency>
			<groupId>com.lyj.springcloud</groupId>
			<artifactId>microservicecloud-api</artifactId>
			<!-- 下面指定的版本是活的,相当于,api通用包随意怎么变,这里都会自动更新,不影响 -->
			<version>${project.version}</version>
		</dependency>
		<!-- 将微服务注册provider到Eureka注册中心中 ==start -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
		<!-- 将微服务注册provider到Eureka注册中心中 ==end -->
		<!-- 引入监控信息完善的依赖==start -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<!-- 引入监控信息完善的依赖==end -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jetty</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>
		<!-- 引入热部署,修改后立即生效===start -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>springloaded</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
		<!-- 引入热部署,修改后立即自动发布和构建生效===end -->
	</dependencies>


</project>
  1. 修改application.yml文件,只用修改“instance-id”服务实例名称,application.yml 文件如下:
server:
#指定服务器端口号8001
  port: 8001
  
mybatis:
#mybatis配置文件所在路径
  config-location: classpath:mybatis/mybatis.cfg.xml
   #所有Entity别名类所在包
  type-aliases-package: com.lyj.springcloud.entities
   #mapper映射文件所在路径
  mapper-locations:
  - classpath:mybatis/mapper/**/*.xml
  
spring:
  application:
   # 当前应用(服务)起的名字
    name: microservicecloud-dept       
  datasource:
  #当前数据源操作类型
    type: com.alibaba.druid.pool.DruidDataSource
    #mysql驱动包
    driver-class-name: com.mysql.jdbc.Driver
     #数据库名称
    url: jdbc:mysql://localhost:3306/cloudDB01
    username: mysql
    password: 15827318595
    dbcp2: 
       min-idle: 5 #数据库连接池的最小维持连接数
       initial-size: 5 #初始化连接数5个
       max-total: 5 #设置最大连接数
       max-wait-millis: 200 #s等待连接获取的最大超时时间
      

#配置Eureka
eureka:
#客户端注册进Eureka服务列表内
  client:   
    service-url:
# defaultZone: http://localhost:7001/eureka #(单机版服务器)
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
# 集群配置(多台服务器共同使用)
# defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
# eureka-server-connect-timeout-seconds: 60000
# eureka-connection-idle-timeout-seconds: 60000
# eureka-server-read-timeout-seconds: 60000
# eureka-service-url-poll-interval-seconds: 60000
      
  #自定义实例的statusIP地址名字(主机名字:服务名称修改,不采用默认的名字)
  instance:
    instance-id: microservicecloud-dept8001-hystrix  #自定义服务名称信息
    prefer-ip-address: true #访问路径可以显示IP地址
    
    
#配置Eurekaweb界面访问各个服务的的详细信息的info界面
info:
  app.name: lyj-microservicecloud
  company.name: www.lyj.com
  build.artifactId: $project.artifactId$
  build.version: $project.version$
  
#debug: true 


  1. 修改DeptController控制器文件,
    在方法上标注@HystrixCommand注解。
package com.lyj.springcloud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.lyj.springcloud.entities.Dept;
import com.lyj.springcloud.service.DeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@RestController
public class DeptController {

    @Autowired
    private DeptService service;

    /** * 一旦调用服务方法失败并抛出了错误信息后, 会自动调用@HystrixCommand标注好 的fallbackMethod调用类中的指定方法 * * @param id * @return */
    @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
    @HystrixCommand(fallbackMethod = "processHystrix_Get")
    public Dept get(@PathVariable("id") Long id) {
        Dept dept = this.get(id);
        if (null == dept) {
            throw new RuntimeException("该ID" + id + "没有对应的信息");
        }
        return dept;
    }

    public Dept processHystrix_Get(@PathVariable("id") Long id) {

        return new Dept().setDeptno(id).setDname("该ID" + id + "没有对应的信息,null--@HystrixCommand")
                .setDb_source("no this database in MySQL");

    }

}

  1. 在Hystrix8001工程的主启动类上添加对Hystrix功的支持,添加注解@EnableCircuitBreaker。
package com.lyj.springcloud;

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

@EnableDiscoveryClient // 开启服务发现功能,控制器里的服务发现方法能正常运行。
@EnableEurekaClient // 本服务启动后会自动将服务注册进Eureka服务中心中
@SpringBootApplication
@EnableCircuitBreaker // 开启Hystrix功能支持,即开启对Hystrix熔断机制的支持
public class DeptProvider8002_App {

    /** * 使用服务发现功能步骤: 1、先要启动EurekaServer 2、再启动DeptProvider8001_App主启动类,需要 稍等一会 3、访问http://localhost:8001/dept/discovery * * @param args */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        SpringApplication.run(DeptProvider8002_App.class, args);

    }

}

测试:

成功界面:

服务降级用法:

=<mark>服务降级是什么?</mark>:
整体资源快不够了,忍痛将某些服务关掉,待度过难关,再开启回来。

服务降级处理是在客户端实现完成的,与服务端没有关系。

服务降级开发流程:
服务降级开发流程图:

  1. 修改api工程,在api工程添加注解==@FeignClient(value = “MICROSERVICECLOUD-DEPT”, fallbackFactory = DeptClientServiceFallbackFactory.class)==,重点是指定fallbackFactory属性:
package com.lyj.springcloud.service;

import java.util.List;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.lyj.springcloud.entities.Dept;

/** * @FeignClient(value = "MICROSERVICECLOUD-DEPT") 指该接口中的方法用在哪个服务上,并且针对接口编程, 其实REST与Feign都是Client。 * * @author 96971 * */
//@FeignClient(value = "MICROSERVICECLOUD-DEPT")
/** * fallbackFactory=DeptClientServiceFallbackFactory.class 这里在@FeignClient注解里添加属性fallbackFactory, * 这个属性的意思:这个被@FeignClient标注的类方法,类中任何方法出了问题都由 这个FallBackFactory指定的类来处理。 * * @author 96971 * */
@FeignClient(value = "MICROSERVICECLOUD-DEPT", fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {

    @RequestMapping(value = "/dept/add", method = RequestMethod.POST)
    public boolean add(@RequestBody Dept dept);

    @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
    public Dept get(@PathVariable("id") Long id);

    @RequestMapping(value = "/dept/list", method = RequestMethod.GET)
    public List < Dept > list();

}


  1. 创建DeptClientServiceFallBackFactory实现类,实现FallBackFactory接口,和DeptService接口。记住==一定要把该类添加@Component注解,标为组件。
package com.lyj.springcloud.service;

import java.util.List;

import org.springframework.stereotype.Component;

import com.lyj.springcloud.entities.Dept;

import feign.hystrix.FallbackFactory;

@Component // 重点!!不能忘记在这里把它设置为组件。
public class DeptClientServiceFallbackFactory implements FallbackFactory < DeptClientService > {

    @Override
    public DeptClientService create(Throwable cause) {
        // TODO Auto-generated method stub
        return new DeptClientService() {

            @Override
            public Dept get(Long id) {
                // TODO Auto-generated method stub
                return new Dept().setDeptno(id).setDname("该ID" + id + "没有对应的信息,Consumer客端提供的服务降级信息,此刻服务Provider已经关闭")
                        .setDb_source("no this database in MySQL");

            }
//以下两个方法没有实现,这里只是测试
            @Override
            public boolean add(Dept dept) {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public List < Dept > list() {
                // TODO Auto-generated method stub
                return null;
            }

        };
    }

}

  1. 把api工程运行,mvn clean和install,保证是最新的。
  2. 修改feign-80服务消费者工程的application.yml文件,只需要在文件里添加如下代码就可以了:
#开启Hystrix的服务降级功能
feign:
  hystrix:
    enabled: true
  1. 按照如下进行测试:



    这里会调用我们的fallbackFactory中定义的方法,此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

<mark>总结</mark>:
<mark>服务熔断</mark>:一般是某个故障或者异常引起,类似现实世界中的“保险丝”,当某个异常条件被触发,直接熔断整个服务,而不是一直等到此服务超时。
<mark>服务降级</mark>:所谓降级,一般是从整体负荷考虑,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强。