到目前为止,在本系列中,我们已经了解了 Resilience4j 及其 [Retry](
https://icodewalker.com/blog/... ), [RateLimiter](
https://icodewalker.com/blog/... ) 和 [TimeLimiter](
https://icodewalker.com/blog/... ) 模块。在本文中,我们将探讨 Bulkhead 模块。我们将了解它解决了什么问题,何时以及如何使用它,并查看一些示例。
代码示例
本文附有 [GitHub 上](
https://github.com/thombergs/... )的工作代码示例。
什么是 Resilience4j?
请参阅上一篇文章中的描述,快速了解 [Resilience4j 的一般工作原理]
( https://icodewalker.com/blog/... )。
什么是故障隔离?
几年前,我们遇到了一个生产问题,其中一台服务器停止响应健康检查,负载均衡器将服务器从池中取出。
就在我们开始调查这个问题的时候,还有第二个警报——另一台服务器已经停止响应健康检查,也被从池中取出。
几分钟后,每台服务器都停止响应健康探测,我们的服务完全关闭。
我们使用 Redis 为应用程序支持的几个功能缓存一些数据。正如我们后来发现的那样,Redis 集群同时出现了一些问题,它已停止接受新连接。我们使用 Jedis 库连接到 Redis,该库的默认行为是无限期地阻塞调用线程,直到建立连接。
我们的服务托管在 Tomcat 上,它的默认请求处理线程池大小为 200 个线程。因此,通过连接到 Redis 的代码路径的每个请求最终都会无限期地阻塞线程。
几分钟之内,集群中的所有 2000 个线程都无限期地阻塞了——甚至没有空闲线程来响应负载均衡器的健康检查。
该服务本身支持多项功能,并非所有功能都需要访问 Redis 缓存。但是当这一方面出现问题时,它最终影响了整个服务。
这正是故障隔离要解决的问题——它可以防止某个服务区域的问题影响整个服务。
虽然我们的服务发生的事情是一个极端的例子,但我们可以看到缓慢的上游依赖如何影响调用服务的不相关区域。
如果我们在每个服务器实例上对 Redis 设置了 20 个并发请求的限制,那么当 Redis 连接问题发生时,只有这些线程会受到影响。剩余的请求处理线程可以继续为其他请求提供服务。
故障隔离背后的想法是对我们对远程服务进行的并发调用数量设置限制。我们将对不同远程服务的调用视为不同的、隔离的池,并对可以同时进行的调用数量设置限制。
术语舱壁本身来自它在船舶中的使用,其中船舶的底部被分成彼此分开的部分。如果有裂缝,并且水开始流入,则只有该部分会充满水。这可以防止整艘船沉没。
Resilience4j 隔板概念
resilience4j-bulkhead 的工作原理类似于其他 Resilience4j 模块。我们为它提供了我们想要作为函数构造执行的代码——一个进行远程调用的 lambda 表达式或一个从远程服务中检索到的某个值的 Supplier,等等——并且隔板用代码装饰它以控制并发调用数。
Resilience4j 提供两种类型的隔板 - SemaphoreBulkhead
和 ThreadPoolBulkhead
。
SemaphoreBulkhead
内部使用
java.util.concurrent.Semaphore
来控制并发调用的数量并在当前线程上执行我们的代码。
ThreadPoolBulkhead
使用线程池中的一个线程来执行我们的代码。它内部使用
java.util.concurrent.ArrayBlockingQueue
和
java.util.concurrent.ThreadPoolExecutor
来控制并发调用的数量。
SemaphoreBulkhead
让我们看看与信号量隔板相关的配置及其含义。
maxConcurrentCalls
确定我们可以对远程服务进行的最大并发调用数。我们可以将此值视为初始化信号量的许可数。
任何尝试超过此限制调用远程服务的线程都可以立即获得 BulkheadFullException
或等待一段时间以等待另一个线程释放许可。这由 maxWaitDuration 值决定。
当有多个线程在等待许可时, fairCallHandlingEnabled
配置确定等待的线程是否以先进先出的顺序获取许可。
最后, writableStackTraceEnabled
配置让我们可以在 BulkheadFullException
发生时减少堆栈跟踪中的信息量。这很有用,因为如果没有它,当异常多次发生时,我们的日志可能会充满许多类似的信息。通常在读取日志时,只知道发生了 BulkheadFullException
就足够了。
ThreadPoolBulkhead
coreThreadPoolSize
、 maxThreadPoolSize
、 keepAliveDuration
和 queueCapacity
是与 ThreadPoolBulkhead
相关的主要配置。 ThreadPoolBulkhead
内部使用这些配置来 构造一个 ThreadPoolExecutor 。
internal ThreadPoolExecutor
使用可用的空闲线程之一执行传入的任务。 如果没有线程可以自由执行传入的任务,则该任务将排队等待线程可用时稍后执行。如果已达到 queueCapacity
,则远程调用将被拒绝并返回 B