告别 Redis/MQ —— ionet 分布式事件总线实战

一个熟悉的场景

你刚接到一个需求:玩家登录后,系统需要做以下事情------

  1. 记录登录时间
  2. 发放离线奖励
  3. 发送欢迎邮件
  4. 推送今日活动信息
  5. 更新排行榜在线状态

你的第一反应可能是:上 Redis pub/sub 或 MQ。

但这意味着:安装 Redis/RabbitMQ 服务、维护连接池、处理消息序列化、配置消费者组、担心消息丢失......原本一个简单的"通知"需求,变成了一场中间件战争。

ionet 的答案是:不需要安装任何东西。


三个概念,全部学完

ionet 的分布式事件总线只有 3 个概念:

概念 说明 类比
EventSource(事件源) 业务数据载体 MQ 中的消息体
Subscriber(订阅者) 接收事件并处理业务 MQ 中的消费者
Fire(发布事件) 发送事件给订阅者 MQ 中的 publish

学完这三个概念,你就可以开始使用了。


实战:玩家登录事件

第一步:定义事件源

java 复制代码
@ProtobufClass
public class UserLoginEventMessage {
    public long userId;

    public static UserLoginEventMessage of(long userId) {
        var message = new UserLoginEventMessage();
        message.userId = userId;
        return message;
    }
}

事件源需要添加 @ProtobufClass 注解,因为事件可能跨进程传输,需要序列化支持。

第二步:编写订阅者

java 复制代码
@EventBusSubscriber
public class EmailEventBusSubscriber {
    @EventSubscribe
    public void mail(UserLoginEventMessage message) {
        log.info("发送欢迎邮件给用户 {}", message.userId);
        // 实际业务:调用邮件系统发送欢迎邮件
    }
}

@EventBusSubscriber
public class RewardEventBusSubscriber {
    @EventSubscribe
    public void calcReward(UserLoginEventMessage message) {
        log.info("计算离线奖励给用户 {}", message.userId);
        // 实际业务:查询上次登录时间,计算奖励
    }
}

订阅者的编写规则非常简单:

  1. 方法必须是 public void
  2. 方法只能有一个参数(事件源)
  3. 方法需要添加 @EventSubscribe 注解

第三步:注册订阅者到逻辑服

java 复制代码
public final class EmailLogicServer implements LogicServer {
    @Override
    public void settingBarSkeletonBuilder(BarSkeletonBuilder builder) {
        builder.addRunner((EventBusRunner) (eventBus, _) -> {
            eventBus.register(new EmailEventBusSubscriber());
        });
    }
}

第四步:在业务中发布事件

java 复制代码
@ActionMethod(0)
private UserMessage login(FlowContext flowContext, LoginMessage message) {
    // 处理登录逻辑...

    // 发布登录事件
    var event = UserLoginEventMessage.of(flowContext.getUserId());
    flowContext.fire(event);

    return new UserMessage("登录成功");
}

完成。 就这么简单。登录方法只关心登录本身,邮件、奖励等逻辑在各自的订阅者中实现,分散在不同的逻辑服中。


四种发布粒度

ionet 提供了 4 种事件发布方式,适用于不同场景:

fireMe:仅当前逻辑服

java 复制代码
flowContext.fireMe(event);    // 异步
flowContext.fireMeSync(event); // 同步

只通知当前逻辑服的 EventBus 中的订阅者。适合逻辑服内部的模块解耦。

fireLocal:当前进程所有逻辑服

java 复制代码
flowContext.fireLocal(event);
flowContext.fireLocalSync(event);

如果用户逻辑服和邮件逻辑服在同一进程中启动,fireLocal 会通知两者的订阅者,但不会通知其他进程的订阅者。

fire:全局发布

java 复制代码
flowContext.fire(event);
flowContext.fireSync(event);

所有订阅者都能收到------包括当前进程的、其他进程的、甚至其他机器的。

fireAny:同类型只发一个

java 复制代码
flowContext.fireAny(event);
flowContext.fireAnySync(event);

当有多个同类型逻辑服实例(比如 3 个邮件逻辑服)时,fireAny 只会给其中一个发送事件。适合"只需要一个实例处理"的场景,如发放奖励------防止重复发放。


杀手级特性:零订阅者 = 零开销

这是 ionet EventBus 最精妙的设计:

如果没有任何远程订阅者,将不会触发网络请求。

这意味着什么?

假设你在登录业务中埋了一个事件发布点。即使所有相关的逻辑服都没有上线(没有任何订阅者),这行 fire 代码也不会产生任何网络开销。它只是在内存中检查:有订阅者吗?没有。结束。

这是 Redis pub/sub 和 MQ 做不到的------它们无论有没有消费者,消息都会发送到中间件服务器。


高级用例:可插拔的临时活动

利用"零订阅者 = 零开销"这个特性,你可以实现可插拔的业务逻辑。

场景:春节活动期间,玩家每日在线 60 分钟奖励一些物品。

实现方式:

  1. 在登录 Action 中发布事件(这个代码永远不变)
  2. 为活动创建一个独立的【春节活动逻辑服】,注册活动订阅者
  3. 活动期间:启动【春节活动逻辑服】→ 订阅者上线 → 开始处理事件
  4. 活动结束:关闭【春节活动逻辑服】→ 订阅者消失 → 零开销

你不需要修改任何主业务代码。 只是通过启动或关闭一个逻辑服,就实现了功能的上线和下线。

同样的思路还可以用于:

  • 热更:通过事件批量推送新的配置到所有逻辑服实例
  • 全局配置更新:GM 后台发布配置变更事件
  • 统计数据收集:单独的统计逻辑服订阅各种业务事件

订阅者线程执行器

对于有并发安全要求的场景,ionet 允许你为订阅者指定线程执行器:

java 复制代码
@EventBusSubscriber
public class MySubscriber {
    // 默认策略:使用用户线程执行器(与 Action 共用线程,线程安全)
    @EventSubscribe
    public void onLogin(UserLoginEventMessage message) { ... }

    // 虚拟线程策略:适合 IO 密集型操作
    @EventSubscribe(ExecutorSelector.userVirtualExecutor)
    public void saveToDB(UserLoginEventMessage message) { ... }

    // 方法级线程策略:同一订阅者方法永远在同一线程
    @EventSubscribe(ExecutorSelector.methodExecutor)
    public void updateRank(UserLoginEventMessage message) { ... }
}

5 种可选策略:userExecutor(默认)、userVirtualExecutor、methodExecutor、simpleExecutor、customExecutor。


与 Redis pub/sub、MQ 的全面对比

特性 ionet EventBus Redis pub/sub MQ (RabbitMQ 等)
安装依赖 无 Redis 服务器 MQ 服务器
服务器费用 免费 需要 需要
跨进程通信 ✅ ✅ ✅
跨机器通信 ✅ ✅ ✅
全链路追踪 ✅ ❌ ❌
零订阅者零开销 ✅ ❌ ❌
线程执行器控制 ✅ ❌ ❌
使用复杂度 3 个概念 需要配连接、序列化 需要配交换器、队列

小结

ionet 的分布式事件总线用 3 个概念替代了一整套中间件生态。它不只是"功能上的替代",在性能(零网络开销)、可观测性(全链路追踪)和运维成本(零安装)上都有显著优势。

更重要的是,它让"事件驱动"这种强大的架构模式变得真正低门槛------你不需要理解 Exchange、Binding、Queue、Consumer Group 这些概念,只需要知道:定义事件、写订阅者、发布事件。


更多资源

下一篇预告:无锁并发不再难 ------ ionet 线程执行器设计哲学

相关推荐
海上小飞龙5 分钟前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
一条破秋裤37 分钟前
Linux 共享内存通信:POSIX 共享内存与 mmap
linux·运维·服务器
谢亮_vipxieliang1 小时前
泛型擦除、通配符与 PECS 一次讲透
java·面试
贵州山魈羡民1 小时前
【玩转手机】通过 Podroid 上的 Alpine 部署 1Panel,把微型服务器装进口袋
运维·服务器
八解毒剂1 小时前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
打工仔折腾 AI2 小时前
工业场景下时序库与实时计算一体化架构选型实践对比
java·开发语言·后端·python·性能优化·架构·ai agent 实战
ZealSinger2 小时前
Java虚拟线程上线后瓶颈转移到哪看三层
java·开发语言·firefox
2601_961133282 小时前
盘立方软件指标文华期货公式下载
java
蜗牛互联网2 小时前
长任务多Agent共享文件系统的Manifest交接模式
java·人工智能·后端
Sirens.3 小时前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法