i007.cc

i007.cc

优先队列-降维打击

05.价值资料

使用Lua开发游戏服务器是主流么?你推荐么?有什么好处和坏处?

这是个很有历史背景的问题,答案在不同年代、不同地区差异很大。


历史背景:为什么 Lua 会进入游戏服务器

Lua 本来是客户端脚本语言(魔兽世界的插件系统、游戏内配置),进入服务器端主要是因为一个框架:

Skynet(云风,2012 年开源)

┌─────────────────────────────────────────────┐
│               Skynet 架构                    │
│                                             │
│  C 核心层(高性能,稳定)                    │
│  ├─ Actor 模型(每个 Service 是独立 Actor)  │
│  ├─ 消息调度                                │
│  ├─ 网络 I/O                               │
│  └─ Lua VM 管理                             │
│                                             │
│  Lua 逻辑层(快速迭代,可热更)              │
│  ├─ 游戏逻辑 Service                        │
│  ├─ 协议处理                               │
│  └─ 业务配置                               │
└─────────────────────────────────────────────┘

 

Skynet 把 Lua 协程和 Actor 模型结合得非常优雅,在中国游戏行业影响深远,2013~2018 年间成为国内中小游戏公司做 MMO 服务器的事实标准。


现在还是主流吗

中国市场:曾经是主流,现在是存量主流。大量线上运营的游戏仍在跑 Skynet + Lua,但新项目选它的比例在明显下降。

海外市场:从来不是主流。欧美游戏公司的服务器以 Java、Go、C++ 为主,Lua 只作为嵌入式脚本语言出现。


Lua 做服务器最大的优势

1. 热更新:这是真正的杀手级特性

这是 Lua 在游戏服务器存活至今的最核心原因。

lua
-- 线上游戏发现技能伤害计算有 bug
-- C++/Go 需要:编译 → 灰度 → 重启服务器 → 玩家掉线重连
-- Lua 可以:直接推送新的 Lua 文件,运行中替换,玩家无感知

-- skynet 热更新:
skynet.call(".launcher", "lua", "RELOAD", "skill.damage")
-- 技能伤害逻辑立即生效,服务器不重启,玩家不断线

 

对于有大量线上玩家的 MMO,每次 bug 修复都要停服是不可接受的。Lua 热更让”随时打补丁”成为可能。

2. Lua 协程:与游戏逻辑天然契合

lua
-- Skynet 的协程风格:看起来同步,实际上异步
-- 写一个需要等待多步 IO 的任务,代码极其清晰

local function player_buy_item(player_id, item_id, count)
    -- 等 Redis 检查库存(协程挂起,不阻塞其他服务)
    local stock = skynet.call(".redis", "lua", "get", "stock:"..item_id)
    
    if stock < count then
        return false, "库存不足"
    end
    
    -- 等 MySQL 扣款(协程挂起)
    local ok = skynet.call(".mysql", "lua", "deduct_gold", player_id, price)
    
    if not ok then
        return false, "金币不足"
    end
    
    -- 等发放道具(协程挂起)
    skynet.call(".item_service", "lua", "grant", player_id, item_id, count)
    
    return true, "购买成功"
end

 

每个 skynet.call 都是协程挂起点,写起来完全像同步代码,但底层是全异步的。

3. 降低逻辑开发门槛

策划和初级程序员可以直接写 Lua 逻辑,不需要懂 C++ 模板或 Go 的并发模型。对快速堆内容的 MMO 类游戏,这个优势很实际。


Lua 做服务器的劣势

1. 动态类型:大型项目的噩梦

lua
-- 这段代码在运行前没有任何报错
function calculate_damage(attacker, defender)
    -- 如果某个字段改了名字,or 传错了参数类型
    -- 直到运行时才会崩,而且错误信息可能很难追踪
    return attacker.atk * (1 - defender.def_ratio) * skill_multiplier
end

-- 调用时传错了参数顺序,没有编译器提醒你
calculate_damage(damage_data, player)  -- 顺序传反了,运行时才发现

 

项目超过 10 万行 Lua 后,重构几乎是噩梦。改一个字段名需要全局搜索,改错了只能在线上崩溃时发现。

2. 性能天花板低

纯计算性能对比(相对值):
C++         100
Go           60
Java         55
LuaJIT       20   ← LuaJIT 已经是 Lua 里最快的
Lua 5.4       8
Python        5

 

LuaJIT 可以跑得不错,但它的维护状况堪忧(原作者 Mike Pall 长期处于半退出状态),对新 CPU 特性和 ARM 的支持滞后。Lua 5.4 本身性能很有限。

3. 工具链薄弱

Go:   goroutine profiler、pprof、race detector、内置测试框架
Java: JProfiler、VisualVM、成熟的 APM 生态
Lua:  调试器很原始,profiler 选择少,IDE 支持弱

 

线上出了性能问题,Lua 的定位工具比主流语言差一个量级。

4. 热更新是双刃剑

lua
-- 热更新代码质量失控的真实场景:
-- v1:正常逻辑
-- v2:修了 A bug,引入了 B bug(热更进去了,没有完整测试)
-- v3:修了 B bug,引入了 C bug(又热更了)
-- v4:修了 C bug,但 v2 的某个副作用还在...
-- 
-- 三个月后,没人知道现在跑的是什么版本的逻辑
-- 代码库和线上状态严重不一致

 

热更能力如果没有严格的流程管控,会导致线上代码状态混乱,这在快速迭代的小团队里非常常见。

5. 人才市场在萎缩

招一个熟悉 Skynet + Lua 的服务器开发,比招 Go 或 Java 后端难得多,而且越来越难。新毕业的学生学 Lua 游戏服务器的越来越少。


我的推荐

推荐用 Lua + Skynet 的场景

你们已经有成熟的 Skynet 代码库,游戏已经在线上运营,技术团队熟悉这套框架——继续用,不要因为”不够新潮”而迁移。迁移成本极高,热更新的优势是真实的。

不推荐 Lua 的场景

新项目从零开始:2024 年以后基本不推荐了。首选方案:

战斗服(性能敏感):C++ + Asio(就是我们前面讨论的那套)

大厅/匹配/社交(快速迭代):Go
  └─ 协程(goroutine)原生支持
  └─ 类型安全
  └─ 编译速度快
  └─ 工具链成熟

如果团队 Java 背景深:Java + Netty / Vert.x
  └─ Virtual Thread(Java 21)已经让 Java 的并发变得非常简单
  └─ 生态最成熟

 

Go 在很多方面是”Lua 想做但做不到”的事情的更好答案:协程原生支持、快速迭代、相对简单的语法,但有类型安全和更好的工具链。

如果你的核心需求就是热更新

考虑 TypeScript + Node.js 或者 Go + 插件机制,而不是 Lua。TypeScript 给了你类型安全 + 热更新,比 Lua 好维护得多。


一句话总结

Lua + Skynet 是上一个时代中国游戏行业的优秀解法,它解决了那个时代真实的工程问题(热更新、快速迭代、协程)。但在 Go 的 goroutine、Java 的 Virtual Thread、C++20 协程都已成熟的今天,Lua 的核心优势被大幅侵蚀,而它的劣势(动态类型、工具链、人才)却没有改善。存量项目继续用,新项目换个方向。

发表回复