在微服务架构中,⼀个应⽤可能会有多个微服务组成,微服务之间的数据交互经过远程过程调⽤完成。java
这就带来⼀个问题,假设微服务A调⽤微服务B和微服务C,微服务B和微服务C⼜调⽤其它的微服务,这就是所谓的“扇出”。若是扇出的链路上某个微服务的调⽤响应时间过⻓或者不可⽤,对微服务A的调⽤就会占⽤愈来愈多的系统资源,进⽽引发系统崩溃,所谓的“雪崩效应”。nginx
如图中所示,最下游简历微服务响应时间过⻓,⼤量请求阻塞,⼤量线程不会释放,会致使服务器资源耗尽,最终致使上游服务甚⾄整个系统瘫痪。git
扇⼊:表明着该微服务被调⽤的次数,扇⼊⼤,说明该模块复⽤性好web
扇出:该微服务调⽤其余微服务的个数,扇出⼤,说明业务逻辑复杂spring
扇⼊⼤是⼀个好事,扇出⼤不⼀定是好事数据库
从系统的可靠性与可用性出发,常见的解决雪崩效应的三种技术方案:服务熔断、服务降级、服务限流。浏览器
服务熔断体如今一个“断”字上,通俗来说,就是当检测到某个扇出链路的微服务不可用或者响应时间太长时,熔断该服务的调用,进行服务降级,快速返回错误的响应信息。当该服务正常以后,再恢复该服务的调用链路。
服务熔断与服务降级是一般在一块儿使用的,好比Hystrix。
服务消费者工程引入jar坐标:springboot
<!--熔断器Hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
服务消费者启动类上添加@EnableCircuitBreaker开启熔断注解。服务器
在服务消费者的controller中调用代码方法上添加注解@HystrixProperty,里面配置超时时间属性,设置为2秒。多线程
@GetMapping("/checkStateTimeout/{userId}") @HystrixCommand( commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "2000") } ) public Integer findResumeOpenState(@PathVariable Long userId) { String url="http://lagou-service-resume/resume/openstate/"+userId; Integer forObject =restTemplate.getForObject(url,Integer.class); System.out.println("======>>>从eureka server获取服务提供者实例:"+url); return forObject; }
为了测试,咱们在8080端口的服务提供者工程里面的方法中添加上线程休眠语句,模拟请求超时:
@GetMapping("/openstate/{userId}") public Integer findDefaultResumeState(@PathVariable Long userId) { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } return port; }
效果测试:
上面咱们说的是服务熔断,当服务提供者出现异常或者请求超时时,返回给调用方的是异常信息。咱们不想显示异常页面,这样对用户太不友好,因此咱们返回一个预设的默认值(兜底数据)给服务的调用者,这就是服务降级。
服务降级,通俗讲就是总体资源不够⽤了,先将⼀些不关紧的服务停掉(调⽤个人时候,给你返回⼀个预留的值,也叫作兜底数据),待渡过难关⾼峰过去,再把那些服务打开。服务降级⼀般是从总体考虑,就是当某个服务熔断以后,服务器将再也不被调⽤,此刻客户端能够⾃⼰准备⼀个本地的fallback回调,返回⼀个缺省值,这样作,虽然服务⽔平降低,但好⽍可⽤,⽐直接挂掉
要强。
在上一步服务熔断的代码基础上,咱们添加服务降级的代码。
咱们定义一个预设方法,与原方法的入参与返回结构类型保持一致,在@HystrixProperty注解里配置fallbackMethod参数为预设的方法名便可。
以下,咱们在预设方法中返回默认值-1:
@GetMapping("/checkStateTimeoutFallBack/{userId}") @HystrixCommand( commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "2000") }, fallbackMethod = "myFallback" ) public Integer findResumeOpenStateFallBack(@PathVariable Long userId) { String url="http://lagou-service-resume/resume/openstate/"+userId; Integer forObject =restTemplate.getForObject(url,Integer.class); System.out.println("======>>>从eureka server获取服务提供者实例:"+url); return forObject; } //预设方法 public Integer myFallback(Long userId){ return -1; //返回预设值(兜底数据) }
效果以下:
服务降级是当服务出问题或者影响到核⼼流程的性能时,暂时将服务屏蔽掉,待⾼峰或者问题解决后再打开;可是有些场景并不能⽤服务降级来解决,⽐如秒杀业务这样的核⼼功能,这个时候能够结合服务限流来限制这些场景的并发/请求量
限流措施也不少,⽐如:
Hystrix默认是有一个线程池,来统一维护须要熔断及降级的方法,全部添加了@HystrixCommand注解的方法共用一个线程池。
若是不进⾏任何设置,全部熔断⽅法使⽤⼀个Hystrix线程池(默认10个线程),那么这样的话会致使问题,这个问题并非扇出链路微服务不可⽤致使的,⽽是咱们的线程机制致使的,若是⽅法A的请求把10个线程都⽤了,⽅法2请求处理的时候压根都无法去访问B,由于没有线程可⽤,并非B服务不可⽤。
为了不问题服务请求过多致使正常服务⽆法访问, Hystrix 不是采⽤增长线程数,⽽是单独的为每⼀个控制⽅法建立⼀个线程池的⽅式,这种模式叫作“舱壁模式",也是线程隔离的⼿段。
舱壁模式在现实生活中的例子:
好比一艘船,里面混乱的放着牛奶、面包、药品等各类物品,若是这艘船某一个地方漏水,那么这艘船里的全部物品都会受到影响。(相似于Hystrix默认的全局一个线程池管理全部方法)
怎么解决这个问题呢?
给船舱里面加上隔板,好比把面包和药品完全隔离开,这样当药品这边漏水了以后,也不会影响到面包。(这就是Hystrix的舱壁模式)
咱们能够先看一下Hystrix的默认的全部方法共用一个线程池的效果:
在PostMan里面批量请求加了@HystrixCommand的两个方法,循环请求二十次,模拟多线程:
在Windows命令行中经过jps及jstack命令查看线程信息:(Linux系统中将findstr命令改成grep命令便可)
能够看到一共有10个线程。
接下来咱们运用舱壁模式配置线程池参数,部分代码以下(完整代码见文末源码):
@GetMapping("/checkStateTimeout/{userId}") @HystrixCommand( //线程池 的标识,要保持惟一,否则与其它方法相同名的线程池就共用 threadPoolKey = "checkStateTimeout", //线程数细节配置 threadPoolProperties = { @HystrixProperty(name="coreSize",value = "1"), //线程数(同时运行的线程数) @HystrixProperty(name="maxQueueSize",value = "20") //等待队列长度 }, commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "2000") } ) public Integer checkStateTimeout(@PathVariable Long userId) { String url="http://lagou-service-resume/resume/openstate/"+userId; Integer forObject =restTemplate.getForObject(url,Integer.class); System.out.println("======>>>从eureka server获取服务提供者实例:"+url); return forObject; } @GetMapping("/checkStateTimeoutFallBack/{userId}") @HystrixCommand( //线程池 的标识,要保持惟一,否则与其它方法相同名的线程池就共用 threadPoolKey = "findResumeOpenStateFallBack", //线程数细节配置 threadPoolProperties = { @HystrixProperty(name="coreSize",value = "2"), //线程数(同时运行的线程数) @HystrixProperty(name="maxQueueSize",value = "20") //等待队列长度 }, commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "2000") }, fallbackMethod = "myFallback" ) public Integer findResumeOpenStateFallBack(@PathVariable Long userId) { String url="http://lagou-service-resume/resume/openstate/"+userId; Integer forObject =restTemplate.getForObject(url,Integer.class); System.out.println("======>>>从eureka server获取服务提供者实例:"+url); return forObject; } //预设方法 public Integer myFallback(Long userId){ return -1; //返回预设值(兜底数据) }
咱们在代码中给checkStateTimeout方法配置线程数为1,给checkStateTimeoutFallBack方法配置线程数为2,而后在postman中批量运行线程,
结束以后查看线程信息以下:
能够看到名为checkStateTimeout的线程只有1个,名为checkStateTimeoutFallBack的线程有2个,与咱们程序中设置一致。
在微服务调用链路中当某个服务某一次出现问题时,Hystrix并非直接一刀切的切断有问题的链路,今后再也不启用,而是经过一段时间间隔的阈值来进行检测判断,具体的流程以下图:
工做流程:
当调⽤出现问题时,开启⼀个时间窗(10s)
在这个时间窗内,统计调⽤次数是否达到最⼩请求数(本身设定)
<1>若是没有达到,则重置统计信息,回到第1步
<2>若是达到了,则统计失败的请求数占全部请求数的百分⽐,是否达到阈值?
①若是达到,则跳闸(再也不请求对应服务)
②若是没有达到,则重置统计信息,回到第1步
若是跳闸,则会开启⼀个活动窗⼝(默认5s),每隔5s, Hystrix会让⼀个请求经过,到达那个问题服务,看是否调⽤成功,若是成功,重置断路器回到第1步,若是失败,回到第3步
springboot中暴露健康检查等断点接⼝,在服务消费者一方的application.yml中配置以下
management: endpoints: web: exposure: include: "*" # 暴露健康接⼝的细节 endpoint: health: show-details: always
查看健康状态:
接下来咱们配置一些参数,
实现“8秒钟内,请求次数达到2个,而且失败率在50%以上,就跳闸,跳闸后活动窗⼝设置为3s”,经常使用的参数以下:
//8秒钟内,请求次数达到2个,而且失败率在50%以上,就跳闸 //跳闸后活动窗⼝设置为3s @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"), @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
在postman中批量执行调用checkStateTimeoutFallBack方法,在健康状态监测中能够看到hystrix的状态变化,由熔断到恢复:
连续调用几回后熔断:
后来变为正常:
前面经过/actuator/health接口查看到的信息是一个hystrix的总体的运行状态,要想看具体的细节,好比每一个加了@HystrixCommand注解的方法的hystrix的运行信息的话,咱们应该寻找另外的方式。Hystrix Dashboard断路监控仪表盘就是干这个的。
/actuator/hystrix.stream接⼝能够获取到监控的⽂字信息,可是不直观,Hystrix官⽅还提供了基于图形化的DashBoard(仪表板)监控平台,咱们能够经过新建单独的DashBoard服务经过图形化的页面来直观查看hystrix的详细信息。
在父工程下新建一个监控工程,导入依赖:
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!--hystrix 仪表盘--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
启动类上加上@EnableHystrixDashboard注解。
在被监测的微服务中注册监控servlet,咱们在服务启动类中注入bean:
@Bean public ServletRegistrationBean getServlet(){ HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/actuator/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }
在被监控微服务发布以后,能够直接访问监控servlet:
能够看到数据,可是并不直观。
发布监控微服务,访问http://localhost:9000/hystrix以下:
进入监控详情页面后以下:
咱们能够实时的监控方法的状态。
前面咱们是针对一个微服务实例的Hystrix进行数据查询分析, 在微服务架构下,⼀个微服务的实例每每是多个(集群化)
⽐如⾃动投递微服务咱们能够部署三台:
实例1(hystrix) ip1:port1/actuator/hystrix.stream
实例2(hystrix) ip2:port2/actuator/hystrix.stream
实例3(hystrix) ip3:port3/actuator/hystrix.stream
按照已有的⽅法,咱们就能够结合dashboard仪表盘每次输⼊⼀个监控数据流url,进去查看。可是这样比较麻烦,咱们可否有一个聚合的工具把同一个服务的全部实例的Hystrix状态信息聚合起来呢?
答案是:Hystrix Turbine聚合(聚合各个实例上的hystrix监控数据)
Turbine的原理图以下:
创建一个hystrix-turbine微服务,引入依赖:
<dependencies> <!--hystrix turbine聚合监控--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-turbine</artifactId> </dependency> <!-- 引⼊eureka客户端的两个缘由 一、微服务架构下的服务都尽可能注册到服务中⼼去,便于统⼀管理 二、后续在当前turbine项⽬中咱们须要配置turbine聚合的服务,⽐如,咱们但愿聚合 lagou-service-autodeliver这个服务的各个实例的hystrix数据流,那随后 咱们就须要在application.yml⽂件中配置这个服务名,那么turbine获取服务下具 体实例的数据流的 时候须要ip和端⼝等实例信息,那么怎么根据服务名称获取到这些信息呢? 固然能够从eureka服务注册中⼼获取 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
application.yml中进行turbine的相关配置:
#turbine配置 turbine: #appCofing配置须要聚合的服务名称,⽐如这⾥聚合⾃动投递微服务的hystrix监控数据 #若是要聚合多个微服务的监控数据,那么可使⽤英⽂逗号拼接,⽐如 a,b,c appConfig: lagou-service-autodeliver clusterNameExpression: "'default'" # 集群默认名称
在启动类上添加@EnableTurbine开启仪表盘以及Turbine聚合。
依次启动eureka注册中心、服务提供者、服务消费者、HystrixDashboard服务、HystrixTuibin服务。
完毕后在浏览器输入http://localhost:9001/turbine.stream,在postman中请求8090和8091两个服务消费者实例,能够看到turbine的监控信息:
访问http://localhost:9000/hystrix,将tuibine.stream地址输入:
postman中批量访问两个服务消费者实例:
能够观察多台主机里的方法聚合的状态信息:
hystrix-demo源码地址: hystrix-demo源码地址
欢迎访问个人博客:https://www.liuyj.top