领域事件 disruptor 使用场景之实现Spring事件驱动模型 ApplicationEvent

发表时间:2024-11-07 09:09

前言

Disruptor 是一个开源的并发框架。由英国外汇交易公司LMAX开发的一个高性能队列,并且大大的简化了并发程序开发的难度,获得2011Duke’s程序框架创新奖。

假设场景

假设有这么一种业务场景,业务为【用户注册】处理完后,同时触发【邮件通知】业务、【赠送积分】业务的执行,在不利用MQ的情况下,会有什么样的解决思路?可能的解决思路有如下

  1. 业务【用户注册】处理后,开启线程处理【邮件通知】、【赠送积分】的业务

  2. 使用disruptor进行处理

  3. 生产消费者模式

  4. 观察者模式 … 解决的思路有很多。

本文就介绍的是基于disruptor实现的,类似Spring事件驱动模型 ApplicationEvent,这里称为领域事件。事件驱动模式与观察者模式在某些方面极为相似;当一个主体发生改变时,所有依属体都得到通知。不过,观察者模式与单个事件源关联,而事件驱动模式则可以与多个事件源关联。下面先看一下对于领域事件的介绍。

简介 Event Source 领域事件

  1. 领域驱动设计,基于LMAX架构。

  2. 单一职责原则,可以给系统的可扩展、高伸缩、低耦合达到极致。

  3. 异步高并发、线程安全的、使用disruptor环形数组来消费业务。可并发执行,性能超高,执行1000W次事件只需要1.1秒左右(这个得看你的电脑配置)。

  4. 使用事件消费的方式编写代码,使得业务在复杂也不会使得代码混乱,维护代码成本更低

  5. 可灵活的定制业务线程模型

  6. 插件形式提供事件领域,做到了可插拔,就像玩乐高积木般有趣。

领域事件的本质是对disruptor的封装使用。

使用场景

计算密集型的业务推荐使用,比如斗地主中的出牌,所有牌桌可使用同一个领域事件来处理,但牌局结算时如果涉及IO (类似 DB 的入库时 ),就可切换到其他的领域事件,这样就可以不阻塞出牌业务线程。

个领域事件可以看成是一个线程,那么也就是说,我们可以用一个出牌的领域事件,做计算密集型的出牌业务,用N个结算的领域事件来做结算业务。可以举例不太恰当,但大概就是这个意思。

下面的示例中也介绍了其他的使用场景,类似Spring事件驱动模型 ApplicationEvent。

图解

在使用的角度,程序员只需要关注两件事

  1. 定义业务数据载体(领域消息实体

  2. 处理该业务数据载体的事件 (领域事件)

示例

自定义领域消息实体

  1. 定义领域实体 – 并实现 Eo 接口

  2. Eo 接口是框架提供, 领域实体类用户随意编写, 建议以Eo结尾.

  3. 领域消息实体的对象字段随意定制 (根据你的业务)

/** * 领域消息 - 学生 * <pre> *     推荐定义领域事件实体类的时候都使用final *     避免某个领域事件对该实体进行数据修改 * </pre> */public record StudentEo(int id) implements Eo {}

record 是值类型,好像是 java14 的出的(具体忘记的),转为java类的大概意思就是类中声明了一个属性 id,并自动提供 getter 方法。

定义领域事件

用于处理 StudentEo 领域消息实体

// 事件处理类,事件消费, 实现领域事件消费接口。一个事件消费类只处理一件事件(单一职责原则)public final class StudentEmailEventHandler1 implements DomainEventHandler {    @Override    public void onEvent(StudentEo studentEo, boolean endOfBatch) {        log.debug("给这个学生发送一个email消息: {}", studentEo);    }}

测试用例

public class StudentDomainEventTest {    DomainEventContext domainEventContext;    @After    public void tearDown() throws Exception {        // 事件消费完后 - 事件停止        domainEventContext.stop();    }    @Before    public void setUp() {        // ======项目启动时配置一次(初始化)======        // 领域事件上下文参数        DomainEventContextParam contextParam = new DomainEventContextParam();        // 配置一个学生的领域事件消费 - 给学生发生一封邮件        contextParam.addEventHandler(new StudentEmailEventHandler1());        // 配置一个学生的领域事件消费 - 回家        // contextParam.addEventHandler(new StudentGoHomeEventHandler2());        // 配置一个学生的领域事件消费 - 让学生睡觉        // contextParam.addEventHandler(new StudentSleepEventHandler3());        // 启动事件驱动        domainEventContext = new DomainEventContext(contextParam);        domainEventContext.startup();    }    @Test    public void testEventSend() {        // 这里开始就是你的业务代码        StudentEo studentEo = new StudentEo(1);        /*         * 发送事件、上面只配置了一个事件。         * 如果将来还需要给学生发送一封email,那么直接配置。(可扩展)         * 如果将来还需要记录学生今天上了什么课程,那么也是直接配置 (可扩展) 这里的业务代码无需任何改动(松耦合)         * 如果将来又不需要给学生发送email的事件了,直接删除配置即可,这里还是无需改动代码。(高伸缩)         */        studentEo.send();    }}

当使用上领域事件后,可以在不改变原有业务代码,就可以添加将来新增的业务逻辑。如上面示例代码中的

  • 如果将来还需要记录学生今天上了什么课程,那么也是直接配置 (可扩展) 这里的业务代码无需任何改动(松耦合)

  • 如果将来又不需要给学生发送email的事件了,直接删除配置即可,这里还是无需改动代码。(高伸缩)

在回到开头的【用户注册】处理完后的业务。我们只需要预留一个【用户注册】的Eo,将来如果有新增业务如:

  • 【邮件通知】业务

  • 【赠送积分】业务

各种 XXX 等新业务,我们也不需要改动【用户注册】这块的代码。只要单一职责原则,我们的代码维护性会非常的高。

总结

使用基于 disruptor 的领域驱动设计的领域事件,可以使得我们的代码有如下好处:

  1. 单一职责原则,可以给系统的可扩展、高伸缩、低耦合达到极致。

  2. 异步高并发

  3. 业务在复杂也不会使得代码混乱,维护代码成本更低

  4. 插件形式提供事件领域,做到了可插拔,就像玩乐高积木般有趣。

源码参考地址:light-domain-event 模块


文章分类: 移动端
分享到:
联系我们
服务热线
深圳公司:深圳市龙华区明珠支路源创空间科技园南区2栋503、605

东莞公司:东莞市塘厦镇四村八达高新产业园9栋1楼

7*24全国服务热线
0755-8320 8959 紧急联系 :13632796137