服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几十秒内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以达到单个依赖关系的失败而不影响整个应用程序或系统运行。这时候我们需要,弃车保帅!
什么是Hystrix?
Hystrix是一个应用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控 (类似熔断保险丝) ,向调用方返回一个服务预期的,可处理的备选响应 (FallBack) ,而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix能干嘛?
- 服务降级
- 服务熔断
- 服务限流
- 接近实时的监控
- ......
服务熔断
什么是服务熔断?
熔断机制是赌赢雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阀值缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是:@HystrixCommand。
服务熔断解决如下问题:
当所依赖的对象不稳定时,能够起到快速失败的目的;
快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复。
新建模块 springcloud-provider-hystrix-8004
- pom.xml
<dependencies>
<!--导入Hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--我们需要拿到实体类,所以要配置api module-->
<dependency>
<groupId>com.dong</groupId>
<artifactId>springcloud-common</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>
</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>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty:尝试着用这个当应用服务器,与Tomcat没什么区别-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
- application
server:
port: 8004
#mybatis配置
mybatis:
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
type-aliases-package: com.dong.common.pojo
mapper-locations: classpath:mapper/*.xml
#spring的配置
spring:
application:
name: springcloud-provider-hystrix
datasource:
type: com.alibaba.druid.pool.DruidDataSource #数据源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true
username: root
password: 123456
logging:
level:
com.dong.provider.dao: debug
# Eureka配置:配置服务注册中心地址
eureka:
client:
service-url:
#defaultZone: http://localhost:7001/eureka/
# 然而现在服务发布要发布到3个注册中心上面去
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-hystrix #修改Eureka上的默认描述信息
prefer-ip-address: true #改为true后默认显示的是ip地址而不再是localhost
# info配置
info:
# 项目的名称
app.name: springcloud
# 公司的名称
company.name: 狂神说
prefer-ip-address: false:
prefer-ip-address: true:
- mapper
<?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.dong.hystrix.dao.DeptMapper">
<select id="queryById" resultType="Dept" parameterType="Long">
select * from dept where deptno = #{deptno};
</select>
</mapper>
- dao
package com.dong.hystrix.dao;
@Mapper
@Repository
public interface DeptMapper {
//根据ID查询部门
Dept queryById(@Param("deptno") long id);
}
- service
package com.dong.hystrix.service;
import com.dong.common.pojo.Dept;
public interface DeptService {
Dept queryById(long id);
}
package com.dong.hystrix.service.impl;
import com.dong.common.pojo.Dept;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public Dept queryById(long id) {
return deptMapper.queryById(id);
}
}
- controller
package com.dong.hystrix.controller;
import com.dong.common.pojo.Dept;
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
/**
* 根据id查询部门信息
* 如果根据id查询出现异常,则走hystrixGet这段备选代码
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "hystrixGet")
@RequestMapping("/dept/get/{id}")//根据id查询
public Dept get(@PathVariable("id") Long id){
Dept dept = deptService.queryById(id);
if (dept==null){
throw new RuntimeException("这个id=>"+id+",不存在该用户,或信息无法找到~");
}
return dept;
}
/**
* 根据id查询备选方案(熔断)
* @param id
* @return
*/
public Dept hystrixGet(@PathVariable("id") Long id){
return new Dept().setDeptno(id)
.setDname("这个id=>"+id+",没有对应的信息,null---@Hystrix~")
.setDbSource("在MySQL中没有这个数据库");
}
}
- 为主启动类添加对熔断的支持注解@EnableCircuitBreaker
package com.dong.hystrix;
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;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class ProviderHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderHystrixApplication.class, args);
}
}
common 再加一个feign接口调用 springcloud-provider-hystrix-8004 模块接口
package com.dong.common.service;
@FeignClient(value = "SPRINGCLOUD-PROVIDER-HYSTRIX")
public interface HystrixService {
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
}
consumer使用 springcloud-consumer-81
package com.dong.consumer2.controller;
import com.dong.common.pojo.Dept;
@RestController
public class HystrixController {
@Autowired
HystrixService hystrixService;
@GetMapping("/hystrix/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return hystrixService.queryById(id);
}
}
运行各个模块进行测试
访问:http://localhost:81/hystrix/get/2
结果:{"deptno":2,"dname":"人事部","dbSource":"springcloud"}
访问:http://localhost:81/hystrix/get/8
结果:
{"deptno":8,"dname":"这个id=>8,没有对应的信息,null---@Hystrix~","dbSource":"在MySQL中没有这个数据库"}
到此使用熔断,成功将没有查询到的结果以提示信息展示给了客服端。而不会造成服务的雪崩。