zoukankan      html  css  js  c++  java
  • 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)

  • 相关阅读:
    数据库版本管理工具flyway
    spring webapp的配置文件放置在项目外的方法
    logback
    linux as4 bind9 设置进程中的一些小效果
    设置/勾销Debian的屏保
    Linux内存:内存管理的天禀
    用YUM晋级CentOS体系中PHP和MySQL
    solaris的故事
    Solaris 的防火墙ipfilter设置
    mysql安置设置文件的成绩
  • 原文地址:https://www.cnblogs.com/zou-rong/p/12589390.html
Copyright © 2011-2022 走看看