1. 4.1 网关的职责与架构

第二、三章分别固定了帧解析与 Actor 语义;本节说明 zhenyi/zgate.Server 如何把 IChannel 上行收敛为 zmsg.Message 路由:先到 Gate 自身 handler,再 本地 Actor,再 跨进程,最后 无路由处理。实现以 gate.go 为准。

1.1. 4.1.1 网关是什么?

网关(Gate)是客户端和业务服务之间的桥梁:

客户端 → [网关 Gate] → 业务 Actor(IM / Match / Chat / ...)

如果没有网关,每个业务 Actor 都要直接管理连接——接收数据、协议解析、心跳检测、限流……这些和业务逻辑无关的事情重复实现。

网关把这些和业务无关的职责统一接手,让业务 Actor 只专注于业务逻辑。

1.2. 4.1.2 网关的职责

从代码看,zhenyi 的 Gate 做了这些事:

职责 说明
连接管理 接受连接、断开清理、在线人数统计
协议接入 TCP / WebSocket / KCP,统一处理
心跳检测 超时断开连接
限流 单连接级令牌桶,防刷
消息路由 根据消息类型转发到对应 Actor
回复转发 把业务 Actor 的响应发回客户端
会话管理 channelId → authId 映射
性能监控 RTT、QPS、在线人数、内存指标

业务 Actor 不需要关心以上任何一件事。

1.3. 4.1.3 Gate 本身也是一个 Actor

zhenyi 的 Gate 不是一个独立的服务,它本身就是 Actor,和其他业务 Actor 一起在 Group 中运行:

// 主干节选;另含 remoteStrategy、HTTP、TLS、payloadEncrypt、noRouteHandler 等
type Server struct {
    *zactor.Actor
    *SessionManager
    server   baseziface.IServer // ztcp/zws/zkcp 等实现的 IServer
    router   ziface.LocalRouter
    metrics  *ServerMetrics
    // ...
}

(完整字段见 zhenyi/zgate/gate.go。)

这意味着 Gate 享受 Actor 模型的所有好处:

  • 消息串行处理:不需要锁
  • 自动重启:异常退出后 Group 自动拉起
  • 热更新:运行时调整参数
  • 统一监控:和其他 Actor 一起被 Group 监控

1.4. 4.1.4 消息流转的完整路径

一条客户端消息从发达到处理完成,经过了这些步骤:

1. 客户端发送消息
       ↓
2. 网络层接收(RingBuffer + 协议解析)
       ↓
3. Gate.OnRead() 收到消息
   ├── 限流检查
   ├── 心跳刷新
   └── Push 到 Mailbox
       ↓
4. Gate Actor 从 Mailbox 取出消息(Run 主循环)
       ↓
5. Gate.HandleClientMessage()
   ├── 检查 Gate 自己能否处理(已注册的 handler)
   ├── 尝试本地路由(转发到同进程的其他 Actor)
   ├── 尝试远程路由(通过 NATS 转发到其他进程)
   └── 无路由 → 回复错误
       ↓
6. 业务 Actor 处理消息,生成响应
       ↓
7. 响应回到 Gate(`Actor.Push`,`zhenyi/zactor/base.go`)
   ├── `IsResponse && !ToClient` → `SetReply`,不进邮箱
   ├── `IsResponse && ToClient` → Gate 实现 `IToClientFastPath` 时直达 `sendClient`,否则入邮箱
   └── 其它 Cmd 类型 → 入邮箱
       ↓
8. `sendClient`:`GetChannel(SessionId)` → `Channel.Send`

1.4.1. 路由优先级

Gate 的路由有明确的优先级顺序:

1. Gate 自身处理    → 已注册的 handler(如登录、心跳等 Gate 层面的协议)
2. 本地 Actor      → 同进程内的其他业务 Actor(零网络开销)
3. 远程 Actor      → 其他进程的 Actor(通过 NATS)
4. 无路由          → `sendNoRouteError`(默认打日志;可注册 `OnNoRoute` 钩子决定回包)

Gate 自身优先:认证、心跳等可在入口闭环,避免无谓的跨 Actor 投递与序列化。

1.5. 4.1.5 Gate 和业务 Actor 的关系

从架构上看,Gate 是一个入口 Actor

                    ┌─────────────┐
客户端 ───→ Gate ──→│  IM Actor    │
                    ├─────────────┤
                    │ Match Actor  │
                    ├─────────────┤
                    │ Chat Actor   │
                    └─────────────┘

特点:

  • Gate 是唯一入口:所有客户端消息都先到 Gate,再由 Gate 路由
  • 业务 Actor 不直接持有连接:下行时由 Gate 根据 zmsg.Message 中的元数据(如 SessionId)定位连接,业务不直接操作 IChannel
  • Gate 仍持有会话相关状态:例如嵌入的 SessionManagerauthId → actorType → actorId)以及底层 BaseServer 上的 authId → Channel 映射;路由决策主要依赖消息与路由表,但不要把 Gate 理解成“零状态进程”

1.5.1. 为什么不让业务 Actor 直接接收连接?

如果每个业务 Actor 都直接接收连接:

问题 说明
重复实现 每个业务 Actor 都要写限流、心跳、协议解析
连接分散 客户端要连不同端口访问不同服务
跨服务调用复杂 业务 Actor 之间要互相转发连接

统一走 Gate,这些问题都不存在。

1.6. 4.1.6 会话管理:channelId 与 authId

Gate 维护两个 ID 的关系:

ID 含义 谁分配
channelId 连接 ID,底层网络层分配 网络层自动递增
authId 业务用户 ID(如 userId) 业务逻辑在登录时设置

连接刚建立时只有 channelId,没有 authId。用户登录成功后,业务 Actor 调用:

gate.SetSessionAuth(channelId, userId)

建立绑定关系。之后路由消息时,可以通过 authId 找到对应的连接。

绑定时机由业务决定。 zhenyi 没有规定"必须在登录时绑定",这取决于业务逻辑。比如有的系统允许匿名连接,只有特定操作才需要认证。

1.7. 4.1.7 HTTP 支持

除了长连接(TCP/WS/KCP),Gate 还可以同时启动 HTTP 服务:

gate.SetHTTPAddr(":8080")

zhenyi 的 HTTP 模块(zhttp)基于标准库 net/http,提供了一套路由 + 中间件能力,handler 直接接收 Gate Actor 引用。

1.7.1. 路由注册

func init() {
    gate := ... // Gate 实例

    http := gate.HTTP()

    // 健康检查
    http.GET("/ping", func(actor ziface.IActor, w http.ResponseWriter, r *http.Request) error {
        w.Write([]byte("ok"))
        return nil
    })

    // 分组路由(统一前缀 + 中间件)
    api := http.Group("/api/v1")
    api.Use(authMiddleware)  // 给这个组加认证中间件
    api.GET("/users/:id", getUserHandler)
    api.POST("/config/update", updateConfigHandler)
}

handler 签名是 func(actor IActor, w, r) error,第一个参数直接是 Gate Actor。这意味着 HTTP handler 可以直接调用 Actor 的能力——发消息、查路由、访问 Actor 状态。

1.7.2. 中间件

func authMiddleware(next ziface.HttpHandlerFunc) ziface.HttpHandlerFunc {
    return func(actor ziface.IActor, w http.ResponseWriter, r *http.Request) error {
        token := r.Header.Get("Authorization")
        if !verifyToken(token) {
            http.Error(w, "unauthorized", http.StatusUnauthorized)
            return nil
        }
        return next(actor, w, r)
    }
}

中间件链式调用,和常见的 Web 框架用法一致。

1.7.3. 和长连接共享 Gate Actor

HTTP 和长连接在同一个 Gate Actor 进程里运行。HTTP handler 若要给长连接客户端下行,应使用 zactor.Actor 上已实现的发送 APIIActor 接口本身不包含这些方法,需对具体类型断言,例如 *zgate.Server 嵌入了 *zactor.Actor)。

推荐:回包路径——若业务上存在“对应的客户端请求信封”(同一会话、同一条链路的上下文),用 SendToClient 复制信封里的 SessionId连接 id / channelId)、SrcActorSeqId 等:

func notifyClient(actor ziface.IActor, w http.ResponseWriter, r *http.Request) error {
    gate := actor.(*zgate.Server)
    var clientReq *zmsg.Message   // 业务侧保存的最近一次该用户请求信封(示意)
    var payload ziface.IMessage   // 已实现 GetMsgId + MarshalToVT 的 protobuf 消息
    gate.SendToClient(clientReq, payload)
    return nil
}

主动推送(无现成信封)时,zgate.Server.sendClient 使用 msg.SessionId 调用 GetChannel(SessionId),因此 zmsg.Message.SessionId 必须是目标连接的 channelId,不能简单用 userId/authId 代替。需由业务维护「用户 → channelId」或在 Gate 侧通过 BaseServer.GetChannelByAuthId 等能力解析后再投递。

func pushByChannelId(actor ziface.IActor, channelId uint64, data ziface.IMessage) error {
    gate := actor.(*zgate.Server)
    a := gate.Actor
    m := zmsg.GetMessage()
    defer m.Release()
    if err := ziface.MarshalVTToMsg(data, m); err != nil {
        return err
    }
    m.MsgId = data.GetMsgId()
    m.ToClient = true
    m.IsResponse = true
    m.SessionId = channelId
    m.TarActor = gate.GetActorId()
    m.SrcActor = a.GetActorId()
    a.SendMsg(m.Retain())
    return nil
}

(历史上若见到 SendToClientByChannel 等名称,应以当前源码中的 SendToClient / SendMsg 为准。)

HTTP 常用于运维/控制面触发向长连接下行(须自行维护 channelId / SessionId 与鉴权)。

1.7.4. 安全防护

  • ReadHeaderTimeout: 5s:防止 slowloris 攻击(慢速连接耗尽服务器资源)
  • 错误信息不暴露内部细节:handler 返回 error 时,客户端只看到 500 状态码

1.8. 4.1.8 本节要点

  1. 入口Server 嵌入 zactor.Actor + SessionManager,持 IServerLocalRouter
  2. 路由序gateHandler — 自身 GetMsgListRouteLocalrouteToRemoteActorsendNoRouteError
  3. 下行sendClientSessionId(channelId) 找连接;ToClient 响应可经 Push 快路径(见 4.4)。
  4. 会话:连接维度映射在 BaseServer/Channel;业务 authId → 分片 ActorSessionManager4.2)。
  5. HTTPSetHTTPAddr 非空时随 RunServerzhttp,与 Gate 共享同一 Actor。

4.2 节:两层映射与线程安全约定。

results matching ""

    No results matching ""