五分钟跑通第一个请求
Quick Start快速上手部分只做一件事:创建一个可被调用的顶层 Actor,然后依次从进程内、HTTP 和 AGP 入口访问它。三种入口最终复用同一张 Method Table。
创建工程
初始化 Go Module,安装 ActorGo,并准备最小节点配置。
新增 Actor
嵌入 Base,声明 AliasID,在 OnInit 注册稳定 Method ID。
选择入口
本地调用直接 Invoke;后台接口挂 HTTP;实时客户端使用 AGP。
mkdir actorgo-quickstart && cd actorgo-quickstart
go mod init example.com/actorgo-quickstart
go get github.com/actorgo-game/actorgo@latest建议的学习顺序:先完成 Actor + HTTP 的 JSON 请求,确认 Method ID、Handler 和响应链路正确,再接入 TCP / WebSocket 与 Protobuf 客户端。
开发环境与依赖边界
Go 1.26.1+
版本以仓库 go.mod 为准。先用 go version 确认工具链,再初始化 Module 并安装 ActorGo。
Protobuf 工具链
使用 PB 契约时准备 protoc 与 Go 插件;JSON 快速上手不依赖代码生成。
单机零中间件
Standalone 模式可直接运行 Actor、HTTP、TCP 与 WebSocket,不需要先启动 NATS。
按需启动集群依赖
切换 Cluster 后再准备 NATS 或 RabbitMQ,并单独配置 NATS / ETCD Discovery。
新增第一个 Actor
Add an Actor顶层 Actor 是外部请求的业务边界。下面的示例定义 JSON 请求与响应,注册 Method ID 1001,并随应用启动。代码结构与当前 ActorGo API 保持一致。
package main
import (
cfacade "github.com/actorgo-game/actorgo/facade"
cactor "github.com/actorgo-game/actorgo/net/actor"
)
const HelloMethodID uint32 = 1001
type HelloRequest struct {
Name string `json:"name"`
}
type HelloResponse struct {
Greeting string `json:"greeting"`
}
type HelloActor struct { cactor.Base }
func (*HelloActor) AliasID() string { return "hello" }
func (a *HelloActor) OnInit() {
a.Methods().Register(HelloMethodID, a.hello)
}
func (a *HelloActor) hello(
_ *cfacade.RequestContext,
req *HelloRequest,
) (*HelloResponse, error) {
return &HelloResponse{Greeting: "Hello, " + req.Name}, nil
}{
"env": "local",
"debug": true,
"print_level": "debug",
"node": {
"3": [{
"node_id": "0.0.3.1",
"enabled": true,
"address": "127.0.0.1:9080"
}]
}
}package main
import "github.com/actorgo-game/actorgo"
func main() {
app := actorgo.Configure(
"profile.json",
"0.0.3.1",
actorgo.Standalone,
)
app.AddActors(&HelloActor{})
app.Startup()
}AliasID 是服务边界顶层 Actor 在启动时创建,方法会写入应用级 Method Table。
Method ID 必须稳定业务方法建议集中管理,编号大于 5,并纳入协议版本控制。
开放 Actor HTTP 接口
Actor HTTPHTTP Actor 不需要重复编写 Controller。注册组件后,所有顶层 Actor 的业务方法统一通过 POST /actor/{methodID} 暴露,Body 仍在目标 Actor 的 Mailbox 中解码。
import httpactor "github.com/actorgo-game/actorgo/net/httpactor"
app := actorgo.Configure("profile.json", "0.0.3.1", actorgo.Standalone)
app.AddActors(&HelloActor{})
app.Register(httpactor.NewComponent("actor-api", app.Address()))
app.Startup()curl -X POST "http://127.0.0.1:9080/actor/1001" \
-H "Content-Type: application/json" \
-H "X-ActorGo-Timeout-Ms: 3000" \
-d '{"name":"developer"}'Request 成功
响应 Body 为业务 Response,并保持请求使用的 JSON 或 Protobuf 编码。
Notify 已接收
通知成功进入 Mailbox,不返回业务 Body。
统一错误
参数、认证、超时和框架状态会映射为明确的 HTTP 状态码。
用于后台与服务集成:HTTP 适合管理后台、机器人、运维工具和内部服务;实时游戏长连接仍建议使用 AGP。
接入 TCP 与 WebSocket
AGP ConnectionAGP 连接器只负责连接、分帧和 Packet 传输,业务仍按 Method ID 分发到同一个 HelloActor。TCP 使用长度帧,WebSocket 的一条 Binary Message 对应一个 Packet。
import (
cfacade "github.com/actorgo-game/actorgo/facade"
"github.com/actorgo-game/actorgo/net/connector"
"github.com/actorgo-game/actorgo/net/parser"
)
app.Register(parser.New("client", []cfacade.IConnector{
connector.NewTCP(":9000"),
connector.NewWS(":9001"),
}))| 入口 | 传输格式 | 适用场景 |
|---|---|---|
| TCP | uint32 big-endian length + Packet PB | 原生客户端、稳定长连接 |
| WebSocket | 1 Binary Message = 1 Packet | 浏览器与跨平台客户端 |
| 业务 Body | JSON / Protobuf | 由每次调用的 Codec 决定 |
客户端不提交 ActorPath客户端只发送 Method ID 与 Body,服务端保留路由控制权。
先定义协议,再写 Handler生产项目建议用 Protobuf 管理请求、响应与跨版本兼容。
框架的四个核心对象
Core ConceptsActorGo 把业务状态、调用协议与基础设施分成清晰的边界。开发时先确定状态归属,再定义方法契约,最后选择入口和部署方式。
| RequestContext 字段 | 用途 |
|---|---|
Transport | 标识 AGP、HTTP 或 Cluster 入口 |
Codec | 决定本次业务 Body 使用 JSON 或 Protobuf |
Session | 保存 Sid、Uid、IP 和自定义 Data |
RequestID | 用于请求关联,Method ID 由 Message 持有 |
Metadata | 跨入口透传调用元数据 |
框架分层与能力类别
Framework Layers把 ActorGo 看成一组可以独立理解、组合使用的设计类别。它不是只提供 Actor 容器,而是覆盖接入、调用、分布式、基础设施和运行保障的完整服务端骨架。
Actor 状态与层级设计
Actor ModelingActor 的价值不是“每个对象一个 goroutine”,而是给可变状态指定唯一所有者。只有这个 Actor 能修改自己的状态,其他对象必须通过消息表达意图。
顶层 Actor
在应用启动时注册,是外部请求和跨节点调用的服务边界。它的 Method ID 会写入 Application 方法表。
- 路径
- NodeID.ActorID
- 生命周期
- 随节点启动和停止
- 适合
- Gate、Match、World、Center
子 Actor
由父 Actor 在运行时创建,只安装本地 Mailbox,不向外暴露方法表。首次路由可通过 OnFindChild 动态恢复。
- 路径
- NodeID.ActorID.ChildID
- 生命周期
- 由父 Actor 管理
- 适合
- Player、Room、Battle、Scene
开发者建模准则
- 1
围绕一致性边界拆分。需要原子更新的一组状态放在同一个 Actor;无需一致性的状态不要强行合并。
- 2
顶层 Actor 负责路由,子 Actor 负责实体状态。外部客户端不能传 target,服务端拥有最终路由权。
- 3
避免同步自调用。Actor 不能同步 Invoke 自己;父到子使用 InvokeChild / NotifyChild,避免重新排队到父 Mailbox。
- 4
让 Handler 足够短。串行模型消除了锁,但长时间阻塞会直接扩大同一 Actor 后续消息的排队时间。
游戏场景建模示例
不要按数据表机械拆 Actor,应从“谁拥有可变状态、哪些修改必须串行、哪些实体需要独立扩缩容”出发确定边界。
Match · Room · Player
Match Actor 负责匹配队列,Room / Battle Actor 拥有单局状态,Player 子 Actor 保存局内玩家快照。
World · Scene · Entity
World Actor 维护全局服务边界,Scene Actor 按区域拆分,Entity 子 Actor 承载玩家与 NPC 的动态状态。
Player · City · Alliance
围绕玩家、城池和联盟的一致性边界拆分 Actor;跨实体操作通过消息编排并设计幂等。
Lobby · Guild · Chat
大厅处理入口编排,Guild Actor 串行化公会状态,Chat Actor 按频道或房间隔离消息流。
消息运行时与方法契约
Message RuntimeAGP、HTTP、本地 Invoke 和集群消息最终都变成同一种内部 Message。运行时只在边界处做协议转换,Actor 内部始终处理 typed request。
Resolve
Method Table 根据 Method ID 定位顶层 Actor。
Clone
克隆 RequestContext、Session 与 Metadata,隔离可变状态。
Queue
Message 进入目标 Actor 的单一 Mailbox。
Decode
网络字节按本次 Codec 解码;进程内 typed value 跳过序列化。
Invoke
Typed Handler 串行执行并生成 InvokeResult。
Recycle
回写结果后释放 Context 并将 Message 放回对象池。
Mailbox、Event 与 Timer 在同一个 Actor goroutine 中被 select 和串行执行。
当 payload 已是目标 Go 类型时直接调用;只有网络字节才经过 Body Codec。
结果通道带缓冲;即使调用方已经超时,Actor 完成工作后也能安全回收 Message。
Typed Handler 如何注册
所有顶层入口共用同一张方法表。注册时校验签名,重复 ID 或非法 Handler 会在 Actor 初始化阶段直接暴露。
const LoginMethodID uint32 = 1001
func (a *PlayerActor) OnInit() {
a.Methods().Register(LoginMethodID, a.Login)
}
func (a *PlayerActor) Login(
ctx *facade.RequestContext,
request *playerv1.LoginRequest,
) (*playerv1.LoginResponse, error) {
return &playerv1.LoginResponse{}, nil
}func(*RequestContext, *Request) (*Response, error)func(*RequestContext, *Request) error协议、会话与调用入口
Protocol & Entry客户端只提交 Method ID 与 Body,不指定 Actor Target。外部请求先进入顶层 Actor,再由服务端决定是否访问动态子 Actor。
| 入口 | 传输格式 | 用途 / 返回 |
|---|---|---|
| TCP | uint32 big-endian length + Packet PB | 长连接客户端 |
| WebSocket | 一条 Binary Message 对应一个 Packet | 子协议 agp.v1 |
| HTTP | POST /actor/{methodID} | Request 200 · Notify 202 |
| Cluster | Protobuf ClusterMessage | Invoke / Notify |
HTTP JSON 示例:POST /actor/1001 Content-Type: application/json。Request 成功返回 200;Notify 成功返回 202;错误 Body 统一为 HTTPError。
AGP 必须遵守的协议约束
控制包固定 Protobuf
Packet 与 ClusterMessage 固定使用 Protobuf;JSON / PB 只决定业务 Body 的编解码。
三种业务消息
客户端交互只有 Request、Response、Notify;Request 返回结果,Notify 成功入队即结束。
业务 ID 必须大于 5
1–5 保留给 Handshake、Heartbeat、Cancel、GoAway 与 Kick,业务 Method ID 在节点内全局唯一。
WebSocket 只收 Binary
一条 Binary Message 对应一个 Packet,并声明 agp.v1 子协议;Text Message 会被拒绝。
事件、定时器与子 Actor
Event, Timer & Child方法调用解决明确的请求与通知;事件、定时器和子 Actor 则负责领域内的异步协作。三者都回到 Actor 自己的串行执行上下文,避免额外并发状态。
async 参数要谨慎:定时器支持异步触发选项,但一旦业务函数脱离 Actor goroutine,就需要由开发者自己保证并发安全。
集群、发现与远程调用
Local & Cluster本节点使用 Invoke / Notify,跨节点使用 InvokeNode / NotifyNode。只有服务端内部定向访问子 Actor 时才使用完整 ActorPath。
ctx := facade.NewRequestContext(context.Background())
ctx.Codec = facade.CodecProtobuf
// 本节点顶层方法
result := app.ActorSystem().Invoke(ctx, methodID, req)
// 指定远端节点;底层可选择 NATS 或 RabbitMQ
result = app.ActorSystem().InvokeNode(
ctx, "center-1", methodID, req,
)NATS 默认
默认选择,适合节点间低延迟 RPC,部署与使用更轻量。
RabbitMQ 可选
需要统一 MQ 基础设施时可选,当前实现为尽力投递。
Discovery
维护 NodeID、NodeType 与节点信息。可使用 NATS 或 ETCD,独立于消息传输选择。
Node Selection
业务根据服务类型和路由策略选择节点,再调用 InvokeNode / NotifyNode。
Cluster Transport
把 RequestContext、Method ID、Payload 与 Deadline 编码为 Protobuf ClusterMessage。
Remote Dispatch
目标节点仍通过本地 Method Table 定位顶层 Actor,调用方不需要知道远端 ActorPath。
跨节点传输的实际语义
发现与传输独立
Discovery 可选择 NATS 或 ETCD;Cluster Transport 可独立选择 NATS 或 RabbitMQ。
请求响应可关联
Invoke 使用 Request / Reply;RabbitMQ 通过 ReplyTo 与 CorrelationId 关联响应并执行本地超时。
通知单向发布
Notify 不等待业务响应。RabbitMQ 直接发布到目标 remote routing key,不设置 ReplyTo。
明确背压与交付语义
工作队列满时 Request 返回 RESOURCE_EXHAUSTED、Notify 丢弃;当前 RabbitMQ 实现为尽力投递。
应用与组件生命周期
Application & ComponentApplication 是节点进程的组合根。组件按注册顺序启动、按逆序停止,使数据库、连接器、Actor System 等依赖关系可以确定性释放。
- 01Set注入 Application
- 02Init按注册顺序初始化
- 03OnAfterInit所有组件就绪后执行
- 04OnBeforeStop逆序准备关闭
- 05OnStop逆序释放资源
基础设施与生产实践
Infrastructure & ProductionActor 模型减少了共享状态问题,但不会自动解决慢 I/O、重复消息、协议兼容和观测缺口。上线前应显式确认这些工程边界。
设置调用边界
为 Invoke 设置端到端 CallTimeout,并配置 ExecutionTimeout 记录慢 Handler。
避免阻塞 Actor
Handler 应保持短小。慢数据库、外部 HTTP 或长计算应拆分,避免占住 Actor 串行执行权。
尊重串行语义
Actor 不能同步 Invoke 自己;父 Actor 调子 Actor 使用 InvokeChild,避免消息重入导致死锁。
固定协议契约
Method ID 与 Protobuf 定义应版本化管理;切换默认 Codec 只在 Startup 前执行。
明确交付语义
RabbitMQ 当前为非持久、自动删除队列和尽力投递;关键业务需自行设计幂等与补偿。
建立观测基线
记录 RequestID、Method ID、ActorPath、耗时和错误码,结合 Gops 排查运行时问题。
隔离持久化细节
将 GORM、Mongo 与 Redis 封装在组件或仓储层,不让 Actor 业务直接依赖连接生命周期。
覆盖关键测试
重点验证 Method 表、Codec、上下文取消、子 Actor、连接协议和集群响应关联。
game/
├── api/ # Protobuf 契约与 Method ID
├── actor/ # 状态、Handler、事件与定时器
├── service/ # 跨 Actor 用例编排
├── repository/ # 数据访问接口与实现
├── component/ # 外部资源与生命周期适配
├── profile/ # 节点与环境配置
└── cmd/ # 各节点启动入口按需组合基础设施
组件通过 Application 生命周期统一注入。只注册节点真正需要的能力,并通过接口或仓储层隔离业务代码与基础设施实现。
接入与 API
Connector · Parser · HTTP Actor · Gin
数据与状态
GORM · MongoDB · Redis · Data Config · Distributed Lock
运行与工具
Cron · Gops · Logger · Profile · Time Wheel · Snowflake
提交前执行核心验证
优先覆盖协议编解码、Method Table、连接边界、HTTP 分发和 Actor 上下文生命周期;集群项目还应补充真实双节点与压力测试。
go test ./net/proto ./net/serializer ./net/method \
./net/parser ./net/httpactor ./net/actor ./net/connector从一个顶层 Actor 开始
先在单机模式完成业务闭环,再根据真实规模拆分节点。