4、服务拆分实战(上):按业务领域拆分

好,咱们进入实战环节了。前面几章讲了那么多理论,什么DDD、什么限界上下文,现在终于要动手了。

这一章,我带你走一遍最经典的游戏服务器拆分路径——按业务领域拆分。说白了,就是把一个巨大的单体服务器,切成几个独立的小服务。

我个人习惯,先画一张业务大图,再下刀。你想想看,一个典型的游戏服务器,核心业务无非就是:登录、匹配、战斗、社交、商城。这五个领域,几乎覆盖了90%以上的游戏类型。

4.1 为什么是这五个领域?

我在项目中遇到过不少团队,一上来就拆成十几个微服务,结果运维成本直接爆炸。其实,拆分的粒度不是越细越好

这五个领域,每个都有自己独立的生命周期和资源需求:

  • 登录:高并发、短连接,对安全要求极高
  • 匹配:实时性要求高,需要维护大量玩家状态
  • 战斗:计算密集,对延迟极度敏感
  • 社交:读写比例悬殊,好友关系复杂
  • 商城:事务性强,数据一致性要求高

你看,每个领域的“脾气”都不一样。硬塞在一起,只会互相拖累。

核心原则:每个微服务应该有自己的数据库,而不是共享一个库。这是微服务架构的底线。

4.2 登录服务:第一道门

登录服务,说白了就是玩家的“门卫”。它负责验证身份、发放令牌、管理会话。

嗯,这里要注意:登录服务不要做任何游戏逻辑。我曾经见过一个项目,登录服务里还顺带查了玩家的VIP等级,结果登录高峰期直接把数据库打崩了。

登录服务的边界定义:

  • 职责:账号注册、密码验证、Token签发与刷新、第三方登录(微信、Google等)
  • 不负责:玩家数据加载、好友列表、邮件通知
  • 数据存储:账号表、设备信息表、黑名单表
  • 对外接口Login(account, password) → TokenVerifyToken(token) → playerId

个人经验:登录服务最好独立部署,并且做限流。我习惯用Redis做Token缓存,TTL设成30分钟。玩家活跃时自动续期,不活跃就过期释放。

4.3 匹配服务:撮合的艺术

匹配服务,是游戏里最“热闹”的地方。玩家排队、段位匹配、队伍平衡,全在这里完成。

为什么会这样?因为匹配算法通常需要维护一个“等待池”,里面全是正在排队的玩家。这个池子,必须放在内存里,不能放数据库。

匹配服务的边界定义:

  • 职责:玩家入队、出队、段位匹配、队伍平衡、超时处理
  • 不负责:战斗房间创建、玩家属性修改
  • 数据存储:Redis(等待队列、匹配分数)、MySQL(匹配记录、历史数据)
  • 对外接口JoinQueue(playerId, mode, rank) → queueIdLeaveQueue(queueId) → bool

避坑指南:我曾经踩过一个坑——匹配服务直接调用了战斗服务的接口来创建房间。结果匹配高峰期,战斗服务被打挂了,所有匹配成功的玩家都进不了房间。正确的做法是:匹配服务只负责“撮合”,撮合成功后通过消息队列通知战斗服务。

4.4 战斗服务:核心中的核心

战斗服务,是游戏服务器里最“重”的服务。它负责处理每一帧的逻辑、技能释放、伤害计算、胜负判定。

我个人习惯,战斗服务一定要做成无状态的。什么意思?就是战斗房间里的所有状态,都只存在内存里,不落盘。战斗结束后,把结果写入数据库即可。

战斗服务的边界定义:

  • 职责:房间创建、帧同步/状态同步、技能逻辑、AI行为、胜负判定
  • 不负责:玩家数据持久化、匹配逻辑、聊天消息
  • 数据存储:Redis(房间信息缓存)、MySQL(战斗回放、结算数据)
  • 对外接口CreateRoom(players, mode) → roomIdSubmitAction(roomId, playerId, action) → bool

关键点:战斗服务是唯一一个可以“丢数据”的服务。因为战斗过程中的中间状态,丢了也就丢了,只要最终结算结果正确就行。所以,战斗服务可以用纯内存方案,追求极致性能。

4.5 社交服务:关系网的中心

社交服务,负责处理玩家之间的互动。好友、公会、聊天、邮件,全归它管。

你想想看,社交服务的特点是:读多写少,关系复杂。一个玩家可能有几百个好友,每个好友又有自己的状态。如果用关系型数据库硬查,性能会非常难看。

社交服务的边界定义:

  • 职责:好友添加/删除、公会管理、聊天频道、邮件系统、排行榜
  • 不负责:玩家战斗数据、商城购买
  • 数据存储:图数据库(好友关系)、Redis(在线状态、聊天缓存)、MySQL(邮件、公会信息)
  • 对外接口AddFriend(playerId, friendId) → boolSendMessage(from, to, content) → msgId

个人经验:社交服务最容易出现“扇出”问题。比如一个公会会长发了一条全服公告,结果要推送给几千个玩家。我习惯用消息队列做异步推送,避免同步阻塞。

4.6 商城服务:最后的金库

商城服务,直接和钱打交道。商品购买、订单生成、支付回调、发货确认,每一步都不能出错。

嗯,这里要特别强调:商城服务必须保证数据一致性。不能出现“玩家付了钱,但没收到道具”的情况。

商城服务的边界定义:

  • 职责:商品列表、下单、支付对接、发货、退款、限购逻辑
  • 不负责:玩家背包管理、战斗道具使用
  • 数据存储:MySQL(订单表、商品表、支付记录)、Redis(限购计数器、热销商品缓存)
  • 对外接口CreateOrder(playerId, goodsId, count) → orderIdPayCallback(orderId, result) → bool

避坑指南:我曾经遇到过一个经典问题——玩家下单后,支付回调还没到,但玩家已经关掉了游戏。结果订单状态卡在“待支付”,玩家再登录时无法重新购买。解决方案是:引入订单超时机制,30分钟未支付自动取消。

4.7 服务边界总结

好了,五个服务的边界都定义完了。我整理了一张表,方便你对照:

服务名称 核心职责 数据存储 关键特征
登录服务 身份认证、Token管理 MySQL + Redis 高并发、安全敏感
匹配服务 玩家撮合、段位匹配 Redis为主 实时性、内存操作
战斗服务 房间管理、逻辑计算 内存为主 计算密集、无状态
社交服务 好友、公会、聊天 图数据库 + Redis 读多写少、关系复杂
商城服务 订单、支付、发货 MySQL + Redis 事务性强、一致性要求高

记住一句话:边界清晰,服务才能独立演进。每个服务只关心自己的那一亩三分地,不要越界。下一章,我会带你看看服务拆分后的通信问题——服务之间怎么说话,才能又快又稳。