8

④SpringCloud 实战:引入Hystrix组件,分布式系统容错

 3 years ago
source link: https://jinglingwang.cn/archives/hystrix
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

简介

Hystrix 是一个延迟和容错库,旨在隔离对远程系统、服务和第三方库的访问点,停止级联故障,并在故障不可避免的复杂分布式系统中实现恢复能力。

服务雪崩

在分布式微服务的架构体系下,一般都会存在多层级服务服务的调用链,当链路中的某个服务发生异常,最后导致整个系统不可用,这种现象称为服务雪崩效应。

IvyiAvE.png!mobile

如上图所示,从最开始的整个系统正常状态,到单个服务出现异常,再到多个服务出现异常,到最后整个系统可不用,整个过程就是服务雪崩效应。如果在单个服务出现异常的时候,我们能及时发现、预防、处理,也就不会出现级联效果导致整个系统不可用。Hystrix 就是来保证上面的情况发生时能停止级联故障,保证系统稳定运行的。

Hystrix遵循的设计原则

  1. 避免线程耗尽
    由于被调用方出现问题,调用方无法及时获取响应结果,而一直在发送请求,最终会耗尽所有线程的资源。
  2. 快速失败
    当被调用方出现问题后,调用方发起的请求可以快速失败并返回,这样就不用一直阻塞住,同时也释放了线程资源。
  3. 支持回退
    发起的请求在返回失败后,我们可以让用户有回退的逻辑,比如获取备用数据,从缓存中获取数据,记录日志等操作。
  4. 资源隔离
    当你的服务依赖了 A、B、C 三个服务,当只有 C 服务出问题的时候,如果没做隔离,最终也会发生雪崩效应,导致整个服务不可用,如果我们进行了资源隔离,A、B、C 三个服务都是相互隔离的,即使 C 服务出问题了,那也不影响 A 和 B。这其实就跟不要把所有的鸡蛋放进一个篮子里是一样的道理。
  5. 近实时监控
    它能帮助我们了解整个系统目前的状态,有哪些服务有问题,当前流量有多大,出问题后及时告警等。

Hystrix 两种隔离方式

Hystrix 支持线程池和信号量两种隔离方式,默认使用的线程池隔离。

线程池隔离是当用户请求到 A 服务后,A 服务需要调用其他服务,这个时候可以为不同的服务创建独立的线程池,假如 A 需要调用 B 和 C,那么可以创建 2 个独立的线程池,将调用 B 服务的线程丢入到一个线程池,将调用 C 服务的线程丢入到另一个线程池,这样就起到隔离效果,就算其中某个线程池请求满了,无法处理请求了,对另一个线程池也没有影响。

信号量隔离就比较简单了,信号量就是一个计数器,比如初始化值是 100,那么每次请求过来的时候就会减 1,当信号量计数为 0 的时候,请求就会被拒绝,等之前的请求处理完成后,信号量会加 1,同时也起到了限流的作用,这就是信号量隔离,信号量隔离是在请求主线程中执行的。

线程池隔离的特点是 Command 运行在独立的线程池中,可以支持超时,是单独的线程,支持异步。信号量隔离运行在调用的主线程中,不支持超时,只能同步调用。

Hystrix 实战

引入Hystrix 依赖

  1. 引入相关依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
  2. ribbon-client 服务的启动类上添加注解 @EnableHystrix
    这时候服务已经可以启动成功了

Hystrix 的三种使用方式

1.@HystrixCommand 注解方式

HystrixCommand 注解作用于方法上,哪个方法想要使用 Hystrix 来进行保护,就在这个方法上增加 HystrixCommand 注解。

比如在我们的queryPort方法上添加@HystrixCommand注解:

@HystrixCommand(commandKey = "queryPort")
@GetMapping("queryPort")
public String queryPort(){
    return providerFeign.queryPort();
}

其中commandKey不指定的话,会默认使用方法名,这里也是queryPort;

@HystrixCommand 有很多默认的配置,比如超时时间,隔离方式等;我们可以手动指定配置信息有比如 commandKey、groupKey、fallbackMethod 等。

配置回退方法fallbackMethod

使用@HystrixCommand 注解方式配置回退方法,需要将回退方法定义在HystrixCommand所在的类中,且回退方法的签名与调用的方法签名(入参,返回值)应该保持一致,比如:

private String queryPortFallBack(){
    return "sorry,jinglingwang.cn no back!";
}

//调用方法改造
@HystrixCommand(commandKey = "queryPort",fallbackMethod = "queryPortFallBack")

然后我们把eureka-provider服务停掉或者故意超时,访问接口会出现如下图所示的结果:

NFVbeaF.png!mobile

我们也可以结合 @HystrixProperty 注解来丰富我们的配置

@HystrixCommand(commandKey = "queryPort",commandProperties ={
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"),//超时时间,默认1000,即1秒
        @HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),//信号量隔离级别
        @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "50") //信号量模式下,最大请求并发数,默认10
    },fallbackMethod = "queryPortFallBack")
@GetMapping("queryPort")
public String queryPort(){
    return providerFeign.queryPort();
}

上面的一些配置信息我们还可以配置到配置文件中,效果是一样的:

# queryPort 是@HystrixCommand注解里面的commandKey,默认值是1000,也就是1秒;在HystrixCommandProperties类可以看到
# 隔离方式,SEMAPHORE:信号量隔离,THREAD:线程隔离(默认值)
hystrix.command.queryPort.execution.isolation.strategy = SEMAPHORE
# 信号量模式下,最大请求并发数,默认10
hystrix.command.queryPort.execution.isolation.semaphore.maxConcurrentRequests = 50
# 超时时间
hystrix.command.queryPort.execution.isolation.thread.timeoutInMilliseconds = 3000

下面的代码展示了线程隔离级别下的配置示例:

@HystrixCommand(commandKey = "queryTempPort",
        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")
        }
        ,fallbackMethod = "queryTempPortFallBack")
@GetMapping("queryTempPort")
public String queryTempPort(){
    return providerTempFeign.queryPort();
}

我们也可以使用 @DefaultProperties 注解来配置默认属性;

@DefaultProperties是作用在类上面的,可以配置一些比如groupKey、threadPoolKey、commandProperties、threadPoolProperties、ignoreExceptions和raiseHystrixExceptions等属性。方法级别的@HystrixCommand命令中单独指定了的属性会覆盖默认的属性,比如:

@RestController
@DefaultProperties(groupKey = "DefaultGroupKey")
public class RibbonController{
   ...

    @HystrixCommand(commandKey = "queryTempPort",groupKey="eureka-provider-temp",
            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")
            }
            ,fallbackMethod = "queryTempPortFallBack")
    @GetMapping("queryTempPort")
    public String queryTempPort(){
        return providerTempFeign.queryPort();
    }
}

2.Feign 整合 Hystrix

开启Feign对Hystrix的支持

在配置文件添加如下配置

# 如果为true,则将使用Hystrix断路器包装OpenFeign客户端,默认是false
feign.hystrix.enabled=true

配置fallback

  1. 为Feign配置回退方法,将fallback属性设置成回退的类名,例如:

    @Component
    public class ProviderTempFeignFallback implements ProviderTempFeign{
    
        @Override
        public String queryPort(){
            return "sorry ProviderTempFeign, jinglingwang.cn no back!";
        }
    }
    
    @FeignClient(value = "eureka-provider-temp",fallback = ProviderTempFeignFallback.class)
    public interface ProviderTempFeign{
    
        @RequestMapping("/queryPort")
        String queryPort();
    }
  2. 我们保留上面的@HystrixCommand注解,然后启动项目,把eureka-provider项目的接口加一个断点,保证接口会超时。同时配置有两个fallback时,发现最后生效的是@HystrixCommand注解配置的fallback,说明@HystrixCommand注解的优先级要高一些,返回结果如图:

    R36zmyr.png!mobile

    然后我们把@HystrixCommand注解注释掉,再重启,成功执行了Feign配置的fallback,效果如图:

    eEJbIfb.png!mobile

fallback返回失败的原因

如果需要访问导致失败回退的原因,可以使用@FeignClient内的fallbackFactory属性。

@Component
public class ProviderFeignFallbackFactory implements FallbackFactory<ProviderFeign>{

    @Override
    public ProviderFeign create(Throwable cause){
        return new ProviderFeign(){
            @Override
            public String queryPort(){
                return "sorry ProviderFeignFallbackFactory, jinglingwang.cn no back! why? ==>" + cause.getCause();
            }
        };
    }
}

@FeignClient(value = "eureka-provider",fallbackFactory = ProviderFeignFallbackFactory.class)
public interface ProviderFeign{
    /**
     * 调用服务提供方,其中会返回服务提供者的端口信息
     * @return jinglingwang.cn
     */
    @RequestMapping("/queryPort")
    String queryPort();

}

3.网关中使用Hystrix

网关中使用Hystrix等到了整合网关的时候再细讲。

hystrix配置总结

  1. 默认配置是全局有效的

    # 配置 Hystrix 默认的配置
    # To set thread isolation to SEMAPHORE
    hystrix.command.default.execution.isolation.strategy: SEMAPHORE
    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000
    hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests: 40
  2. 单独为Feign Client 来指定超时时间

    # 单独为 ProviderFeign 配置
    hystrix.command.ProviderFeign.execution.isolation.strategy = SEMAPHORE
    # 超时时间
    hystrix.command.ProviderFeign.execution.isolation.thread.timeoutInMilliseconds = 5000
    # 最大请求并发数,默认10
    hystrix.command.ProviderFeign.execution.isolation.semaphore.maxConcurrentRequests: 200
  3. 单独为ProviderTempFeign类的queryPort()方法进行配置

    # 单独为ProviderTempFeign类的queryPort()方法配置
    hystrix.command.ProviderTempFeign#queryPort().execution.isolation.strategy = THREAD
    # 超时时间
    hystrix.command.ProviderTempFeign#queryPort().execution.isolation.thread.timeoutInMilliseconds = 5000
  4. 使用 @HystrixCommand 注解配置

    具体做法可以参考上面的示例代码

Hystrix的配置项有很多,其他属性的配置key可以参考 HystrixCommandProperties 类。

如何合理的配置Hystrix和Ribbon超时时间

Hystrix 的超时时间是和Ribbon有关联的,如果配置的不对,可能会出现莫名其妙的问题。

在Hystrix源码里面是建议 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.");

而在取 ribbonTimeout 配置值的时候,是有一个计算公式的:

ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);

假如我们Ribbon的超时时间配置如下:

#读超时
ribbon.ReadTimeout=3000
#连接超时
ribbon.ConnectTimeout=3000
#同一台实例最大重试次数,不包括首次调用
ribbon.MaxAutoRetries=0
#重试负载均衡其他的实例最大重试次数,不包括首次调用
ribbon.MaxAutoRetriesNextServer=1

将上面的值代入到公式计算,得到结果:ribbonTimeout=(3000+3000) (0+1) (1+1),结果为12000,也就是说Hystrix 的超时时间建议配置值要大于等于12000,也就是12秒。

Hystrix Dashboard

引入dashboard依赖:

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

在启动类上加入注解@EnableHystrixDashboard

然后启动项目,访问 http://localhost:7071/hystrix ,我们可以看到如下页面:

QvuiiqZ.png!mobile 但是这时候并不能直接使用,需要对项目进行监控,首先要有对应的 Stream 地址,Stream 产生数据源,然后将仪表板指向Hystrix客户端应用程序中的单个实例/hystrix.stream端点。

我们在被ribbon-client项目中加入 spring-boot-starter-actuator 依赖,只有加入了 actuator 才能暴露出 hystrix.stream 端点。

然后再配置文件添加如下配置:

management.endpoints.web.exposure.include = hystrix.stream

启动项目,访问 http://localhost:7071/actuator/hystrix.stream 接口,你会发现页面一直在显示ping;

然后把该地址配置到上面的页面中,点击monitor,OK,等待loading。

然后我们随便访问一些接口,就可以看到监控内容了。

RvUVb2e.png!mobile

Hystrix 总结

  1. Hystrix 支持@HystrixCommand 命令和配置文件两种方式进行配置
  2. Hystrix 支持两种隔离级别,在网关中建议使用信号量的方式,能起到一定限流的作用
  3. Hystrix 的线程池隔离级别可以为每个client分别配置线程池,起到资源隔离的作用
  4. Hystrix 的线程池隔离级别中使用 ThreadLocal 时数据可能会丢失,需要单独处理
  5. Hystrix 的fallback我们可以用来记录日志或者进行相应的业务告警
  6. Hystrix 超时时间的合理计算和ribbon的配置有关系,否则可能出现莫名其妙的问题

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK