是否应该将 Disruptor (LMAX) 与内存和 CQRS 中的大模型一起使用?

Posted

技术标签:

【中文标题】是否应该将 Disruptor (LMAX) 与内存和 CQRS 中的大模型一起使用?【英文标题】:Should one use Disruptor (LMAX) with a big model in memory and CQRS? 【发布时间】:2012-10-07 11:16:36 【问题描述】:

出于性能原因,我们的系统将结构化模型(大约 30 个具有多种关系的不同实体)完全保存在内存中(大约 10 Gb)。 在这个模型上,我们要做 3 种操作:

    更新一个或几个实体 查询特定数据(这通常需要读取数千个实体) 获取统计数据(使用了多少内存、多少种查询等)

目前该体系结构是一种相当标准的体系结构,具有用于使用共享模型的 servlet 的线程池。 在模型内部有很多并发集合,但仍然有很多等待,因为一些实体“更热”并且大多数线程想要读/写它们。 另请注意,通常查询比写入更耗费 CPU 和时间。

我正在研究切换到 Disruptor 架构的可能性,将模型保持在一个线程中,将所有可能的东西(有效性检查、审计等)从模型中移出一个单独的消费者。

第一个问题当然是:这有意义吗?

第二个问题是:理想情况下,写请求应该优先于读请求。在颠覆者中获得优先权的最佳方式是什么?我在考虑 2 个环形缓冲区,然后尝试从高优先级的缓冲区读取比从低优先级的缓冲区读取的频率更高。

澄清这个问题比关于 LMAX Disruptor 的实际代码更具架构性。

更新更多细节

数据是一个复杂的领域,许多不同类型(约 20 个)的实体(>100k)在它们之间以具有许多不同集合的树状结构链接。

查询通常涉及遍历数千个实体以找到正确的数据。 更新很频繁,但非常有限,比如一次只有 10 个实体,所以在整个数据中变化不大(比如每小时 20%)。

我做了一些初步测试,发现并行查询模型的速度优势超过了偶尔的写入锁延迟。

【问题讨论】:

嗨 Uberto - 你能补充一些细节吗?您正在运行哪种查询?实体的更新发生在相同的少数实体或许多不同的实体上?这些实体是相互关联的还是大部分是独立的?它们之间的关系如何? 关于问题 2:读取优先于写入,LMAX 自然适用于事件溯源,这表示您保留事件而不是那些模型,您当前的模型(或更优化的读取速度超快的模型)操作)仍然存在,但你永远不会改变,甚至发生在,如果你在写入之前有一个读取操作,你应该处理它以便得到它,以便在重放事件链时具有可重现的相同状态...所以在这种情况下,我这里的优先级是错误的,当两个线程写入集合时,你可能会这样做...... 【参考方案1】:

“理想情况下,写入请求应优先于读取请求。”

为什么?大多数像 C# ReaderWriterLockSlim 这样的快速锁都相反。写入需要阻止所有读取以避免部分读取。因此,这样的锁允许许多并发读取,希望事情变得“相当”,然后进行写入..(写入确实以队列中的编号运行,但很可能在锁定之前处理后进行了许多读取)..

优先写入是杀死并发的好方法..

最终并发/CQRS 是一种选择吗?

【讨论】:

假设读取查询的流程或多或少是恒定的,延迟写入没有任何好处。在读取之前更新模型具有一些业务优势。 是的 - 锁这样做的原因是他们希望读取突发停止它经常这样做,连续流到单个实体非常罕见(可能是你有一个表锁,它不是高性能的好主意),如果流量比你此时有严重的拥塞,应该看看其他的事情,比如服务读取副本/自旋锁等。我们正在谈论 1/10 秒的毫秒数对企业来说很重要,当来自 ORM 层的实体被同时修改和服务时,大多数企业甚至会遇到部分写入问题。 最简单的方法可能只是在内存中创建一个命令队列,然后每 1-10 毫秒将它们放入环形缓冲区,并首先将所有写入。破坏者不是多对多(好吧,我从来没有见过有人像那样使用它们,所以我想查看基准测试)..他们也不会从多个环形缓冲区读取,每个消费者(域)从一个环形缓冲区读取..虽然你可以从单个缓冲区读取多个域【参考方案2】:

LMAX 可能合适..

LMAX 的人首先实现了传统的,然后他们实现了actors(带有队列),发现actors 大部分时间都在队列中。然后他们去了单线程架构。现在破坏者不是架构的关键,关键是单线程 BL。使用 1 个编写器(单线程)和小对象,您将获得高缓存命中率并且没有争用。为此,他们必须将所有长时间运行的代码移出业务层(包括 IO)。现在为了做到这一点,他们使用了中断器,它基本上只是一个带有单个写入器的环形缓冲区,这已经在设备驱动程序代码中使用了一段时间,但消息规模很大。

首先我对此有一个不同意,LMAX 是一个演员系统..所有 BL 都有一个演员(并且破坏者连接其他演员)..他们可以显着改进演员系统而不是跳到 1 BL的演员,即

    不要有很多服务/演员,尽量在一个服务中有常用的组件。 (这在 SOA / 分布式系统中也一次又一次地出现) 在参与者之间进行通信时,使用点对点队列而不是多到 1。(就像所有服务总线一样!) 当您拥有点对点队列时,请确保尾部是指向单独内存区域的指针。 使用 2 和 3,您现在可以使用无锁队列,并且队列/线程只有 1 个写入器(您甚至可以不使用临时 256,而是使用 YMM 位写入队列)。然而,系统现在有更多线程(如果你正确地完成了 1,那么参与者之间的消息量相对较少)。队列类似于中断器,可以批量处理许多条目,并且可以使用环形缓冲区样式。

有了这些演员,你就有了一个更加模块化(因此也是主表)的系统(并且系统可以启动更多的演员来处理队列 - 请注意 1 作家!)

你的情况我认为一个小时内 20% 的变化是巨大的......查询总是在内存对象中吗?你有内存哈希表/索引吗?你可以使用只读集合吗?如果您的数据是旧的,这是否重要,例如 Ebay 对其项目集合使用 1 小时刷新,因此项目集合本身是静态的。使用静态集合和静态项目简介,它们有一个静态索引,您可以快速搜索和查找项目并且全部在内存中。每小时重建一次,完成后(重建可能需要几分钟)系统切换到新数据。请注意项目本身不是静态的。

在您的域很大的情况下,单个线程可能会获得较低的缓存命中率..这与 LMAX 不同,LMAX 的每个消息传递的域较小..

基于代理的系统可能是最好的选择,因为可以对一堆实体进行分组,因此缓存命中率很高。但我需要知道更多。例如,将有效性检查、审计、注销等移出可能是一个好计划。更少的代码 = 更小的对象 = 更高的缓存命中率并且 LMAX 对象很小。

希望这个快速转储有所帮助,但仅一眼就很难。

【讨论】:

以上是关于是否应该将 Disruptor (LMAX) 与内存和 CQRS 中的大模型一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

监控 LMAX Disruptor

LMAX Disruptor 最简单实际的示例代码

LMAX Disruptor Timeout EventHandler

LMAX Disruptor 依赖图/带有 SequenceBarrier 的门控

Log4j2 - java.lang.NoSuchMethodError: com.lmax.disruptor.dsl.Disruptor

LMAX Disruptor - 啥决定了批量大小?