线程安全
使用并发消息监听容器时,单个监听器实例会在所有消费者线程上被调用。因此,监听器需要是线程安全的,最好使用无状态监听器。如果无法使监听器线程安全,或者添加同步会显著降低并发带来的收益,你可以采用以下几种技术之一:
- 使用
n个容器,将concurrency=1,并配合一个原型作用域的MessageListenerbean,使每个容器拥有自己的实例(使用@KafkaListener时无法做到这一点)。 - 将状态保存在
ThreadLocal<?>实例中。 - 让单例监听器委托给一个在
SimpleThreadScope(或类似作用域)中声明的 bean。
为便于清理线程状态(针对前一列表中的第二项和第三项),从 2.2 版本开始,当每个线程退出时,监听容器会发布一个 ConsumerStoppedEvent。你可以用 ApplicationListener 或 @EventListener 方法来消费这些事件,以从作用域中移除 ThreadLocal<?> 实例或对线程作用域 bean 调用 remove()。请注意,SimpleThreadScope 不会销毁具有销毁接口(例如 DisposableBean)的 bean,因此你应当自行 destroy() 该实例。
important
默认情况下,应用程序上下文的事件多播器会在调用线程上调用事件监听器。如果你将多播器更改为使用异步执行器,线程清理将不会生效。
关于虚拟线程与并发消息监听容器的特别说明
由于底层库类在进行线程协同时仍使用 synchronized 块而存在某些限制,应用程序在将虚拟线程与并发消息监听容器一起使用时需要谨慎。当启用虚拟线程时,如果并发数超过可用平台线程数量,虚拟线程很可能被固定(pin)在平台线程上,并可能出现竞态条件。因此,在 Spring for Apache Kafka 所使用的第三方库演进到完全支持虚拟线程之前,建议将消息监听容器的并发数保持为等于或小于平台线程数量。这样,应用程序就能避免线程之间的竞态条件,以及虚拟线程被固定在平台线程上的问题。