Hystrix:断路器
主要内容
1.什么是灾难性的雪奔效应
2.如何解决灾难性雪奔效应
3.Hystrix简介
4.降级
5.熔断
6.请求缓存
7.请求合并
8.隔离
9.Hystrix-dashboard
一.射模式灾难性的续本效应
在微服务架构的项目中,尤其是中大型项目,肯定会出现一个服务调用其他的服务,其他服务又调用别的服务,服务和服务之间形成了一种链式的调用关系
当少量请求时,对于整个服务链条是没有过多的影响的
虽然每个服务的请求都是少量的,但是最终都访问服务T.所以对于服务T来说请求量就是比较大的.所在非服务器CPU压力比较高
当其中某一个服务突然遇到大量请求时.整个链条上所有服务负载均衡骤增.
导致服务U和服务T的负载过高.运行性能下降.会导致其他调用服务U和服务T的链条出现问题.从而所有的项目可能都出现的问题
这种情况就称之为灾难性的雪奔效应
造成灾难性续本效应的原因,可以简单归结为下述三种:
服务提供者(Application Service)不可用.如:硬件故障,程序BUG,缓存击穿,并发请求量大等
重试加大流量.如:用户重试,代码重试逻辑等
服务调用者(Application Client)不可用.如:同步请求zuse造成的资源耗尽等
雪奔效应最终的结果就是:服务链条中的某一个服务不可用,导致一系列IDE服务不可用,最终造成服务逻辑奔溃.这种问题造成的后果,往往是无法预料的
二.如果放置灾难性雪奔效应
降级
超时降级,资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据.实现一个fallback方法.当请求后端服务出现异常的时候,可以使用fallback方法返回的值.
保证:服务出现问题整个项目还可以继续运行
熔断
当失败率(如因网络故障/超时造成的失败率高)达到阈值自动触发降级,熔断器触发的快速失败会进行快速恢复
通俗理解:熔断就是具有特定条件的降级,当出现熔断是在设定的时间内容就不在请求Application Service了.所以在代码撒花姑娘熔断和降级都是一个注解
保证:服务出现问题整个项目还可以继续运行
请求缓存
提供了请求缓存.服务A调用B,如果在A中添加请求缓存,第一次请求后走缓存了,就不在访问服务B了,即使出现大量请求时,也不会对B产生高负载
请求缓存可以使用Spring Cache实现
保证:减少对Application Service的调用
请求合并
提供请求合并.当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负载就会大大减少,解决了对于服务B负载激增的问题
保证:减少对Application Service的调用
隔离
隔离分为线程池和信号量隔离,通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用
三.Hystrix简介
在Spring Cloud中解决灾难性续本效应就是通过Spring Cloud Netflix Hystrix实现的
Hsytrix中文含义是豪猪,因为背上长满荆棘,从而拥有了自我保护的能力.本文所说的Hystrix(中文:断路器)是Netflix开源的一款容错框架,同样具有自我保护能力
通俗解释:Hystrix就是保证在高并发下即使出现问题也可以保证程序继续运行一系列方案.作用包含两点:容错和限流
在Spring cloud中处理服务雪奔效应,都需要依赖hystrix组件.在Application Client应用的pom文件中都需要引入下述依赖:
四.降级
降级是指,当请求超时,资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩.
解决服务雪奔效应,都是避免application client请求application servcie时,出现服务调用错误或网络问题.处理手法都是在appliication client中实现.我们需要在application client相关工程中导入hystrix依赖信息.并在对应的启动类上增加新的注解@EnableCircuitBreaker,这个注解是用于开启hystrix熔断器的,简言之,就是让代码中的hystrix相关注解失效
1.新建ApplicationServiceDemo
1.1配置pom.xml
1.2新建配置文件
新建application.yml
spring:
application:
name:application-service-demo
eureka:
client:
service-url:
defaultZone:http://eurekaserver1:8761/eureka/
1.3新建控制器
新建com.bjsxt.controller.DemoController
@Controller
public class DemoController{
@RequestMapping("/demo")
@ResponseBody
public String demo(){
return "demo-service";
}
}
1.4新建启动器
新建com.bjsxt.DemoApplication
@SpringBootApplication
public class DemoApplication{
public static void main(String[] args){
SpringApplication.run(DemoApplication.class,args);
}
}
2.新建DemoFallback
新建项目DemoFallback作为Application Client
2.1编写pom.xml
2.2新建配置文件
新建application.yml
注意不要端口号冲突
spring:
application:
name:fallback-demo
eureka:
client:
service-url:
defaultZone:http://eurekaserver1:8761/eureka/
server:
port:8081
2.3新建配置类
新建com.bjsxt.config.RibbonConfig
此处使用@LoadBalancer方式快捷配置负载均衡
@Configuration
public class RibbonConfig{
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.4新建service及实现类
新建com.bjsxt.service.DemoService及实现类
实现类中@HystrixCommand的fallbackMethod配置的就是降级方法
myFallback方法:
参数:和test的参数是相同的
返回值:和test中调用的application-service-demo/demo控制器方法返回值相同
publci interface DemoService{
String test();
}
@Service
public class DemoServiceImpl implements DemoService{
@Autowired
private RestTempalte restTemplate;
@HystrixCommand(fallbackMethod="myFallback")
@Override
public String test(){
String result = restTemplate.postForObject("http://application-service-demo/demo",null,String.class);
System.out.println(result);
return result;
}
publci String myFallback(){
return "托底数据";
}
}
2.5新建控制器
新建com.bjsxt.controller.FallbackController
@Controller
public class FallbackController{
@Autowired
private DemoService demoService;
@RequestMapping("/demo")
@ResponseBody
public String demo(){
return demoService.test();
}
}
2.6新建启动类
@SpringBootApplication
@EnableCircuitBreaker
public class ApplicationClientApplication{
public static void main(String[] args){
SrpingApplication.run(ApplicationclientdemoApplication.class,args);
}
}
2.7访问
在浏览器输入http://localhost:8081/demo
3.测试降级
停止ApplicationServiceDemo项目.再次访问
五.熔断
当一定时间内,异常请求比例(请求超时,网路故障,服务异常等)达到阈值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快读返回托底数据,保证服务链的完整.
熔断有自动恢复机制,如:当熔断器启动后,每个5秒,尝试将新的请求发送给Application Service,如果服务可正常执行并返回结果,则关闭熔断器,服务恢复.如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状态
降级时出错了返回托底数据,而熔断器是出错后如果开启了熔断将会一定时间不在访问application service
1.属性
熔断的实现是在调用远程服务的方法上增加@HystrixCommand注解.当注解配置满足则开启或关闭熔断器
@HystrixProperty的name属性取值可以使用HystrixPropertiesManager常量,也可以直接使用字符串进行操作
注解属性描述:
CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
是否开启熔断策略.默认值为true
CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
单位时间内(默认10s内),请求超时数超出则触发熔断策略.默认值为20次请求数.通俗说明:单位时间内要判断多少次请求
EXECUTION_ISOLATION_THERAD_TIMEOUT_IN_MILLISECONDS
execution.isolation.thread.timeoutlnMilliseconds
设置单位时间,判断circuitBreaker.requestVolumeThreshold的时间单位,默认10秒.单位毫秒
CIRCUIT_BREAKER_SJEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowlnMilliseconds";
当熔断策略开启后,延迟多久尝试再次请求远程服务.默认为5秒.单位毫秒.这5秒直接执行fallback方法,不再请求远程application service;
CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
单位时间内,出现错误的请求百分比达到限制,则触发熔断策略.默认为50%.
CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
是否强制开启熔断策略.即所有请求都返回fallback托底数据.默认为false.
CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
是否强制关闭熔断策略.即所有请求一定调用远程服务.默认为false.
2.代码示例
在原有降级代码上修改@HystrixCommand如下.
关闭ApplicationServiceDemo项目,访问DemoFallback控制器,刷新5次后会发现页面加载快了,这是就开启熔断了.此时打开AplicationServiceDemo,发现依然返回托底数据.达到30秒后再次访问才能正常访问ApplicationServiceDemo中内容.
注意:单位时间内容请求数必须达到5个(无论成功还是失败)才能满足条件
@HystrixCommand(fallbackMethod="myFallback",commandProperties={
//条件一:请求数量达到3个
@HystrixProperty(name="circuitbreaker.requestVolumeThreshold",value="3"),
//判断时间,每10秒作为一个判断单位
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="10000"),
//条件二:失败了达到50%
@HystrixProperty(name="circuitbreaker.errorThresholdPercentage",value="50"),
//结果:开启熔断后,30秒不在请求远程服务
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="30000")
})
六.请求缓存
Hystrix为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理.如果再次请求的URL没有变化,那么Hystrix不会请求服务,而是直接从缓存中将结果返回.这样可以大大降低访问服务的压力
hystrix自带缓存.有两个缺点
1.是一个本地缓存.在集群情况下缓存是不能同步的
2.不支持第三方缓存容器.Redis,memcached不支持的.
所以可以利用spring cache.实现请求缓存.
在降级处理的代码基础上完成线面变化
1.添加依赖
2.修改配置文件
添加redis的配置,此处使用的是redis单机版.如果是redis集群使用spring.redis.cluster.nodes进行配置
spring:
redis:
host:192.168.232.132
3.修改启动类
在启动类上添加@EnableCaching注解
4.在HystrixService和实现类上添加
在service实现类方法上面额外在添加一个注解
@Cacheable(key="'client'",cacheNames="com.bjsxt")
5.编写控制器,进行测试
控制器代码没有变化,直接调用service即可
6.检查结果
通过在redis中keys*查看是否天啊及/删除key成功
七.请求合并
没有请求合并
Application Service负载是Application Client发送请求的总数量
请求合并
把一段时间分为内的所有请求合并为一个请求,大大降低了Application Service负载
什么情况下使用请求合并
在微服务架构中,我们将一个项目查封成很多个独立的项目,这些独立的项目通过远程调用来互相配合工作,但是在搞并发情况下,通信次数的增加会导致总得通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题.我们需要来了解Hystrix的请求合并
请求合并的缺点
设置请求合并之后,本来一个请求可能5ms就搞定了,但是现在必须再等10ms看看还有没有其他的请求一起的,这安阳一个请求的耗时就从5ms增加到5ms了,不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景
1.请求合并参数介绍
请求合并理论学习明白后,代码实现起来还是很容易的.关键就是一个@HystrixCollapser注解
2.代码实现
实现请求合并时,不仅仅需要修改Application Client代码,还需要修改Application Service的代码,因为Application Service必须支持把所有参数捆绑到一起的方式,同时还支持把多个值一起返回
2.1修改Application Service控制器
没有请求合并时的控制器
@RequestMapping("/service")
public String demo(String name){
return "姓名:"+name;
}
有请求合并时必须要支持一次传递过来多个参数和一次返回多个值.在额外天啊及一个控制器方法
参数天啊及@RequestBody是因为Application Client使用请求体传递数据
返回值也是List,List里面数据是由顺序的
@RequestMapping("/service2")
public List
System.out.println("接收到的内容:"+names);
List
for(Stirng name:names){
list.add("姓名:"+name);
}
return list;
}
2.2修改Application Client的Service
在请求缓存的代码基础上,在service及实现类中添加新方法
@HystrixCollapser进行请求合并
batchMethod:处理请求合并的方法
scope-合并请求的请求作用域.可选值有global和request
global代表所有的请求线程都可以等待可合并.常用
request代表一个请求线程中的多次远程服务调用可合并
timerDelaylnMilliseconds:等待时长,默认10毫秒
maxRequestlnBatch:最大请求合并数量
@HystrixCommand处理请求合并的方法必须有此注解
实现类中client(Stirng)方法一旦被@HystrixCollapser标记,方法就不会被执行,方法体中为空即可.直接执行batchMethod对应的方法
batchmethod方法返回值顺序金额传递进来的参数顺序有关系的
注意:
在实际测试中scope使用默认值REQUEST会出现空指针异常,请换成Global
Future
@Override
@HystrixCollapser(batchMethod="myBatchMethod",scope=com.netflix.hystrix.HystrixCollapser.Scope.Global,collapserProperties=
{@HystrixProperty(name="timerDelayInMilliseconds",value="10"),@HystrixProperty(name="maxRequestsInBatch",value="200")})
public Future
System.out.println("client方法,有请求合并时将不支持这个方法");
String result = restTempalte.getForObject("http://APPLICATION-SERVICE/service?name={1}",String.class,name);
return null;
}
@HystrixCommand
public List
System.out.println("传递过去的参数:"+name);
List
return list;
}
2.3修改consumer中控制器方法
在一个控制器中多次调用service的方法进行模拟并发操作
控制通过休眠模拟两次并发请求
f1和f2的输出语句必须放到请求后面.否则无法合并
@RequestMapping("/client")
public String client(){
try{
Future
Future
System.out.println("f1:"+f1.get());
System.out.println("f2:"+f2.get());
}catch(InterruptedException e){
e.printStackTrace();
}catch(ExecutionException e){
e.printStackTrace();
}
return "ok";
}
八.隔离
1.线程池隔离
1.1为什么使用线程池隔离
没有线程池隔离的时候可能因为某个接口的高并发导致其他接口也可用
当使用线程池隔离.不同接口有着自己独立的线程池
即使某个线程池都被占用.也不影响其他线程
1.2Hystrix的线程池隔离
Hystrix采用Bulkhead Partition舱壁隔离技术
舱壁隔离指的是讲船体内部分为多个个舱,一旦其中某几个各舱发生破损进水,水流不会在其他舱壁中流动,从而保证船舱依然具有足够的浮力和稳定性,降低沉船危险
1.3线程池隔离的优缺点
优点:
1.任何一个服务都会被隔离在自己的线程池内,即使自己的线程池资源填满也不会影响其他服务
2.当依赖的服务重新恢复时,可通过清理线程池,瞬间恢复服务的调用.但是如果是tomcat线程池被填满,在恢复就会很麻烦
3.每个都是独立线程池.一定程度上解决了高并发问题
4.由于想城池中线程个数是有限制,所以也解决了限流问题
缺点:
1.增加了cpu开销.因为不仅仅有Tomcat的线程池,还需要有Hystrix线程池.
2.每个操作都是独立的线程,就有排队,调度和上下文切换等问题
1.4代码演示
访问thread方法和访问thread2方法时发现已经不再使用同一个线程池了
@HystrixCommand(groupKey="jqk",commandKey="abc",threadPoolKey="jqk",threadPoolProperties={
@HystrixProperty(name="coreSize",value="8")
@HystrixProperty(name="maxQueueSize",value="5")
@HystrixProperty(name="keepAliverTimeMinutes",value="2")
@HystrixProperty(name="queueSizeRejectionThreshold",value="5")
})
@Override
public String thread(){
System.out.println(Thread.currentThread().getName());
return "thread1";
}
@Override
public String thread2(){
System.out.println(Thread.currentThread().getName());
return "thread2";
}
1.5参数说明
2.信号量隔离
2.1信号量是什么
java.util.concurrent.Semaphore用来控制可同时并发的线程数.通过构造方法指定内部虚拟许可的数量.每次线程执行操作时先通过acquire方法获得许可,执行完毕再通过release方法释放许可.如果无可用许可,那么acquire方法将一直阻塞,知道其他线程释放许可
如果采用信号量隔离技术,每接受一个请求,都是服务自身线程去直接调用依赖服务,信号量就相当于一道关卡,每个线程通过关卡后,信号量数量减1,当为0时不再允许线程通过,而是直接执行fallback逻辑并返回,说白了仅仅做了一个限流
2.2代码实现
@HystrixCommand(commandProperties={
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRAEGY,value="SEMAPHORE"),
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="10")
},fallbackMethod="jdk")
@Override
public String semaphore(){
System.out.println("执行了,访问service");
return "结果";
}
2.3参数说明
3.线程池隔离和信号量隔离
4.限流说明
在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求时服务器过载,导致服务不可用,另一方面是为了防止网络攻击
通过Hystrix的线程池隔离和信号量隔离控制了线程数量也就实现了限流效果
九.Hystrix-dashboard
Hystrix-dashboard能够让Actuator从json转换为界面
在包含Hystrix的项目中(DemoFallback)进行操作
1.添加依赖
2.修改启动器注解
@EnbaleHystrix
@EnableHystrixDashboard
3.修改配置文件
management:
endpoints:
web:
exposure:
include:hystrix.stream
4.在浏览器访问
先访问非界面版,等待ping成功.必须访问的是具有熔断,降级,线程池隔离,信号量隔离等具有Hystrix的方法,才会返回对应结果
地址栏输入:http://localhost:8081/actuator/hystrix.stream
访问界面版
在地址栏输入:http://localhost:8081/hystrix访问界面版.在界面的输入框中输入监控中心地址http://localhost:8081/actuator/hystrix.stream后点击"Monitor Stream";
跳转到结果页面
只有包含@HystrixCommand的方法,被执行后就会显示对应的图片
十.Feign的降级处理
当使用OpenFeign调用远程服务超时会出现500错误.如果不希望出现500可以使用OpenFeign自带的Hystrix进行降级处理
1.编写配置文件
默认情况下Feign的hystrix是不开启的,需要手动开启.其他方式个之前配置是相同的
spring:
application:
name: hystrix-provider
server:
port:8082
eureka:
client:
service-url:
defaultZone:http://eurekaserver1:8761/eureka/,http://eurekaserver2:8761/eureka/
feign:
hystrix:
enabled: true
2.添加依赖
Feign中包含Hystrix中 部分功能,所以不需要单独导入Hystrix的依赖
3.编写service
编写HystrixService内容.依然使用Feign进行声明式调用
在案例中是直接使用内部类进行实现的,也可以新建一个类进行完成
fallback属性表示降级后处理类
@EurekaClient(name="eureka-application-provider",fallback=HystrixService.HystrixServiceFallback.class)
public interface HystrixService{
@Postmapping("/show1")
Map
@Component
class HystrixServiceFallback implements HystrixService{
@Override
public Map
System.out.println("执行方法,出现服务降级,返回托底数据");
Map
map.put("a","因为Provider连接不上了,返回托底数据");
return map;
}
}
}
4.编写启动器
@SpringBootApplication
@EnableDiscoverClient
@EnableFeignClients
public class HystrixApplication{
public static void main(String[] args){
SpringApplication.run(HystrixApplication.class,args);
}
}