目前市面上的应用集群稳定性的工具,不仅有 Sentinel 还有 Hystrix。前者是 Alibaba 出品,后者是 Spring-Cloud 生态使用的稳定性组件。

两者都是通过控制流量(熔断、限流、降级等手段),在流量洪峰或异常情况下,实现应用服务稳定性。但它们使用原理封装出的策略,及用户体验都截然不同。

今天我主要带你学习 Hystrix,然后再通过对 Sentinel 与 Hystrix 的比较,带你更深入地理解 APM 稳定性工具在并发编程上的作为和思考。

简单回顾 Sentinel

Sentinel 以流量为切入点,提供流量控制、流量塑形、熔断降级、过载保护等维度的高可用保障策略。在架构上,被识别的流量会转换为 Sentinel Context 对象,通过使用责任链模式,在一系列的功能插槽链中完成稳定性策略验证。

关于 Sentinel 的技术骨架和责任链模式,你可回顾“11 | 资源节点树:如何通过 Sentinel 无侵入实现流量链生成规则?”

详解 Hystrix 组件

Hystrix 是为了公司的服务集群不稳定而设计的,是国外视频网站 Netflix 开源的组件。

Hystrix 提供了多种实现策略,这些策略的设计主旨是监控应用程序的核心执行单元,也就是代码块的执行情况。当执行情况非符合预期时,将执行单元的调用切换为调用一个符合预期的,或是可正确处理的备选响应。

这样的设计的好处是,在故障持续的时间内,流量会被切换至降级执行单元,保证在一段时间内,不会有持续的报错进而导致故障的发生。

而且 Hystrix 还支持健康探测,也就是会定期将小流量引导至非降级的核心模块上去执行。当监测到核心模块可以健康执行后,应用服务接收到的新流量就会自动切换回去。

如下图所示,Hystrix 的架构设计非常清晰。

首先应用服务在引入 Hystrix 组件后,通过注解或是继承,将核心执行单元封装成 Hystrix Command 类;

然后在执行前,Hystrix 会先后检查应用服务内部“熔断”和“线程池”两个策略是否开启;

若通过策略的验证,将监视执行单元的执行过程;

并在出现异常或是验证策略失败时,执行注解或继承中降级策略。

可以看出 Hystrix 的执行原理非常清晰。正因为 Sentinel 和 Hystrix 的架构实现都非常简单,一经发布就在社区得到了非常多用户的拥趸。

那接下来,我们就对比 Hystrix,看下 Sentinel 在并发编程上使用了哪些设计,使其在流量洪峰下,依旧计算精准且性能损耗低。

在并发编程上,流量控制工具如何作为?

接下来我将以 Sentinel 为主,专注流量控制工具在并发编程上的思考。我会对 “并发请求隔离技术”和“吞吐的并发流量计算技术”进行展开;最后再以原理视角,重新审视 Sentinel 和 Hystrix 在产品形态上的异同。

下图是 Sentinel 官方对 Sentinel 和 Hystrix 的对比。

可进入“官方 Sentinel 和 Hystrix 对比”了解更多。

1.并发隔离

通过上面的对比表,我们可以看出:在并发流量的隔离原理上,两者都可使用了信号量隔离技术和线程池隔离技术。由此可见,这两个技术在并发流量的隔离领域非常常见。

但这两个技术对一线开发还是很陌生,所以有必要对这两个技术进行讲解。

1)信号量隔离

信号量隔离比较简单,在 Java 中对应的类名为 Semaphore。常见的 APM 工具,在处理并发时多有涉及该技术。只不过由于业务开发用不到这些技术,所以他们对此多感到陌生。

信号量技术可以理解为计数器,比如我们设计的被监视的执行单元 QPS 是 100,那对应的信号量计数器就设置为 100。

每一次调用执行单元时,就会去申请信号量,造成计数器减 1;

当退出执行单元时,计数器会加 1;

当申请信号量不足时,会无法申请到信号量,进而阻断执行单元被调用。

总的来说,请求会转化为申请信号量的操作。在信号量不足时,会拒绝申请。

2)线程池隔离

顾名思义,就是在流量到达被监视的执行单元时,为执行单元的执行单独创建线程池,进而实现隔离。

常见的策略:1.多个执行单元有对等不同的线程池。2.单一执行单元由于调用来源不同,有着不同的线程池。这样的编程结果是:当接收到并发流量时,由于策略不同,执行单元的任务线程会在不同的线程池里面执行,从而实现了以线程池为维度的并发流量隔离。

综上,这两种隔离技术的核心思想都是:识别并发流量,然后申请对应的统一资源(申请到资源即可执行),并保持申请资源之间的相互隔离;反观没有隔离技术的话,应用服务中的任意一个模块不稳定,都会造成集群的雪崩。

我们再对比一下信号量隔离和线程池隔离,更形象深入地认识一下它们。

信号量隔离,如同高并发下的精准计数器,使架构更加轻量,让引入开销最小。

线程池隔离,将被执行单元封装到指定线程池执行,让隔离更彻底。由于线程池技术的存在,被执行单元支持在异步线程池排队。在任务线程执行超时时,可以主动断掉工作线程等场景,但这样也会使线程模型架构更加复杂。

2.并发控制

在流量洪峰下想要精准限流,就必须使用高性能的并发计算对象。而 Sentinel 底层便是通过LongAddr 对象,解决了并发流量计算的两个难题——精确度难题和性能难题。

精准度难题

说到精准度,就要提到CAS(Compare and Swap)原理了。Compare 表示比较,Swap 表示交换,两者通过 and 连接。其意思是:在替换一个值时,需要使用“原始值想要替换的值”一起进行并发操作;过程中伴随着“比较原始值是否发生变化”,然后“锁住变化的值进行更新”这两个操作。

性能难题

在我们的业务开发中,解决流量洪峰下的精准限流场景的方法是使用AtomicLong 对象。AtomicLong 对象在实现 CAS 上,使用操作系统的 lock 信号,从而保证原子性。在竞争激烈的并发场景下,Atomic 原子类通过自旋锁实现值的累加,过程中会存在大量的操作失败尝试,也会带来极大的 CPU 消耗。

而 LongAddr 对象为了在性能上超越 AtomicLong 原子对象,便在内部封装了一个 Cell 元素数组。并通过并发线程的 ID 的哈希值,分散访问数组中的元素,从而进行 CAS 操作。

如下图所示,其原理就是利用哈希算法分散操作 CAS 对象带来的竞争。

在使用 AtomicLong 计算并发流量的 QPS 时,所有线程都会访问同一个 CAS 变量进行 CAS 操作。

改用 LongAddr 后,再计算并发流量的 QPS 时,通过 Hash 算法将其分为“线程 1、2”和“线程 3、4”两组,从而去操作 CAS 元素数组中的不同变量。

原理易懂,但问题也明显:在并发线程同时操作数组的 CAS 元素时,统计数据会有误差。这时你可能会问:那在读取数据时,可否增加写锁来保证读取的正确性呢?答案是否定的。

因为限流降级会频繁读取对象中的数据。增加写锁的话,LongAddr 就会被降级为类似 AtomicLong 原子对象,所带来的并发优势便荡然无存了。

因此,根据场景来选型合适的并发控制对象非常必要。因为 Sentinel 仅用 LongAddr 对象进行统计,这在高并发场景下其性能损耗优先级最高,所以较小的误差是被允许的。

3.控制台界面

关于这两个流量控制工具的底层编程实现,我们的讲解就告一段落了,我们再回看下两个组件的控制台。

1)Hystrix 控制台

上图为 Hystrix 的示例控制台,图中展示了九个区域。

蓝色趋势线:表示被监控的执行代码,显示的是最近两分钟内的并发流量的请求速率。

圆圈的大小标识 QPS:圆圈越大表示被执行的次数越多,而颜色表示执行单元的当前健康度。

右上侧数据:是最近十秒的异常情况分析。

右下侧数据:为最近 1 分钟的延迟情况

用简短的话描述 Hystrix 控制台,那就是可以实时展示并发流量引起监控的核心执行单元的执行情况。

更详尽的介绍可以参见Hystrix 控制台介绍。

2)Sentinel 控制台

Sentinel 控制台不仅可以对并发流量进行实时监控,它还有很多其他的丰富功能。

簇点链路:正如上节课时所讲的 AOP 思想,通过线程本地变量(也就是 ThreadLocal 技术),在应用框架暴露出的拦截器或是过滤器中埋下监测点,无侵入地实现簇点链路的自发现。

流控降级规则:异常情况支持响应时间、异常比例、异常数、多规则配置的应用降级策略。

系统保护规则:通过获取应用系统的负载情况,使应用服务的整体指标处于安全水位。

可以看出,Sentinel 在动态调控的规则上,通过同台数据源,实现了控制端和服务端的远程实时连接,从而赢得了更多先天优势;而且 Sentinel 在绝大多数场景下,是包含了 Hystrix 的各种功能能力的。

所以若你需要对应用集群进行稳定性工具的产品选型的话,我更向你推荐 Sentinel。

小结与思考

今天,我先带你学习了 Hystrix 的架构原理;之后又学习了稳定性工具在并发编程上的常用技术手段,包括信号量隔离技术、线程池隔离技术、高性能并发控制对象的设计等;最后通过产品功能,全面对比了 Sentinel 和 Hystrix,并得出 Sentinel 相对更优的结论。

在工作中,你肯定也使用过 Sentinel 和 Hystrix。那么你使用了什么规则呢?又应用到什么场景呢?欢迎你在评论区写下你的思考,非常期待与你讨论。

-– ### 精选评论