Agili 的 Hacker Podcast 今日速览
把手机变成个人云服务器、用 8086 汇编重建四十年前的图形桌面、用数学技巧让寻路算法快上十倍。今天的文章里有动手派把抽屉里的闲置手机跑成数据中心,有复古派在淘汰硬件上做操作系统考古,也有算法派在旧数学里挖出新加速。
Shopify 把 Redis 换成 MySQL 做库存预留,反而更稳了
库存预留的老问题
下单时“到底有没有货”是个看似简单实则棘手的问题。Shopify 原来的系统把预留存在 Redis,库存账本在 MySQL,两个系统没办法同时更新。支付成功后的扣库存和清理 Redis 这两步如果顺序不对,就会出现超卖(商品卖了但账本没扣)或欠卖(库存已扣但还被标记为预留)。Redis 本身也缺少多地点库存感知,团队还要多维护一个集群。
一行一件的妙招
新方案把思路倒过来:不再用“一行一个商品、一列库存数量”的模型,而是“一个可售单元一行”。10 个库存就是 10 行,预留 3 个就锁定并转移 3 行。整个过程在同一个 MySQL 数据库里完成,预留和扣账共享 ACID 保证,不再需要协调两个系统。
为了不让表太大,每个“商品 × 地点”组合只维护上限 1,000 行的可用行池。预留从池里消耗行,补充进程在后台回填。1,000 这个数字是根据闪购期间的预留速率估算的,既要接住突发流量,又要保持表紧凑。
锁才是真正的瓶颈
几个技术细节直接影响性能。第一个原型用自增主键,每次预留产生两行锁。改用复合主键后一行一个锁。空表上跑 SELECT … FOR UPDATE SKIP LOCKED 出现间隙锁挡住了补充进程,把事务隔离级别从 REPEATABLE READ 换成 READ COMMITTED 后解决。结账路径上其他代码持有连接时间过长,把连接池推到崩溃边缘——预留本身并不慢。
Hacker News 上有读者担心 1,000 行池会在高峰时不够用,还有人讨论“为什么不在加购物车时扣库存”,回答是购物车放弃率太高,提前锁定会让库存大量闲置。也有评论认为 Redis 的问题不在性能,而在两个系统之间的数据一致性。
A* 寻路变快的数学技巧:先算好几个地标
启发函数可以不瞎猜
A* 寻路算法的效率大半靠启发函数——它告诉算法“往哪边走离目标近”。普通的距离启发不知道墙在哪,常把人往错误方向推。Red Blob Games 的文章介绍了一种叫差分启发的方法:先选几个地标,用 Dijkstra 算法算出所有点到每个地标的最短距离,存进二维数组。之后每次寻路时,用三角不等式推导出一个比普通距离好得多的下界。
从几万降到几千
方法的核心很简单:如果起点是 B,目标是 X,地标是 L,那么 B 到 X 的真正距离一定大于等于 |B 到 L 的距离减去 X 到 L 的距离|。这个下界知道墙的位置,因为它基于真实路径算出来的。单个地标只对部分路径有效,所以要用多个地标,取所有下界的最大值。文章演示中,迷宫地图的探索点数从 92054 降到 12585,只用 4 个地标。
地标放哪是个好问题
地标如果放在路径“之后”才有效。可以用算法自动找:随机生成大量路径,统计哪些位置能覆盖尽可能多的路径。实验里地标通常落在地图外缘,第二个远离第一个,第三个远离前两个,逐个评估新增收益。地标放得不好效果有限,但不会比普通 A* 更差。
我让手机变成了服务器,跑着我的个人应用
从刷机失败到妥协
作者把个人服务放 VPS 上跑 Chrome 太吃力,贵的又觉得不值。抽屉里那台 CMF Phone 1 有 8 核、8GB 内存、128GB 闪存,还有自带电池备份,比 VPS 划算。第一次尝试刷 postmarketOS(面向手机的 Linux 发行版),结果 Wi-Fi、蓝牙全是坏的,手机险些变砖。教训是 Android 已经为所有硬件写好了驱动,丢掉它去追求传统 Linux 是错误取舍。
架构:Termux + chroot + Tailscale
最后方案保留 Android,用 Termux(Android 上的终端环境)做宿主。起一堆服务:OpenSSH、runit 进程监督、Caddy 反向代理、Cloudflared 隧道。Tailscale 给手机一个稳定私有地址。省电机制对服务器不友好,写了一套配置关闭深度休眠、防止 Wi-Fi 挂起。跑 Linux 应用先试了 proot(用户态根文件系统模拟),普通 Web 服务没问题,但 Chrome 负载受不了翻译层。给手机 root 后用真正 chroot 挂载 Debian 文件系统,速度提升明显。
运维与安全
整个主机由 Ansible 管理,版本、服务定义、密钥、健康检查都在一个私有仓库里。发布按校验和固定,通过原子符号链接切换,回滚就是改回旧版本再应用。密钥不留手机,Ansible Vault 加密值存在基础设施仓库,保险库密码由 1Password SSH agent 远程签署。对外流量走 Cloudflare Tunnel,没有入站路由器规则,手机换网络隧道自动重连。
社区争论集中在电池安全和标题语序。有人担心锂聚合物电池 24/7 插电会鼓包,作者把充电限制在 80%。标题“My server is a phone now”引起语言学讨论,有评论比喻“就像说我的水现在是冰,大家会以为水结冰,而不是冰化成水”。
四十年后,有人在 IBM XT 上重建了 1984 年的 Mac 桌面
8086 汇编写的图形操作系统
os8088 是给 Intel 8086/8088 处理器设计的图形操作系统,从软盘引导启动,不依赖 DOS。桌面模仿 1984 年 Macintosh System 1:重叠窗口、下拉菜单、串口鼠标、可加载程序。甚至还加了当年 Mac 没有的抢占式多任务——定时器以 18.2Hz 触发,保存当前任务寄存器、切换栈指针,全程约三十条指令。1987 年的 MultiFinder 才给 Mac 带来合作式多任务。
如何在 256KB 内存里画窗口
系统用真实模式 NASM 汇编写成,内核 78,950 字节,核心代码加缓冲区必须装进 64KB 段。256KB 内存就能跑,没有后备缓冲,直接画屏幕——256KB 机器的后备缓冲会吃掉一半内存。窗口拖拽用一像素 XOR 轮廓的橡皮筋方式,松手才重绘,4.77MHz 下全窗重绘根本不现实,和当年 Mac 做法一样。扫雷程序只有 1,510 字节,同屏可开多个实例。磁盘镜像已写到真实软盘,在 1981 年 IBM 5150 上成功启动,和模拟器速度一致。
历史与争议
1985 年 Digital Research 的 GEM 1.0 是 PC 上的 Mac 风格桌面,Apple 起诉后移除了重叠窗口。os8088 四十年后在同一类机器上把想法重建出来。项目代码主要由 Claude 生成,作者称“手提示的”,这一度引起争议。有评论质疑 AI 生成的项目算不上成就,也有人反驳说这不是念一句咒语,大量迭代和调试仍要人做,“以前没人做过的事,现在存在了”。
把图片藏进 QR 码:抖动是怎么做到的
QR 码的两种区域
QR 码由功能图案和数据模块组成。功能图案帮扫描器定位,必须保持清晰;数据模块在定位完成后才被读取,可以动。做法是把每个数据模块缩到三乘三网格的中心一格,其余空间放图片。图片以黑白位图显示,QR 码仍然可扫。单纯压成黑白会丢掉中间调,需要抖动。
两次误差扩散
用 Floyd-Steinberg 误差扩散从左上到右下逐像素处理:每个像素被阈值化成黑或白后,把亮度误差分给周围未处理的像素。这里做了两次扩散:第一次处理数据模块,像素颜色已被 QR 码决定,不能改,设为正确颜色后把误差扩散到周围八格,图片看起来干净很多。第二次处理普通像素。生成器还支持旋转码、尝试不同编码,以及允许改变少数高误差模块。
社区提醒这是美观和可扫性的权衡。大屏幕上可能没问题,纸质传单或弯折场景就需要更多冗余。有人把这种现象比作汽车安全配置:原本用于应对缺损的纠错余量,被品牌 Logo 逐渐吃掉。还有人遇到支付二维码扫不出来,只能手动输入下方文字。
“难度曲线”该换成“难度锯”——一位《Lemmings》开发者的反思
曲线不是斜坡
游戏开发中常说的“难度曲线”被多数人默认成持续上升的斜坡。Dave Strachan 曾是《Lemmings》开发团队成员,他提出游戏的目标不是让玩家越玩越难,而是让玩家感受到自己变强。把新机制分组,每个机制单独走一条“学会—熟练—变着花样用”的小曲线。
怎么教玩家蹬墙跳
具体关卡设计给了一个模板:先在安全区用唯一出口逼玩家学会操作;再进入主体,与该机制和旧机制混用,加入敌人或追击压力;最后在收尾处加带变式的挑战和用该技巧换取收集品的隐藏路线。教学阶段要排除干扰,用粒子特效、脚印、金币等视觉提示引导。
动态难度的正反两面
“根据玩家表现动态调整难度”引发最激烈讨论。有开发者直言“永远别这么做”,觉得玩家会把系统看穿。支持者搬出《求生之路》的导演系统:根据队伍状态随机生成尸潮和补给品,很多人根本没察觉。《上古卷轴 4》式的等级缩放则是反面教材——路边强盗穿神装,练级像在受罚。好的动态难度要么完全透明(如《异星工厂》污染值越高虫族越多),要么只是避免玩家陷入死局。
用 AI 克隆开源应用还骗记者,道歉被说“像狗吃了作业”
事件经过
开发者 Terry Godier 发布名为 Dark Hours 的天文工具,高度相似另一位开发者的开源应用 DarkHours.app。对方向他在 Bluesky 指出后,他承诺改名、写博客介绍原项目。一个多小时后承认用 Claude 生成的应用与原项目几乎一模一样,连原开发者后来修过的一个 bug 都复现了。更严重的是,他此前一款占星应用被 App Store 拒绝后,把内容换成克隆的天文应用,还联系了 Daring Fireball 的 John Gruber 写了篇批评苹果审核的文章。Gruber 事后发现自己被误导,发布了写作 24 年来的第一篇撤回声明。
“Claude 干的”不成立
Hacker News 普遍不接受这篇道歉。“Claude 干的”被比作政治人物说“狗吃了我的作业”。多位开发者指出,Claude 不会在未被明确指示的情况下复刻另一个项目的名称、功能和特定 bug。一条评论强调:“只要你用 AI 生成内容并发布,你就要对发布的每个字负责。”全文没有向 Gruber 道歉,而被误导最深的正是 Gruber。
Word 1.1a 被移植成原生 64 位应用,源码不是开源
移植思路
一位开发者把微软 Word for Windows 1.1a(代号 Opus)移植成了原生 Windows x64 应用,没有用模拟器,也不是用现代编辑器重新实现。16 位 x86 汇编入口点翻译成定宽 C/C++,分段和双重间接内存句柄映射到 x64 安全的原生运行时,Win16 行为适配到现代 Win32 API。CMake 构建,测试套件覆盖运行时、数据结构、命令表和自动化 UI 工作流。
社区提到源码来自 Computer History Museum,是“源码可用”而非开源,许可对研究用途够用。有人看到截图后调侃 Word 1.0 时代就有 3D GUI 元素,当时跑得动它的电脑想必很厉害。移植过程是否用了 Claude Opus 也成了个小议论点。
每阶都有魔法六边形?数学家用 AI 发现并证明了新结论
为什么 n=3 是唯一的
魔法六边形要求格子沿三个方向的每条直线总和相同。正规魔法六边形把数字 1 到 3n²−3n+1 全部用上,非平凡的只有 n=3 这一个,因为所有数字的总和必须能被 2n−1 整除,而 n>3 时通不过。但放宽条件——数字仍然连续,只是不必从 1 开始——立刻打开新世界。
反演对称和势场
作者加了反演对称条件:数字取自对称区间 −K…K,中心放 0,旋转 180 度后相对的格子互为相反数,约束少一大半。还用“势场”表示六边形,把局部环组合成基,让直线总和自动为零,搜索空间大幅缩小。GPT-5.6 Sol 顺着思路把问题跟 Heffter 数组联系起来,写出自定义模拟退火求解器,很快得到 n 直到 21 的所有反演零和魔法六边形。
从求解到证明
模型随后给出确定性构造方案,覆盖所有 n>114。结合暴力搜索的 n≤21 的解,所有 n>3 的阶都被覆盖。证明尚未在 Lean 中形式化,也没有独立验证。作者反思 AI 承担了大部分创造性工作,自己更像乘客,但也注意到模型容易“隧道视野”,方向对时高效,方向错时需要人来提醒。
一个赌局赌一个 URL 能活 11 年,后来它真活下来了
2011 年的赌注
Long Bets 上曾有一个赌约:2011 年 Jeremy Keith 认为链接失效是 web 的熵,11 年对大多数资源已太长。Matt Haughey 认为有技术基础的人完全能维持稳定 URL,他的网站当时已运行 13 年。赌约规定 2022 年 2 月 22 日打开 longbets.org/601 必须返回包含原预测文字的 HTML。结果页面还在,HTML 里也保留着那段话。保守派输了。
保持 URL 存活没那么难,也没那么简单
讨论中有人说保持 URL 不难:写测试、把静态内容转成 HTML 就行。但也有人指出多数 URL 不是被误删的——公司破产、老板要删旧东西、域名过期,都不是技术问题。有人注意到赌约条款的漏洞:“http://”前缀最可能出问题,现在都在往 HTTPS 迁移;JavaScript 单页应用返回的初始 HTML 往往只有一个空壳,真正内容要靠脚本渲染,curl 能不能抓到也成了实际问题。
播客全文
女:Hello 大家好,欢迎收听 Agili 的 Hacker Podcast,我是莓莓。
男:大家好,我是阿迪。
女:今天这期节目,咱们的信息量有点大。我整理了一圈,发现好多话题其实都绕着一个挺有意思的点——就是大家怎么在已有的东西上,折腾出新的玩法。不管是一台旧手机、一段四十年前的代码,还是一个数学上的老问题。
男:对,而且有些折腾看起来好像是在给自己找麻烦,但最后往往能挖出一些不常见的思路。比如今天要聊的第一个,就是有人真的把一台安卓手机,变成了自己的服务器。
女:我刚看到的时候,第一反应是:图什么呢?现在云服务这么方便,开一台 VPS 也就几分钟的事。但看完之后发现,还真不是图省事,而是他为了一台老 iPad 能继续用,想着法子省钱,最后发现最便宜好用的东西就躺在自己抽屉里。
男:是。这个开发者的需求很具体。他有个叫 Surf 的服务,能让老 iPad 连上一个远程的 Chrome 浏览器,等于给老设备续命。之前跑在一台便宜的 VPS 上,但那点算力跑 Chrome 太吃力了,升级配置又觉得不值。
女:然后他就瞄上了自己那台 CMF Phone 1?八个核心、8GB 内存,还有 128GB 的存储。看参数比不少低配 VPS 都强。关键是,这手机还自带一块电池,等于白送一个 UPS 不间断电源。
男:他的第一反应跟很多技术人一样:刷机。他想把原生安卓干掉,装一个叫 postmarketOS 的纯 Linux 系统。结果翻车了。
女:怎么会翻车呢?
男:因为手机所有的硬件驱动,比如 Wi-Fi、蓝牙、GPU 加速,都是安卓原生支持的。刷成普通的 Linux 之后,这些全坏了,手机一度变砖。他费了好大劲才恢复过来。后来他想明白了,他要的不是把手机变成一台小电脑,而是让手机在保持安卓硬件能力的同时,能跑 Linux 的应用。
女:就是用安卓来当底座,上面再跑他需要的服务?
男:对,他选了 Termux 这条路。Termux 可以理解为安卓上的一个终端模拟器,但生态很完整,能装 OpenSSH、Caddy 这些标准工具。他让 Termux 在手机开机后自动启动一系列服务,再配合 Tailscale 组网,这样他在任何一台机器上,直接敲 ssh cmf 就能连上这台手机服务器。
女:听起来跟连一台正经服务器没什么区别了。但我比较担心那个,就是手机它毕竟是为省电设计的,插着电 24 小时跑服务,它不会自己休眠吗?
男:这就是折腾的地方。安卓的省电机制对服务器很不友好,会自动杀后台、断 Wi-Fi。他写了一大套配置,强制拿了一个持久的唤醒锁,关掉深度休眠,把 Tailscale 设为常开 VPN。还设置了一条重启后的恢复链条:安卓启动,Tailscale 连上,Termux 起来,所有服务自动恢复。
女:那性能呢?他那个最吃资源的 Chrome 浏览器,在这上面跑起来跟普通电脑有差别吗?
男:最开始是有差别的。他先用 proot 跑,这像一个翻译层,让应用以为自己活在 Debian 系统里。普通小服务没问题,但 Chrome 这种重负载经过翻译层就明显慢了。最后他把手机 root 了,用真正的 chroot 切换根目录,绕开翻译层,让程序直接调用 Linux 内核。速度提升很明显。
女:我看到他还提了一个运维仪表盘,能直接看到手机的温度、电池、CPU 占用率什么的。
男:对,这是他最近加的。可以直观看到这台手机的八核 CPU 状态、内存占用,还有内外网的健康检查。他甚至用 Cloudflare Tunnel 来做对外访问,流量都是从手机主动打出去的,不需要在路由器上开端口,手机换个地方插电,隧道自己就重连了。
女:那电池一直插着,评论区有人担心安全吗?
男:很多人讨论这一点。24 小时插电,锂电池长期满电可能会鼓包。他自己也意识到这个问题,把充电限制到 80% 来缓解压力。有人分享说这么跑了好几年没事,也有人提醒这还是有安全隐患。不过相对 VPS 月费来说,他觉得这是一个真正可用的选项。
女:从一台旧手机变服务器,说到底还是对旧硬件潜力的挖掘。我们接下来要聊的这个,是对旧代码的潜力挖掘。有个人,把当年 IBM PC 上跑的 8086 汇编代码,重新写出了一个带图形界面的操作系统。
男:项目叫 os8088。目标不是写个新东西,而是回到 1984 年,在那时候的硬件上重建一个像 Macintosh System 1 那样的桌面操作系统。有重叠窗口、下拉菜单、串口鼠标。系统是从软盘引导扇区直接启动的,完全不依赖 DOS。
女:我比较意外的是,它居然还带了一个当年 Mac 都没有的功能——抢占式多任务。那是个什么概念?
男:抢占式多任务,简单说就是系统能强制在不同程序之间快速切换,让它们看起来在同时运行。当时的 Macintosh 一次只能运行一个程序,切换到别的程序前一个就冻结了。直到 1987 年的 MultiFinder 才给 Mac 加上多任务,而且那个还是合作式的,就是程序得主动让出资源。os8088 是用一个定时器,每秒触发 18.2 次,强行保存当前任务的状态,切换到下一个。
女:用四十年前的芯片,每秒切换十八次?这能跑得动吗?
男:他用了很多那个年代写游戏才用的技巧。比如窗口拖动,不是真的实时重绘整个窗口,而是用橡皮筋方式,只移动一个一像素的轮廓线,松手了才重绘。因为 4.77 MHz 的频率下,整窗重绘是不现实的。1984 年的 Mac 也是这么干的。
女:我读的时候看到,扫雷这个程序才 1510 字节,连图标都算上了。这感觉像是当代程序员去考古,然后用古人的工具重新盖了一座小金字塔。
男:对,而且他配套的软件很全,有 Markdown 编辑器、Amiga MOD 播放器、位图编辑器,甚至还有一个 Mandelbrot 分形生成器。有人真的把这些写到一张真实的软盘里,在 1981 年的 IBM 5150 上跑了起来,说跟模拟器里感觉不到速度差别。
女:但这些代码,其实不是他一行一行手写的。这才是这个项目最有争议的地方。
男:作者自己在 Hacker News 的讨论里现身说的,代码主要是 Claude 生成的。他一开始在项目页面写的是“手写的真实模式汇编”,后来承认那是开玩笑,改成了明确标注 AI 辅助。
女:这引发了很大的讨论。一方觉得,你让 AI 写的,这算是你的技能展示还是成就?作者自己说得很直白,就是找点乐子。另一方说,这种几十年前没人做过的事,现在靠 AI 把想法变成能跑的东西,这本身就挺有意思的。
男:有读者仔细看了他的 VGA 驱动源码,发现一些多余的汇编指令,说确实有优化空间。也有人觉得这些代码比现在编译器的输出更像人手写的风格。争论归争论,一个客观结果是,四十年前的硬件上,现在有了一个能跑多任务图形界面的系统,这件事本身存在了。
女:刚才这两个故事,不管是把手机变成服务器,还是给古董芯片写系统,其实都涉及一个概念——寻路。比如系统怎么决定下一步去哪里。正好今天有一个很硬核的算法话题,就是关于 A* 寻路算法的优化。阿迪,这个领域我一直觉得已经很成熟了,还有什么可挖的吗?
男:大部分人优化 A*,都是盯着它的优先队列或者地图数据结构。但这篇文章讲的是怎么改进启发函数本身。他们在《龙腾世纪:起源》的一张地图上做实验,把启发函数从 130 优化到接近真实距离的 446,A* 需要探索的节点数从一万两千多个骤降到几千个。
女:等等,启发函数我理解就是一个估算,大概告诉算法“应该往那个方向走”。如果我们已经知道真实距离了,那还找什么路呢?
男:对。这就是矛盾点。知道墙确切位置的完美启发是最好的,但它是随着目标和墙体变化而变的,每次跑 A* 前算一遍太慢,提前存下来又太大了。这篇文章提出一个很巧的中间方案,叫差分启发。
女:怎么个差分法?
男:先在地图上选几个地标,预先算好所有节点到这些地标的精确路径代价,存下来。然后当要计算从 B 到 X 的代价时,用三角不等式推导出一个下界:B 到 L 的代价减去 X 到 L 的代价,这个数值一定小于等于 B 到 X 的真实代价。而且它比普通的直线距离启发更准确,因为它间接知道了墙的存在。
女:也就是说,之前的启发函数像没有障碍物的鸟瞰距离,这个新函数像是在用几个固定参照物来绕开墙?
男:可以这么理解。一个地标不够,因为地标得在路径“后面”才有用。所以要用多个地标,取它们给你估算值的最大值。文章里的实验很直观。一张迷宫地图,原启发跑了 92054 个瓦片才找到路,只用了 4 个地标后,降到 12585 个。这些地标通常落在迷宫的外缘,可以用算法自动找出来。
女:这个优化对我们普通人有什么影响吗?比如导航或者游戏里那些 NPC 的走动?
男:直接影响就是游戏里大量单位的寻路可以更快,节省 CPU。而且这方法实现成本很低,就是跑几次 Dijkstra 算法把地标的距离表算出来存好,然后修改一下启发函数的取值逻辑,A* 本身一行都不用改。对于地图不怎么变的游戏,性价比很高。
女:从虚拟世界的寻路,回到现实世界的买卖。我们接下来聊个和钱直接相关的——购物车里最后一步“立即购买”背后的技术。Shopify 刚发了一篇文章,讲他们怎么把库存预留系统从 Redis 搬到 MySQL。他们黑色星期五的销售峰值是一分钟 510 万美元,每一笔交易都得瞬间判断库存够不够。
男:这个场景的难点在于一致性问题。原来的做法是,库存余额放在 MySQL 里记账,但被人拍下还没付款的预留库存,放在 Redis 里。这两个地方不是原子更新的。一旦顺序不对,就出现超卖——东西卖出去了但账本没扣,或者欠卖——库存扣了但还显示有货、别人又买了一次。卖家只能取消订单道歉。
女:所以他们干脆把 Redis 扔了,全放在 MySQL 里?但 MySQL 不是一直被说锁表什么的,能扛住这种瞬发高并发吗?
男:新方案的灵感来自 37signals 的一个数据库队列设计,核心靠 MySQL 8 的一个特性,叫 SKIP LOCKED。一行被别的事务锁了,它就跳过,继续找下一行可用行。他们做了一个很大胆的设计:不再是一行放一个商品和剩余数量,而是把库存拆成一单元一行。10 个库存就是 10 行,预留 3 个就锁定并转移 3 行。这样预留和扣账都在同一个数据库的同一个事务里完成,共享 ACID 保证。
女:等等,如果一个商品有 5 万库存,难道要建 5 万行?那表得多大。
男:所以他们做了个变通。每个商品在每个地点维护一个固定上限 1000 行的可用行池。预留从池里消耗行,后台有个补充进程负责从库存账本回填。1000 这个数是根据他们闪购时的峰值预留速率估出来的,既能接住突发流量,又能让表保持紧凑,保证 SKIP LOCKED 扫描足够快。
女:万一这 1000 行瞬间被耗光了呢?
男:预留路径会立刻触发补货。他们用了一把锁保证只有一个事务在补充,其他并发的请求等着它完成。买家那边不会看到无货,只是那单的延迟稍微高一点。
女:文章里提到了几个他们踩过的锁竞争的坑,还挺细节的。比如自增主键的问题。
男:对,第一个原型用了自增主键,导致 InnoDB 同时锁了两个索引。改成复合主键后,就只锁一行了。还有一次是空表上跑加锁查询,出现了间隙锁,把补充进程写新行的路给堵死了,造成死锁。最后他们把事务隔离级别从可重复读改成了读已提交才解决。他们甚至去解析每条 SQL 语句的注释标签,来统计连接持有时间,才发现真正的瓶颈不在预留本身,而是结账路径上其他代码占了连接太久,把整个连接池拖垮了。
女:就是一个看似很笨的去掉缓存、全用数据库的方案,最后是靠对锁行为和连接持有的极端精细控制才跑通的。
男:社区里对这个设计本身有争议。有人担心 1000 行池会造成欠卖,补充跟不上流量就会拒单。也有人提出各种替代办法,比如在加购物车时就扣库存。但评论里也解释了,购物车放弃率很高,提前锁会让库存大量闲置,付钱的人才应该锁定商品。另一个焦点是 Redis 到底该不该背这个锅。有观点觉得只要 Redis 也每次都 fsync 到硬盘,一样能保证不丢数据,但另一些人说那样的话 Redis 的性能优势就没了,而且排查数据不方便。说到底,原方案的问题不在 Redis 性能,而在两个系统之间的数据一致性难保证。
女:我发现一个很有意思的题外话,就是这篇文章本身的文风在 HN 上也被讨论了。
男:很多人说这是很明显的 LLM 生成内容,举出一堆特征:大量对比句式、小标题加列表、空洞的对句。有人甚至说看到破折号开始就怀疑是 AI 写的,这都快成条件反射了。有前员工出来说,这不是工程师能力问题,是公司自上而下在推 AI 写作。
女:这种技术文用 AI 来写,感觉调性上确实容易变得很官方很虚,少了很多工程师自己写的那种实在感。
男:对,数据和逻辑都在,但声音没了。
女:我们从最硬核的数据库锁竞争出来,聊点视觉上有趣的吧。你有没有见过那种,中间嵌着一个 Logo 但还能扫的二维码?
男:见过。这种叫艺术化 QR 码。今天有一篇文章把这中间的数学和原理讲得很透。
女:作者开头只用两百多个字就把 QR 码的机制讲清楚了,我自己读的时候觉得特别舒畅。简单说,QR 码由两部分组成,一部分是给扫描器用来定位的功能图案,这部分绝对不能动;另一部分是存数据的模块。数据模块只要在定位完成之后被读到就行,所以中间是可以动手脚的。
男:对,他用的方法是把每个数据模块缩到一个三乘三网格的中心一小格,外围八个格全用来放图片。但直接放的话,图片只有黑白没有中间调,就需要抖动处理。他用的是 Floyd-Steinberg 误差扩散算法,从左到右、从上到下,每个像素被强制变成黑或白之后,把产生的亮度误差分给周围还没处理的像素,这样整体看起来的明暗就更像原图了。
女:而且他还做了一个特别的事,叫两次误差扩散。第一次是专门给那些已经被 QR 码规定死了颜色的数据模块,比如这里必须是黑或者必须是白。这些像素被强制设好颜色后,把误差像泼水一样分给周围八个像素,让图片不会因为嵌入了 QR 码显得很脏。
男:这确实是一个美观和可扫性之间的权衡。作者自己也说了,大屏幕上或者电子海报上可能没问题,但印在纸质传单上或者贴在有弧度的表面,冗余度就不够了。社区里对这个做法的担心主要是,大量公司根本不测试,直接把自己的 Logo 嵌进 QR 码,把纠错余量全吃光了,结果扫不出来。
女:你这么说我想起来,有人分享说在卡车背后见过一个巨大 QR 码,但 Logo 已经把纠错全吃完了。还有餐厅桌上摆的支付码,扫不出来,最后只能手动输下面那串长数字。这感觉就像汽车的安全气囊上画了个吉祥物,是挺可爱,但撞车时可能会失效。
男:评论区还提到一种更规范的做法,是 Russ Cox 的 QArt Codes。它不是拿纠错余量来做文章,而是通过修改 URL 编码方式来把图片嵌进去,整份 QR 码是完全有效的。
女:从二维码里的美学取舍,我们进入一个更深的话题——游戏难度设计。我看到一篇文章直接说,“难度曲线”这个概念本身就是错的。
男:作者 Dave Strachan 参与过《Lemmings》的开发。他的核心观点是,游戏的目标不是越玩越难,而是让玩家感受到自己在变强。就算通关难度没怎么升,只要你对旧敌人越来越轻松,这种成长感就足够推着你往前走了。
女:所以他提出了一个“难度锯”的说法,挺形象的。不要全局搞一条单调上升的曲线,而是每个新机制单独走一条“学会、熟练、变着花样用”的小曲线。
男:对。他举了个教玩家蹬墙跳的模板例子。第一步在安全区里,只有一个出口逼你学会这个操作;然后进入主体,把蹬墙跳和之前学的东西混起来用,加点敌人和尖刺制造压力;最后再给一个带变式的挑战和一条用蹬墙跳拿收集品的隐藏路线。教学要排除干扰,用粒子特效、脚印、金币来引导视线。
女:他还列了一堆该做和不该做。我印象比较深的是,不该惩罚失败者——就是不要因为玩家在某个地方死了太多次,就把难度偷偷降下来让他觉得没劲。但这个点也是讨论最激烈的地方。
男:这叫动态难度调整。有人直接说“永远别这么做”,玩家会看穿,觉得自己赢了是系统让的。但评论区马上有人拿《半衰期:爱莉克斯》举例子。那游戏的弹药补给系统就是动态的,你省资源它就少给,你大手大脚它就在下一片区域多掉弹药。有个人说,自己发现规律后很出戏,但一起玩的朋友完全没察觉,只觉得资源管理做得好。
女:如果藏得足够深,大部分玩家其实不会发现。我记得《求生之路》的导演系统也是,根据队伍状态随机刷丧尸和补给,玩的时候只会觉得紧张感控制得很好。
男:反面例子就是《上古卷轴 4:湮没》那种等级缩放。你练级,路边的强盗也跟着变强,甚至一身神装。玩家感觉不是在成长,而是在被惩罚。好的动态难度,要么透明,比如《异星工厂》的污染值引发虫族攻击,玩家完全懂规则;要么就是兜底用的,防止玩家死档,不是暗中操纵成败。
女:难度曲线的设计是一种创作上的诚实,但接下来的这个案例,正好是在创作的诚实上翻了车。一个叫 Terry Godier 的开发者做了一个天文观测网站 Dark Hours,结果被发现是克隆了另一个开源应用 DarkHours.app。昨晚我仔细看完了整个经过,现在复述一遍都觉得不可思议。
男:被克隆的原作者在社交平台上公开指出了这一点。Godier 一开始还承诺会修改功能、改名,写文章介绍原项目。但仅仅一个多小时后就全盘承认了,说整个应用是用 Claude 生成的,不仅界面雷同,连原开发者后来修过的一个 bug 都一模一样复现了。
女:最让人不舒服的是,他在完全照搬之前,还有一个占星应用叫 Asterly,被苹果应用商店以占星类为由拒绝了。他就把应用里的内容换成这个克隆的天文应用,名字都没改,拿去申诉。他还联系了知名科技博客 Daring Fireball 的 John Gruber,让 Gruber 写了一篇文章批评苹果审核员分不清天文和占星。
男:这篇文章稿子发出前,他还过目了。后来 Gruber 发现自己被当枪使,发布了 Daring Fireball 写作 24 年来的第一篇撤回声明。文字写得很有分量:“我在多个方面被误导,包括直接误导和隐瞒,但发表的内容是我的责任。”
女:而且这篇道歉里,从头到尾没有向 Gruber 道歉。社区里直接把他的道歉称作“有限坦白”,就是只承认被抓个正着的部分,把更有破坏力的细节藏起来。
男:黑客新闻上的评论一边倒地不接受“Claude 干的”这个借口。有人把它比作政客说“我被黑客攻击了”。多位开发者说得很明白,Claude 不会在没有明确指令的情况下去复刻另一个应用的名称、功能和特定 bug。只要你用 AI 生成内容并发布,你就要对发布的每个字负责,AI 永远不能成为挡箭牌。
女:我看到一条高赞评论把这件事跟吉他弦制造商 D'addario 近期的一个翻车事件做类比,也是用 AI 生成广告音乐,最后甩锅给员工说“第一次用 AI 不知情”,结果员工个人账户里有大量 AI 作品。“AI 干的”正在变成新的“假新闻”式推脱话术。
男:从这件事里也能看到,Godier 之前做的一款叫 Current 的 RSS 阅读器口碑不错,这次一下把他的声誉砸了。原作者已经把 DarkHours.app 开源,Godier 把域名重定向给原项目,但这个域名 2027 年就到期了,到时候入口会失效。
女:聊完创作诚信的争议,我们再回到技术本身,看一个我个人觉得很浪漫的移植项目。有人把微软 Word for Windows 1.1a,也就是 80 年代末的那个字处理软件,做成了一个能在现代 64 位 Windows 上直接运行的原生应用。
男:项目代号叫 Opus。它既不是模拟器,也不是用现代编辑器控件重新实现一遍 Word。他是直接把当年微软放出来的 C 源码和资源文件,加了一个移植层,在现代 64 位 Windows 上编译链接出来的。
女:这就相当于找到一本 16 世纪的菜谱,不是拍个照片欣赏,而是真的把里面的菜在现代厨房做出来,还能吃。
男:你比喻得挺准。最麻烦的是,16 位代码时代有分段内存,那种远近指针和内存句柄在 64 位下全都不存在了。他得把这些全部翻译成 x64 能理解的方式。把 Win16 的消息循环和绘图调用搬到了现代 Win32 API 上。连对话框、位图、光标这些素材,都是在 CMake 构建时重新从原始定义生成出来的。
女:我看到有一个评论说,看到这个 Word 1.1a 的截图后,感觉它跟现在的 Windows 10 很像,微软算是回到了根源。他们还说,原来 Word 1.0 时代就已经有 3D GUI 元素了。
男:这个项目还有个争议小插曲,就是它的代号 Opus,碰巧和 Anthropic 的 Claude Opus 撞上了。有人开玩笑说,如果移植过程中用 Claude Opus 辅助,那就很讽刺。还真有读者觉得 README 本身有 LLM 写的味道。
女:技术考古做完,我们最后聊一个纯粹的数学浪漫。数字 19 有什么特别的?
男:这其实是两条知识交汇在一起。一是 19 是孪生素数。二是,唯一的非平凡正规魔法六边形,它的格子数正好是 19。
女:魔法六边形?是把魔法方阵那种每行、每列、对角线之和相等的玩法,搬到六边形格子上吗?
男:对。六边形格子上,每条直线沿着三个方向排,总和得一样。而且正规的版本要求数字从 1 开始连续排列。数学家早些年就证明了,非平凡的这类正规魔法六边形,就只有一个,格子数 19。
女:既然唯一的那个已经找到了,故事该结束了吧?
男:但作者偏不。他放松了一个条件——数字还是连续,但不用从 1 开始。这一放松,世界就打开了。但要找到更多的解,需要在巨大的排列空间里搜索,非常难。他做了两件聪明事。第一,引入反演对称性,数字取正负对称,中心放 0,这样过中心的所有线自动为零,约束少了一大半。第二,发现了一种叫“势场”的数学表示,能把每个合法的零和六边形唯一地表达成一堆局部环的线性组合。
女:找到这个表示之后,他做了什么呢?
男:他没自己写求解器。他让 GPT-5.6 Sol 顺着这个思路去写了一个自定义的模拟退火程序。他自己又在 Python 上用 Numba 做性能优化,用 perf 找 CPU 的瓶颈,最后在家用服务器上跑了几天,一口气找出了直到 n 等于 21 的所有反演零和魔法六边形。
女:这些六边形内部的数字看起来很乱,但它们的势场却非常平滑,像真实地形图的等高线。他还提出了一个更强的猜想,说每个大于 2 的阶数都存在这种魔法六边形。
男:对。他让 GPT-5.6 和一个基于 Lean 的证明代理一起工作。过程很不顺,高强度模型卡了很久,反复绕圈。就在快放弃的时候,一个对话里模型突然把几条积累的思路组合起来,给出了一个构造方案。虽然最初只覆盖了 n 大于 800 且能被 16 整除的情况,但作者一步步推着模型往下泛化,最后门槛降到了 114,再到覆盖所有 n 大于 3 的情况。
女:所以结论是,所有大于 3 的阶数,都有这种异常魔法六边形?
男:对,而且这个构造是确定性的。不过作者自己也说了,写这篇文章时,完整的证明还没有在 Lean 里走完形式化验证,也没有经过独立审查。文章结尾他有一段很诚恳的反思:开始做这个项目的时候,AI 是副驾驶,他是司机;到项目结束时,他更像一个乘客,AI 承担了大部分创造性工作,他只负责指方向。
女:这几乎就是我们今天所有故事的缩影,从手搓旧手机服务器,到用 AI 重建 8088 时代操作系统,再到让 AI 来驱动数学发现,人类在折腾这件事上的角色,正在悄悄发生变化。
男:对。有的折腾是为了省下 VPS 的月费,有的折腾是让四十年后的机器跑起 Word 1.1a,有的折腾是单纯想在数学里找到某种美。但所有这一切的起点,都是那种“为什么不试试看”的冲动。
女:好,那今天的节目就到这里。感谢大家收听,别忘了用你喜欢的泛用型播客客户端订阅我们,下次见。
男:下次见。
参考链接
- My server is a phone now
- Os8088: A powerful Mac-like OS for the IBM XT, 286, 386
- Improving Heuristics for A* Pathfinding
- We replaced Redis with MySQL for inventory reservations and it scaled
- Dithered QR Codes
- Making difficulty curves in games
- Mea Culpa – Dark Hours
- Microsoft Word for Windows 1.1a, Native X64 Port
- There Are Magic Hexagons of Every Order
- The original URL for this prediction will no longer be available in 11 years (2011)
