《游戏服务端编程实践》2.3.3 Skynet(Lua)

解析游戏服务器的 Skynet(Lua)框架,包括其定位、价值、架构与使用场景。同时,介绍 Skynet 的核心组件(Service、Message、Cluster),展示如何基于 Skynet 构建高性能游戏服务器。

一、引言:为什么 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 实现)
serviceLua 逻辑服务层
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.waitskynet.sleep)。

3.3 服务模型

每个服务运行在独立的 C 层线程中:

  • 维护一个消息队列;
  • 顺序取出消息;
  • 分发到对应协程执行;
  • 协程阻塞时自动挂起;
  • 当消息到达或超时时被唤醒。

四、消息机制(Message Passing)

4.1 消息类型

类型功能
Request / Response请求-应答
Send单向消息
CallRPC 式同步调用(通过协程等待)
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

流程说明:

  1. 框架初始化;
  2. 加载配置与主服务;
  3. 启动 launcher;
  4. 注册服务;
  5. 开启消息循环;
  6. 执行 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/httpSkynet(Lua)
并发模型Reactor + 线程池M:N 协程调度Actor + 协程
编程语言JavaGoLua + C
异步实现Future + Callback隐式协程调度显式 coroutine
线程安全显式锁 / 无锁池自动调度完全隔离
性能极限百万连接百万连接百万消息/s
模块化极强
逻辑复杂度
热更新支持较难简单天然支持
适用场景通用服务 / RPCWeb / 微服务游戏 / 实时逻辑

十三、典型应用架构(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 是介于“语言级协程”和“系统级线程”之间的完美中间层。
它将并发、消息、逻辑三者整合成统一语义,
是小团队高性能游戏服的理想选择。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页