14.0、springcloud-Hystrix:服务熔断实现
分布式系统面临的问题:
复杂分布式体系结构中的应用程序有数十个依赖关系,某个依赖关系在某些时候将会出现不可避免的失败!
服务雪崩:
多个微服务之间调用的时候,假设微服务A调用微服务B和C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”、如果扇出的链路上某个微服务的调用相应时间 过长 或者 不可用,对微服务A的调用就会占用越来越多的系统资源【因为会有越来越多的用户来访问A,但是都会在这里卡住】,进而引起系统崩溃,所谓的 “雪崩效应” 。
对于 高流量 的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和【就比如抢购的时候可能一秒钟就上千上万甚至是上十万,所以只要流程中有一个服务资源访问异常,就很可能本来只是一个地方异常,但是由于资源在几秒钟内就饱和了的原因导致整个系统雪崩】。
比失败更糟糕的是:这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,由此让单个依赖关系的失败,不会导致取消整个应用程序或系统
因此我们需要 “弃车保帅”
所以为了在访问某个资源出现异常的时候,不让用户卡在某个位置,要保证整个流程能够继续走完、正常执行,这就需要容灾机制啥的了~
什么是 Hystrix?
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整合服务失败、避免级联故障、以提高分布式系统的弹性。
“断路器” 本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方法返回一个服务预期的,可处理的备选相应( FallBack ),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩~
他能做什么:
·服务降级
·服务熔断
·服务限流
·接近实时的监控
·......
服务熔断是什么:
熔断机制是对应雪崩效应的一种微服务链路保护机制
当扇出链路的某个微服务不可用或者相应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的相应信息。
当检测到该节点微服务调用相应正常后恢复调用链路。在 SpeingCloud框架 里熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解时 @HystrixCommand
接下来我们来体验一下如何使用Hystrix:
就比如说最简单的一种解决方案:当一个服务崩了的时候,我们的备选方案就是:直接将这个服务down掉然后启动备选流提示用户:“当前服务不可用,稍后再试!”,然后结束~
第一步:创建一个新的module,springcloud-provider-dept-hystrix-8001内容和架构和之前的springcloud-provider-dept-8001模块一样【这个模块在之前的4.0文章中有写~】
还是老套路,首先导入相关依赖,如下:
<!--Hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--导入eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--actuator完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--我们需要拿到实体类,所以要配置api module-->
<dependency>
<groupId>com.hkl</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<!--springboot启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--热部署工具,配置了这个之后,更新代码不需要重新启动,只需要稍等一会儿即可生效-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
第二步:Application.yaml配置文件,如下:【IDEA没连数据库的先连数据库】
server:
port: 8001
# mybatis配置
mybatis:
type-aliases-package: com.hkl.springcloud.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
#spring配置
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8
username: root
password: root
test-while-idle: true
validation-query: SELECT 1
#Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
instance:
instance-id: springcloud-provider-hystrix-dept8001 #修改eureka上的默认描述信息
#info配置
info:
app.name: hkl-springcloud
company.name: blog.hklspringcloud.com
第三步:在resource文件夹下的mybatis文件夹下的mapper文件夹下创建一个DeptDaoMapper.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">
<!--根据id查询客户信息-->
<mapper namespace="com.hkl.springcloud.dao.DeptDao">
<insert id="addDept" parameterType="Dept">
insert into dept (dname, db_source)
values (#{dname},DATABASE())
</insert>
<select id="queryById" resultType="Dept" parameterType="Long">
select * from dept where deptno = #{deptno};
</select>
<select id="queryAll" resultType="Dept">
select * from dept;
</select>
</mapper>
在主启动类加上 @EnableCircuitBreaker //添加对熔断的支持
来写一个 DeptController:在 mapping 下加上@HystrixCommand(fallbackMethod = "hystrixGet")注解 即可解决当访问该mapping出现异常的时候会直接去调用执行下面叫做 hystrixGet 的这个方法,如下:
第四步:controller文件夹里创建一个DeptController.java文件
package com.hkl.springcloud.controller;
import com.hkl.springcloud.pojo.Dept;
import com.hkl.springcloud.service.DeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.ws.rs.Path;
import java.util.List;
//提供Restful风格服务
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id") long id) {
Dept dept = deptService.queryById(id);
if(dept == null){
throw new RuntimeException("id=>"+id+",不存在该用户,或者该信息无法找到~");
}
return dept;
}
public Dept hystrixGet(@PathVariable("id") long id) {
Dept dept = new Dept();
dept.setDeptno(id);
dept.setDname("id=>"+id+",没有对应的信息,null--@Hystrix");
dept.setDb_source("no this database in MySQL");
return dept;
}
}
OK,搭建好环境之后来测试一下:
这时候我们先启动eureka注册中心,然后启动hystrix_provider服务类,最后启动消费者consume,
来访问一下 http://localhost:8001/get/1 ,由于id=1存在与我们的mysql数据库中,所以正常运行
再来访问 http://localhost:8001/get/10 因为id=10是不存在我们的数据库中的,所以抛出异常,然后直接会执行hystrixGet()方法,显示:该id信息不存在
【如果不加熔断机制,访问异常时就会直接报错500啥的,而不会给用户返回有用的提示信息】
eureka.client.instance.perfer-id-address: true 设置为true,可以显示服务的ip地址