LMAX
这个架构模式的来源,请参看 The LMAX Architecture。采用事件溯源后,系统的性能得到了极大的增强。但是每次命令到达的时候,需要通过领域事件和快照重建聚合,这个过程依旧耗费性能。为了追求极致的性能,于是有了LMAX这种架构。在采用CQRS、事件溯源的基础上,将聚合常驻内存中,避免了反复重建聚合的开销,从而更进一步提升了性能。
一个聚合有多个实例常驻多个服务器内存中,如何保存一致性?
为了避免同一个聚合在多个服务器上有多个内存中的实例,导致数据不一致,LMAX将某个聚合实例常驻在一个服务器节点上。
那多线程访问同一个聚合对象,会引发数据不一致问题吧?
不允许多线程访问同一个聚合对象。当并发请求到达服务器时,请求进入队列,由线程串行化的执行聚合。
那聚合数量那么多,每个聚合一个线程,岂不是占用线程太多?
一个聚合对象不能在多线程中访问,但是一个线程可以执行聚合的代码。
多个聚合共用一个线程,一个聚合的代码阻塞了线程,岂不是降低了整体并发度?
因此,LMAX架构中,不允许阻塞线程。
所以LMAX必须采用Reactor模式?
可以采用reactor模式,也可以采用go 和 java 的虚拟线程这样的技术。
采用Ractor的DDD项目,就是LMAX?
不是。LMAX的关键点是聚合常驻内存。不做到聚合常驻内存,即使使用reactor,也不是LMAX。
如何确保不会因为聚合太多,导致服务器内存溢出?
聚合的数量是有限可控的,分区后放到不同的服务器上。如果以后聚合增多,就对应的增加更多服务器节点。
如果服务器故障,会丢失数据吗?
不会。因为采用了事件溯源,领域事件已经持久化了,重启服务器后,将会重新重建聚合。
如何实现高可用?
冗余应用服务器。比如,一主一备两个服务器组成一个分区,备份机器监听主机的领域事件,同步更新内存中聚合的状态。当主机不可用时,切换到备份机器上去处理请求。
LMAX如何使用数据库事务?
不使用数据库事务。由于只持久化领域事件,已经没有必要采用数据库事务了。一个命令只变更一个聚合,一个聚合产生的领域事件合并到一起记录下来。如果记录失败,那么这些事件就当都没有发生,重建这个聚合,回退到之前的状态,依旧是一致的。
执行一个命令过程中,先修改聚合,后发现违反业务规则,怎么回滚之前的聚合状态和发出来的领域事件?
- 实现内存事务机制 命令达到时,把聚合先建内存快照,暂存领域事件到临时队列,在业务逻辑执行异常时,通过快照恢复聚合状态,清除暂存领域事件。如果允许一个命令有多个聚合执行,并且允许监听领域事件再执行命令,这个过程将非常复杂。
- 所有校验通过后,才变更聚合 严格分析业务逻辑,所有可能导致失败的校验都放到前面执行,只有确保万无一失后,才变更聚合发布领域事件。这种方式实现简单,但是有时不符合直觉,打破的代码的封装。
LMAX适用哪些场景?
- 需要高性能
- LMAX本来就是为了高兴能而生,如果没有这个需求,没有必要采用这个架构
- 容易把聚合分区
- 聚合粒度更大,减少聚合间交互,最好一个命令只能操作一个聚合
- 聚合数量有限可控
- 对于可能会短时间内快速产生很多聚合的场景,不合适LMAX
- 聚合总体活跃
- 聚合常驻内存,但是很多聚合都是比较冷的,没有多少访问量,那就是对内存的浪费