.NET 微服务中的隔板示例
Example of bulkheads in .NET micro services
在 Sam Newman 的“构建微服务”(O'Reilly) 中,有一个名为 Bulkheads 的部分,该部分是讨论如何防止阻塞的微服务混乱的章节的一部分与整个系统。
如本节所述,隔板的一个示例是使用单独的连接池来连接到每个下游服务。
作者说的是对下游服务的同步调用,所以我将上面的内容理解为“HTTP 客户端池”。
但是,在 .NET 中,越来越多的人认为使用单例 HTTP 客户端来提高可伸缩性是最佳实践。
我是不是直接认为,在 .NET 中,这种隔板不适用?
我们应该更关注哪些其他类型的舱壁(如果有)?
我想解释一些事情,这样你就可以有完整的图片来更好地理解这个模式。
套接字和 Tcp
假设您有 3 个服务 A、B、C。在每个客户端请求中,您都需要使用 http 调用它们。每次创建 http 客户端时,都会在下面创建一个 tcp 连接并打开套接字。套接字的数量有硬性限制,如果您有非常多的 http 调用,您可能最终会耗尽所有套接字连接。这就是为什么需要重复使用单个 http 客户端的原因。在 .net 核心中,您可以使用 HttpClientfactory 来实现这一点。因此,如果您有 3 个服务要通过 http 调用,您可以打开 3 个单独的 http 连接(套接字),在这些连接(套接字)下将被重用。
线程池
另一部分是关于线程池的。即使您使用 shared/singleton http 客户端连接进行调用,您仍然必须为该连接分配线程。假设您总共有 100 个线程可用于满足客户端请求。任何超过 100 个请求都会排队。现在假设您正在使用带有 100 个线程的连接池的 http 独立调用三个服务。在快乐路径中,每个服务都会 return 及时返回,当线程完成工作(http 请求已完成)时,它将返回池以完成队列中的下一个客户端请求。在此期间,100 个线程正在使用 3 个共享的 httpclient 实例来调用外部服务,并且下面只有 3 个套接字。所以到目前为止我们都很好。
服务失败
现在假设一项服务速度缓慢或中断。由于线程池(在本例中为 100)是共享的,并且您正在调用 slow/down 服务,但线程需要更长的时间来响应。其他 2 个服务仍然很好并且可以响应,但是由于服务降级,任何调用降级服务的线程都将花费更长的时间来完成请求,或者最终会在返回线程池之前超时。这意味着越来越多的客户端请求正在排队。此时,消费者对其他(健康)服务的请求会受到影响。最终消费者不能再向其他服务发送请求,而不仅仅是原来无响应的服务。您所有可用的线程都停留在降级服务,队列仍在增长。其他消费者无法再消费该服务,造成级联故障效应。
营救舱壁
这就是舱壁来救援的地方。根据消费者负载和可用性要求,将服务实例划分为不同的组。这种设计有助于隔离故障,并允许您为某些消费者维持服务功能,即使在故障期间也是如此。
消费者还可以对资源进行分区,以确保用于调用一项服务的资源不会影响用于调用另一项服务的资源。例如,调用多个服务的消费者可能会为每个服务分配一个连接池。如果服务开始失败,它只会影响为该服务分配的连接池,从而允许消费者继续使用其他服务。
所以从上面的例子你会说请为每个服务分配 33 个线程。现在失败的服务只会影响分配给它的线程。健康的服务将继续使用分配给它们的线程而不会出现任何问题,并将继续满足客户端请求。
.Net Core 和 Polly
Polly 是处理此类情况的非常著名的库。 Polly 与 .Net Core 自然契合,您可以将多个策略分配给 http 客户端,包括 bulkhead。
您可以找到更多关于 polly 的信息 https://github.com/App-vNext/Polly
希望对您有所帮助!
在 Sam Newman 的“构建微服务”(O'Reilly) 中,有一个名为 Bulkheads 的部分,该部分是讨论如何防止阻塞的微服务混乱的章节的一部分与整个系统。
如本节所述,隔板的一个示例是使用单独的连接池来连接到每个下游服务。
作者说的是对下游服务的同步调用,所以我将上面的内容理解为“HTTP 客户端池”。
但是,在 .NET 中,越来越多的人认为使用单例 HTTP 客户端来提高可伸缩性是最佳实践。
我是不是直接认为,在 .NET 中,这种隔板不适用?
我们应该更关注哪些其他类型的舱壁(如果有)?
我想解释一些事情,这样你就可以有完整的图片来更好地理解这个模式。
套接字和 Tcp
假设您有 3 个服务 A、B、C。在每个客户端请求中,您都需要使用 http 调用它们。每次创建 http 客户端时,都会在下面创建一个 tcp 连接并打开套接字。套接字的数量有硬性限制,如果您有非常多的 http 调用,您可能最终会耗尽所有套接字连接。这就是为什么需要重复使用单个 http 客户端的原因。在 .net 核心中,您可以使用 HttpClientfactory 来实现这一点。因此,如果您有 3 个服务要通过 http 调用,您可以打开 3 个单独的 http 连接(套接字),在这些连接(套接字)下将被重用。
线程池
另一部分是关于线程池的。即使您使用 shared/singleton http 客户端连接进行调用,您仍然必须为该连接分配线程。假设您总共有 100 个线程可用于满足客户端请求。任何超过 100 个请求都会排队。现在假设您正在使用带有 100 个线程的连接池的 http 独立调用三个服务。在快乐路径中,每个服务都会 return 及时返回,当线程完成工作(http 请求已完成)时,它将返回池以完成队列中的下一个客户端请求。在此期间,100 个线程正在使用 3 个共享的 httpclient 实例来调用外部服务,并且下面只有 3 个套接字。所以到目前为止我们都很好。
服务失败
现在假设一项服务速度缓慢或中断。由于线程池(在本例中为 100)是共享的,并且您正在调用 slow/down 服务,但线程需要更长的时间来响应。其他 2 个服务仍然很好并且可以响应,但是由于服务降级,任何调用降级服务的线程都将花费更长的时间来完成请求,或者最终会在返回线程池之前超时。这意味着越来越多的客户端请求正在排队。此时,消费者对其他(健康)服务的请求会受到影响。最终消费者不能再向其他服务发送请求,而不仅仅是原来无响应的服务。您所有可用的线程都停留在降级服务,队列仍在增长。其他消费者无法再消费该服务,造成级联故障效应。
营救舱壁
这就是舱壁来救援的地方。根据消费者负载和可用性要求,将服务实例划分为不同的组。这种设计有助于隔离故障,并允许您为某些消费者维持服务功能,即使在故障期间也是如此。
消费者还可以对资源进行分区,以确保用于调用一项服务的资源不会影响用于调用另一项服务的资源。例如,调用多个服务的消费者可能会为每个服务分配一个连接池。如果服务开始失败,它只会影响为该服务分配的连接池,从而允许消费者继续使用其他服务。
所以从上面的例子你会说请为每个服务分配 33 个线程。现在失败的服务只会影响分配给它的线程。健康的服务将继续使用分配给它们的线程而不会出现任何问题,并将继续满足客户端请求。
.Net Core 和 Polly
Polly 是处理此类情况的非常著名的库。 Polly 与 .Net Core 自然契合,您可以将多个策略分配给 http 客户端,包括 bulkhead。
您可以找到更多关于 polly 的信息 https://github.com/App-vNext/Polly
希望对您有所帮助!