《游戏服务端编程实践》2.3.3 Skynet(Lua)
一、引言:为什么 Skynet 能成为游戏后端事实标准
在中国游戏行业,Skynet 被广泛应用于:
- MMO / SLG / 卡牌游戏;
- 即时战斗 / 聊天 / 世界服;
- 自研分布式框架的基础;
- 各种 Lua 游戏逻辑开发环境。
它具备:
- 极简、高性能、内存占用低;
- 天然分布式设计;
- Actor + 协程模型;
- 完美适配 Lua 逻辑层。
二、Skynet 的总体架构
Skynet 的设计思想可以用一句话概括:
“通过消息驱动的多服务系统实现并发隔离。”
每个服务(service)是一个独立实体,有自己的消息队列、逻辑协程和状态, 不同服务之间只通过消息(message)通信,而不会共享内存。
2.1 架构图
graph TD
A[Main Thread] --> B[Service Manager]
B --> C1[Service 1: Gate]
B --> C2[Service 2: Login]
B --> C3[Service 3: World]
B --> C4[Service 4: DB]
C1 <--> C2
C2 <--> C3
C3 <--> C4
每个 Service:
- 拥有独立逻辑;
- 拥有自己的消息队列;
- 拥有多个协程;
- 在独立线程上执行;
- 通过异步消息通信。
2.2 主要模块
| 模块 | 功能 |
|---|---|
| skynet | 框架核心(C 实现) |
| service | Lua 逻辑服务层 |
| cluster | 分布式节点通信 |
| socket | 网络模块 |
| timer | 定时任务模块 |
| harbor | 节点间消息转发 |
| launcher | 服务启动管理器 |
三、Skynet 的并发模型:Actor + 协程
Skynet 模型融合了 Erlang 的 Actor Model 与 Lua 的 Coroutine 协程模型。
3.1 Actor 模型基本原则
| 原则 | 含义 |
|---|---|
| 一切皆 Actor(服务) | 每个逻辑单元是独立服务 |
| 消息通信代替共享内存 | 服务间完全隔离 |
| 每个 Actor 串行处理消息 | 避免加锁 |
| 异步消息传递 | 调度高效 |
| 透明位置(Location Transparency) | 无论本地/远程,消息机制一致 |
3.2 协程模型
Lua 本身支持轻量级协程(coroutine),Skynet 在此基础上实现了:
- 协程池(coroutine pool);
- 协程挂起/唤醒机制;
- 协程与消息队列结合;
- 协程级异步等待(
skynet.wait、skynet.sleep)。
3.3 服务模型
每个服务运行在独立的 C 层线程中:
- 维护一个消息队列;
- 顺序取出消息;
- 分发到对应协程执行;
- 协程阻塞时自动挂起;
- 当消息到达或超时时被唤醒。
四、消息机制(Message Passing)
4.1 消息类型
| 类型 | 功能 |
|---|---|
| Request / Response | 请求-应答 |
| Send | 单向消息 |
| Call | RPC 式同步调用(通过协程等待) |
| Sleep / Wakeup | 定时器唤醒 |
| System / Error | 框架内部事件 |
4.2 消息流图
sequenceDiagram
participant A as Service A
participant B as Service B
A->>B: skynet.call("B", "lua", "request_data")
B-->>A: return data
Skynet 实现了:
- 跨线程、跨节点消息投递;
- 自动序列化;
- 消息队列调度;
- 异步等待。
4.3 Call 与 Send 区别
| 函数 | 模式 | 是否等待 | 返回值 |
|---|---|---|---|
skynet.call |
请求/应答 | ✅ 等待 | ✅ 有 |
skynet.send |
异步消息 | ❌ 不等待 | ❌ 无 |
-- 同步调用
local result = skynet.call("db", "lua", "get_player", uid)
-- 异步调用
skynet.send("log", "lua", "write_log", uid, "login success")
五、Skynet 的运行流程
graph TD
A[skynet.start] --> B[service bootstrap]
B --> C[launcher 加载服务]
C --> D[message dispatch loop]
D --> E[协程执行]
E --> F[skynet.wait / sleep 挂起]
F --> D
流程说明:
- 框架初始化;
- 加载配置与主服务;
- 启动 launcher;
- 注册服务;
- 开启消息循环;
- 执行 Lua 逻辑。
5.1 服务启动示例
local skynet = require "skynet"
skynet.start(function()
skynet.newservice("gate")
skynet.newservice("login")
skynet.newservice("world")
end)
5.2 服务实现示例(Gate)
local skynet = require "skynet"
local socket = require "skynet.socket"
skynet.start(function()
local listen_fd = socket.listen("0.0.0.0", 8888)
socket.start(listen_fd, function(fd, addr)
skynet.error("new client from", addr)
skynet.newservice("agent", fd)
end)
end)
每个连接由独立 agent 服务处理,完美并发且无锁。
六、服务之间的通信机制
6.1 注册服务
skynet.name(".login", skynet.self())
使用 “.service_name” 注册全局名称。
6.2 获取句柄与调用
local login = skynet.localname(".login")
skynet.call(login, "lua", "auth", uid)
6.3 跨节点通信
Skynet 支持分布式节点集群:
cluster.open("node1")
local node2 = cluster.query("node2", ".world")
skynet.call(node2, "lua", "update_player", uid)
七、Skynet 的多线程与负载模型
虽然每个服务看似独立进程, 实际上多个服务共享少量工作线程(Worker Thread)。
7.1 Worker 调度图
graph TD
A[Global Queue] --> B[Worker 1]
A --> C[Worker 2]
A --> D[Worker 3]
- 每个服务拥有消息队列;
- 调度器(Dispatcher)将消息分配给空闲 worker;
- worker 执行 Lua 协程;
- 任务结束后 worker 归还。
7.2 调度过程
for (;;) {
q = global_queue.pop();
if (q) {
message = q.pop();
dispatch_message(q.service, message);
}
}
- 调度器为每个线程不断分发任务;
- 服务间通信完全基于消息队列;
- Lua 层无需锁;
- 性能极高。
八、Skynet 的协程机制
8.1 协程池
Skynet 内部维护一个协程池(Coroutine Pool):
- 重复利用协程对象;
- 避免频繁创建与销毁;
- 提高性能。
8.2 协程挂起与恢复
local id = skynet.wait()
-- 等待消息到来
skynet.wakeup(id)
或使用:
skynet.sleep(100) -- 100 ticks later wake up
协程阻塞时:
- 当前 worker 可立即执行其他任务;
- 不会卡住线程;
- 完全用户态调度。
九、Skynet 的通信协议与封装
Skynet 提供多种协议模块:
lua:默认消息格式;text:纯文本;json:JSON 协议;sproto:二进制结构化协议;protobuf:支持第三方库。
sproto 是 Skynet 原生高性能协议,适合游戏服。
9.1 sproto 协议示例
local sprotoloader = require "sprotoloader"
local sp = sprotoloader.load(1)
local msg = sp:encode("Login", {uid=1001, name="Alice"})
十、定时与调度系统
Skynet 内置高精度定时器:
skynet.timeout(100, function()
print("100 ticks later")
end)
- 单位:1 tick ≈ 0.01s;
- Timer 精度可配置;
- 适合实现冷却、Buff、心跳、任务调度。
十一、Skynet 的性能特点
| 特性 | 说明 |
|---|---|
| 轻量协程 | 用户态、非抢占 |
| 消息驱动 | 无锁并发 |
| 多线程调度 | C 层并行执行 |
| 服务隔离 | 内存安全 |
| 分布式支持 | 天然 cluster 模型 |
| 极小内存占用 | 每服务数 KB |
| 高性能网络 | 基于 epoll |
| Lua 可热更 | 动态逻辑更新 |
十二、Skynet 与其他框架对比
| 对比项 | Netty(Java) | Go net/http | Skynet(Lua) |
|---|---|---|---|
| 并发模型 | Reactor + 线程池 | M:N 协程调度 | Actor + 协程 |
| 编程语言 | Java | Go | Lua + C |
| 异步实现 | Future + Callback | 隐式协程调度 | 显式 coroutine |
| 线程安全 | 显式锁 / 无锁池 | 自动调度 | 完全隔离 |
| 性能极限 | 百万连接 | 百万连接 | 百万消息/s |
| 模块化 | 强 | 中 | 极强 |
| 逻辑复杂度 | 高 | 低 | 中 |
| 热更新支持 | 较难 | 简单 | 天然支持 |
| 适用场景 | 通用服务 / RPC | Web / 微服务 | 游戏 / 实时逻辑 |
十三、典型应用架构(Skynet 游戏服)
graph TD
A[Gateway Service] --> B[Login Service]
B --> C[Agent Service]
C --> D[World Service]
D --> E[DB Service]
E --> F[Cluster Node]
- Gateway:管理网络连接;
- Login:处理认证;
- Agent:玩家会话逻辑;
- World:世界状态、房间系统;
- DB:异步存取;
- Cluster:节点通信。
13.1 玩家请求流程
sequenceDiagram
participant Client
participant Gateway
participant Agent
participant World
Client->>Gateway: 登录请求
Gateway->>Agent: 转发消息
Agent->>World: 请求进入世界
World-->>Agent: 确认进入
Agent-->>Client: 返回成功
十四、性能调优建议
| 项目 | 建议 |
|---|---|
| 服务数量 | 控制在 CPU 核数 × 2–3 |
| 消息大小 | < 1KB 最优 |
| 协程数量 | 可达 10w+,但需控制内存 |
| 网络模型 | 多 Gate + 多 Agent |
| 跨服务调用 | 尽量异步 Send |
| 数据库操作 | 使用 Async Queue |
| 日志写入 | 独立异步服务 |
| 热更逻辑 | package.loaded[module]=nil 重载 |
十五、扩展与生态
Skynet 生态非常完善:
- sproto:高性能协议;
- sharedata:共享只读数据;
- cluster:跨节点通信;
- snax:轻量服务模块;
- skynet-mongo / mysql / redis:数据库驱动;
- skynet-websocket:网络扩展。
许多自研 MMO/SLG 引擎(如云风的 demo)完全基于此生态。
十六、与 Go / Java 结合实践
在大型项目中,常采用:
graph TD
A["Gateway(Go)"] --> B["Logic Server(Skynet)"]
B --> C["Matchmaking(Go) / Chat(Go) / Pay(Java)"]
- Go:高并发网关;
- Skynet:Lua 逻辑执行;
- Java:重型模块与外部接口;
- Redis / MQ:跨语言消息总线。
Skynet 可通过 socket / RPC / HTTP 与任意语言交互。
十七、学习与掌握路径
| 阶段 | 内容 |
|---|---|
| 入门 | 理解服务与消息机制 |
| 进阶 | 熟悉协程与异步调用 |
| 工程 | 构建多服务架构 |
| 优化 | 调度、分布式通信 |
| 融合 | 与 Go / Java 共存 |
十八、小结
| 核心思想 | 说明 |
|---|---|
| Actor 模型 | 每个服务独立、消息驱动 |
| 协程并发 | 用户态调度,无锁高效 |
| 消息隔离 | 无需加锁,避免竞态 |
| 天然分布式 | Cluster 模块跨节点通信 |
| Lua 热更 | 动态逻辑更新,快速开发 |
| C 性能内核 | 高吞吐、低延迟 |
一句话总结: Skynet 是介于“语言级协程”和“系统级线程”之间的完美中间层。 它将并发、消息、逻辑三者整合成统一语义, 是小团队高性能游戏服的理想选择。