Ben*_*old 16 java performance messaging multithreading disruptor-pattern
我的系统有两种不同类型的消息 - 类型A和B.每条消息都有不同的结构 - 类型A包含int成员,类型B包含双成员.我的系统需要将两种类型的消息传递给众多业务逻辑线程.减少延迟非常重要,因此我正在研究使用Disruptor以机械同情的方式将消息从主线程传递到业务逻辑线程.
我的问题是破坏者只接受环形缓冲区中的一种类型的对象.这是有道理的,因为破坏程序在环形缓冲区中预先分配对象.但是,它也很难通过Disruptor将两种不同类型的消息传递给我的业务逻辑线程.据我所知,我有四种选择:
配置破坏程序以使用包含固定大小字节数组的对象(如何使用Disruptor(Disruptor Pattern)来构建真实的消息系统?).在这种情况下,主线程必须在将消息发布到破坏程序之前将消息编码为字节数组,并且每个业务逻辑线程必须在接收时将字节数组解码回对象.这种设置的缺点是业务逻辑线程并不真正从破坏者共享内存 - 而是从破坏者提供的字节数组创建新对象(从而创建垃圾).这种设置的好处是所有业务逻辑线程都可以从同一个破坏者中读取多种不同类型的消息.
将破坏程序配置为使用单一类型的对象,但创建多个破坏程序,每个对象类型一个.在上面的例子中,将有两个单独的破坏程序 - 一个用于类型A的对象,另一个用于类型B的对象.此设置的优点是主线程不必将对象编码为字节数组并且business less逻辑线程可以共享与disruptor中使用的相同的对象(没有创建垃圾).这种设置的缺点是,不知何故,每个业务逻辑线程都必须订阅来自多个破坏者的消息.
配置破坏程序使用单一类型的"超级"对象,该对象包含消息A和B的所有字段.这非常违反OO样式,但允许在选项#1和#2之间进行折衷.
配置破坏程序以使用对象引用.但是,在这种情况下,我失去了对象预分配和内存排序的性能优势.
你对这种情况有什么建议?我认为选项#2是最干净的解决方案,但我不知道消费者是否或如何从技术上订阅来自多个破坏者的消息.如果有人可以提供如何实施选项#2的示例,那将非常感谢!
配置 Disruptor 以使用包含固定大小字节数组的对象(按照如何使用 Disruptor(Disruptor 模式)构建真实世界的消息系统推荐?)。在这种情况下,主线程必须在将消息发布到干扰器之前将消息编码为字节数组,并且每个业务逻辑线程必须在收到消息后将字节数组解码回对象。这种设置的缺点是业务逻辑线程并没有真正共享来自干扰器的内存 - 相反,它们从干扰器提供的字节数组创建新对象(从而创建垃圾)。这种设置的优点是所有业务逻辑线程都可以从同一个干扰器读取多种不同类型的消息。
这将是我的首选方法,但我对我们的用例略有不同,几乎在我们使用 Disruptor 的每个地方,它都会从某种 I/O 设备接收或发送到某种 I/O 设备,因此我们的基本货币是字节数组。您可以通过使用享元方法进行编组来绕过对象创建。为了查看这方面的示例,我在 Devoxx ( https://github.com/mikeb01/ticketing )上展示的示例中使用了 Javolution 的 Struct 和 Union 类。如果您可以在从事件处理程序的 onEvent 调用返回之前完全处理该对象,那么这种方法效果很好。如果事件需要持续超过该时间点,那么您需要制作某种数据副本,例如将其反序列化为对象。
将干扰器配置为使用单一类型的对象,但创建多个干扰器,每个干扰器对应一种对象类型。在上面的情况下,将有两个单独的干扰器 - 一个用于类型 A 的对象,另一个用于类型 B 的对象。这种设置的优点是主线程不必将对象编码为字节数组,并且业务较少的逻辑线程可以共享与干扰器中使用的相同的对象(不会创建垃圾)。这种设置的缺点是,每个业务逻辑线程都必须订阅来自多个干扰器的消息。
没有尝试过这种方法,您可能需要一个可以从多个环形缓冲区轮询的自定义事件处理器。
将干扰器配置为使用单一类型的“超级”对象,其中包含消息 A 和 B 的所有字段。这非常违反 OO 风格,但允许在选项 #1 和 #2 之间进行折衷。配置干扰器以使用对象引用。但是,在这种情况下,我失去了对象预分配和内存排序的性能优势。
我们已经在一些情况下这样做了,其中一些缺乏预分配的情况是可以容忍的。效果还不错。如果您要传递对象,那么您需要确保在消费者端完成处理后将它们清空。我们发现,对“超级”对象使用双重调度模式可以使实现相当干净。这样做的一个缺点是,与直接对象数组相比,GC 停顿时间会稍长一些,因为 GC 在标记阶段有更多活动对象需要遍历。
对于这种情况你有什么建议?我认为选项#2 是最干净的解决方案,但我不知道消费者是否或如何从技术上订阅来自多个破坏者的消息。如果有人可以提供如何实现选项#2 的示例,我们将不胜感激!
如果您希望在数据使用方面具有完全的灵活性,另一种选择是不使用环形缓冲区,而是直接与 Sequencer 对话并定义您认为最合适的对象布局。