在微服务架构场景中,通常会有很多层的服务互相调用。如果某一个底层服务出现问题,由于网络原因或者其他一些因素,有可能出现服务不可用的情况,当某个服务出现问题时,其他服务如果继续调用这个服务,就有可能出现线程阻塞,但如果同时有大量的请求,就会造成线程资源被用完,这样就可能会导致系统服务瘫痪,由于服务间会相互调用,很容易造成服务连环雪崩效应
,从而故障会被向上传播响应给用户。我们需要一种机制,当底层服务不可用时,可以阻断故障的传播。这就是断路器的作用。他是系统服务稳定性的最后一重保障,是在微服务架构场景中必不可少的一部分。
在Hystrix
是SpringCloud
中的一个断路器组件。Hystrix
是由国外知名的视频网站Netflix
所开源,被SpringCloud
所整合使用。他的功能是,当对某个服务的调用在一定的时间内(默认10s
,由metrics.rollingStats.timeInMilliseconds
配置),有超过一定次数(默认20次
,由circuitBreaker.requestVolumeThreshold
参数配置)并且失败率超过一定值(默认50%
,由circuitBreaker.errorThresholdPercentage
配置),该服务的断路器会打开,实现服务降级, 常见的是返回一个由开发者设定的fallback回调函数,响应友好提示给客户端。 例如:某些知名的抢票网站,在高并发场景下,如果系统人数较多,会实现服务降级、熔断、隔离,响应一个友好提示给用户。
某某某,您好,当前系统人数较多,请稍后重试,给您带来的不便,敬请谅解!!!
在上面常见的微服务架构场景中,断路器的出现很大程度上是应用于解决微服务雪崩效应,起到整个微服务服务链路的保护作用,解决服务雪崩效应可基于Hystrix的服务降级+服务熔断,服务隔离实现。
服务降级: 在高并发场景下,请求过多,为了防止用户一直等待响应结果,微服务链路容易造成连环雪崩效应,这时候会使用服务降级方式,返回一个友好提示给客户端,具体做法是调用fallBack()
本地方法,不再去处理业务请求,目的是为了用户体验的同时,保护了整个微服务链路。
服务熔断: 服务熔断机制的目的是为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,然后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级一起使用。 服务熔断原理跟家庭中的保险丝是一样。
服务隔离: 分为线程池隔离和信号量隔离
1.线程池隔离:
每个服务接口,都有自己独立的线程池,每个线程池互不影响,Hystrix中默认开启线程池隔离。
2.信号量隔离:
使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返 回成功后计数器-1。
3.服务限流:
服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶。计数器也可以进行粗暴限流实现。
以上几点概念摘自于: SpringCloud学习笔记 -----Hystrix 服务降级、熔断机制、服务隔离
Hystrix
有两种使用方式:分别采用注解形式( @HystrixCommand) 和 接口形式(基于Feign)
启动类加入@EnableHystrix
注解,表示启用Hystrix
断路器
继前面章节基于 Spring Cloud系列教程(五) - 服务消费者Feign+Ribbon(Finchley版本) 的源码,继续使用服务提供者springcloud-app-member
工程,端口为8762
和注册中心springcloud-eureka-server
工程,端口为9000
,消费者消费服务springcloud-feign-client
工程,端口为8787
。 然后修改springcloud-feign-client
工程的代码,其他两个工程不变,springcloud-feign-client
工程需要加入Hystrix
断路器组件。分别采用注解形式和接口形式配置Hystrix
断路器服务保护。,下面先贴一下项目完整结构图:
Hystrix
依赖 <!-- springcloud整合hystrix服务保护组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--SpringBoot依赖版本-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.thinkingcao</groupId>
<artifactId>springcloud-feign-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud-feign-client</name>
<description>SpringCloud整合Feign客户端组件消费服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<!--声明管理SpringCloud版本依赖信息-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBootWeb组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springcloud整合eureka客户端组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- springcloud整合Feign客户端组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- springcloud整合hystrix服务保护组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--lombok代码简化工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
##=========服务消费者-订单服务配置========
#服务端口号
server:
port: 8787
#定义服务名称(服务注册到eureka名称)
spring:
application:
name: app-thinkingcao-feign
#在此指定服务注册中心地址,将当前订单服务注册到eureka注册中心上
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9000/eureka
#启动注册操作,该值默认为true。若设置为fasle将不会启动注册操作。是否需要去检索寻找服务,默认是true
register-with-eureka: true
#是否需要从eureka上获取注册信息
fetch-registry: true
#表示eureka client间隔多久去拉取服务器注册信息,默认为30秒
registry-fetch-interval-seconds: 10
##心跳检测与续约时间(测试环境和本地开发环境将值设置小一点,保证服务关闭后,注册中心能够及时踢出)
instance:
#客户端向Eureka注册中心发送心跳的时间间隔,单位为秒(默认为30s),(客户端会按照此规则向Eureka服务端发送心跳检测包)
lease-renewal-interval-in-seconds: 2
#Eureka注册中心在收到客户端最后一次心跳之后等待的时间上限,单位为秒(默认为90s),超过时间则剔除(客户端会按照此规则向Eureka服务端发送心跳检测包)
lease-expiration-duration-in-seconds: 2
##启用hystrix
feign:
hystrix:
#打开Feign自带的断路器
enabled: true
@EnableHystrix : 表示启用Hystrix断路器组件相关功能
package com.thinkingcao.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class AppFeignClient {
public static void main(String[] args) {
SpringApplication.run(AppFeignClient.class, args);
}
}
语法: @HystrixCommand(fallbackMethod= "hystrixMethod")
,fallbackMetho
为服务降级的回调方法,同时@HystrixCommand
默认开启了服务的熔断、降级、隔离(线程池隔离);
分别新建两个方法:
- 没有使用断路器解决服务雪崩效应方法(getOrderToMemberInfoNoHystrix),因为这个是基于Feign客户端调用服务提供者(springcloud-app-member),当停掉springcloud-app-member服务时,直接导致服务不可用;
- 使用断路器解决服务雪崩效应方法(getOrderToMemberInfoForHystrix),因为这个是基于Feign客户端调用服务提供者(springcloud-app-member),当停掉springcloud-app-member服务时,由于采用了直接Hystrix断路器实现服务降级、熔断处理,不会导致服务不可用,而是执行服务降级方法,返回友好提示给前端用户;
package com.thinkingcao.api.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.thinkingcao.api.Feign.IFeignClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @desc:
* @author: cao_wencao
* @date: 2020-02-22 12:30
*/
@RestController
public class ApiOrderController {
@Autowired(required=false)
private IFeignClientService feignClientService;
//没有使用断路器解决服务雪崩效应方法
@RequestMapping(value = "/getOrderToMemberInfoNoHystrix",method = RequestMethod.GET)
public String getOrderToMemberInfoNoHystrix(@RequestParam("userName") String userName){
String memberInfo = feignClientService.getOrderToMemberInfo(userName);
return memberInfo;
}
//使用断路器解决服务雪崩效应方法
@HystrixCommand(fallbackMethod= "excuteHystrixHandle")
@RequestMapping(value = "/getOrderToMemberInfoForHystrix",method = RequestMethod.GET)
public String getOrderToMemberInfoForHystrix(@RequestParam("userName") String userName){
String memberInfo = feignClientService.getOrderToMemberInfo(userName);
return memberInfo;
}
//服务进入保护时,回调方法
public String excuteHystrixHandle(String userName) {
return userName+"您好,当前系统人数较多,请稍后重试,给您带来的不便,敬请谅解!!!";
}
}
在微服务项目架构中,以及日常开发中,使用基于接口形式较多,如果以接口形式配置Hystrix,可基于Feign客户端配置;
- 服务提供方加一个服务降级的回调方法excuteHystrixHandler
package com.thinkingcao.api.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @desc: 会员服务接口
* @author: cao_wencao
* @date: 2020-02-19 18:12
*/
@RestController
public class MemberController {
@Value("${server.port}")
private String serverPort;
//会员服务接口
@RequestMapping("/getMember")
private String getMember(@RequestParam("userName") String userName) {
return "我是会员服务,订单服务调用会员服务成功啦, 端口号为: " + serverPort +"——用户名: "+ userName;
}
//服务提供方-服务降级方法
@RequestMapping("/excuteHystrixHandler")
private String excuteHystrixHandler(){
return "您好,当前系统人数较多,请稍后重试,给您带来的不便,敬请谅解!!!";
}
}
- 服务消费方加一个Feign接口,声明执行Hystrix降级方法
package com.thinkingcao.api.Feign;
import com.thinkingcao.api.service.impl.GetHystrixResultImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @desc: 接口形式配置Hystrix,fallback服务出现熔断时,需要降级到此回调方法,响应给用户
* @author: cao_wencao
* @date: 2020-02-25 12:53
*/
@FeignClient(value = "app-thinkingcao-member",fallback = GetHystrixResultImpl.class)
public interface IExcuteHystrixHandler {
//获取Hystrix降级熔断执行回调方法的结果
@RequestMapping("/excuteHystrixHandler")
public String getHystrixInfo();
}
Feign接口实现类:
package com.thinkingcao.api.service.impl;
import com.thinkingcao.api.Feign.IExcuteHystrixHandler;
import org.springframework.stereotype.Component;
/**
* @desc: GetHystrixResultImpl注入到Spring容器,交给Spring管理
* @author: cao_wencao
* @date: 2020-02-25 12:59
*/
@Component
public class GetHystrixResultImpl implements IExcuteHystrixHandler {
@Override
public String getHystrixInfo() {
return "您好,当前系统人数较多,请稍后重试,给您带来的不便,敬请谅解!!!";
}
}
Eureka
注册中心AppEurekaServer
AppMemberProvider
我是会员服务,订单服务调用会员服务成功啦, 端口号为: 8762——用户名: “Thinkingcao”
“Thinkingcao”您好,当前系统人数较多,请稍后重试,给您带来的不便,敬请谅解!!!
1. 项目源码: https://github.com/Thinkingcao/SpringCloudLearning/tree/master/springcloud-hystrix
下一篇: Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)