spring-cloud-hystrix实现微服务集群降级、熔断、限流
参考:https://windmt.com/2018/04/15/spring-cloud-4-hystrix/
https://blog.csdn.net/varyall/article/details/99722594?spm=1001.2014.3001.5502
https://www.cnblogs.com/sword-successful/p/13548233.html
注意:springcloud使用Hystrix在springcloud整合ribbon和feign的基础上
一、Hystrix实现降级和隔离
服务降级场景:
- A服务调用B服务的一些接口,现在为了防止B服务的接口出现因为服务宕机造成的问题,需要对B服务的接口增加降级处理
- B服务的接口调用方式
- 采用@FeignClient注解的方式进行调用
- 采用RestTemplate的方式
- 因此服务的降级和服务的隔离,都是建立在A服务调用B服务的接口处理上。
1、Hystrix快速引入
(1)添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
(2)启动类开启Hystrix
- @EnableCircuitBreaker 启用开启断路器熔断命令
- @EnableHystrix 开启断路器组件
@EnableHystrix
@EnableCircuitBreaker
@SpringBootApplication
public class ConsumerMovieFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieFeignApplication.class, args);
}
}
(3)常用配置
# 开启熔断机制
feign:
hystrix:
enabled: true
# ribbon配置
ribbon:
# 开启eureka与ribbon的集成
eureka:
enabled: true
# 暂不开启熔断机制
hystrix:
enabled: false
# 配置ribbon默认的超时时间
ConnectTimeout: 20000
ReadTimeout: 20000
# 是否开启重试
OkToRetryOnAllOperations: true
# 重试的时候实例切换次数
MaxAutoRetriesNextServer: 3
# 每个实例重试次数
MaxAutoRetries: 2
## hystrix相关配置
## hystrix默认会读取classpath下的config.properties文件,application会覆盖config.properties中的属性
hystrix:
threadpool:
# 指定服务的配置
user-service:
coreSize: 20
maxQueueSize: 200
queueSizeRejectionThreshold: 3
# userThreadPool是UserTimeOutCommand中配置的threadPoolKey
userThreadPool:
coreSize: 20
maxQueueSize: 20
queueSizeRejectionThreshold: 3
# 这是默认的配置
default:
coreSize: 10
maxQueueSize: 200
queueSizeRejectionThreshold: 2
command:
# 指定feign客户端中具体的方法
UserService#timeout():
execution:
isolation:
thread:
timeoutInMilliseconds: 20000
userCommandKey:
execution:
isolation:
thread:
timeoutInMilliseconds: 15000
# 这是默认的配置
default:
execution:
timeout:
enabled: true
# 隔离配置
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 15000
interruptOnTimeout: true
interruptOnFutureCancel: false
semaphore:
maxConcurrentRequests: 2
# 降级配置
fallback:
enabled: true
isolation:
semaphore:
maxConcurrentRequests: 10
# 断路器配置
circuitBreaker:
enabled: true
forceOpen: false
forceClosed: false
requestVolumeThreshold: 4
errorThresholdPercentage: 50
sleepWindowInMilliseconds: 10000
metrics:
rollingStats:
timeInMilliseconds: 5000
numBuckets: 10
rollingPercentile:
enabled: true
timeInMilliseconds: 60000
numBuckets: 6
bucketSize: 100
healthSnapshot:
intervalInMilliseconds: 500
2、常用基于FeignClient注解实现
(1)@FeignClient+回调工厂方式
- @FeignClient的fallbackFactory使用工厂方式
@FeignClient(name = "microservice-provider-user",
fallbackFactory = HelloRollBackFactory.class)
public interface UserFeignClient {
@RequestMapping(value = "/simple/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
}
@Slf4j
@Component
public class HelloRollBackFactory implements FallbackFactory<HelloService> {
@Override
public HelloService create(Throwable throwable) {
//可以抓取异常
log.info("fallback; reason was: {}", throwable.getMessage());
//返回实现的一个回调接口
return new HelloService(){
@Override
public User findById(String name) {
log.info("UserFeignClient#findById#id={}服务降级了",id);
User user = new User();
user.setId(0L);
return user;
}
}
}
}
(2)、@FeignClient+继承接口方式
@FeignClient(name = "microservice-provider-user", fallback = HystrixClientFallback.class)
public interface UserFeignClient {
@GetMapping("/simple/{id}")
public User findById(@PathVariable("id") Long id);
}
@Slf4j
@Component
public class HystrixClientFallback implements UserFeignClient {
//回调类,返回一个默认的user,防止调用出现空指针的问题
@Override
public User findById(Long id) {
log.info("UserFeignClient#findById#id={}服务降级了",id);
User user = new User();
user.setId(0L);
return user;
}
}
(3)通过@HystrixCommand+RestTemplate
https://www.cnblogs.com/zou-rong/p/12589390.html
- 实现接口隔离(线程隔离和信号量隔离)
- 实现调用的负载均衡
- 实现调用过程中降级
#设置线程隔离超时时间为5S
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
//在配置类中引入RestTemplate并使用Ribbon的@LoadBalanced注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
//==========================线程隔离,缺点比较费资源=========================
@GetMapping("/user/{id}")
@HystrixCommand(groupKey="UserGroup",
commandKey = "GetUserByIdCommand",
fallbackMethod = "findByIdFallback",
commandProperties = {
//超时时间,单位毫秒。超时进fallback
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100"),
//判断熔断的最少请求数,默认是10;只有在一定时间内请求数量达到该值,才会进行成功率的计算
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
//熔断的阈值默认值50,表示在一定时间内有50%的请求处理失败,会触发熔断
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "10"),
},
//线程池参数配置
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
})
public User findById(@PathVariable Long id) {
//通过restTemplate进行调用
return this.restTemplate
.getForObject("http://microservice-provider-user/simple/" + id,
User.class);
}
public User findByIdFallback(Long id) {
log.info("UserFeignClient#findById#id={}服务降级了",id);
User user = new User();
user.setId(0L);
return user;
}
//========================信号量隔离,建议使用========================
//注意下面这一个注解里面的内容,这样配置更加详细一点
@GetMapping("/user2/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallback",
commandProperties = @HystrixProperty(
name = "execution.isolation.strategy",
value = "SEMAPHORE"))
public User findById(@PathVariable Long id) {
return this.restTemplate
.getForObject("http://microservice-provider-user/simple/" + id,
User.class);
}
//服务降级出口
public User findByIdFallback(Long id) {
log.info("UserFeignClient#findById#id={}服务降级了",id);
User user = new User();
user.setId(0L);
return user;
}
}
//=========================可以配置统一的服务降级的处理==============//
@RestController
@RequestMapping("/consumer")
@Slf4j
@DefaultProperties(defaultFallback = "getUserByIdFallBack")//指定默认降级函数
public class ConsumerController {
@Autowired
private RestTemplate template;
@GetMapping("/{id}")
@HystrixCommand //启用熔断降级
public String getUserById(@PathVariable("id")Long id){
String url="http://user-service/user/"+id;
long start=System.currentTimeMillis();
String user = template.getForObject(url, String.class);
long end=System.currentTimeMillis();
log.debug("调用时长:{}",end-start);
return user;
}
//注意此时的返回值没有限制,不能写参数,因为这是当前类通用的降级方法
public String getUserByIdFallBack(){
return "很抱歉,服务器正忙,请稍后再试。";
}
}
(4)HystrixCommand注解源码
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
//配置全局唯一标识服务的名称
String groupKey() default "";
//配置全局唯一标识服务分组的名称,比如,库存系统、订单系统、系统用户就是一个单独的服务分组
String commandKey() default "";
//对线程池进行设定,细粒度的配置,相当于对单个服务的线程池信息进行设置,也可多个服务设置同一个threadPoolKey构成线程组
String threadPoolKey() default "";
//执行快速失败的回调函数,@HystrixCommand修饰的函数必须和这个回调函数定义在同一个类中,因为定义在了同一个类中,所以fackback method可以是public/private均可
String fallbackMethod() default "";
//配置该命令的一些参数,如executionIsolationStrategy配置执行隔离策略,默认是使用线程隔离
HystrixProperty[] commandProperties() default {};
//线程池相关参数设置,具体可以设置哪些参数请见:com.netflix.hystrix.HystrixThreadPoolProperties
HystrixProperty[] threadPoolProperties() default {};
//调用服务时,除了HystrixBadRequestException之外,其他@HystrixCommand修饰的函数抛出的异常均会被Hystrix认为命令执行失败而触发服务降级的处理逻辑(调用fallbackMethod指定的回调函数),所以当需要在命令执行中抛出不触发降级的异常时来使用它,通过这个参数指定,哪些异常抛出时不触发降级(不去调用fallbackMethod),而是将异常向上抛出
Class<? extends Throwable>[] ignoreExceptions() default {};
//定义hystrix observable command的模式
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
//任何不可忽略的异常都包含在HystrixRuntimeException中
HystrixException[] raiseHystrixExceptions() default {};
//默认的回调函数,该函数的函数体不能有入参,返回值类型与@HystrixCommand修饰的函数体的返回值一致。如果指定了fallbackMethod,则fallbackMethod优先级更高
String defaultFallback() default "";
}
(5)通过OrderHystrixCommand编码实现
//线程池隔离
@Slf4j
public class OrderHystrixCommand extends HystrixCommand<String> {
//这种方式可以调用Feign的接口
OrderService orderService;
//设置线程池隔离
public OrderHystrixCommand(OrderService orderService) {
super(Setter
//服务分组
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orderGroup"))
//命令分组
.andCommandKey(HystrixCommandKey.Factory.asKey("orderCommandKey"))
//线程池配置
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("OrderPool"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(10).withKeepAliveTimeMinutes(5)
.withMaxQueueSize(10).withQueueSizeRejectionThreshold(10000))
//断路器配置
.andCommandPropertiesDefaults(
//设置线程池隔离
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(
HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
);
//设置Service
this.orderService = orderService;
}
// 方式一:通过线程池的run方法执行getOrderList 最后得到结果
@Override
protected List run() throws Exception {
return orderService.getOrderList();
}
/*** 方式二:采用Rest调用的方式 直接采用父类的实现
public OrderHystrixCommand() {
super(HystrixCommandGroupKey.Factory.asKey("orderGroup"));
}
//这里也可以使用RestTemplate进行调用
@Override
protected String run() throws Exception {
LOGGER.info("start query timeout endpoint");
URL url = new URL("http://localhost:8002/user/timeout");
byte[] result = new byte[1024];
url.openStream().read(result);
return new String(result);
}
@Override
protected String getFallback() {
// 执行超时、出错或者开启熔断之后,使用这个方法返回
// 这种策略称为服务降级
// 这里可以是一个固定返回值,查询缓存等
return "服务降级,暂时不可用";
}
***/
}
//测试采用命令方式调用
public class UnitTest {
@Test
public void testGetOrder(){
Future<List> future =new GetOrderCommand("hystrix-order").queue();
}
}
//信号量隔离
public class QueryUserIdCommand extends HystrixCommand<Integer> {
private final static Logger logger = LoggerFactory.getLogger(QueryUserIdCommand.class);
//调用Feign的接口
private UserServiceProvider userServiceProvider;
public QueryUserAgeCommand(UserServiceProvider userServiceProvider) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("userService"))
.andCommandKey(HystrixCommandKey.Factory.asKey("queryByUserId"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
//至少有10个请求,熔断器才会开始进行错误率计算
.withCircuitBreakerRequestVolumeThreshold(10)
//熔断器中断请求,5秒后会进入一个半打开状态,放开部分请求去进行重试
.withCircuitBreakerSleepWindowInMilliseconds(5000)
错误率达到50%就开启熔断保护
.withCircuitBreakerErrorThresholdPercentage(50)
//设置为信号量隔离
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
.withExecutionTimeoutEnabled(true))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties
.Setter().withCoreSize(10)));
this.userServiceProvider = userServiceProvider;
}
@Override
protected Integer run() {
return userServiceProvider.queryByUserId();
}
@Override
protected Integer getFallback() {
return -1;
}
}
//异步的方式调用
Future<List> future =new QueryUserIdCommand("hystrix-order").queue();
//同步的方式调用
new UserExceptionCommand().execute();
/**响应式**/
//立即执行
Observable<String> ho = new UserExceptionCommand().observe();
//只有当被订阅的时候才会开始执行
Observable<String> co = new UserExceptionCommand().toObservable();
//阻塞的
ho.toBlockingObservable().single();
//非阻塞的
ho.subscribe(new Observer<String>() {
@Override
public void onCompleted() {
// nothing needed here
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(String v) {
System.out.println("onNext: " + v);
}
});
// 非阻塞的
ho.subscribe(new Action1<String>() {
@Override
public void call(String v) {
System.out.println("onNext: " + v);
}
});
二、Hystrix实现熔断
@RestController
public class DeptController
{
@Autowired
private DeptService service;
@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(@RequestBody Dept dept)
{
return service.add(dept);
}
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) //一旦调用服务方法失败抛出“错误信息后,胡自动范湖用@HystrixCommand标注好的fallbackMethod调用类中的指定方法”
@HystrixCommand(fallbackMethod = "processHystrixGet")
public Dept get(@PathVariable("id") Long id)
{
Dept dept = service.get(id);
//模拟异常
if(null == dept)
{
throw new RuntimeException("该id的信息不存在! id=" + id);
}
return service.get(id);
}
public Dept processHystrixGet(@PathVariable("id") Long id)
{
return new Dept().setDname("id没有对应的信息, null -- @HystrixCommand, id=" + id)
.setDb_source("no this database in MySQL.");
}
}
三、Hystrix实现限流
https://blog.csdn.net/qq_40990836/article/details/108500728
四、请求缓存功能@CacheResult
- @CacheResult 该注解用来标记请求命令返回的结果应该被缓存,它必须与@HystrixCommand注解结合使用 cacheKeyMethod
- @CacheRemove 该注解用来让请求命令的缓存失效,失效的缓存根据定义Key决定 commandKey, cacheKeyMethod
- @CacheKey 该注解用来在请求命令的参数上标记,使其作为缓存的Key值,如果没有标注则会使用所有参数。如果同事还是使用了@CacheResult和@CacheRemove注解的cacheKeyMethod方法指定缓存Key的生成,那么该注解将不会起作用 value
附录、使用断路器监控
1、创建项目springcloud-hystrix-dashboard
(1)、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(2)、新建项目 hstrix-dashboard
hstrix-dashboard支持的三种不同的集群监控
- 默认的集群监控:
- 通过 URL:http://turbine-hostname:port/turbine.stream 开启,实现对默认集群的监控。
- 指定的集群监控:
- 通过 URL:http://turbine-hostname:port/turbine.stream?cluster=[clusterName] 开启,实现对 clusterName 集群的监控。
- 单体应用的监控:
- 通过 URL:http://hystrix-app:port/hystrix.stream 开启 ,实现对具体某个服务实例的监控。
- 现在这里的 URL 应该为 http://hystrix-app:port/actuator/hystrix.stream,
- Actuator 2.x 以后 endpoints 全部在 /actuator 下,可以通过 management.endpoints.web.base-path 修改)
- 前两者都对集群的监控,需要整合 Turbine 才能实现。这一部分我们先实现对单体应用的监控,这里的单体应用就用我们之前使用 Feign 和 Hystrix 实现的服务消费者
(3)、在已有项目上添加依赖
<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-hystrix</artifactId>
</dependency>
(4)、启动类
- @EnableHystrixDashboard 开启DashBoard面板
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
(5)、配置文件
spring:
application:
name: hystrix-dashboard
server:
port: 11000
(6)、使用Hystrix的客户端,为服务实例添加 endpoint
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、使用Dashboard面板实现Hystrix监控
1、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3、修改配置
server:
port: 8030
4、启动类
- @EnableDiscoveryClient 开启服务注册和发现
- @EnableFeignClients 开启远程调用
- @EnableHystrixDashboard 开启DashBoard面板
- @EnableCircuitBreaker 开启断路器
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrixDashboard
@EnableCircuitBreaker
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
测试
打开http://localhost:9001/hystrix
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-93MLhG3Y-1655862891397)(…/…/typora-img/hystrix-dashboard-1.jpg)]
访问:http://localhost:9001/hystrix.stream
看到监控图形化显示
http://favorites.ren/assets/images/2017/springcloud/hystrix-dashboard-2.jpg
使用Turbine面板实现监控
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
添加配置
server:
port: 8031
spring:
application:
name: microservice-hystrix-turbine
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
instance:
prefer-ip-address: true
turbine:
aggregator:
clusterConfig: default
appConfig: microservice-consumer-movie-ribbon-with-hystrix,microservice-consumer-movie-feign-with-hystrix
clusterNameExpression: "'default'"
- turbine.appConfig :配置Eureka中的serviceId列表,表明监控哪些服务
- turbine.aggregator.clusterConfig :指定聚合哪些集群,多个使用”,”分割,默认为default。
- turbine.clusterNameExpression : 1. clusterNameExpression指定集群名称,默认表达式appName