从 Pomelo 到 TSRPC:打造下一代有状态单实例游戏服务器
引言
在游戏服务器开发领域,Pomelo 框架曾经是许多开发者的首选。它的集群架构和路由机制为早期大规模多人在线游戏提供了坚实的基础。然而,随着游戏技术的演进和开发需求的变化,我们开始思考:是否存在一种更简洁、更高效的方式来构建游戏服务器?
今天,我想向大家介绍我们团队正在开发的一款全新游戏服务器框架——DonkJS。这是一个基于 TSRPC 的有状态单实例服务器,它抛弃了传统的集群加路由模式,采用了一种更直接、更灵活的服务提供方式。
技术选型:为什么选择 TSRPC?
在开始项目之前,我们评估了多种通信框架,最终选择了 TSRPC(TypeScript Remote Procedure Call)。做出这个选择主要基于以下几个原因:
1. 类型安全
作为 TypeScript 原生的 RPC 框架,TSRPC 提供了端到端的类型检查。这意味着客户端和服务器之间的通信协议会在编译时进行验证,大大减少了运行时错误。
2. 高性能
TSRPC 采用了二进制序列化(TSBuffer),比 JSON 序列化更高效。对于游戏服务器来说,这意味着更低的延迟和更高的吞吐量。
3. 简单易用
与复杂的微服务框架不同,TSRPC 提供了简洁的 API,使开发者能够快速上手并专注于业务逻辑的实现。
4. 灵活性
TSRPC 支持多种传输协议(HTTP、WebSocket),并提供了丰富的扩展接口,使我们能够根据游戏需求进行定制开发。
架构设计:有状态单实例服务器
DonkJS 采用了有状态单实例服务器架构,这与传统的无状态微服务架构有很大不同。
核心设计理念
- 单实例,全状态
- 服务器保持游戏的完整状态,包括玩家数据、游戏世界状态等
- 避免了分布式状态同步的复杂性和性能开销
- 路径化服务提供
- 开发者通过定义清晰的路径地址来提供服务
- 客户端直接通过路径调用相应的服务,无需经过复杂的路由层
- 组件化架构
- 服务器功能被划分为多个独立组件
- 组件之间通过明确的接口通信,便于维护和扩展
与 Pomelo 架构的对比
| 特性 | Pomelo | DonkJS |
|---|---|---|
| 架构模式 | 无状态集群 + 路由 | 有状态单实例 + 路径服务 |
| 通信协议 | 自定义二进制协议 | TSRPC + TSBuffer |
| 服务发现 | 基于 ZooKeeper | 直接路径访问 |
| 状态管理 | 分布式状态同步 | 单实例全状态 |
| 开发复杂度 | 高 | 低 |
| 性能开销 | 中(路由和同步开销) | 高(避免了网络开销) |
核心特性介绍
1. 实时 WebSocket 服务
DonkJS 内置了高性能的 WebSocket 游戏服务器,支持:
- 低延迟的双向通信
- 消息广播和定向发送
- 连接管理和心跳检测
2. 基于组件的模块化设计
服务器功能通过组件化方式实现:
typescript
Apply
// 组件注册示例
const globalVarComp: GlobalVarComponent = new GlobalVarComponent();
ComponentManager.instance.register(EComName.GlobalVarComponent, globalVarComp);
// 组件启动
await ComponentManager.instance.startAll();
3. 灵活的日志系统
基于 log4js 实现,支持:
- 多级别日志输出
- 按日期和大小滚动的日志文件
- 自定义 CSV 格式日志(用于数据分析)
4. 优雅的关闭机制
支持在各种环境下(包括 PM2)的优雅关闭:
typescript
Apply
process.on("SIGINT", () => {
gameLogger.log("SIGINT received, shutting down gracefully...");
stopFrontServer();
});
process.on("SIGBREAK", () => {
gameLogger.log("SIGBREAK received, shutting down gracefully...");
stopFrontServer();
});
开发体验:通过路径提供服务
在 DonkJS 中,开发者通过定义路径来提供服务,客户端直接通过这些路径调用服务。这种方式大大简化了开发流程:
服务定义示例
typescript
Apply
// 定义服务接口
export interface ReqJoinRoom {
roomId: string;
playerName: string;
}
export interface ResJoinRoom {
success: boolean;
playerId: string;
roomInfo: RoomInfo;
}
// 实现服务
export async function joinRoom(req: ReqJoinRoom): Promise<ResJoinRoom> {
// 业务逻辑实现
return {
success: true,
playerId: generatePlayerId(),
roomInfo: getRoomInfo(req.roomId)
};
}
客户端调用示例
typescript
Apply
// 客户端直接通过路径调用服务
const result = await client.callApi('/room/join', {
roomId: 'room123',
playerName: 'Player1'
});
未来规划:向分布式演进
虽然 DonkJS 当前是一个单实例服务器,但我们已经为未来的分布式扩展做好了准备:
- 水平扩展支持
- 计划通过区域分片(Sharding)实现水平扩展
- 不同区域的服务器保持独立状态,通过网关进行通信
- 微服务集成
- 将非核心功能(如统计、分析)拆分为独立微服务
- 使用 TSRPC 实现服务之间的高效通信
- 容器化部署
- 支持 Docker 和 Kubernetes 部署
- 提供自动化的部署和管理工具
为什么选择有状态单实例?
您可能会问:在分布式架构盛行的今天,为什么我们还要开发一个单实例服务器?
适合的场景
- 中小型游戏
- 对于玩家规模在数千人以下的游戏,单实例服务器已经足够
- 避免了分布式架构带来的复杂性
- 快速迭代开发
- 单实例架构便于快速开发和测试
- 适合游戏原型开发和早期版本迭代
- 状态密集型游戏
- 对于需要频繁访问和修改状态的游戏(如实时策略游戏、沙盒游戏)
- 单实例架构可以避免分布式状态同步的开销
结语
DonkJS 代表了我们对游戏服务器架构的一种新思考。它不是要取代所有传统框架,而是为游戏开发者提供了一种新的选择——一种更简洁、更高效、更适合现代游戏开发需求的选择。
我们相信,在许多场景下,有状态单实例服务器架构能够提供比传统集群架构更好的性能和开发体验。而通过 TSRPC 实现的路径化服务提供方式,则为游戏开发者打开了一扇新的大门。
如果您对 DonkJS 感兴趣,欢迎访问我们的 GitHub 仓库,了解更多技术细节并参与到项目的开发中来!
[GitHub 仓库链接:https://github.com/lyh1091106900/donkjs.git)
关于作者 :本文由 DonkJS 开发团队撰写,我们致力于打造下一代游戏服务器框架,为游戏开发者提供更好的开发体验和性能表现。