5.邮件系统系列总结

5.总结


5.1 知识点

系列按「总览 → Gate → 数据模型 → Mail 服实现」排布:先定分层与消息走向,再写网关会话与路由,接着把 Mail / MailBox 与枚举钉死,最后用组件与流程把登录领取、列表、开信领附件、发送和两类过期清理串起来。


5.2 核心要点速览

业务边界与部署形态

邮件系统承接的是玩法与运营侧「道具 + 文案」在多人、多进程之间的可靠投递:全服公告式投放、按维度圈人、点名、离线暂存、带附件结算,同时满足审计和扩展。工程上默认 Mail 不外网、客户端只连 Gate,把暴面、会话和路由收口在一层。

常见部署:

模式 做法 注意
无分区 多 Mail 实例;playerId % mailServerCount 或按负载选机 同一大世界吞吐高,要避开单节点过热
分区服 每区一枚 Mail 运维简单;跨区投递需另设计

与配图一致的几条硬事实:客户端只连 Gate;Gate 再连 Map、其它业务服、邮件服;邮件服与其它服一样走「公网进网关、业务只在内网」;必要时邮件服也可向 Map 等节点发内网消息做联动,玩家可见协议仍经 Gate。


Gate:会话、鉴权、透明路由

Gate 只做三件事:会话生命周期、账号与会话绑定、把邮件 RPC 指到正确的 MailUnit。和「聊天网关」同类,邮件业务不在 Gate 里展开。

组件与职责

对象 作用 和邮件链的关系
GateScene 启服、接入会话、OnSessionAccept / OnSessionDisconnect 每个外网连接一条会话线
GateAccountFlagComponent Session 上挂 Account(含 MailRouteId),销毁时走下线 断线必须通知 Mail 落库、卸 MailUnit
RouteComponent RouteType.MailRoute → MailUnitRouteIdSend / Call 走内网 客户端以为在打 Mail,实际是 Gate 转发
GateLoginHelper.Online G2Mail_LoginRequest,回包带 MailUnitRouteIdAddAddress 之后 C2Mail_* 才有确定的 Mail 侧落点
GateLoginHelper.Offline G2Mail_ExitRequest 与 Mail 侧 Exit 成对

登录与邮件 RPC 走向

登录链:

C2G_LoginRequest
  → Gate 挂 GateAccountFlagComponent、RouteComponent
  → GateLoginHelper.Online → G2Mail_LoginRequest
  ← Mail2G_LoginResponse(含 MailUnitRouteId)
  → RouteComponent.AddAddress(MailRoute, MailUnitRouteId)
  → G2C_LoginResponse

邮件请求:C2Mail_*RouteComponent.Call(MailRoute, 请求) → 命中对应 MailUnit → 响应沿原会话返回客户端。


数据模型:内容与投递分离

Mail 实体

玩家邮箱里的「一封」,管展示与状态。

字段 用途
OwnerId 归属玩家,对齐账号或单元
Title / Content 标题与正文
CreateTime / ExpireTime 单封生命周期,驱动清理
Money / Items 附件;Item 可先只做 Name 再扩展
MailState 未读 / 已读 / 未领 / 已领,驱动红点与按钮
MailType 系统 / 奖励 / 玩家互发

MailBox 实体

「一批怎么投、投给谁」的载体,内嵌一份 Mail,适合全服一类信共用一份正文。

字段 用途
Mail 嵌套的 Mail,内容与附件
CreateTime / ExpireTime 袋子级过期,可与内层 Mail 解耦
MailBoxType 投递策略,见下表
AccountId 目标玩家集合
Received 已领取集合,防重领;全员领完可整袋回收
SendAccountId 发件人,审计用

枚举

枚举 记住这几档
MailBoxType Specify 点名;Online 仅在线;All 全服;AllToDate 按注册时间与邮件创建时间筛人
MailRemoveActionType 手删、超时删、超量淘汰

数据量上来后可在 ExpireTimeAccountId 等字段做索引;再大再谈分片或热点缓存。


Mail 服:在线单元、离线箱、统一发送口

组件分工

组件 一句话 关键点
MailUnitManageComponent MailScene 上管所有 MailUnit Init 载入;Online 建单元、挂 MailComponent、触发离线投递
MailUnit 运行期玩家单元 GateRouteId,推送经 Gate 回到客户端
MailBoxManageComponent 离线邮件「物流中心」 加载未过期 MailBoxGetHaveMail 把符合条件的信灌进玩家 MailComponent;定时 Timeout 清过期箱
MailComponent 在线个人邮箱 Add / Remove / OpenMail / Receive / CheckTimeOut;超量可删最老
MailHelper 外部唯一发送入口 Send 判断本机或跨服;InnerSendMailBoxType 分支

两套过期,别混

谁负责 清什么
MailBoxManageComponent.Timeout 尚未进个人邮箱的 MailBox,整袋过期
MailComponent.CheckTimeOut 已在玩家邮箱里的单封 Mail,列表请求前扫一遍可避免客户端看到过期残留

登录灌信与拉列表

  • Online 里会 GetHaveMail(mailUnit, sync: true),合流全服、限时、指定等待,多次 MailComponent.Add 并推 Mail2C_HaveMail
  • 之后 C2Mail_GetHaveMailRequest:handler 里先 CheckTimeOut(),再从当前 MailComponentMailSimplifyInfo,**不必再整表扫 MailBox**。

开信、领附件、删信:RouteRPC 进 MailUnit,对应 OpenMailReceive(内部调用货币与道具服务)、Remove(..., Remove, sync: true),状态用 Mail2C_MailState 等同步。

发送:C2Mail_SendMailRequestHandler 构建 MailBoxMailHelper.SendSpecify 在线立刻推、离线可落箱;Online 只打在线;All / AllToDate 进箱管理、登录批量下发。活动服等统一调 Send 即可。


5.3 面试题精选

基础题

1. 玩家登录成功后,Gate 如何把后续的 C2Mail_* 请求送到正确的 MailUnit

题目

请按时间顺序说明:从 C2G_LoginRequest 到客户端能发 C2Mail_*,Gate 侧必须完成哪些步骤?MailUnitRouteId 起什么作用?

深入解析

Gate 自己不认识「邮件业务」,只认会话和路由表。登录时 GateLoginHelper.Online 把账号信息发到 MailScene,Mail 创建或定位 MailUnit 后,用 Mail2G_LoginResponse 带回 **MailUnitRouteId**。Gate 必须把它登记进 RouteComponent,即 AddAddress(RouteType.MailRoute, MailUnitRouteId),这样后续 RouteComponent.Call(MailRoute, rpc) 才能在内网打到那一颗 MailUnit。若只做鉴权却不登记路由,客户端会话有了、C2Mail_* 却会无处投递。下线时对称地 Offline / G2Mail_ExitRequest,避免 Mail 侧仍向已断会话推送。

答题示例

登录时 Gate 调 Online,Mail 回 MailUnitRouteId。Gate 把它写进 RouteComponent 的 Mail 路由映射里。

之后客户端发 C2Mail_*,Gate 用 Call(MailRoute, 请求) 转到对应的 MailUnit,响应再走同一条会话返回。没这一步,MailUnitRouteId 只在登录回包里出现过一次、没进路由表,邮件 RPC 就找不到人。

参考文章
  • 1.邮件系统架构设计总览
  • 2.邮件系统Gate服设计

进阶题

1. 为何把 MailMailBox 拆开?MailBoxType 如何改变投递路径?

题目

从存储与逻辑两方面说明:MailMailBox 各解决什么问题?结合 Specify / Online / All / AllToDate,配合 InnerSend / MailBoxManageComponent / MailComponent 简述典型走向。

深入解析
  • 拆分动机Mail 是「一封信」的状态与附件,挂在玩家侧列表里;MailBox 是「一次投放任务」和多目标领取进度,内嵌一份 Mail 做 payload。全服同类邮件共用一箱,靠 AccountId/Received 维护谁该领、谁已领,避免把同一内容复制成 N 份独立 Mail 再分别过期。
  • 类型与路径MailHelper.InnerSendMailBoxType 分支。Specify 对名单:在线则 MailComponent.Add 即时推送,离线进箱等人上线。Online 只遍历当前在线 MailUnitAll / AllToDate 通常持久化在 MailBoxManageComponent,以 GetHaveMail 在登录时批量灌入个人邮箱;AllToDate 额外用注册时间与邮件创建时间过滤「晚于发信注册」的玩家。
答题示例

Mail 管单挑邮件内容与读领状态;MailBox 管一批人怎么领、谁领过,里面嵌一个 Mail。全服发奖一箱就够,Received 防重领。

Specify 可在线直达邮箱、离线先进箱;Online 只打在线;AllAllToDate 多进离线箱,AllToDate 还要按注册时间筛谁能领。具体分支在 InnerSend 里走箱管理或个人 MailComponent

参考文章
  • 3.邮件系统邮件数据结构设计
  • 4.邮件系统Mail服设计

深度题

1. MailBoxManageComponent.TimeoutMailComponent.CheckTimeOut 如何分工?怎样避免「登录灌信」和「拉邮件列表」重复扫同一批离线数据?

题目

两类定时/触发清理分别删除哪一层数据?OnlineGetHaveMailC2Mail_GetHaveMailRequestHandler 各自该做多少事?

深入解析
  1. 清理对象MailBoxManageComponent 管理未并入个人邮箱MailBoxTimeout 到期整袋 Remove,清缓存与库。MailComponent已进入某 MailUnitMail 集合CheckTimeOut 删单封过期信并同步状态给客户端。一个是「仓库里的箱子」,一个是「玩家手里的信」。
  2. 职责边界Online 中的 GetHaveMail(sync: true) 负责把离线侧应得的信批量 AddMailComponent,并推 Mail2C_HaveMail,属于数据迁移 + 通知。之后客户端若只拉简略列表,GetHaveMail 请求 handler 应 **CheckTimeOut + 从内存中的 MailComponent 序列化 MailSimplifyInfo**,而不是再次遍历全局 MailBox。否则每次打开邮箱都要 O(离线池) 扫表,延迟与锁竞争都会放大。
答题示例

箱级过期归 MailBoxManageComponent,清的是还没灌进玩家邮箱的 MailBox。灌进去之后,单封过期归 MailComponent.CheckTimeOut

登录时 GetHaveMail 把该领的离线信一次性加进 MailComponent。以后拉列表只读内存里的邮箱字典,先过期扫描再返回简略信息,不要重复全库扫 MailBox

参考文章
  • 1.邮件系统架构设计总览
  • 4.邮件系统Mail服设计


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏