Warm tip: This article is reproduced from serverfault.com, please click

java-如何使用具有多种消息类型的干扰器

(java - How to use a disruptor with multiple message types)

发布于 2013-05-30 21:12:20

我的系统有两种不同类型的消息 - 类型 A 和 B。每条消息都有不同的结构 - 类型 A 包含一个 int 成员,类型 B 包含一个 double 成员。我的系统需要将这两种类型的消息传递给众多业务逻辑线程。减少延迟非常重要,因此我正在研究使用 Disruptor 以一种机械上同情的方式将消息从主线程传递到业务逻辑线程。

我的问题是干扰器只接受环形缓冲区中的一种类型的对象。这是有道理的,因为干扰器预先分配了环形缓冲区中的对象。但是,这也使得通过 Disruptor 将两种不同类型的消息传递到我的业务逻辑线程变得困难。据我所知,我有四个选择:

  1. 配置干扰程序以使用包含固定大小的字节数组的对象(如“如何使用干扰程序(Disruptor Pattern)来构建真实的消息系统?”中所建议)。在这种情况下,主线程必须在将消息发布到干扰器之前将消息编码为字节数组,并且每个业务逻辑线程必须在收到时将字节数组解码回对象。这种设置的缺点是业务逻辑线程并没有真正共享来自破坏者的内存——而是从破坏者提供的字节数组中创建新对象(从而创建垃圾)。这种设置的好处是所有业务逻辑线程都可以从同一个干扰器读取多种不同类型的消息。

  2. 将干扰器配置为使用单一类型的对象,但会创建多个干扰器,每个对象类型对应一个。在上面的例子中,会有两个独立的干扰器——一个用于类型 A 的对象,另一个用于类型 B 的对象。这种设置的好处是主线程不必将对象编码为字节数组,并且业务较少的逻辑线程可以共享与破坏者中使用的相同的对象(不创建垃圾)。这种设置的缺点是,不知何故,每个业务逻辑线程都必须订阅来自多个破坏者的消息。

  3. 将破坏者配置为使用包含消息 A 和 B 的所有字段的单一类型的“超级”对象。这非常违反 OO 风格,但将允许选项 #1 和 #2 之间的折衷。

  4. 配置干扰程序以使用对象引用但是,在这种情况下,我失去了对象预分配和内存排序的性能优势。

对于这种情况,你有什么建议?我觉得选项#2 是最干净的解决方案,但我不知道消费者是否或如何从技术上订阅来自多个破坏者的消息。如果有人可以提供如何实施选项 #2 的示例,将不胜感激!

Questioner
Ben Baumgold
Viewed
0
gtzinos 2015-05-19 00:16:02

将干扰器配置为使用包含固定大小字节数组的对象(如 How should one use Disruptor (Disruptor Pattern) to build real-world message systems? 推荐的那样)。在这种情况下,主线程必须在将消息发布到干扰器之前将消息编码为字节数组,并且每个业务逻辑线程必须在收到时将字节数组解码回对象。这种设置的缺点是业务逻辑线程并没有真正共享来自破坏者的内存——而是从破坏者提供的字节数组中创建新对象(从而创建垃圾)。这种设置的好处是所有业务逻辑线程都可以从同一个干扰器读取多种不同类型的消息。

这将是我的首选方法,但我对我们的用例略有不同,几乎每个我们使用 Disruptor 的地方都是从某种 I/O 设备接收或发送到某种 I/O 设备,所以我们的基本货币是字节数组。你可以通过使用享元方法进行编组来绕过对象创建。要查看此示例,我在 Devoxx 上展示的示例中使用了 Javolution 的 Struct 和 Union 类(https://github.com/mikeb01/ticketing)。如果你可以在从事件处理程序的 onEvent 调用返回之前完全处理该对象,那么这种方法效果很好。如果事件需要超过那个点,那么你需要制作某种数据副本,例如将其反序列化为一个对象。

将干扰程序配置为使用单一类型的对象,但创建多个干扰程序,每种对象类型一个。在上面的例子中,会有两个独立的干扰器——一个用于类型 A 的对象,另一个用于类型 B 的对象。这种设置的好处是主线程不必将对象编码为字节数组,并且业务较少的逻辑线程可以共享与破坏者中使用的相同的对象(不创建垃圾)。这种设置的缺点是,不知何故,每个业务逻辑线程都必须订阅来自多个破坏者的消息。

没有尝试过这种方法,你可能需要一个可以从多个环形缓冲区轮询的自定义 EventProcessor。

将破坏者配置为使用包含消息 A 和 B 的所有字段的单一类型的“超级”对象。这非常违反 OO 风格,但将允许选项 #1 和 #2 之间的折衷。将干扰器配置为使用对象引用。但是,在这种情况下,我失去了对象预分配和内存排序的性能优势。

我们已经在几种情况下这样做了,在某些情况下,缺少预分配是可以容忍的。它工作正常。如果你正在传递对象,那么你需要确保在消费者端完成它们后将它们清零。我们发现对“超级”对象使用双重调度模式可以保持实现相当干净。这样做的一个缺点是,由于 GC 在标记阶段有更多的活动对象要遍历,因此它会导致 GC 停顿时间稍长一些,因为它是对象的直接数组。

对于这种情况,你有什么建议?我觉得选项#2 是最干净的解决方案,但我不知道消费者是否或如何从技术上订阅来自多个破坏者的消息。如果有人可以提供如何实施选项 #2 的示例,将不胜感激!

另一种选择是,如果你希望在使用数据方面具有完全的灵活性,则不使用环形缓冲区,而是直接与 Sequencer 对话并定义你认为最合适的对象布局。