领域事件 disruptor 使用场景之实现Spring事件驱动模型 ApplicationEvent
前言
Disruptor 是一个开源的并发框架。由英国外汇交易公司LMAX开发的一个高性能队列,并且大大的简化了并发程序开发的难度,获得2011Duke’s程序框架创新奖。
假设场景
假设有这么一种业务场景,业务为【用户注册】处理完后,同时触发【邮件通知】业务、【赠送积分】业务的执行,在不利用MQ的情况下,会有什么样的解决思路?可能的解决思路有如下
- 业务【用户注册】处理后,开启线程处理【邮件通知】、【赠送积分】的业务
- 使用disruptor进行处理
- 生产消费者模式
- 观察者模式 … 解决的思路有很多。
本文就介绍的是基于disruptor实现的,类似Spring事件驱动模型 ApplicationEvent,这里称为领域事件。事件驱动模式与观察者模式在某些方面极为相似;当一个主体发生改变时,所有依属体都得到通知。不过,观察者模式与单个事件源关联,而事件驱动模式则可以与多个事件源关联。下面先看一下对于领域事件的介绍。
简介 Event Source 领域事件
- 领域驱动设计,基于LMAX架构。
- 单一职责原则,可以给系统的可扩展、高伸缩、低耦合达到极致。
- 异步高并发、线程安全的、使用disruptor环形数组来消费业务。可并发执行,性能超高,执行1000W次事件只需要1.1秒左右(这个得看你的电脑配置)。
- 使用事件消费的方式编写代码,使得业务在复杂也不会使得代码混乱,维护代码成本更低。
- 可灵活的定制业务线程模型
- 插件形式提供事件领域,做到了可插拔,就像玩乐高积木般有趣。
领域事件的本质是对disruptor的封装使用。
使用场景
计算密集型的业务推荐使用,比如斗地主中的出牌,所有牌桌可使用同一个领域事件来处理,但牌局结算时如果涉及IO (类似 DB 的入库时 ),就可切换到其他的领域事件,这样就可以不阻塞出牌业务线程。
一个领域事件可以看成是一个线程,那么也就是说,我们可以用一个出牌的领域事件,做计算密集型的出牌业务,用N个结算的领域事件来做结算业务。可以举例不太恰当,但大概就是这个意思。
下面的示例中也介绍了其他的使用场景,类似Spring事件驱动模型 ApplicationEvent。
图解
在使用的角度,程序员只需要关注两件事
- 定义业务数据载体(领域消息实体)
- 处理该业务数据载体的事件 (领域事件)
示例
自定义领域消息实体
- 定义领域实体 – 并实现 Eo 接口
- Eo 接口是框架提供, 领域实体类用户随意编写, 建议以Eo结尾.
- 领域消息实体的对象字段随意定制 (根据你的业务)
/**
* 领域消息 - 学生
* <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 的领域驱动设计的领域事件,可以使得我们的代码有如下好处:
- 单一职责原则,可以给系统的可扩展、高伸缩、低耦合达到极致。
- 异步高并发
- 业务在复杂也不会使得代码混乱,维护代码成本更低
- 插件形式提供事件领域,做到了可插拔,就像玩乐高积木般有趣。
源码参考地址:light-domain-event 模块
最后
本文可以转载,但必须保留所有内容。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 领域事件 disruptor 使用场景之实现Spring事件驱动模型 ApplicationEvent
有个问题,如果事件发布后系统异常停机,有什么补偿措施吗?