分布式系统遇到的问题
- 我们在使用分布式架构模式的情况下,会遇到服务雪崩等问题,先来明确几个概念
- 服务扇出
在分布式系统中,存在服务A调用服务B,而服务B又去调用服务C,服务D,这样的调用过程就是服务扇出
- 服务雪崩或者级联故障
在我们某一条扇出的服务调用链路中有一个服务,由于响应时间过程或者抛出异常,导致服务调用者老被占用越来越多的资源,从而导致整个系统崩溃,整个的过程就叫做服务雪崩或者级联故障
如何解决服务雪崩?
- 解决方案:
(1)超时机制
- 在我们没有使用Hystrix的时候,我们会设置restTemplate的超时机制,然后捕获异常,全局处理该异常
(2)舱壁隔离
- 拿船来举例,现代的轮船会分为很多的舱室,舱室之间用钢板焊死,彼此隔离。这样即使有某个/某些船舱进水,也不会影响其他的舱室,浮力够的话,船就不会沉。
- 软件工程可以这么理解:M类使用线程池1,N类使用线程池2,彼此的线程池不同,并且为每个类分配线程池的大小,例如:coreSize=10。举个例子来说,M类调用B服务,N类调用C服务,如果M类和N类使用相同的线程池,那么如果B服务挂了,M类调用B服务的接口并发又很高,你又没有任何保护措施,你的服务很可能就会被M类拖死。而如果M类有自己的线程池,N类也有自己的线程池,如果B服务挂了,M类项顶多就是将自己的线程池沾满,不会影响N类的线程池—于是N类依然能正常工作
- 思路:不要把鸡蛋放在一个篮子里,你有你的线程池,我有我的线程池,你的和我的没关系,我的和你的也没关系
(3)熔断器
- 现实世界的断路器大家肯定都很了解,每个人家里都会有断路器。断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁
- 软件世界的断路器可以这么理解,实时监测应用,如果发现在一定时间内失败的次数/失败率达到一定的阈值,就“跳闸”,断路器打开,此时,请求直接返回,而不去调用原本调用的逻辑
- 跳闸一段时间后(例如15s后),断路器就会进入半开状态,这是一个瞬间态,此时允许一次请求调用正常的业务逻辑,如果成功,则断路器关闭,应用恢复正常,如果还是不成功,断路器继续回到打开状态,过段时间后再次进入半开状态并尝试打开,通过“跳闸”,应用可以保护自己,而且避免浪费资源,而通过半开的设计,可以实现应用的“自我修复”。
- 注意:
- 服务熔断是条件,服务降级是手段(熔断就是直接给一个错误,降级就是服务返回自己的异常封装响应)
- 我们只需要对高频接口实现降级,并不需要对每一个接口降级
什么是Hystrix?
- Hystrix(豪猪)是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统,服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
Hystrix能做什么?
- hystrix可以提供最基础的服务的熔断和降级。
- 服务的熔断是条件,降级是实现的手段,熔断就是直接报错,不进行调用,而降级是在熔断的基础上,自己调用自己本地的方法,返回客户一个自己定义的响应(比如:淘宝的店家不在,稍后再来等,都是服务的降级)
Java实现非注解版本的Hystrix(TODO)
- 包裹请求
(1)创建工程,工程结构图:
(2)导入依赖
。。。。
- 跳闸机制
2.1 Hystrix的默认配置跳闸父阈值
- 宕机跳闸
(1)启动服务注册中心以及服务消费者(不启动服务的提供者模拟宕机) - 超时跳闸
- 使用Ribbon+Hystrix来进行调用,需要调用超时时间(设置全局超时时间)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
注意:如果这里没有和Fegn联用,则无需配置Ribbon的超时时间- 指定设置接口的超时设置
hystrix.command.queryUserInfoByName.execution.isolation.thread.timeoutInMilliseconds=10
(1)在服务提供方的被调用方法中设置睡眠时间,来达到超时
- 异常跳闸
(1)查询一个不存在的id,就是让服务的提供方主动抛出一个异常对其进行测试
2.2 跳闸机制的三转换图
- 当熔断器开关关闭的时候,请求允许通过熔断器,如果当前健康状况高于设定的阈值,开关就继续保持关闭状态,如果当前健康状况低于设定的阈值的时候,开关则切换成打开状态
- 当熔断器开关打开的时候,请求禁止通过直接返回
- 当熔断器开关处于打开状态的时候,经过一段时间后(这段时间中真实服务器无法访问,都是直接返回一个错误的或者用户自定义的响应),熔断器会自动进入半开状态,这时候熔断器只能够允许一个请求通过,当该请求调用成功的时候,熔断器恢复关闭的状态,若该请求失败的话,熔断器继续保持打开的状态,接下来的请求被禁止通过(继续半开状态)。
- 熔断器的核心配置:
Feign整合Hystrix
- Feign默认是对Hystrix不支持的(Dalston版本之后),需要开启配置,因此使用Feign集成Hystrix的时候,需要手动开启,否则断路器不会生效
#开启feign支持hystrix就将其设置为true 默认是关闭的(将其设置为true是为了测试局部关闭,在config类中配置)
feign:
hystrix:
enabled: true
搭建工程----FeignAPI
- 该工程用来抽象出Fegin所需要的service
- 之前已经搭建过了,这里只贴出核心代码
package com.xiyou.api.api;
import org.springframework.web.bind.annotation.*;
/**
* Feign的Service接口
* @author 92823
*/
public interface FeignApiService {
/**
*
* @return
*/
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET)
public String test(@PathVariable("id") String id);
/**
*
* @param cookie
* @return
*/
@RequestMapping(value = "/testCookie", method = RequestMethod.GET)
public String testCookie(@RequestHeader("Cookie") String cookie);
/**
*
* @param token
* @return
*/
@RequestMapping(value = "/testToken", method = RequestMethod.GET)
public String testToken(@RequestHeader("Token") String token);
}
- 打包
搭建工程my-feign-hystrix
- 该工程就是用来实现远程通信的,并且集成了hystrix
- 代码架构:
(1)pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>com.xiyou</groupId>
<artifactId>my-feign-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
这里导入了刚刚我们自己打包的feign-api
(2)配置文件yml
server:
port: 8006
# 注册到eureka服务端的服务名称
spring:
application:
name: my-feign-hystrix
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-feign-hystrix-8006
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
#开启feign支持hystrix就将其设置为true 默认是关闭的(将其设置为true是为了测试局部关闭,在config类中配置)
feign:
hystrix:
enabled: true
(3)主启动类
package com.xiyou.hystrix;
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.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author 92823
*/
@SpringBootApplication
@EnableCircuitBreaker
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.xiyou.hystrix.service")
public class FeignHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(FeignHystrixApplication.class, args);
}
}
(4)FeignController
package com.xiyou.hystrix.controller;
import com.xiyou.hystrix.service.FeignHystrixService;
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;
/**
* @author 92823
*/
@RestController
public class FeignHystrixController {
@Autowired
private FeignHystrixService feignHystrixService;
@GetMapping("/feign/{id}")
public String test(@PathVariable("id") String id) {
String test = feignHystrixService.test(id);
return test;
}
}
(5)service
package com.xiyou.hystrix.service;
import com.xiyou.api.api.FeignApiService;
import com.xiyou.hystrix.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
/**
* @author 92823
*/
@FeignClient(name = "my-provider", fallbackFactory = FeignApiFallBackFactory.class)
public interface FeignHystrixService extends FeignApiService {
}
这里利用了fallbackFactory来实现hystrix的集成,实现降级服务
name属性就是要调用的目标服务的Eureka中的服务的名(applicationName)
(6)降级服务的类
package com.xiyou.hystrix.service;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 错误处理的方法
* @author 92823
*/
@Component
public class FeignApiFallBackFactory implements FallbackFactory<FeignHystrixService> {
@Override
public FeignHystrixService create(Throwable throwable) {
return new FeignHystrixService(){
@Override
public String test(String id) {
return "test方法的回退方法.....";
}
@Override
public String testCookie(String cookie) {
return null;
}
@Override
public String testToken(String token) {
return null;
}
};
}
}
下面的相关知识点的配置,都是在上面的基础上进行的,所以下面只给出核心的代码
Hystrix调用的超时重试设置
(1)导入maven的依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
(2)加入yml配置文件
server:
port: 8006
# 注册到eureka服务端的服务名称
spring:
application:
name: my-feign-hystrix
# 默认就是开启重试的机制
cloud:
loadbalancer:
retry:
enabled: true
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-feign-hystrix-8006
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
#开启feign支持hystrix 默认是关闭的
feign:
hystrix:
enabled: true
# ribbon的超时机制,若用feign+hystrix则需要配置这个
ribbon:
ReadTimeout: 4000
ConnectTimeout: 4000
# 出现异常的时候当前实例重试的次数
MaxAutoRetries: 1
# 切换实例重试的次数
MaxAutoRetriesNextServer: 1
# 对所有的操作进行重试 值可以是true(所有的进行重试),get, post, put, delete
OkToRetryOnAllOperations: true
# 默认值是1s
hystrix:
command:
default:
execution:
isolation:
thread:
# 这个的时间应该大于(MaxAutoRetries+MaxAutoRetriesNextServer+本次调用这一次) * ribbon的超时时间
# 所以这里是:(1+1+1)* (4000+4000)= 24000
timeoutInMilliseconds: 24000
# 局部配置
# 微服务实例名称.ribbon.ConnectTimeout=250
# 微服务实例名称.ribbon.ReadTimeout=1000
# 微服务实例名称.ribbon.OkToRetryOnAllOperations=true
# 微服务实例名称.ribbon.MaxAutoRetriesNextServer=2
# 微服务实例名称.ribbon.MaxAutoRetries=1
- 这里注意一点,因为我们利用的是Feign和Hystrix,所以设置超时时间的时候,不止需要设置hystrix的,还需要设置ribbon的。我们设置的hystrix的超时时间需要大于(MaxAutoRetries+MaxAutoRetriesNextServer+本次调用这一次) * ribbon的超时时间
关闭Feign对Hystrix的支持
- 全局关闭
- 默认我们就是全局关闭的,yml中配置即可
#开启feign支持hystrix就将其设置为true 默认是关闭的
feign:
hystrix:
enabled: false
- 局部关闭
(1)写一个配置类,在配置中写一个FeignBuilder类
package com.xiyou.hystrix.config;
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
/**
* @author 92823
* 不能写@Configuration 否则全局生效
*/
public class FeignConfig {
@Scope("prototype")
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
这里我们在类上面写@Configuration注解,否则改配置又成全局生效了
(2)修改Feign的调用Service加入Configuration属性
package com.xiyou.hystrix.service;
import com.xiyou.api.api.FeignApiService;
import com.xiyou.hystrix.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
/**
* 测试局部关闭feign对hystrix的支持
* @author 92823
*/
@FeignClient(name = "my-provider", fallbackFactory = FeignApiFallBackFactory.class, configuration = FeignConfig.class)
public interface FeignHystrixService1 extends FeignApiService {
}
(3)全局开启Feign对Hystrix的支持
#开启feign支持hystrix就将其设置为true 默认是关闭的
feign:
hystrix:
enabled: true
- 测试结果:
(1)不指定config使用的是全局的配置,那么由于超时会出现服务的降级
(2)指定config,由于自己配置的FeignBuilder,导致不支持服务降级,直接报错(该报错就由SpringBoot默认处理了,不是自己的服务降级)
关闭熔断
(1)全局关闭
hystrix:
command:
default:
circuitBreaker:
enabled: false
(2)局部关闭
hystrix:
command:
<HystrixCommandKey>:
circuitBreaker:
enabled: false
- 其中< HystrixCommandKey> 是一个变量,具体的值就是@FeignClient标注的类#方法名(参数类型)
- 如:
设置超时
(1)全局关闭
# 全局设置超时:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1000
(2)局部关闭
# 局部设置超时:
hystrix.command.<HystrixCommandKey>.execution.isolation.thread.timeoutInMilliseconds: 1000
关闭超时
(1)全局关闭
# 全局关闭:
hystrix.command.default.execution.timeout.enabled: false
(2)局部关闭
# 局部关闭:
hystrix.command.<HystrixCommandKey>.execution.timeout.enabled: false
hystrix整合监控点
- 单纯的引入hystrix.stream的监控端点
(1)导入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(2)加入注解
- 在主启动类上加入@EnableCircuitBreaker注解
(3)加入监控端点的配置
- SpringCloud的E版本是不需要加入的,但是E版本之后,就必须手写过滤器来加入监控端点的配置
package com.xiyou.hystrix.config;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 加入的端点监控配置
* @author 92823
*/
@Configuration
public class HystrixDashboardConfig {
@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;
}
}
(4)访问http://localhost:端口号/hystrix.stream
- 该接口访问出来的接口全部都是json格式的数据,很难使用
- Hystrix+DashBoard监控
(1)重新创建一个新的工程:
(2)pom文件依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
(3)yml配置文件没有做其他的配置
(4)主启动类
package com.xiyou.dashnoard;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* @author 92823
*/
@SpringBootApplication
@EnableHystrixDashboard
public class DashboardApplication {
public static void main(String[] args) {
SpringApplication.run(DashboardApplication.class, args);
}
}
- 此时访问的时候先访问当前工程的http://localhost:XXXX/hystrix
- 然后在输入框中输入,要监控服务的监控地址:如http://localhost:8006/my-feign-hystrix.stream
注意事项
(1)访问的hystrix地址,是我们新搭建的工程的
(2)监控的地址是我们要监控的地址的ip+端口号+注册到applicationName.stream
(3)切记监控的工程也需要引入hystrix.strream的相关依赖,否则没有地址,报错(引入可以参考1. 单纯的引入hystrix.stream的监控端点)