消息通道与消息分发

1,165 阅读4分钟

SCS 用于构建高度可扩展的基于事件驱动的微服务,其目的是为了简化消息在 Spring Cloud 应用程序的开发。本章将讲述其中两个重要的模块,Messaging 和 Integration。

Spring Messaging:

Spring Messaging 是 Spring Framework 中的一个模块,其作用就是同意消息的编程模型。

  • 消息 Messaging 的模型包括一个消息体 Payload 和 消息头 Header
# 构建一个空头消息
MessageBuilder.withPayload(payload).build();
  • 消息通道 MessageChannel 用于接收消息,调用其中的 send 方法可以将消息发送到该消息通道中:
# 发送一条信息到消息通道,并设置过期时间
messageChannel.send(message, timeout);

发生消息的目的当然是为了消息被消费,那么如何消费消息通道的消息呢?

  • MessageChannel 只是一个提供 send 方法的函数式接口,而 SubscribeChannel 对 MessageChannel 进行了增强,使得消费者可以通过订阅的方式获取到消息。
# message handler 订阅消息
messageChannel.subscribe(messageHandler);
# 取消订阅
messageChannel.unsubscribe(messageHandler);

PollableChannel 也是继承了 MessageChannel 的接口,但与 SubscribeChannel 不同的是,他是通过消费者对消息通道的轮询实现的。

  • 消息的消费者 MessageHandler MessageHandler 也是一个函数式接口,实现 handleMessage 方法后,订阅 messageChannel 即可在发布消息后,收到消息并处理。
# 函数式接口支持 lambda 表达式
messageChannel.subscribe(msg -> { System.out.println("receive: " + msg)});

以上就是 Messaging 的主要功能。


String Integration

Spring Integration 提供了 Spring 编程模型的扩展,是对 Spring Messaging 的扩展。 在开发过程中,我们可能会遇到几个场景:

  1. 广播发送消息
  2. 非阻塞发送消息
  3. 确保消息被消费
  4. 单播发送消息

Integration 内置了许多实现了不同功能的 Channel 来应对我们开发过程中遇到的问题。我们看看这些场景,Integration 是如何解决的:

消息通道分发器接口/父类生产者消费者特点
PublishSubscribeChannel广播SubscribeChannel阻塞直到消息分发完异步消费信息广播
QueueChannel单播PollableChannel队列不满非阻塞异步消费信息异步非阻塞
RendezvousChannel单播QueueChannel队列不满非阻塞异步消费信息确保消息被消费才能发送消息
DirectChannel单播SubscirbeChannel阻塞直到消息分发完异步消费信息负载均衡
  1. PublishSubscribeChannel 是 SubscribeChannel 的实现类。在消费者订阅时,在内部将通过 OrderedAwareCopyOnWriteArraySet 这个数据结构将 Message 的消费者,也就是 MessageHandler 存储下来。当消息到达时,会通过内置的 BroadcastingDispatcher 消息分发器,根据优先级进行广播。

OrderedAwareCopyOnWriteArraySet 是对 CopyOnWriteArraySet 的增强,内置了 OrderComparator 对象,他可以对泛型元素进行排序(元素需要实现 Ordered 接口),并且使用了 ReetrantReadWriteLock 保证他的强一致性。

  1. QueueChannel 默认在内部通过 LinkedBlockingQueue 实现对消息的接收,也可以设置为其他的队列。对于阻塞队列和非阻塞队列,QueueChannel 有着不同的策略。
  • 阻塞队列是线程安全的,QueueChannel 仅在队列满时会阻塞消息生产者,仅在队列空时会阻塞消息消费者。
  • 非阻塞队列是线程不安全的,虽然 QueueChannel 内部使用了 Semaphore,在这里可以视 Semaphore 为减少轮询的策略。QueueChannel 在队列满时不会阻塞消息生产者,而是交给队列执行饱和策略(不同的队列有不同的饱和策略,也许是扩容,也许是直接丢弃)。在队列空时,QueueChannel 将通过轮询执行。
  • 在生产过程中,建议选择阻塞队列实现消息的接收与发送。
  1. RendezvousChannel 是 QueueChannel 的子类,唯一的不同是他仅使用 SynchronousQueue 作为内置队列。由于 SynchronousQueue 的特性,可以确保消息发送到消费者后才能继续往消息通道中发送消息。
  2. DirectChannel 属于 SubscribeChannel 的一种,它和 PublishSubscribeChannel 类似,拥有众多的订阅者。但他发送消息时,仅选择一名订阅者发送。所以 DirectChannel 实质上是一个单播的消息通道,内部是通过 UnicastingDispatcher 这个消息分发器实现单播的。而这个消息分发器内置 LoadBalancingStrategy 负载均衡器策略,在默认情况下采用轮询的负载均衡策略。

Spring Cloud Stream

开发中遇到的场景可能远不止这些,上面的消息通道在单机多线程场景下可以满足大多数的需求,但在分布式场景下,还需要做另外的增强。 分布式场景下,使用消息队列作为消息通道已经成为实现异步、削峰、解耦问题的常见解决方案。 Spring Cloud Stream 是 Spring Integration 的增强,它屏蔽了消息中间件的实现细节,并以统一的一套 API 来进行消息的发送/消费,消息中间件的实现细节由各消息中间件的 Binder 完成。 Binder 是提供与外部消息中间件继承的组建,为构造 Binding 提供了两个方法,分别是 bindConsumer 和 bindProducer,分别用于消费者和生产者。 Binding 是连接应用程序和消息中间件的桥梁。