Hystrix服务降级

为了避免微服务中因为启动某一个服务宕机,而导致“雪崩”,使整个应用阻塞;

熔断器Hystrix使用了线程隔离和服务降级的方式,提高整体应用的容错能力。

我使用的SpringCloud版本是Hoxton.SR3

线程隔离:Hystrix使用自己的线程池,和主应用服务器线程隔离开来。每个服务都使用独立的线程池。

服务降级:优先保证核心服务可用,非核心服务不可用或弱可用。

     当某个服务的线程池已满或者响应时间过长,就会响应一个友好提示,而不是无限阻塞或者直接报错。

     Hystrix实现了弹性容错,当情况好转之后可以自动重连。

服务消费方:

Eureka-client包已经依赖导入了Hystrix,可以直接使用,无需再导包才怪咧

第一步:导包

 

 上面那个是Eureka-client包中的,不一样。必须导入以下依赖才可以使用@HystrixCommand注解

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

第二步:启动类加注解

  

package com.company;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

//@EnableDiscoveryClient //启用Eureka客户端
//@SpringBootApplication
//@EnableCircuitBreaker //启用Hystrix熔断功能
@SpringCloudApplication //替代以上三个注解
public class ConsumerApplication {

public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

第三步:

@GetMapping("/{id}")
//@HystrixCommand(defaultFallback = "getUserByIdFallBack")这里千万不要写错了,如果写成了defaultFallback则会出现异常说找不到降级方法,其实是默认降级不能有参数,单个方法使用的应该是fallbackMethod
@HystrixCommand(fallbackMethod= "getUserByIdFallBack")
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(Long id){
return "很抱歉,服务器正忙,请稍后再试。";
}

使用配置当前类降级函数,配置成功

package com.company.controller;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@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 "很抱歉,服务器正忙,请稍后再试。";
}

}

默认调用超时时间为1秒,超过一秒就会触发熔断

服务提供方:

  添加休眠模仿超时

package com.company.service.impl;

import com.company.mapper.UserMapper;
import com.company.pojo.User;
import com.company.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@Service("userService")
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User findOne(Long id) {
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return userMapper.selectByPrimaryKey(id);
}

@Override
@Transactional
public void insert(User user) {
user.setUserName("test5");
user.setPassword("test5");
user.setName("ceshi5");
userMapper.insert(user);
// int i=10/0;
}
}

测试结果,调用时长超过1秒的都触发了,低于1秒的都响应成功。

  

   

 Hystrix默认超时时长为1秒。

配置单个方法的超时时间

@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") //设置3秒后超时,value的单位是毫秒
})

   

   

/**
 * Copyright 2012 Netflix, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.netflix.hystrix;

import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forBoolean;
import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forInteger;
import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forString;

import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.hystrix.strategy.properties.HystrixDynamicProperty;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import com.netflix.hystrix.util.HystrixRollingNumber;
import com.netflix.hystrix.util.HystrixRollingPercentile;

/**
 * Properties for instances of {@link HystrixCommand}.
 * <p>
 * Default implementation of methods uses Archaius (https://github.com/Netflix/archaius)
 */
public abstract class HystrixCommandProperties {
    private static final Logger logger = LoggerFactory.getLogger(HystrixCommandProperties.class);

    /* defaults */
    /* package */ static final Integer default_metricsRollingStatisticalWindow = 10000;// default => statisticalWindow: 10000 = 10 seconds (and default of 10 buckets so each bucket is 1 second)
    private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;// default => statisticalWindowBuckets: 10 = 10 buckets in a 10 second window so each bucket is 1 second
    private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;// default => statisticalWindowVolumeThreshold: 20 requests in 10 seconds must occur before statistics matter
    private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;// default => sleepWindow: 5000 = 5 seconds that we will sleep before trying again after tripping the circuit
    private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;// default => errorThresholdPercentage = 50 = if 50%+ of requests in 10 seconds are failures or latent then we will trip the circuit
    private static final Boolean default_circuitBreakerForceOpen = false;// default => forceCircuitOpen = false (we want to allow traffic)
    /* package */ static final Boolean default_circuitBreakerForceClosed = false;// default => ignoreErrors = false 
    private static final Integer default_executionTimeoutInMilliseconds = 1000; // default => executionTimeoutInMilliseconds: 1000 = 1 second

在HystrixCommandProperties定义了很多属性,按住Ctrl再点击要配置的属性。就可以找到要配置的key,例:

  

this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);

也可以全局配置修改Hystrix的默认超时时间

在消费方的application.yaml文件中配置

  前缀+key

  hystrix:

    command:

      default:

        execution.isolation.thread.timeoutInMilliseconds

  如下:

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000

也可以在yaml文件中配置单个方法
单个服务
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
user-service://配置单个服务id,实测无效
execution:
isolation:
thread:
timeoutInMilliseconds: 1000

  配置单个方法

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
getUserById://配置单个方法,实测有效;并且如果有多个Controller中有重复的方法名,则多个同名方法均有效。
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
  

 另外有一点需要注意,Ribbon重试超时时长设置需要小于Hystrix降级超时时长,否则还没来得及重试就已经服务降级了。

 org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand 

    protected static int getHystrixTimeout(IClientConfig config, String commandKey) {
        int ribbonTimeout = getRibbonTimeout(config, commandKey);
        DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();
        int defaultHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 0).get();
        int commandHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command." + commandKey + ".execution.isolation.thread.timeoutInMilliseconds", 0).get();
        int hystrixTimeout;
        if (commandHystrixTimeout > 0) {
            hystrixTimeout = commandHystrixTimeout;
        } else if (defaultHystrixTimeout > 0) {
            hystrixTimeout = defaultHystrixTimeout;
        } else {
            hystrixTimeout = ribbonTimeout;
        }

        if (hystrixTimeout < ribbonTimeout) {
            LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey + " is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms.");
        }

        return hystrixTimeout;
    }

    protected static int getRibbonTimeout(IClientConfig config, String commandKey) {
        int ribbonTimeout;
        if (config == null) {
            ribbonTimeout = 2000;
        } else {
            int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout", Keys.ReadTimeout, 1000);
            int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout", Keys.ConnectTimeout, 1000);
            int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries", Keys.MaxAutoRetries, 0);
            int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer", Keys.MaxAutoRetriesNextServer, 1);
            ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);
        }

        return ribbonTimeout;
    }

Ribbon超时计算公式:(连接时长+读取时长)*(当前实例重试次数[默认是0]+1)*(切换实例重试次数[默认是1]+1)

原文地址:https://www.cnblogs.com/zou-rong/p/12589390.html