系统十年演进:从Java到Rust的迁移之路
我负责开发的最古老项目,即将迎来它的10岁生日。它这十年的技术栈演化,可以说是见证了一个长期运行项目会面临的各种挑战。这个物联网终端系统,其实只需要做一件很简单的事情:把云端发过来的内容显示在屏幕上。但落实到代码上,还是会有很多细节可以考究,很多地方可以被优化的。
追溯其源头,从2016年以Java加Selenium为核心,到后来Docker的引入,Selenium被Puppeteer取代,Java被JavaScript取代,Docker被systemd取代,JavaScript又被bash取代,再到后期用定制的Linux替换掉Ubuntu作为底层OS,这个系统的每一次改动都值得回味。这段跌宕起伏的历史,我后面会用一个单独的视频来回顾。
Rust的考察期:新技术选型与期望
因为今天的主题不是缅怀过去,而是畅想未来。看过【让编程再次伟大#1】的老粉丝都知道,我会先考察一个技术栈几年,再决定要不要把它用到自己的项目中。而Rust就是那个刚刚通过考察期的技术。虽然这次的目标是用Rust重写终端系统的功能组件,但我们对它的期望其实没有很高,仅仅是希望能够让我们少点遇上【让编程再次伟大#25】里提到的那种要debug几天几夜的抽象故障就可以了。
JavaScript生态困境与AI的“造轮子”能力
然而,在这个用Rust重写的任务中,最大的挑战并非Rust本身的学习曲线,而是现有Node.js难以替代。JavaScript生态说好听的是更新迭代快,说难听的是弃坑更快,能维护10年的工具库少之又少。而相比较而言,Rust社区则是又小又分散,很多小众功能没有人做,或者是做了也没有人知道。这就引出了一个可能的最坏情况:我们需要用Rust徒手造轮子 😨。
说到造轮子,这就不得不提 编程大模型(Programming Large Models: 依赖海量数据和经验,擅长生成代码和解决经验型任务的 AI 模型)。你让它做个新东西,它可能给你胡编乱造,但提到造轮子这种很依赖经验的任务,它就显得游刃有余。对大模型这种靠着吃数据堆积经验而发家的技术路线来说,这简直就是如鱼得水。这也难怪全球AI厂商都在卷编程这个赛道了。
蓝牙组件重写:AI定制化解决方案
首先要重写的,就是整个系统里最岌岌可危的蓝牙组件。这个组件原本用的是一个叫 bleno 的Node.js工具库,开发者维护了它5年之后弃坑了。后来一个叫做 AabandonWare 的专门收留被遗弃软件的组织接手,但是他们接盘了5年后也弃坑了 😂。被一个叫做 AbandonWare 的组织 abandon 的软件,可见其处境有多惨。
令人意外的是,我在给AI的命令中,其实并没有明确提到要它去“造轮子”。它却选择了通过d-bus调用bluez接口(全是OS层的原生接口),自己实现了蓝牙外设功能。横向对比原来的 bleno 代码就可以看出,它并不是一比一还原了 bleno,而是在理解了我们蓝牙组件的应用场景的基础上,专门为我们手搓了一个定制版,将我们应用层代码都融入进去了。
仔细想想,这种做法其实是合理的。毕竟我们不是要造一个像 bleno 那样囊括了所有蓝牙功能,可以发布出去让大家都安装使用的通用蓝牙工具库。我们的重写只需要服务我们自己的系统,所以就没有必要造一个完整的轮子,也不需要刻意地把应用逻辑剥离出来。从这点来看,现在的AI已经经过了一定的软件工程层面的思考,已经不像以前的编程AI那样,只是一个无情的码字机器,为了彰显自己的上下文窗口很大(和多点消耗你的token)而写得臃肿不堪。
消息队列重写:AI的架构选择与风险控制
在蓝牙组件重写之后,我又尝试了重写所有组件都会共享的消息队列框架。这个框架作为组件中通讯和交流数据的主要渠道,基本上就是在Redis数据库上套一个叫做 bull 的Node.js工具库。这个 bull 也是被开发组弃坑了,只不过这次是因为他们发现这个东西很有赚头,所以另开新坑,做起了付费版本。对于遵循开源精神的我来说,这是绝对不能接受的,所以换掉它们也是名正言顺的事情。
这个重写的难度更高,因为这个消息队列被几乎所有组件依赖,是牵一发而动全身的关键节点。这次AI很明显也意识到这个问题,所以它在下手之前,给了我三个选择:
A. 用Rust重写 bull 和所有用到它的Nodejs组件
B. 用Rust重写 bull,所有Nodejs组件通过socket来对接Rust版本的新框架
C. 用Rust封装一个命令行工具,提供类似 bull 的消息队列接口,组件可以生成子进程来调用它。
那么问题来了,如果让你来做这个项目的架构师,这里你会选哪个?我选择了B,因为这个做法是改动范围最小的,风险最低的。而从最后的生成结果可以看到,除了重写整个 bull 框架,它还很聪明地用JS对照着 bull 的API风格,封装了Rust框架的socket接口。这样子所有用到消息队列的组件,都只需改一下导入工具包的那一行,其余代码几乎不需要改动。不要冒风险,尤其是没有必要的风险,是一个程序员成长和成熟的体现。在这个颇有难度的挑战上,AI做出了基本满分的回答,这让我还有点意外呢。
超大规模任务的瓶颈:Transformer模型的上下文限制
其实在重写这两个组件之前,我还做了另一个尝试,那就是让AI一次性地把整个项目代码全部用Rust重写。而不出意料的是,效果不太行。反正现在超大规模任务的瓶颈是出在 Transformer模型(Transformer Model: 一种基于自注意力机制的深度学习模型架构,常用于自然语言处理任务)的设计上。什么时候全球科学家能解决上下文超过64K,准确率就会断崖式下跌的问题,我们再来考虑一口气重写整个项目的可能性吧。