为什么通信模型如此重要?

很多项目把所有的网络交互都做成一种模式:客户端发请求、服务器回响应。这在简单场景下没有问题,但随着业务复杂度的增加,你会遇到这些困境:

  • 进入房间后,如何给所有人推送状态变化? 用 request/response 需要客户端主动轮询,既浪费带宽又有延迟。
  • 匹配系统需要查询多个逻辑服的负载情况,如何一次请求获取多个响应? 用 request/response 需要串行调用,效率低下。
  • 玩家登录后需要通知多个系统(邮件、任务、活动),如何解耦? 都塞在登录方法里,代码臃肿到无法维护。

选对通信模型,业务代码会更清晰,性能和可维护性也更稳。

ionet 提供了 4 种通信模型,覆盖几乎所有场景。本文将逐一解析,并给出选型建议。


模型一:request/response —— 最经典的交互

这是最常见的通信方式:客户端发送请求,服务器返回一个响应。

@ActionController(1)
public class HallAction {
    @ActionMethod(0)
    private UserMessage loginVerify(LoginVerifyMessage message) {
        var userMessage = new UserMessage();
        userMessage.name = "Hello, " + message.jwt;
        return userMessage;
    }
}

适用场景:登录验证、数据查询、表单提交——所有"问一句答一句"的业务。

特点

  • 一次请求,一次响应
  • 最简单、最直观
  • 支持跨进程跨机器调用

模型二:request/multiple_response —— 分段返回

当你需要一次请求从多个逻辑服收集数据时,这个模型就派上用场了。

典型场景:匹配系统需要知道哪个房间逻辑服最空闲。你启动了 3 个房间逻辑服,需要一次请求拿到每个逻辑服的房间数量。

// 匹配逻辑服中发起请求
var communication = CommunicationKit.getCommunication();

// 向所有同类型逻辑服广播请求,收集多个响应
var responseItems = communication.requestMultipleResponse(cmdInfo, requestData);

// 找到房间数最少的那个
var bestServer = responseItems.stream()
    .min(Comparator.comparingInt(item -> item.roomCount))
    .orElseThrow();

适用场景

  • 查询多个逻辑服的状态(负载均衡)
  • 收集分布式系统中的聚合数据
  • 类似 LOL/王者荣耀的匹配后资源分配

特点

  • 一次请求,多个响应(每个逻辑服实例返回一个)
  • 支持流式感知
  • 适合分布式数据收集

模型三:broadcast —— 广播推送

当服务器需要主动向多个客户端推送消息时使用广播。游戏中最常见:房间内玩家的每次操作,都需要同步给其他所有玩家。

@ActionMethod(3)
private void triggerBroadcast() {
    var communication = CommunicationKit.getCommunication();

    // 广播一个对象给所有在线玩家
    var message = new HelloMessage();
    message.name = "有新活动开启了!";
    communication.broadcastMulticast(cmdInfo, message);
}

适用场景

  • 房间内状态同步(棋牌游戏、对战游戏)
  • 全服公告
  • 实时排行榜更新
  • 聊天消息分发

特点

  • 服务器主动推送,客户端被动接收
  • 支持全体广播和定向广播
  • 即使启动了多个对外服,框架也会确保所有在线用户都能收到消息

关于最后一点:当你有多个对外服时,用户分布在不同的对外服上。框架会自动处理跨对外服的消息分发——在开发者眼中,只有"一个"对外服。


模型四:EventBus —— 分布式事件总线

这是 ionet 最强大、最独特的通信方式。它类似于 Redis pub/sub 和 MQ,但完全不需要安装任何中间件

为什么需要事件总线?

回到之前的例子:玩家登录后,需要做这些事——

  1. 记录登录时间
  2. 计算离线奖励
  3. 发送欢迎邮件
  4. 检查任务完成状态

如果把这些逻辑都塞在登录 Action 里,代码会变得臃肿、耦合严重。

用事件总线的做法:

1. 定义事件源

@ProtobufClass
public class UserLoginEventMessage {
    public long userId;

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

2. 编写订阅者

@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);
    }
}

3. 发布事件

@ActionMethod(0)
private UserMessage login(FlowContext flowContext, LoginMessage message) {
    // 发布登录事件 —— 所有订阅者都会收到
    flowContext.fire(UserLoginEventMessage.of(flowContext.getUserId()));

    // 返回登录结果
    return new UserMessage("登录成功");
}

登录方法干干净净,只做登录的事。邮件、奖励、任务等逻辑分散在各自的订阅者中——它们可以在不同的逻辑服中,甚至在不同的机器上。

EventBus 的独特优势

ionet 的分布式事件总线与 Redis pub/sub 和 MQ 相比,有几个独特的优势:

特性 ionet EventBus Redis pub/sub / MQ
安装依赖 需要安装中间件
全链路追踪 ✅ 支持 ❌ 不支持
跨进程跨机器 ✅ 支持 ✅ 支持
无订阅者时的行为 不触发网络请求 仍然发送消息到中间件
费用 免费 Redis/MQ 需要服务器费用

其中**“无订阅者时不触发网络请求”**是一个非常精妙的设计。这意味着你可以放心地在业务中埋点(发布事件),不用担心性能开销——只有当有订阅者在线时,才会触发实际通信。

EventBus 的发布方式

框架提供了多种发布粒度:

方法 范围
fireMe 仅当前逻辑服的订阅者
fireLocal 当前进程内所有逻辑服的订阅者
fire 所有订阅者(含远程)
fireAny 同类型逻辑服中只发给其中一个

每种方法都有同步和异步版本。


选型指南

场景 推荐通信模型 理由
登录、查询、更新 request/response 一问一答,最简单
匹配后选最空闲节点 request/multiple_response 需要多个逻辑服同时返回
房间内操作同步 broadcast 服务器主动推送给多人
全服公告 broadcast 面向所有在线用户
登录后触发多系统 EventBus (fire) 解耦,支持跨进程
统计数据收集 EventBus (fireAny) 只需一个实例处理
热更配置 EventBus (fire) 所有实例批量更新
临时活动上下线 EventBus 逻辑服下线 = 订阅者消失 = 零开销

所有通信方式的共同特性

无论你使用哪种通信模型,ionet 都保证:

  1. 支持跨进程、跨机器通信
  2. 具备全链路调用日志跟踪(每个请求分配唯一 traceId)
  3. 可扩展

这在排查分布式系统问题时非常关键——你可以通过一个 traceId 追踪完整的请求链路,无论请求经过了多少个逻辑服。


小结

通信模型选对了,你的代码就是清晰的;选错了,你就在和框架对抗。

ionet 提供的 4 种通信模型,从最简单的 request/response 到最强大的 EventBus,覆盖了分布式系统中几乎所有的通信场景。更重要的是,它们的使用方式都围绕一个核心设计原则:写起来像调用普通 Java 方法。


更多资源

下一篇预告:[告别 Redis/MQ —— ionet 分布式事件总线实战]

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐