1. 1.3 服务级 Actor vs 对象级 Actor

Actor 粒度 上,业界常见两档:对象级(细、多)与 服务级(粗、少)。路由方式、监督形状与状态存放位置随之不同。本节讨论其差异与在 Go 环境下的常见取舍;与 zhenyi 的直接对应见 1.3.7

1.1. 1.3.1 两种流派

1.1.1. 对象级 Actor(Erlang / Akka 等语境下常见)

往往一实体一 Actor,例如:

100 万在线用户 → 100 万个 UserActor
1 万个房间   → 1 万个 RoomActor

单个 Actor 很轻,调度、生命周期、迁移多由专用运行时承担。

特点概览:

  • 数量可达百万、千万级
  • 监督结构常较深
  • 位置透明与迁移是体系的常规能力
  • 路由、序列化等与平台紧耦合

1.1.2. 服务级 Actor(粗粒度 / 分片式)

一个可对外命名的服务或分片对应少量 Actor,例如:

IM 业务     → 少量进程内实例(按负载再分片)
匹配业务    → 少量实例
入口        → 少量统一接入实例

单进程内 Actor 数量常为数十量级;大量业务实体状态存放在各 Actor 内部的 map、切片等结构中。

特点概览:

  • Actor 个数少
  • 监督常扁平
  • 实体状态集中在少数 Actor 的内部表结构
  • 入口层 + 分片规则将请求导向正确的处理实例(网关、路由、目录等名称因项目而异)

后文将这一档简称为 服务级

1.2. 1.3.2 在 Go 环境下,服务级为何更常出现

这通常不是单一「架构评审」的结论,而是 语言成本与工程范围 共同作用的结果。

Goroutine 与 Erlang process 的成本模型不同:数量极大时,栈与调度相关开销不可忽视;Go 也未内置「百万级轻进程 + 全局邮箱 + 内建分布」的 Actor 机。

若在 Go 上坚持对象级全套:海量 Actor 的调度、生命周期、迁移、深层监督与全局路由,接近重做一层运行时,工作量通常显著高于在服务级形态下把复杂度收进「少数处理主线 + 内部数据结构」。

服务级时:每条主线仍是「邮箱 + 顺序处理」,但实体态落在 map 等结构的条目上;调度由少量 goroutine 承担;监督常用单层策略即可;路由由统一入口与分片规则完成。对中小团队而言,更易在可维护性与性能之间取得平衡。

对照表(概括用,非严格定理):

维度 对象级 服务级
调度 强依赖平台 少量 goroutine + 进程内结构
生命周期 平台托管多 Init → Run → Close 等常规模式
跨节点 常围绕单 Actor 迁移 常围绕分片或业务层搬迁
监督 深树常见 扁平常见
路由 全局 Actor 寻址 入口 → 分片 → 本地查表

许多 Go 系实时框架 偏向服务级,与「是否合乎某经典 Actor 定义」关系不大,更多与交付与维护成本有关。本书示例代码也采用这一路。

1.3. 1.3.3 同一业务的两条叙事(示意)

1.3.1. 对象级

用户 A → 发消息给用户 B:

1. 投递到 UserActor(B)
2. 在 UserActor(B) 内处理(B 的状态附着于该 Actor)
3. 再投递到 B 的下行路径(连接、推送等)

B 所在节点由全局目录等机制解析;Actor 迁移时更新目录,调用形态可保持稳定。

1.3.2. 服务级

用户 A → 发消息给用户 B:

1. 入口收到报文
2. 按规则选择承载 B 所在分片的业务 Actor
3. 在该 Actor 内的表中访问 B 的状态并处理
4. 下行经入口或约定出口送达 B 的连接

B 所在分片由 userId → 分片 等规则决定;语义上选择的是分片与服务实例,而非全局唯一的「UserActor 进程句柄」。

1.3.3. 差异小结

方面 对象级 服务级
用户态主要位置 独占的 UserActor 分片 Actor 内表项
路由目标 具体 Actor 分片 + 表查找
扩容时常见操作 迁移单个轻量 Actor 调整分片映射与数据面搬迁

对象级在模型上强调一实体一隔离单元;服务级在工程中强调少量实例扛大规模实体。在 Go 生态中,后者更为常见。

1.4. 1.3.4 服务级的常见代价

1.4.1. 内部并发

单线程顺序处理消息的前提下,进程内表结构往往无需外加锁

// 示意:仅当消息始终在同一线程处理时成立
func (svc *imShard) onChat(msg Envelope) {
    u := svc.users[msg.FromSession]
    u.unread++
    svc.users[msg.FromSession] = u
}

若使用后台 worker 池并行执行且并发触及同一张共享表,则需锁、分片、或将结果回填邮箱再合并等设计,否则会出现数据竞争。

1.4.2. 分片热点

单分片承载过多实体,或个别热点实体流量过大,会使该分片成为瓶颈。缓解手段包括:再分片、垂直拆分服务、或将重计算异步化等。

1.4.3. 状态搬迁

扩缩容改变分片映射时,状态多在内存表中;数据面(脏标记、拉取、双写等)需业务与运维设计。本书 第四章 4.5 从路由与粘性角度讨论映射变化时的一种现象;框架一般不提供通用的「整表搬家」编排。

1.5. 1.3.5 混合粒度

实践中可同时存在:主体用服务级扛量,局部用更细粒度的 Actor(如按房间、按回合单独一节流)。只要消息抽象不过度绑定某一种粒度,后续调整空间更大。


1.6. 1.3.6 本节要点(模型层)

对象级:Actor 数量大、平台能力强、监督与路由深度集成。服务级:Actor 少、状态在进程内表中、入口加分片、监督常扁平。在 Go 上完整复刻对象级体系成本高,服务级更普遍;代价是并行与共享表热点搬迁需自行设计。是否混合粒度依业务而定。


1.7. 1.3.7 附录:与 zhenyi 的对应

正文用语 zhenyi 中常见落点
统一入口 zgate.Server
分片与选址 本地 LocalRouter,远程 RemoteRouteStrategy(如 HRW);第四、五章
进程内用户表等 各业务 Actor 内 map / 结构体
后台任务 AsyncRun、协程池;第三章(注意与主线程共享数据)
粘性 / 扩缩容路由 4.5
监督 Group.superviseActorMaxRestarts 等;第三章

阅读正文时区分 「调整分片映射」「搬迁表内多行数据」,比记忆符号更能指导实现。

results matching ""

    No results matching ""