我的系统有两种不同类型的消息 - 类型A和B.每条消息都有不同的结构 - 类型A包含int成员,类型B包含双成员。我的系统需要将两种类型的消息传递给众多业务逻辑线程。减少延迟非常重要,因此我正在研究使用Disruptor以机械同情的方式将消息从主线程传递到业务逻辑线程。
我的问题是破坏者只接受环形缓冲区中的一种对象。这是有道理的,因为破坏程序在环形缓冲区中预先分配对象。但是,它也很难通过Disruptor将两种不同类型的消息传递给我的业务逻辑线程。据我所知,我有四个选择:
将破坏程序配置为使用包含固定大小字节数组的对象(按How should one use Disruptor (Disruptor Pattern) to build real-world message systems?的建议)。在这种情况下,主线程必须在将消息发布到破坏程序之前将消息编码为字节数组,并且每个业务逻辑线程必须在接收时将字节数组解码回对象。这种设置的缺点是业务逻辑线程并不真正从破坏者共享内存 - 而是从破坏者提供的字节数组创建新对象(从而创建垃圾)。这种设置的好处是所有业务逻辑线程都可以从同一个破坏者中读取多种不同类型的消息。
将破坏者配置为使用单一类型的对象,但创建多个破坏者,每个对象类型一个。在上面的例子中,将有两个单独的破坏程序 - 一个用于类型A的对象,另一个用于类型B的对象。此设置的优点是主线程不必将对象编码为字节数组并且business less逻辑线程可以共享与disruptor中使用的相同的对象(没有创建垃圾)。这种设置的缺点是,不知何故,每个业务逻辑线程都必须订阅来自多个破坏者的消息。
将破坏者配置为使用单一类型的“超级”对象,其中包含消息A和B的所有字段。这非常违反OO样式,但会允许妥协选项#1和#2之间。
将破坏者配置为使用对象引用。但是,在这种情况下,我失去了对象预分配和内存排序的性能优势。
你对这种情况有什么建议?我认为选项#2是最干净的解决方案,但我不知道消费者是否或如何从技术上订阅来自多个破坏者的消息。如果任何人都可以提供如何实现选项#2的示例,那将非常感谢!
答案 0 :(得分:3)
配置破坏程序以使用包含固定大小字节数组的对象 (按照如何使用Disruptor(Disruptor Pattern)构建的建议 现实世界的消息系统?)。在这种情况下,主线程必须编码 将消息发布到disruptor和每个之前的消息到字节数组 业务逻辑线程必须将字节数组解码回对象 收到后。这种设置的缺点是业务逻辑线程 不是真的与破坏者分享记忆 - 而是他们 从字节数组创建新对象(从而创建垃圾) 由破坏者提供。这种设置的好处是所有业务 逻辑线程可以从同一个读取多种不同类型的消息 破坏者。
这将是我的首选方法,但我的颜色略有不同 用例,几乎我们使用Disruptor的每个地方 接收或发送到某种I / O设备,所以我们的 基本货币是字节数组。你可以绕过对象创建 通过使用flyweight方法进行编组。看一个例子 这个,我在一个例子中使用了Javolution的Struct和Union类 我在Devoxx(https://github.com/mikeb01/ticketing)上发表了演讲。如果你 可以在从onEvent返回之前完全处理对象 从事件处理程序调用然后这种方法很好。如果 事件需要超越这一点,然后你需要做一些事情 数据副本,例如,将其反序列化为一个对象。
将disruptor配置为使用单一类型的对象但创建多个 干扰器,每个对象类型一个。在上面的情况下,会有 两个独立的破坏者 - 一个用于A类对象,另一个用于对象 这种设置的优点是主线程不必 将对象编码为字节数组,业务较少的逻辑线程可以 共享与破坏者中使用的相同的对象(没有创建垃圾)。该 这种设置的缺点是每个业务逻辑线程都会有 订阅来自多个破坏者的消息。
没试过这种方法,你可能需要一个自定义的EventProcessor 可以从多个环缓冲区轮询。
将disruptor配置为使用包含的单一类型的“超级”对象 消息A和B的所有字段。这非常违反OO风格,但会 允许在选项#1和#2之间进行折衷。 配置破坏程序以使用对象引用。但是,在这种情况下我 失去对象预分配和内存排序的性能优势。
我们已经在一些情况下做到这一点,其中一些情况缺乏 预分配是可以容忍的。它工作正常。如果你路过 对象然后你需要确保你一旦将它们清空 在消费者方面与他们完成。我们发现使用双 “超级”对象的调度模式使实现公平 清洁。这样做的一个缺点是你会稍微长一些 GC将那些直接对象的东西停在那里 GC在标记阶段有更多活动对象可以遍历。
你对这种情况有什么建议?我觉得选项#2是 最干净的解决方案,但我不知道消费者是否或如何在技术上 订阅来自多个破坏者的消息。如果有人可以提供 如何实现选项#2的例子,非常感谢!
另一个选择,如果你想要完全的灵活性 使用数据,是不使用环形缓冲区,而是直接对话 到你最好看的定序器并定义你的对象布局 配件。
答案 1 :(得分:1)
Ben Baumgold,我相信你现在找到了解决方案。您可以通过创建事件持有者来轻松实现#4(或#3)。把它想象成对象的枚举。为了加速查找,应该使用枚举类型来丰富事件。请注意,我在持有者中存储对原始事件的引用。在插入环形缓冲区时创建复制构造函数或clone()并复制事件可能更合适。
以示例说明:
//这是事件中使用的枚举
public enum MyEventEnum {
EVENT_TIMER,
EVENT_MARKETDATA;
}
//这是持有人。在任何时候, ringbuffer中的这个实例只保存一个由array [type.ordinal()] 索引的事件。为什么数组应该从代码中显而易见。
public class RingBufferEventHolder {
private MyEventEnum;
private EventBase array[];
public RingBufferEventHolder() {
array=new EventBase[MyEventEnum.values().length];
}
// TODO: null the rest
public void setEvent(EventBase event) {
type=event.getType();
switch( event.getType() ) {
case EVENT_TIMER:
array[MyEventEnum.EVENT_TIMER.ordinal()]=event;
break;
case EVENT_MARKETDATA:
array[MyEventEnum.EVENT_MARKETDATA.ordinal()]=event;
break;
default:
throw new RuntimeException("Unknown event type " + event );
}
}
//发布活动
EventBase newEvent=new EventMarketData(....);
// prepare
long nextSequence = ringBuffer.next();
RingBufferEventHolder holder = ringBuffer.get(nextSequence);
holder.setEvent(newEvent);
// make the event available to EventProcessors
ringBuffer.publish(nextSequence);
答案 2 :(得分:0)
与 Vortex 的答案太相似,但在保留子事件方面有所不同。 它是#3 和#4 的混合体。如果我可以管理业务逻辑的复杂性,我会去#2 多个干扰器。
与基于数组的枚举事件类型实现相比,主要关注的是不同事件类型的共享对象类型。
public enum ExchangeEventType{
PLACE_ORDER, // -> OrderEvent
CANCEL_ORDER, // -> OrderEvent
MARKET_FEED, // -> MarketEvent
MARKET_UPDATE, // -> MarketEvent
ADD_USER, // -> AccountEvent
SUSPEND_USER, // -> AccountEvent
RESUME_USER // -> AccountEvent
}
public ExchangeEvent{
private EventType type;
private EventResultCode resultCode;
private long timestamp;
// event type objects
private OrderEvent orderEvent;
private MarketEvent marketEvent;
private AccountEvent accountEvent;
}
在业务逻辑中,多个处理器消耗并产生多种类型的事件,因此我有意识地选择不使用单独的干扰器进行权衡。
例如;
OrderEvent
和 AccountEvent
MarketEvent
和 OrderEvent