服务容错保护-Hystrix 基础篇

本文详细介绍了Hystrix服务容错机制的基础知识,包括其设计原则、工作原理及在分布式系统中如何防止服务雪崩。同时,还探讨了Hystrix在RestTemplate、Feign和Zuul中的具体应用,以及配置、监控和使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务容错保护-Hystrix 基础篇

前言

本章主要介绍 Hystrix 的相关概念和基本用法,简单的了解 Hystrix 工作机制,配置以及相关监控页面,最后介绍了一些 Hystrix 中的使用经验。下篇源码分析我们再详细了解其中的设计细节。

项目环境

1.什么是 Hystrix?

在分布式系统中,服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务会出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞。

Hystrix 是 Netflix 公司开源的一个项目,它提供了熔断器的功能,能够阻止分布式系统中出现的联动故障。

Hystrix 是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。

2.什么是服务雪崩?

微服务架构下,服务与服务之间相关依赖调用,当某个服务不可用时,很容易因为服务之前的依赖关系使故障扩大,甚至造成整个系统不可用的情况,这种现象称为服务雪崩效应。

在这里插入图片描述

上图为服务雪崩的发生过程

  • 系统服务均正常,客户端调用服务A,服务A调用服务B,服务B调用服务C,结果返回客户端;
  • 此时服务C发生故障或者性能出现问题,导致服务B的请求无法及时响应,产生延迟,随着时间的推移,延迟会越来越大,最终导致服务C资源耗尽,无法正常处理请求;
  • 因为请求都堵在服务C上,服务B作为调用方,请求也会出现延迟,最终也会导致服务B的资源耗尽,变为不可用状态,再也无法及时响应服务 A 的请求结果;
  • 依此类推,最终服务 A 也会被拖垮,导致整个系统不可用,这个过程就是服务雪崩效应。

3.如何解决服务雪崩?

可以从以下两个方面来进行分析:

  • 服务提供者方面,对于这种请求量超出承受能力的问题,我们可以进行扩容来支持高并发或者进行限流,自己能处理多少请求就处理多少,处理不了的请求直接拒绝,这样才不会将自己拖垮。

  • 服务消费者方面,我们需要做的就是资源隔离,快速失败,这也是最有效的方式,当我们发现被调用方迟迟不响应出现问题的时候,就不要再继续发起调用请求了,此时应该停止,等待被调用方恢复后再发起调用。

4.Hystrix 设计原则

Hystrix 设计下以下原则用来解决前面讲到的这些问题:

  • 防止单个服务的故障耗尽整个服务的 Servlet 容器的线程资源
  • 快速失败机制,如果某个服务出现故障,则调用该服务的请求快速失败,而不是线程等待
  • 提供回退(fallback)方案,在请求发生故障时,提供设定好的回退方案
  • 资源隔离,使用熔断机制,防止故障扩散到其他服务
  • 提供熔断器的监控组件 Hystrix Dashboard,可以实时监控熔断器的状态

5.Hystrix 的工作机制

在这里插入图片描述

  • 首先,当服务的某个 API 接口的失败次数在一定时间内小于设定的阈值时,熔断器处于关闭状态,该 API 接口正常提供服务。

在这里插入图片描述

  • 当该 API 接口处理请求的失败次数大于设定的阈值时,Hystrix 会判断该 API 接口出现故障,打开熔断器,这时请求该 API 接口会执行快速失败的逻辑(即 fallback 回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态。

在这里插入图片描述

  • 处于打开状态的熔断器,一段时间后会处于半打开状态,并将一定的请求执行正常逻辑,剩余的请求会执行快速失败;若执行正常逻辑的请求失败了,则熔断器打开;若成功了,这熔断器关闭。这样熔断器就有了自我修复的功能。

6.Hystrix 使用

6.1 在 RestTemplate 和 Ribbon 上使用熔断器

新增一个 hystrix-client 的服务,调用方法如下:

  • 使用 @HystrixCommand 注解
  • 设置超时时间为 100 毫秒
  • 回退方法为 fallback,注意方法签名要一致
@RestController
public class HystrixController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/hystrix/getUser")
    @HystrixCommand(commandKey = "getUser", groupKey = "user", fallbackMethod = "fallback", threadPoolKey = "tpk1",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")})
    public User getUser(Long id) {
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", id);
        User user = restTemplate.getForObject("http://user-service/hystrix/getUser?id={id}", User.class, uriVariables);
        return user;
    }

    /**
     * 回退方法
     * 注意方法签名一致
     *
     * @param id
     * @return
     */
    public User fallback(Long id) {
        return new User(id, "默认");
    }
}

user-service 服务中增加一个对应的接口方法

  • await 方法随机睡眠 200 毫秒以内
    @GetMapping("/hystrix/getUser")
    public User getHystrixUser(Long id) throws InterruptedException {
        await();
        User user = new User();
        user.setId(id);
        user.setName("小仙 port:" + port);
        return user;
    }

    private void await() throws InterruptedException {
        int value = random.nextInt(200);
        System.out.println("port:" + getPort() + " cost " + value + "ms");
        Thread.sleep(value);
    }

启动 Eureka 注册中心,以及 hystrix-client 和 user-service 两个服务

浏览器输入 http://127.0.0.1:8381/hystrix/getUser?id=2

在这里插入图片描述

user-service 后台控制台打印

port:8181 cost 68ms

再次刷新浏览器

在这里插入图片描述

user-service 后台控制台打印

port:8181 cost 105ms

可以看到当服务返回延迟超过我们设置的 100ms,那么该请求就会超时,则直接执行 fallback 方法中的逻辑返回给调用方。

6.2 Feign 中使用熔断器

在本系列 Feign 博客中会演示相关示例

6.3 Zuul 中使用熔断器

在本系列 Zuul 博客中会演示相关示例

7.Hystrix 配置

Hystrix 的配置 github 官网地址:https://github.com/Netflix/Hystrix/wiki/Configuration

我们这里只介绍几个比较常用的配置

执行(Execution)相关:

  • execution.isolation.strategy
    • 隔离策略,可选 Thread / Semaphore
  • execution.isolation.thread.timeoutInMilliseconds
    • 执行命令超时时间,默认 1000ms
  • execution.isolation.semaphore.maxConcurrentRequests
    • 最大并发请求数,默认 10,信号量隔离有效

断路器(Circuit Breaker)相关:

  • circuitBreaker.enabled
    • 开启断路器默认 true
  • circuitBreaker.forceOpen
    • 强制打开断路器,拒绝所有 request,默认 false
  • circuitBreaker.forceClosed
    • 强制关闭断路器,无论错误百分比如何,断路器都将允许请求

线程池(Thread Pool)相关:

  • coreSize
    • 线程池的核心线程数
  • maxQueueSize
    • 队列 BlockingQueue 的 size 大小。如果你将其设置为 -1 随后 SynchronousQueue 将被使用,当值为正数是,使用 LinkedBlockingQueue
  • queueSizeRejectionThreshold
    • 达到该值后,请求也会被拒绝

8.Hystrix 监控

三个依赖:

  • spring-cloud-starter-netflix-hystrix
  • spring-boot-starter-actuator
  • spring-cloud-starter-netflix-hystrix-dashboard

application.yaml 配置文件增加

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

启动类增加注解 @EnableHystrixDashboard

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
public class HystrixClientBootstrap {
    public static void main(String[] args) {
        new SpringApplicationBuilder(HystrixClientBootstrap.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

浏览器输入 http://127.0.0.1:8381/hystrix

在这里插入图片描述

在输入框输入需要监控的应用地址 http://127.0.0.1:8381/actuator/hystrix.stream 点击 Monitor Stream 按钮,然后一直访问 http://127.0.0.1:8381/hystrix/getUser?id=2 ,可以看到控制台的变化。

在这里插入图片描述

当然 spring-cloud-starter-netflix-hystrix-dashboard 也可以单独部署一个应用,这里为了方便都放在 hystrix-client 应用服务中。

9.Hystrix 使用小经验

  • 配置可以对接配置中心进行动态调整。

Hystrix 的配置项非常多,很多配置,我们都会根据当时的流量情况来进行调整,如果不对接配置中心,这个工作太难了。其实 Hystrix 内部默认使用 Archaius 来实现的动态配置, Archaius 是 Netflix 的配置框架,也可以直接用 Archaius 来动态管理 Hystrix 的配置信息。

  • 回退逻辑中可以手动埋点或者通过输出日志进行告警。

当请求失败或者超时,会执行回退逻辑,如果有大量的回退,则证明某些服务出问题了,这个时候我们可以在回退的逻辑中进行埋点操作,上报数据给监控系统,也可以输出回退的日志,统一由日志收集的程序去进行处理,这些方式都可以将问题暴露出去,然后通过实时数据分析进行告警操作,当然这只是一个入口,对 Hystrix 进行监控的方式有很多种,我们可以扩展 Hystrix 的插件进行数据收集,也可以分析 Hystrix.stream 端点的数据来进行告警。

  • 用了线程池隔离模式再用 ThreadLocal 会有坑。

一个请求进来,这时是容器的线程在负责执行,对于同一个线程传递上下文 ThreadLocal 是没有问题的,当我们用了线程池隔离模式的时候,被隔离的方法会包装成一个 Command 丢入到独立的线程池中进行执行,这个时候就是从 A 线程切换到了 B 线程,ThreadLocal 的数据就会丢失。

解决方案:

http://cxytiandi.com/blog/detail/18782

http://cxytiandi.com/blog/detail/13331

  • 网关中尽量用信号量隔离。

之所以建议在网关中用信号量隔离,是因为网关是所有请求的入口,路由的服务数量会很多,几十个到上百个都有可能,如果用线程池隔离,那么需要创建上百个独立的线程池,开销太大了。用信号量隔离开销就小很多,还能起到限流的作用。

  • 插件机制可以实现很多扩展。

Hystrix 提供了插件机制,可以通过插件来改变 Hystrix 的行为,比如我们可以使用事件通知的插件来做一些数据收集和告警的工作,可以使用配置插件来改变配置的默认行为,目前默认是 Archaius,我们可以将其改变成其他的配置组件。可以使用并发插件来改变线程池的行为,可以对 Callable 进行装饰,来解决 ThreadLocal 跨线程传递的问题。

  • Hystrix 各种超时配置方式。

Hystrix 中用的最多的配置可能就是超时时间,可以配置全局的默认超时时间,那么在 HystrixCommand、Feign 以及 Zuul 中超时时间怎么配置呢?相关博客地址: Hystrix 超时配置的N种玩法

  • commandKey、groupKey、threadPoolKey 的使用。

在使用 HystrixCommand 注解的时候,我们会配置 commandKey、groupKey、threadPoolKey,当然这些也可以不用配置,因为有默认值。commandKey 表示这个请求被封装成了 Command 去执行,commandKey 就是这个 command 的名称,我们可以给指定的 commandKey 进行参数的配置。比如 commandKey1 的超时时间我们设置成 3 秒,commandKey2 的超时时间我们可以设置成 10 秒。

groupKey 是将一组 command 进行分组,groupKey 就是组的名称,同时如果没有设置 threadPoolKey 的话,那么线程池的名称会用 groupKey。

threadPoolKey 是线程池的名称,多个 command 的 threadPoolKey 相同,那么会使用同一个线程池。建议大家手动配置一个简短的、友好的 threadPoolKey,同时使用 threadPoolKey 来对 command 进行线程池隔离的划分。

10.参考

  • 《深入理解 Spring Cloud 与微服务架构》 方志朋

  • 《300分钟搞懂 Spring Cloud》尹吉欢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值