GCP PubSub Java 订阅者客户端的并发设置

Concurrency settings for GCP PubSub Java subscriber client

Java GCP PubSub consumer中控制并发级别的两个重要字段:

来自official example

setParallelPullCount determines how many StreamingPull streams the subscriber will open to receive message. It defaults to 1. setExecutorProvider configures an executor for the subscriber to process messages. Here, the subscriber is configured to open 2 streams for receiving messages, each stream creates a new executor with 4 threads to help process the message callbacks. In total 2x4=8 threads are used for message processing.

所以parallel pull count,如果我没记错的话,直接指的是Java个executor(=thread pools)的数量,executor threads的数量设置了每个pool的线程数量。

通常我认为单独的线程池具有不同的用例或职责,因此我们可能有一个用于 IO 的无界缓存线程池,一个用于 CPU 绑定操作的固定线程池,一个单一的(或低数)用于异步 IO 通知的线程池,等等。

但是,与仅拥有一个具有最大所需线程数的线程池相比,拥有两个或更多具有相同属性的线程池来消费和处理 pubsub 消息有什么好处?例如,如果我可以在订阅者上保留总共 8 个线程,那么使用 1x8 与 2x4 组合的具体原因是什么? (一个包含 8 个线程的池,而 pull count=2 每个使用 4 个线程)?

setParallelPullCount 选项不仅仅指 Java Executor 的数量,它指的是创建的从服务器请求消息的流的数量。由于各种因素,不同的流可能 return 不同数量的消息。人们可能想要增加并行拉取计数,以便在单个客户端中处理比在单个流 (10MB/s) 上传输的消息更多的消息。这与是否共享 executors/thread 个池的选择无关。

是否跨流共享线程池将通过调用 setExecutorProvider 来处理。如果您在每次调用 getExecutor 时设置 return 与 Executor 相同的 ExecutorProvider,则流将共享它。如果你有它 return 一个新的 Executor 每次调用,那么他们每个人都有自己专用的 Executor。默认 ExecutorProvider 执行后者。

如果调用 setParallelPullCount(X),则 setExecutor 会被调用 X 次以获得每个流的 Executor。在绝大多数情况下,在所有人共享一个或每个人单独一个之间的选择可能不会改变太多。如果您试图将总线程数保持在相对较低的水平,那么共享一个 Executor 可能会有所帮助。

X Executor 与 Y 线程和一个 Executor 与 X*Y 线程之间的选择实际上归结为共享此类资源的能力,如果来自每个流的数据量是大不相同,大多数时候可能不会出现这种情况。如果是,那么共享 Executor 意味着特别饱和的流可以从不饱和的流中“借用”线程。另一方面,使用单个 Executor 可能意味着在这种情况下,消息较少的流中的消息与饱和流中的消息一样能够通过。