为什么通信模型如此重要?
很多项目把所有的网络交互都做成一种模式:客户端发请求、服务器回响应。这在简单场景下没有问题,但随着业务复杂度的增加,你会遇到这些困境:
- 进入房间后,如何给所有人推送状态变化? 用 request/response 需要客户端主动轮询,既浪费带宽又有延迟。
- 匹配系统需要查询多个逻辑服的负载情况,如何一次请求获取多个响应? 用 request/response 需要串行调用,效率低下。
- 玩家登录后需要通知多个系统(邮件、任务、活动),如何解耦? 都塞在登录方法里,代码臃肿到无法维护。
选对通信模型,业务代码会更清晰,性能和可维护性也更稳。
ionet 提供了 4 种通信模型,覆盖几乎所有场景。本文将逐一解析,并给出选型建议。
模型一:request/response ------ 最经典的交互
这是最常见的通信方式:客户端发送请求,服务器返回一个响应。
java
@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 个房间逻辑服,需要一次请求拿到每个逻辑服的房间数量。
java
// 匹配逻辑服中发起请求
var communication = CommunicationKit.getCommunication();
// 向所有同类型逻辑服广播请求,收集多个响应
var responseItems = communication.requestMultipleResponse(cmdInfo, requestData);
// 找到房间数最少的那个
var bestServer = responseItems.stream()
.min(Comparator.comparingInt(item -> item.roomCount))
.orElseThrow();
适用场景:
- 查询多个逻辑服的状态(负载均衡)
- 收集分布式系统中的聚合数据
- 类似 LOL/王者荣耀的匹配后资源分配
特点:
- 一次请求,多个响应(每个逻辑服实例返回一个)
- 支持流式感知
- 适合分布式数据收集
模型三:broadcast ------ 广播推送
当服务器需要主动向多个客户端推送消息时使用广播。游戏中最常见:房间内玩家的每次操作,都需要同步给其他所有玩家。
java
@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,但完全不需要安装任何中间件。
为什么需要事件总线?
回到之前的例子:玩家登录后,需要做这些事------
- 记录登录时间
- 计算离线奖励
- 发送欢迎邮件
- 检查任务完成状态
如果把这些逻辑都塞在登录 Action 里,代码会变得臃肿、耦合严重。
用事件总线的做法:
1. 定义事件源
java
@ProtobufClass
public class UserLoginEventMessage {
public long userId;
public static UserLoginEventMessage of(long userId) {
var message = new UserLoginEventMessage();
message.userId = userId;
return message;
}
}
2. 编写订阅者
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);
}
}
3. 发布事件
java
@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 都保证:
- 支持跨进程、跨机器通信
- 具备全链路调用日志跟踪(每个请求分配唯一 traceId)
- 可扩展
这在排查分布式系统问题时非常关键------你可以通过一个 traceId 追踪完整的请求链路,无论请求经过了多少个逻辑服。
小结
通信模型选对了,你的代码就是清晰的;选错了,你就在和框架对抗。
ionet 提供的 4 种通信模型,从最简单的 request/response 到最强大的 EventBus,覆盖了分布式系统中几乎所有的通信场景。更重要的是,它们的使用方式都围绕一个核心设计原则:写起来像调用普通 Java 方法。
更多资源
下一篇预告:[告别 Redis/MQ ------ ionet 分布式事件总线实战]