从重启到热更:战斗脚本在线更新的三道防线
上一篇写 gopher-lua 集成时,热更新还停留在”设计思路”阶段——改脚本得重启服务。现在 slg-go 的战斗脚本热更新已经落地了:一个 HTTP POST 就能把新 Lua 脚本推到线上,带版本号、审计日志、一键回滚。 但上线过程踩了三个坑,每个都差点翻车。 第一道防线:版本管理 + 审计日志热更新最怕的不是”更新失败”,是”更新了但不知道什么时候更新的、谁更新的、能不能回滚”。 BattleEngine 用一个 sync.RWMutex 保护脚本状态,每次 reload 都记录审计日志: 12345678910111213141516171819202122232425262728293031// internal/battle/engine/engine.gofunc (e *BattleEngine) ReloadScriptWithVersion(scriptPath string, version string, operator string) (*ScriptAuditLog, error) { e.scriptMu.Lock() ...
SLG 后端的背压控制实战
新服开服那天,监控面板上 CPU 直接飙到 98%,Gate 连接数 10 秒内从 2000 涨到 8 万,Logic 队列深度爆红。服务器没扛住,重启了两次。 那次之后我花了两周加了三级背压控制。这篇文章就是那两周的记录。 先看事故现场开服当天的流量曲线大概是这样的: 1234567时间 连接数 CPU 队列深度 状态10:00 200 5% 0 正常10:01 5000 30% 120 正常10:02 30000 75% 8000 ⚠️ 队列积压10:03 80000 98% 队列满 💥 服务崩溃10:05 重启 — — 恢复10:06 60000 95% 队列满 💥 又崩了 问题很清楚:没有任何流量控制,请求无限制涌入,打满了所有资源。 三级背压:从外到内逐级拦截这次事故后设计了三...
gopher-lua:脚本引擎的集成、并发和热更新
slg-go 用 gopher-lua 做了两件事:战力计算和等级计算。目前还没有实现运行时热更新——改脚本还是得重启服务。这篇文章记录我们怎么集成 gopher-lua、踩了哪些坑、以及热更新要做的话该怎么设计。 为什么选 Lua? 语言 嵌入难度 性能 热更新潜力 游戏行业生态 Lua 低(gopher-lua) 中 原生支持 标准 JavaScript 中(goja) 中高 需额外机制 非主流 Python 高(嵌入 CPython) 低 受 GIL 限制 非主流 Lua 天生为嵌入设计,gopher-lua 是纯 Go 实现,不依赖 CGO,交叉编译无障碍。go.mod 里直接 go get github.com/yuin/gopher-lua 就行。 实际的集成方式项目的 Lua 集成集中在两个计算器:player_power_lua.go 和 player_level_lua.go。结构一样,以战力计算器为例: 12345678// internal/logic/app/player_power_lua.gotype LuaPlayerPow...
Gate→Logic→Battle:SLG 百万在线的三层微服务架构
SLG(策略模拟游戏)后端的核心挑战是百万在线连接。单机扛不住,必须分层。slg-go 采用 Gate→Logic→Battle 三层架构,每层独立扩缩容。 三层职责 graph TD C["客户端 100万连接"] --> G["Gate × 4-8"] G -->|"HTTP/gRPC"| L["Logic × 40"] G -->|"gRPC"| B["Battle × 15"] L -->|"Kafka"| K["消息总线"] B --> K K --> L 层 职责 特点 扩容单位 Gate TCP/WS 连接管理、协议解析、鉴权 无状态,CPU 低 每台 10-25 万连接 Logic 游戏逻辑(建筑、科技、资源) 有状态分片 每台 2.5 万玩家 Battle 战斗模...
学习热力图:用 Canvas 画出学习习惯
easy-word 的学习热力图类似 GitHub 的提交热力图——按日期展示学习强度,让学生和家长一眼看到学习习惯。实现方案是 ArkUI Canvas 组件 + SQLite 聚合查询。 数据模型热力图的数据源是 daily_logs 表,每天一条记录: 123456789CREATE TABLE IF NOT EXISTS daily_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, date TEXT NOT NULL, -- YYYY-MM-DD words_learned INTEGER DEFAULT 0, words_reviewed INTEGER DEFAULT 0, accuracy REAL DEFAULT 0.0, UNIQUE(user_id, date)) 查询最近 365 天的数据: 12345678async getHeatmapData(userId: string, days: number = 365): Pr...
艾宾浩斯复习调度:间隔重复算法的鸿蒙实现
easy-word 的核心功能是按遗忘曲线安排复习。单词学完后不是”学过了”,而是进入一个调度系统,在最佳时间点提醒学生复习。 艾宾浩斯遗忘曲线德国心理学家赫尔曼·艾宾浩斯发现,记忆遗忘遵循指数衰减规律。复习的最佳时间点是在遗忘即将发生时: 12345678第 1 次复习:学完后 20 分钟第 2 次复习:1 小时后第 3 次复习:9 小时后(通常安排到第二天)第 4 次复习:1 天后第 5 次复习:2 天后第 6 次复习:6 天后第 7 次复习:15 天后第 8 次复习:30 天后 graph LR A["学习新词"] --> B["20min 复习"] B --> C["1h 复习"] C --> D["9h 复习"] D --> E["1d 复习"] E --> F["2d 复习"] F --> G["6d 复习"] G --&g...
离线优先:本地 RDB 做事实源的鸿蒙数据架构
easy-word 的核心设计原则是离线优先:所有学习数据(单词、学习记录、复习计划)以本地 RDB 为事实源,云端同步是可选的增强。断网时功能零降级。 为什么离线优先?学习类应用的特殊性: 学生可能在学校(无网络)使用 学习数据不能丢失(一次丢数据 = 失去信任) 高频读写(每次翻卡、拼写、答题都产生记录) 云端同步延迟不能阻塞 UI graph TD A["用户操作"] --> B["本地 RDB"] B --> C["UI 更新"] B --> D{"网络可用?"} D -->|"是"| E["AGC 云同步"] D -->|"否"| F["队列缓存"] F -->|"网络恢复"| E RDB 表设计三张核心表: 123456789101112131415161718192021...
鸿蒙 NEXT 原生开发初体验:ArkTS + ArkUI 的实践经验
easy-word 是一个鸿蒙 NEXT 原生的小学英语单词学习应用。从零开始用 ArkTS + ArkUI 开发,踩了不少和 Android/iOS 完全不同的坑。本文记录开发过程中的关键经验。 ArkTS 和 TypeScript 的差异ArkTS 基于 TypeScript,但不是 TypeScript。几个关键差异: 1. 不支持动态类型:不能用 any、as 强制转换受限、运行时类型检查被限制。这是为了 AOT 编译优化。 2. 装饰器语法不同:@Component、@State、@Prop、@StorageLink 是 ArkUI 的声明式 UI 装饰器,和 Angular/React 的装饰器语义完全不同。 1234567891011121314@Componentstruct WordCard { @State word: string = '' @State flipped: boolean = false @Prop meaning: string = '' // 单向数据流 ...
Docker 一键部署:从 0 到运行的游戏后端
game-bass 的目标是 5 分钟部署。实现方式是 Docker 多阶段构建 + docker-compose 编排,一条命令启动完整的后端服务。 多阶段构建:20MB 的最终镜像1234567891011121314FROM golang:1.26-alpine AS builderWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server ./cmd/serverFROM alpine:3.19RUN apk add --no-cache ca-certificates tzdataWORKDIR /appCOPY --from=builder /app/server .COPY --from=builder /app/scripts ./scriptsEXPOSE 8080CMD ["./server"] graph LR ...
Hub 模式:游戏后端 WebSocket 实时联机的架构设计
game-bass 的实时联机系统基于 gorilla/websocket,采用 Hub 模式管理房间和玩家连接。所有操作通过 channel 串行化到一个主事件循环中,避免并发竞态。 Hub 模式:中央事件循环核心设计:Hub 是唯一的事件处理中心,所有房间管理、消息广播、匹配池扫描都在 Hub 的主循环里串行执行: 12345678910111213141516171819202122232425262728293031type Hub struct { rooms map[string]*Room players map[string]*Player matches map[string]*AuthoritativeMatch register chan *Player // 新玩家连接 unregister chan *Player // 玩家断开 broadcast chan *BroadcastMsg // 房间广播 pingTicker *...