# 游戏服务器架构 ⭐ 核心竞争力
用生活化的比喻,让你从"知道有服务器"到"能设计支持万人在线的游戏服务器架构"
前置知识:第03章 Java NIO 与 Netty(网络通信)、第10章 高并发与分布式一致性
阅读指南(初学者必看)
为什么你需要学习游戏服务器架构?
这是从"Java 开发者"到"游戏架构师"的关键跨越。普通 Web 后端是请求-响应模式,游戏后端是长连接+有状态+实时同步——完全不同的架构思路。
学完本章,你能回答:
- 房间服务器、网关服务器、战斗服务器各自的职责是什么?
- 怎么设计支持 1000 并发的房间服务器?
- 服务器怎么优雅地处理断线重连?
- 有状态服务和无状态服务的区别?游戏服务器应该怎么选?
- 架构怎么从单体演进到微服务?
本文结构
第一部分:游戏服务器 vs Web 服务器(理解本质区别)
第二部分:架构演进(从单体到微服务)
第三部分:房间服务器架构(最经典的游戏架构)
第四部分:网关服务器设计(流量入口)
第五部分:战斗服务器分离(有状态 vs 无状态)
第六部分:匹配服务与排行榜设计
第七部分:断线重连与会话恢复
第八部分:完整游戏服务器案例
一、游戏服务器 vs Web 服务器
生活类比:Web 服务器像"银行柜台"(来一个办一个),游戏服务器像"棋牌室"(一群人持续互动)。
| Web 服务器 | 游戏服务器 | |
|---|---|---|
| 连接模式 | 短连接(HTTP) | 长连接(WebSocket/TCP) |
| 状态 | 无状态 | 有状态(房间、战斗) |
| 通信模式 | 请求-响应 | 推送+请求 |
| 并发模型 | 请求级 | 连接级(玩家级) |
| 数据一致性 | 数据库保证 | 内存+定期持久化 |
| 延迟要求 | < 500ms | < 100ms(战斗 < 50ms) |
| 典型 QPS | 1 万/单机 | 1 万连接/单机 |
二、架构演进
阶段1:单体架构(MVP阶段,<1000人)
一台服务器
├─ 前端文件
├─ WebSocket游戏逻辑
└─ 数据库
特点:简单、快速开发、先跑通!
适用:小游戏、Demo阶段、验证玩法。
阶段2:分离部署(100人-10000人)
前端服务(Nginx)
↓
游戏服(Netty)- DB
管理后台(SpringBoot)- DB
特点:游戏服和后台分离,可以独立部署和扩展。
阶段3:分布式架构(用户 < 100000)
┌─────────────────────────────────────────┐
│ 网关 │
└─────────────┬─────────────────────────────┘
│
┌─────────┼─────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌───────┐
│房间服1│ │房间服2│ │房间服3│ ...
└───────┘ └───────┘ └───────┘
阶段4:微服务架构(1000人-10万人+)
┌─────────────────────────────────────────┐
│ 接入层(Gateway) │
├─────────────────────────────────────────┤
│ 匹配服务 │ 房间服务 │ 排行榜服务 │...│
├─────────────────────────────────────────┤
│ 数据层(Redis/MySQL) │
└─────────────────────────────────────────┘
特点:每个服务独立部署、独立扩展、技术栈灵活!
架构选择原则
| 用户规模 | 架构 | 推荐技术 |
|---|---|---|
| <1000 | 单机 | 原生Socket |
| <10000 | 分离 | Netty |
| <100000 | 分布式 | 微服务 |
| 100000+ | 云原生 | K8s + 微服务 |
三、游戏服务器整体架构
玩家客户端
|
v
+------------------------------------------+
| 网关服务器 (Gateway) |
| - 连接管理(维持TCP长连接) |
| - 消息路由(转发到对应服务) |
| - 心跳检测(踢掉死连接) |
| - 协议编解码 |
+------------------------------------------+
| |
v v
+---------------+ +-------------------+
| 登录服务器 | | 匹配服务器 |
+---------------+ +-------------------+
|
v
+------------------------------------------+
| 房间服务器 (Room) |
| - 创建/加入/离开/销毁房间 |
| - 房间内消息广播 |
| - 游戏状态同步 |
+------------------------------------------+
|
v
+------------------------------------------+
| 战斗服务器 (Battle) |
| - 帧同步/状态同步 |
| - 战斗计算 |
| - 伤害判定 |
+------------------------------------------+
分层架构设计
┌─────────────────────────────────────────────────┐
│ 客户端层 │
│ (H5/小程序/APP) │
└─────────────────────────┬───────────────────────┘
│
┌─────────────────────────┴───────────────────────┐
│ 网关层 │
│ - 协议转换(HTTP↔Socket) │
│ - 统一鉴权 │
│ - 限流熔断 │
│ - 路由分发 │
└─────────────────────────┬───────────────────────┘
│
┌─────────────────────────┴───────────────────────┐
│ 业务逻辑层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户服务 │ │ 房间服务 │ │ 匹配服务 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────┬───────────────────────┘
│
┌─────────────────────────┴───────────────────────┐
│ 数据层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ MySQL │ │ Redis │ │ Kafka │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
四、房间服务器架构
生活类比:房间服务器就像"KTV 包间管理"——管理包间的创建、加入、使用、销毁。
整体架构
玩家 → 网关服务器 → 房间服务器集群
├── Room Server 1(房间 1~100)
├── Room Server 2(房间 101~200)
└── Room Server 3(房间 201~300)
│
├── Redis(房间状态缓存)
├── MySQL(持久化)
└── MQ(事件通知)
房间生命周期
创建 → 等待 → 准备 → 游戏中 → 结算 → 销毁
│ │ │ │ │ │
│ 玩家加入 全部准备 游戏逻辑 计算结果 释放资源
└──────────────────────────────────────────┘
房间状态机
┌─────────────┐
│ WAITING │ 等待玩家
└──────┬──────┘
│ 玩家都准备好了
↓
┌─────────────┐
┌─────│ LOADING │ 加载资源
│ └──────┬──────┘
│ │ 加载完成
有人掉线 │ ↓
│ ┌─────────────┐
└────→│ PLAYING │ 游戏进行中
└──────┬──────┘
│ 游戏结束
↓
┌─────────────┐
│ ENDED │ 结算
└──────┬──────┘
│ 重新开始或解散
↓
┌─────────────┐
│ DESTROYED │ 销毁
└─────────────┘
房间服务器核心代码
@Service
public class RoomService {
private final Map<String, Room> rooms = new ConcurrentHashMap<>();
// 创建房间
public Room createRoom(Player creator, int maxPlayers) {
String roomId = generateRoomId();
Room room = new Room(roomId, maxPlayers);
room.addPlayer(creator);
rooms.put(roomId, room);
// 通知匹配服务
eventPublisher.publish(new RoomCreatedEvent(roomId, maxPlayers));
return room;
}
// 加入房间
public Room joinRoom(String roomId, Player player) {
Room room = rooms.get(roomId);
if (room == null) throw new RoomNotFoundException();
if (room.isFull()) throw new RoomFullException();
if (room.getStatus() != RoomStatus.WAITING) throw new GameAlreadyStartedException();
room.addPlayer(player);
// 通知房间内所有人
broadcast(roomId, new PlayerJoinedMessage(player));
return room;
}
// 开始游戏
public void startGame(String roomId) {
Room room = rooms.get(roomId);
if (room.getPlayers().size() < room.getMinPlayers()) {
throw new NotEnoughPlayersException();
}
room.setStatus(RoomStatus.PLAYING);
// 初始化战斗状态
BattleState battleState = battleService.initBattle(room.getPlayers());
room.setBattleState(battleState);
broadcast(roomId, new GameStartMessage(battleState));
}
// 销毁房间
public void destroyRoom(String roomId) {
Room room = rooms.remove(roomId);
if (room != null) {
// 保存战斗记录
battleRecordService.save(room.getBattleState());
// 通知匹配服务
eventPublisher.publish(new RoomDestroyedEvent(roomId));
}
}
}
房间内广播
public void broadcastToRoom(long roomId, GameMessage msg) {
Room room = RoomManager.getRoom(roomId);
if (room == null) return;
for (Player player : room.getPlayers().values()) {
Channel channel = player.getChannel();
if (channel != null && channel.isActive()) {
channel.writeAndFlush(msg);
}
}
}
五、网关服务器设计
生活类比:网关就像"酒店前台"——所有客人先到前台,前台根据需求分配到不同的楼层(服务)。
网关六大职责
// 1. 连接管理:维护玩家 WebSocket/TCP 连接
public class ConnectionManager {
private final Map<String, GameSession> sessions = new ConcurrentHashMap<>();
public void onConnect(Channel channel) {
GameSession session = new GameSession(channel);
sessions.put(session.getSessionId(), session);
}
public void onDisconnect(String sessionId) {
GameSession session = sessions.remove(sessionId);
if (session != null) {
// 通知房间服务玩家断线
roomService.playerDisconnected(session.getPlayerId());
}
}
}
// 2. 会话保持:玩家断线重连后恢复会话
// 3. 消息路由:根据消息类型分发到不同服务
// 4. 负载均衡:新连接分配到负载最低的房间服务器
// 5. 心跳检测:检测死连接,及时清理
// 6. 防攻击:连接频率限制、消息频率限制
消息路由
客户端消息 → 网关
│
├── 登录/注册 → 认证服务
├── 匹配请求 → 匹配服务
├── 房间操作 → 房间服务
├── 战斗操作 → 战斗服务
├── 聊天消息 → 聊天服务
└── 支付请求 → 支付服务
网关核心功能实现
@Component
public class GatewayHandler extends ChannelInboundHandlerAdapter {
// playerId -> Channel
private static Map<Long, Channel> sessions = new ConcurrentHashMap<>();
@Override
protected void channelRead0(ChannelHandlerContext ctx, GameMessage msg) {
// 1. 鉴权
if (!verifyToken(msg.getToken())) {
ctx.writeAndFlush(buildError(401, "未登录"));
return;
}
// 2. 限流
if (!checkRateLimit(msg.getPlayerId(), msg.getMsgId())) {
ctx.writeAndFlush(buildError(429, "请求太频繁"));
return;
}
// 3. 路由
Route targetServer = route(msg);
forwardToServer(ctx, msg, targetServer);
}
private boolean verifyToken(String token) {
return redisTemplate.hasKey("session:" + token);
}
private boolean checkRateLimit(long playerId, int msgId) {
String key = "ratelimit:" + playerId + ":" + msgId;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
return count <= 100; // 每分钟最多100次
}
private Route route(GameMessage msg) {
switch (msg.getMsgId()) {
case MSG_ROOM_CREATE:
case MSG_ROOM_JOIN:
return roomService;
case MSG_MATCH_START:
return matchService;
default:
return chatService;
}
}
}
六、战斗服务器分离
有状态 vs 无状态设计
无状态设计(适合回合制/休闲游戏):
战斗服务器不存储玩家持久数据
战斗开始 → 从数据库加载 → 战斗逻辑 → 结果写入数据库 → 释放
优点:可以随意扩缩容、重启不影响
缺点:每帧需要从外部获取状态(延迟高)
有状态设计(适合 MOBA/FPS 等实时游戏):
战斗服务器持有战斗状态
状态在内存中,操作直接读写内存
优点:延迟最低、逻辑简单
缺点:扩缩容困难、重启丢失状态、需要断线重连
| 有状态服务 | 无状态服务 | |
|---|---|---|
| 特点 | 保存玩家游戏状态 | 不保存状态 |
| 例子 | 战斗服务器、房间服务器 | 登录服务器、排行榜 |
| 扩展 | 难(需要迁移状态) | 容易(任意实例处理) |
| 部署 | 需要粘性会话 | 负载均衡即可 |
战斗状态管理
// 有状态战斗服务
@Service
public class BattleService {
private final Map<String, BattleRoom> activeBattles = new ConcurrentHashMap<>();
// 战斗主循环(固定 20Hz)
@Scheduled(fixedRate = 50)
public void gameLoop() {
for (BattleRoom battle : activeBattles.values()) {
battle.tick(); // 逻辑帧
}
}
// 处理玩家操作
public void handleAction(String battleId, long playerId, BattleAction action) {
BattleRoom battle = activeBattles.get(battleId);
if (battle == null) return;
battle.processAction(playerId, action);
// 广播状态给所有玩家
broadcast(battleId, battle.getCurrentState());
}
}
战斗服务器设计
public class BattleServer {
// 战斗房间映射到具体服务器
private Map<Long, String> battleRoomMap = new ConcurrentHashMap<>();
// 分配战斗服务器
public String assignBattleServer(long roomId) {
// 选择负载最低的服务器
List<String> servers = discovery.getActiveBattleServers();
String server = selectLeastLoaded(servers);
battleRoomMap.put(roomId, server);
return server;
}
// 帧同步:每50ms广播一次帧
public void frameSync(long roomId) {
Frame frame = generateFrame(roomId);
broadcastToRoom(roomId, frame);
}
}
七、匹配服务设计
匹配算法
@Service
public class MatchService {
// 等待队列(按elo分数分组)
private final Map<Integer, ConcurrentLinkedQueue<Player>> eloQueues = new ConcurrentHashMap<>();
// 玩家匹配信息
private final Map<Long, MatchInfo> playerMatches = new ConcurrentHashMap<>();
// 入队
public void joinQueue(Long playerId, int elo) {
if (playerMatches.containsKey(playerId)) {
throw new GameException("已在匹配中");
}
int eloBucket = elo / 50 * 50;
ConcurrentLinkedQueue<Player> queue = eloQueues.computeIfAbsent(eloBucket,
k -> new ConcurrentLinkedQueue<>());
queue.offer(new Player(playerId, elo));
playerMatches.put(playerId, new MatchInfo(eloBucket, System.currentTimeMillis()));
tryMatch(eloBucket);
}
// 匹配
private void tryMatch(int eloBucket) {
ConcurrentLinkedQueue<Player> queue = eloQueues.get(eloBucket);
if (queue == null || queue.size() < 2) {
return;
}
Player p1 = queue.poll();
Player p2 = queue.poll();
if (p1 == null || p2 == null) {
return;
}
playerMatches.remove(p1.getId());
playerMatches.remove(p2.getId());
Room room = roomService.createRoom(p1.getId(), new RoomConfig(2));
roomService.joinRoom(room.getId(), p2.getId());
}
// 定时清理超时匹配(30秒超时)
@Scheduled(fixedRate = 5000)
public void cleanupTimeout() {
long now = System.currentTimeMillis();
playerMatches.entrySet().removeIf(entry -> {
if (now - entry.getValue().getJoinTime() > 30000) {
leaveQueue(entry.getKey());
return true;
}
return false;
});
}
}
MMR(Match Making Rating)匹配
@Service
public class MMRMatchService {
private static final int ELO_RANGE = 100; // ±100分
public List<Player> findMatch(Long playerId, int elo, int targetCount) {
List<Player> matched = new ArrayList<>();
matched.add(new Player(playerId, elo));
// 从近到远扩散搜索
for (int offset = 0; offset <= 500; offset += ELO_RANGE) {
List<Player> candidates = findCandidatesInRange(elo - offset, elo + offset);
candidates.removeIf(p -> p.getId().equals(playerId));
for (Player candidate : candidates) {
if (matched.size() >= targetCount) {
return matched;
}
if (isCompatible(elo, candidate.getElo())) {
matched.add(candidate);
}
}
if (matched.size() >= targetCount) {
break;
}
}
return matched.size() >= 2 ? matched : Collections.emptyList();
}
private boolean isCompatible(int elo1, int elo2) {
return Math.abs(elo1 - elo2) <= ELO_RANGE;
}
}
八、排行榜服务设计
实时排行榜
@Service
public class RankService {
private static final String RANK_KEY_PREFIX = "rank:";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 更新排行榜
public void updateScore(Long playerId, String rankType, long score) {
String rankKey = RANK_KEY_PREFIX + rankType;
redisTemplate.opsForZSet().add(rankKey, playerId.toString(), score);
// 异步更新MySQL(降级处理)
CompletableFuture.runAsync(() -> {
playerRepository.updateScore(playerId, rankType, score);
});
}
// 获取玩家排名
public RankInfo getRank(Long playerId, String rankType) {
String rankKey = RANK_KEY_PREFIX + rankType;
Long rank = redisTemplate.opsForZSet().reverseRank(rankKey, playerId.toString());
Double score = redisTemplate.opsForZSet().score(rankKey, playerId.toString());
return new RankInfo(
rank != null ? rank + 1 : null,
score != null ? score.longValue() : 0
);
}
// 获取TOP N
public List<RankItem> getTopN(String rankType, int n) {
String rankKey = RANK_KEY_PREFIX + rankType;
Set<Object> top = redisTemplate.opsForZSet().reverseRange(rankKey, 0, n - 1);
List<RankItem> result = new ArrayList<>();
int rank = 1;
for (Object playerIdObj : top) {
String playerId = playerIdObj.toString();
Double score = redisTemplate.opsForZSet().score(rankKey, playerId);
Player player = playerRepository.findById(Long.parseLong(playerId));
result.add(new RankItem(
rank++,
Long.parseLong(playerId),
player.getName(),
score != null ? score.longValue() : 0
));
}
return result;
}
}
九、断线重连与会话恢复
生活类比:断线重连就像"电话掉线后回拨"——你需要知道刚才聊到哪了,从那里继续。
断线重连流程
1. 客户端检测断线
↓
2. 指数退避重试连接(1s → 2s → 4s → 8s → 最大30s)
↓
3. 重连请求(携带 sessionId + lastFrame)
↓
4. 网关查找会话
├── 会话存在 → 恢复连接 → 补发缺失帧
└── 会话超时 → 需要重新登录
↓
5. 恢复游戏
会话恢复代码
public class ReconnectService {
// 会话保留时间:5 分钟
private static final long SESSION_TIMEOUT = 5 * 60 * 1000;
public ReconnectResult reconnect(String sessionId, long playerId, int lastFrame) {
// 1. 查找会话
GameSession session = sessionManager.get(sessionId);
if (session == null) {
return ReconnectResult.expired();
}
// 2. 检查超时
if (session.isExpired(SESSION_TIMEOUT)) {
sessionManager.remove(sessionId);
return ReconnectResult.expired();
}
// 3. 恢复连接
session.setChannel(currentChannel);
session.updateLastActive();
// 4. 补发缺失帧
BattleRoom battle = battleService.getBattle(session.getBattleId());
List<BattleState> missedFrames = battle.getFramesAfter(lastFrame);
return ReconnectResult.success(missedFrames);
}
}
十、跨服通信
服务间通信方案
| 方案 | 适用场景 |
|---|---|
| HTTP/REST | 配置同步、低频调用 |
| gRPC | 高性能服务间调用 |
| Redis Pub/Sub | 广播消息、状态通知 |
| MQ (RocketMQ/Kafka) | 异步解耦、削峰填谷 |
使用 Redis 做跨服消息
// 服务器A发布消息
redis.publish("cross:server:room_100", "player_join:10086");
// 服务器B订阅消息
redis.subscribe(new JedisPubSub() {
public void onMessage(String channel, String message) {
// 处理跨服消息
}
}, "cross:server:room_100");
十一、完整项目结构
game-server/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── game/
│ │ │ ├── GameApplication.java
│ │ │ │
│ │ │ ├── gateway/ # 网关
│ │ │ │ ├── GatewayHandler.java
│ │ │ │ ├── RouteService.java
│ │ │ │ └── RateLimiter.java
│ │ │ │
│ │ │ ├── user/ # 用户服务
│ │ │ │ ├── UserController.java
│ │ │ │ ├── UserService.java
│ │ │ │ └── UserRepository.java
│ │ │ │
│ │ │ ├── room/ # 房间服务
│ │ │ │ ├── RoomController.java
│ │ │ │ ├── RoomService.java
│ │ │ │ ├── Room.java
│ │ │ │ └── MatchService.java
│ │ │ │
│ │ │ ├── rank/ # 排行榜服务
│ │ │ │ ├── RankController.java
│ │ │ │ ├── RankService.java
│ │ │ │ └── RankPersistService.java
│ │ │ │
│ │ │ ├── common/ # 公共模块
│ │ │ │ ├── GameMessage.java
│ │ │ │ ├── GameException.java
│ │ │ │ ├── Result.java
│ │ │ │ └── Constants.java
│ │ │ │
│ │ │ └── config/ # 配置
│ │ │ ├── RedisConfig.java
│ │ │ ├── NettyConfig.java
│ │ │ └── WebSocketConfig.java
│ │ │
│ │ └── resources/
│ │ └── application.yml
│ │
│ └── test/
│ └── java/
│ └── com/
│ └── game/
│ ├── RoomServiceTest.java
│ └── RankServiceTest.java
│
└── docker-compose.yml
十二、性能优化要点
支持 1000 并发的关键设计
| 优化点 | 实现 |
|---|---|
| 连接管理 | Netty NIO + 单线程多连接 |
| 房间隔离 | ConcurrentHashMap + 细粒度锁 |
| 消息广播 | 只发给在线且active的channel |
| 协议编解码 | 自定义二进制协议,比JSON省50%+带宽 |
| 排行榜 | Redis ZSet,O(logN)更新 |
| 背包 | 本地缓存 + 定时持久化 |
压测指标
# 使用wrk或自定义压测工具
# 目标:1000并发连接,每个房间4人
# 压测结果参考:
# - 单服务器支持:1000~3000并发
# - 消息延迟:P99 < 50ms
# - 内存占用:每连接约10KB,1000连接约10MB
# - CPU:4核可支撑,8核更稳
十三、自问自答
Q1:游戏服务器一台能支持多少在线?
取决于游戏类型。休闲游戏(少量消息):1
2 万连接/单机。实时对战(高频同步):30005000 连接/单机。核心瓶颈是 CPU(逻辑计算)和带宽(状态同步)。
Q2:房间服务器和战斗服务器一定要分开吗?
不一定。小项目可以合并。大项目分开是为了独立扩缩——匹配服务无状态可以随意扩,战斗服务有状态需要特殊处理。
Q3:网关单点怎么办?
网关无状态,可以水平扩展。用 Nginx/HAProxy 做负载均衡,客户端连接时随机分配到不同网关实例。
Q4:战斗中服务器挂了怎么办?
这是游戏最大的痛点。方案:1. 战斗状态实时备份到备用节点;2. 玩家断线重连到新节点后从备份恢复;3. 最坏情况:战斗作废,补偿玩家。
Q5:怎么从单机架构演进到分布式?
先单体 → 网关分离 → 匹配服务分离 → 战斗服务分离 → 数据库分库分表。每一步都是渐进的,不要一步到位。
Q6:网关服务器可以用 Nginx 代替吗?
游戏用 TCP 长连接,Nginx 主要支持 HTTP/WebSocket。自定义二进制协议需要 Netty 自己实现网关。
Q7:房间服务器有状态怎么扩展?
用一致性哈希。相同 roomId 总是路由到同一台服务器。新增节点只影响部分房间迁移。
Q8:帧同步和状态同步怎么选?
RTS/格斗用帧同步(延迟低,一致性好)。MOBA/RPG 用状态同步(带宽低,反作弊好)。
实践任务
- 实现房间服务器:创建/加入/退出/销毁,支持 100 并发
- 实现网关服务器:基于 Netty 的 WebSocket 网关,支持消息路由
- 实现断线重连:客户端模拟断线,5 秒内重连恢复游戏
- 实现匹配服务:Elo 匹配算法,支持多段位匹配
- 实现排行榜:Redis Sorted Set,支持 Top N 和附近排名
- 压测:模拟 1000 并发创建房间,观察服务器 CPU 和内存
- 画架构图:为一个 MOBA 游戏画出完整的服务器架构图
与其他章节的关联
| 本节内容 | 关联章节 | 关联点 |
|---|---|---|
| Netty 网关 | 第03章 Java NIO 与 Netty | 网关基于 Netty 实现 |
| 分布式一致性 | 第10章 高并发与分布式一致性 | 有状态服务的容灾 |
| 协议设计 | 第12章 游戏协议设计与优化 | 网关的消息协议 |
| 实时通信 | 第14章 游戏实时通信优化 | WebSocket 优化 |
| 容器化 | 第15章 云原生与容器化 | 服务器 K8s 部署 |
| 网络同步 | 4_1 第02~05章 | 帧同步/状态同步的服务端实现 |
⬅️ 上一章:高并发与分布式一致性 | ➡️ 下一章:游戏协议设计与优化