Skynet 是云风(吴云洋)开源的轻量级游戏服务器框架,采用 Actor 模型:底层用 C 实现消息调度与网络层,上层业务逻辑全部用 Lua 编写,每个服务运行在独立的 Lua 虚拟机中,通过消息传递协作。它在国内游戏服务端领域有广泛影响力,是理解「Lua 作为服务端业务语言」的最佳样本。
架构解析:服务、消息队列与调度
Skynet 的世界观里只有一个核心概念——服务(service)。每个服务是一个独立的 lua_State,拥有自己的消息队列,服务之间不共享任何内存,只能通过消息通信。这从根本上消灭了锁:业务代码写在单线程语义里,并发安全由框架的隔离性保证。
整体消息流可以概括为:
┌──────────── Skynet 节点(单进程) ─────────────┐
│ │
网络层 │ ┌────────┐ 消息 ┌────────┐ 消息 ┌────────┐ │
socket──┼──▶│ gate │────────▶│ agent │────────▶│ world │ │
│ │ 服务 │◀────────│ 服务 │◀────────│ 服务 │ │
│ └────────┘ 应答 └────────┘ 应答 └────────┘ │
│ 每个服务 = 独立 lua_State + 私有消息队列 │
│ C 调度器用工作线程池轮流取出队列消息执行 │
└──────────────────────────────────────────────────┘
│
harbor 集群(跨节点互联)
关键组件:
- 消息队列与调度器:C 层维护全局二级队列,工作线程池按权重取出有消息的服务,回调它的 Lua 消息处理函数。单进程多线程充分吃满多核,而业务代码无锁。
- 定时器:
skynet.timeout注册的定时任务本质也是消息,到点后投递到服务队列,和 network 消息走同一条路径。 - harbor 集群:多个 Skynet 节点通过 harbor 互联,服务地址全局唯一,
skynet.call一个远程服务和调用本地服务的写法完全一致,位置透明。
最小服务与通信示例
一个响应 PING/PONG 的最小服务:
local skynet = require "skynet"
skynet.start(function()
-- 注册 lua 类型消息的处理函数
skynet.dispatch("lua", function(session, source, cmd, ...)
if cmd == "PING" then
-- call 来的消息需要 ret 应答,session 由框架处理
skynet.ret(skynet.pack("PONG"))
else
error("未知命令: " .. tostring(cmd))
end
end)
skynet.error("ping 服务启动:", skynet.self())
end)
服务间通信有 call(请求-应答,挂起当前协程等待结果)和 send(单向投递,不等回应)两种:
local skynet = require "skynet"
skynet.start(function()
-- 启动 ping 服务
local ping = skynet.newservice("ping")
-- call:同步语义,底层是协程挂起 + 应答消息唤醒
local resp = skynet.call(ping, "lua", "PING")
skynet.error("收到应答:", resp) -- 输出 PONG
-- send:只投递不关心结果,适合事件通知
skynet.send(ping, "lua", "PING")
skynet.exit()
end)
要点:skynet.start 是服务的入口,框架在完成初始化后才把消息投递进来;skynet.dispatch 按消息类型注册回调,是服务的「路由表」。所有通信都经过消息队列,call 的同步写法只是协程封装出的假象,服务之间依然是纯异步的。
为什么是 Lua
Skynet 选择 Lua 作为业务语言,是几个特性叠加的结果:
- Actor 模型与协程天然契合。每个服务的消息处理跑在独立的协程里:
skynet.call挂起协程等待应答,应答消息到达后由调度器唤醒。业务代码写着同步逻辑,跑着异步并发,不需要回调地狱,也不需要锁。Lua 协程极低的创建开销让一个进程承载成千上万个服务成为常态。 - 每个服务一个 lua_State 的隔离性。Lua 解释器本身小巧,创建成本低,恰好匹配「服务即虚拟机」的模型;服务崩溃不会拖垮整个进程,沙箱式隔离天然成立。
- 热更新能力。Lua 脚本的动态可替换性让 Skynet 服务端可以在不停机的情况下更新业务逻辑(见 Lua 热更新技术),snax 子框架的
snax.hotfix正是为此而生。 - C/Lua 边界清晰。性能敏感的消息调度、网络 IO 用 C 写好,业务迭代快的玩法逻辑用 Lua 写,两者通过简洁的 C API 衔接(参见 Lua 与 C 集成指南)。
生态与现状
Skynet 诞生以来支撑了大量商业游戏项目,尤其在 MMO、卡牌、SLG 等品类的服务端被广泛采用,基于它衍生出的架构经验(gate/agent/world 分层、服务拆分、数据中心模式)影响了整个国内游戏服务端的技术选型。官方仓库长期维护,配套有 snax 服务框架、共享数据方案 sharedata、以及丰富的社区示例。
与其他方案的定性对比:
- 对比 Go(go-zero 等微服务体系):Go 的 goroutine 提供了相近的轻量并发体验,且工程化工具链、微服务生态更完善,适合通用互联网业务;Skynet 的优势在游戏领域的心智模型(Actor、单点服务、热更)和极低的框架复杂度——核心代码量小,可以完全读懂。
- 对比 Erlang:Erlang/OTP 是 Actor 模型的鼻祖,容错与分布式能力更成熟,但人才储备少、生态小众;Skynet 用 C+Lua 实现了类似的并发哲学,学习曲线和招聘成本都友好得多。
- 对比 C++ 自研框架:Skynet 把「并发正确性」这件最难的事收进了框架层,业务开发效率远高于裸写多线程 C++。
学习建议:先把 Lua 基础 和协程吃透,再通读 Skynet 官方 wiki 的「快速入门」,然后用 snax 写一个带数据库的登录+场景服务,最后研读 gate/agent 模式的开源示例。Skynet 的价值不仅在框架本身,更在它所代表的并发设计思想——理解了它,再看 Erlang、Go 的游戏服务器方案都会有触类旁通的感觉。对 Lua 在游戏中的整体定位感兴趣的话,可以配合阅读 Lua 在游戏开发中的应用。
常见问题(FAQ)
Skynet 现在还值得学吗?
值得。Skynet 维护稳定,仍是国内游戏服务端的主流方案之一,求职市场上需求持续存在。更重要的是它的代码量小、设计干净,是学习 Actor 模型和游戏服务器架构的优质教材,学到的并发思想可以迁移到其他技术栈。
Skynet 和 Go 语言服务端怎么选?
看团队和项目类型。做重度游戏(MMO、SLG)且团队有 Lua/游戏背景,Skynet 的 Actor 模型和热更能力更贴合;做通用互联网服务或微服务体系,Go 的生态、工具链和招聘便利性更有优势。两者都能承载高并发,差异主要在生态与工程化而非性能。
Skynet 适合做 MMO 吗?
适合,这也是它最初的设计目标。单节点承载万级连接的 gate/agent 模式、服务间消息驱动的 world 服务、harbor 跨节点互联,正是为 MMO 场景准备的。多家厂商的商业 MMO 项目已验证了这条路线的可行性。
学 Skynet 需要先掌握哪些 Lua 知识?
至少包括:table 与元表(Skynet 的模块和类机制大量使用)、协程(理解 call/send 的挂起唤醒原理)、模块与包(服务即模块)。错误处理(pcall/xpcall)也建议在写正式服务前熟悉。
相关阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。