I’m Caelum, or, Decarbonized Glucose. 這裡是我的精神病院。
技术围城
我老想找些自由度高的游戏来玩,比如 Minecraft,但找来找去,就是找不到合适的。Minecraft 还在更新,可微软现在端出来的东西,在我这样的老玩家眼里就是敷衍。我们这一代老玩家也长大了,热情耗得差不多了,打开游戏,玩不下去;关掉,又不甘心。我在 Steam 上翻来翻去,没一个能填上这个窟窿。 我到底在找什么?我想自由地创作,亲手搭出点东西。那股劲还在,却找不到地方使。或许,是我对技术最原始的热情又醒了。或许,我该做一个自己的开源项目。别再是什么分布式系统、数据库、Agent 插件了。就做一个我自己感兴趣的东西,不依靠 Coding Agent,自己把它做出来。 我曾经想过自己做游戏。六年前,因为疫情居家,开始长时间接触电脑,那时候刚学 Python。我照着书里已经过时的 Pygame API,一边百度,一边翻 CSDN,做过一个很简陋的飞机大战。 同一个暑假,我用三百多行 Python 加上 EasyGUI,写了一个简陋得要命的笔记系统,定位有点像 Obsidian,实际上难用得很。不知道什么时候该封装函数,那干脆不封装。全程 if-else,重复代码铺满屏幕。我还用 Tkinter 帮老师做过一个随机点名器,服役了一年;又写过一个丑得不行的、类似 GeoGebra 的东西,不能平移,不能缩放,只能画一次函数和二次函数的图像,连线都画不连续。 上高中前,抓着最后那点自由的时间,我花了一周做了个带杰哥、阿伟 cosplay 音效的井字棋,Bug 连连。高中暑假忙里偷闲,买来一个二手树莓派和一些小外设,尝试用 Python 还原 Phigros。我甚至想参与 Phigros 的社区复刻项目 Phira 的开发,却发现,我对软件设计一无所知,不知道什么 Git 版本控制,协作流程,甚至连个 GitHub 账号都没有。 那些作品里,只有井字棋留下过可执行文件。后来,那个 32GB 的 U 盘坏了,它和前面几个项目的代码一起,都成了不可溯源之物了。 那时候做的东西,拿今天的眼光看,没几个像样的。可我真的很快乐。 上一次有这样的热情,是在将近两年前。大一上学期,我凭着记忆,翻着那本 2020 年买来的破烂 Python 书,在 CSDN 上硬搜,对着笨重的 Visual Studio,用八百行 Python 做了一个三阶魔方模拟器。没用 AI——那时我没有科学上网手段,也不会用 AI。 借助 Pygame、Tkinter 和 kociemba,它能变色,能打乱,能计时,有键位,能自动复原,能生成当前状态解法。八百行全塞在一个文件里,坐标全靠硬编码,屎山一座。但它真的跑起来了。那是我一点一点写出来的,它真的能转,真的能还原。 我拿着这东西去参加学校的程序设计比赛,到了答辩现场才发现,我看漏了文件,这设计主题是 AI。真他妈荒唐。可至少,做它的时候,我没为了任何人。我就是觉得好玩。 后来做 C 语言课设,一个学生管理系统,没有数据库,没有网络,也没有前后端。我偏要认真做。回调函数、函数指针解耦、三层架构分离,连文件格式都自己设计,当时觉得自己写得高端大气。无 AI 添加纯手搓。 洋洋洒洒两千多行,却 0人在意我写了什么。 ...
AI时代的知识奶头乐
我越来越担心一件事:AI正在悄悄改变我"学习"这个动作的形状。 以前想弄懂一个复杂问题,得自己去啃、去查、去碰壁,那种摩擦感——查资料的辛苦、想不通的卡顿——恰恰是深度理解得以发生的地方。现在这层摩擦几乎被抹平了。同时被削弱的还有学习到新知的正向反馈。问AI,答案立刻就来,流畅、自信、结构完整。AI时代提供给了我们“知识奶头乐”。它太方便了,方便到我开始怀疑:我是不是正在失去那种"自己动手够到知识"的原始热情? AI本身也不是个省油的灯,它给出的答案本质上是概率模型的产物——是"平均的"、“大概的”、某个视角下的拼凑,甚至可能根本不是事实。但它说话的语气太笃定了,笃定到你根本不会去追问。它还会让你自信心满满,“You’re absolutely right!",而可能根本不会意识到你观点的缺陷。这就像一个谀臣,无形中害了你。 我也担心知识型人才的"缩水”。思考可以外包,工作可以替代。会不会有一大批人,Token花完,天才陨落?这个问题我还没有确定的答案,历史上工具从未让人真正变笨,计算器没有杀死数学家。但这一次的不同在于:AI外包的不是计算,而是理解本身,这条界限值得警惕。当我们已经习惯了用AI代替学习和思考,学习新知不再是振奋人心的探索,而是只是手指麻木地动一动就解决的是,那种原始热情一旦退去,我们对大脑的控制权是否会被一步步剥夺? 我不想简单地把AI当作洪水猛兽,但我也不想假装这种失落感不存在。它成了我大脑里近乎不可分割的一部分,能替我理解、替我工作——可正因如此,我更需要时常停下来问自己:最近一次让我兴奋到不想问AI、只想自己去啃的问题,是什么时候? 如果想不起来,那大概就是该找回那种"笨拙但亲手够到"的体验的时候了。
鱼骨chat法
用 AI 编程助手写代码,最让人头疼的问题之一是上下文污染——主线讨论越来越长,早期的废弃方案、跑偏的对话开始干扰模型的判断,回答质量也跟着劣化。 我现在用一种叫「鱼骨 Chat 法」的方式来应对这个问题。 思路很简单:以鱼骨图为隐喻,主干是一个贯穿始终的主线 session,负责记录整体设计思路、模块划分和关键决策;每一根鱼刺是一个独立的子 session,只处理一个具体问题——比如某个函数的实现、一个报错的调试、或者一段逻辑的优化。子问题解决后,把结论带回主线 session 即可,那根鱼刺的使命就完成了。 这样做有几个好处:子 session 上下文干净,模型不会被无关信息干扰;主线 session 始终保持高层视角,不会因为陷入细节而失去方向;出问题时也方便定位,每个 session 职责单一,回溯很直观。 本质上这是一种关注点分离在对话管理上的应用。AI 工具的上下文窗口是有限资源,像管理代码模块一样管理它,会顺手很多。
黄山奇石
vibe coding 派还在 code 你说什么? 哦,老爷子,你看那:vibe-coding-派-还-在-code 老王啊……我说张主管啊,怎么这种标语现在还留着?……八股余孽嘛,赶紧找人清理掉 算了,算了,留着也好,以示警世。
分布式系统一致性
在分布式系统中,一致性模型定义了多节点间数据更新的可见顺序与实时性,是权衡性能、可用性和正确性的核心。四个经典模型从强到弱依次为: 线性一致性(Linearizability) 顺序一致性(Sequential Consistency) 因果一致性(Causal Consistency) 最终一致性(Eventual Consistency) 越强的一致性模型对系统的性能和可用性要求越高,实际工程需要根据业务场景取舍。 线性一致性 每个操作在现实中需要一段时间才能完成,从逻辑上的“调用(call)开始”到“返回(return)结束”。但在这期间某个瞬间,该操作原子性地生效了,并被其他参与者(线程、节点等)立刻感知到这个变化。 线性一致性保证了系统对外表现得像只有单一副本,无论 client 访问哪个节点,读取到的数据都是一致的。在 Raft 这样的共识协议中,一个操作必须经过多数节点确认,也就是达成共识才能生效,也就对应了操作原子性生效的那个瞬间。 在 CAP 理论中,线性一致对应 $C$(一致性)。当网络分区($P$)发生时,系统必须拒绝请求而不是返回可能过期的数据,因此牺牲了可用性($A$)。Raft、ZooKeeper 等走的就是 CP 优先的道路。 举个例子,用户 A 转账给用户 B 100元,B 立刻查询账户余额必须多出100元而不能是旧值。如果银行网络故障,必须拒绝这个请求,防止写入脏数据。 顺序一致性 顺序一致性比线性一致性稍弱。它要求存在一个操作的全局总序,在这个总序里: 一个参与者内的操作保持程序顺序 所有参与者看到的内存操作顺序是一致的,但不要求这个顺序与真实时钟一致。 也就是说,操作可以看起来“乱序”发生,但只要所有参与者看到的顺序一致即可。 这点区别其实影响很大。线性一致中,节点 A 在 $T_1$ 完成了写入,节点 B 在 $T_2$ 读取,那么节点 B 必须能读到这个写入。但是顺序一致中不保证,只保证所有参与者看到相同的结果。 举个例子,初始 x=0, y=0 Thread 1: x=1 r1=y Thread 2: y=1 r2=x 最终结果应该为:r1=1, r2=1 所以可能的合法的执行顺序只有两种: T1 执行完两条后,T2 执行两条,T2 看到 x=1, r2=1 T2 执行完两条后,T1 执行两条,T1 看到 y=1, r1=1 不符合最终结果一致的执行顺序则不符合顺序一致性。 ...
Linux C/C++ IO多路复用
在linux服务器编写中,为每个连接单独开一个进程/线程不现实, 我们采用io多路复用。 基本TCP socket模型 select/poll select内部有一个定长(1024)的bitsmap,以存储要监听特定事件的fd。fd从用户空间拷贝到内核,内核里遍历修改一遍后,再把就绪的fd拷贝到用户空间,用户再遍历一遍。 poll不再用bitsmap,改用动态数组(链表),不再受限于特定个数的fd。 但二者其实区别不大,本质都是遍历了两遍,查找有事件的fd时间复杂度为O(n),也都需要来回拷贝fd集合。这些操作都很消耗性能。 epoll int sockfd = socket(AF_NET, SOCK_STREAM, 0); bind( ... ); listen( ... ); int epoll_fd = epoll_create1(0); // 添加fd epoll_ctl(epoll_fd, ...); while (1) { int n = epoll_wait( ... ); for ( ... ) { // 有事件的fd } } epoll不再使用线性的存储结构,改用红黑树(一种自平衡二叉树),增删改的时间复杂度一般为O(log n)。 epoll内部维护了一个就绪fd链表,每次epoll_wait只需要处理链表,然后把所有就绪fd拷贝到用户空间。 epoll不适用轮询,而是事件驱动。 epoll有两种触发模式,ET(边缘触发,select/poll不支持)和LT(水平触发),区别主要体现在读上。 LT模式下,只要可读(socket读缓冲区中有数据),就会触发事件,直到缓冲区被读完。 ET模式下,仅当数据到来时(缓冲区数据从无到有)触发一次事件,后续将不再触发,即使缓冲区还有数据。 LT一般搭配阻塞读写,ET一般搭配非阻塞读写。
零拷贝技术
什么是零拷贝? 利用直接内存访问(Direct Memory Access,DMA),消除数据在内存中不必要的拷贝,即零拷贝技术(Zero-Copy)。 零拷贝不是没有任何复制,而是消除了消耗CPU和内存带宽的不必要的冗余复制。 为什么要有零拷贝? 传统IO过程: 用户进程–read()—>CPU(转化为内核态)–请求读取—>磁盘–多次缓冲区拷贝后转化为用户态—>read()返回—->用户进程 CPU被大量文件数据占用,多次切换上下文,期间干不了其他事情。并且,多次缓冲区的拷贝极大限制了文件传输性能。 于是Zero-Copy出现了。 优化思路 减少上下文切换的次数。传统文件IO(read()+write())有4次上下文切换(read用户->内核->用户,write用户->内核->用户)。 减少各种缓冲区的拷贝次数。传统读取:磁盘控制器缓冲区->内核缓冲区->用户缓冲区,每次拷贝都在消耗时间。 mmap()+write() read()会把内核缓冲区的数据拷贝到用户缓冲区,为了减少拷贝,换成mmap()。 mmap(Memory Map)核心思想是利用虚拟内存管理机制,在进程虚拟地址空间与物理资源(比如文件)之间建立直接的映射关系。 #include <sys/mman.h> void *mmap(void addr[.length], size_t length, int prot, int flags, int fd, off_t offset); 使用 mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0); 把内核缓冲区的数据直接映射到用户缓冲区,这样就避免了一次拷贝。 然后write(),把数据直接拷贝到socket缓冲区。由于数据还在内核缓冲区,这些都在内核态发生。 不过系统调用上下文切换的次数没变。 sendfile() #include <sys/socket.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); read from in_fd,然后write to out_fd。offset是in_fd内部的偏移量,count是最大处理长度。返回实际处理长度。 直接减少了一次系统调用,减少上下文切换开销 直接把数据从内核缓冲区拷贝到socket缓冲区,不再经过用户态 这时有磁盘->内核缓冲区->socket缓冲区->网卡,共3次拷贝(CPU一次,从内核读缓冲区到socket写缓冲区)。 如果网卡还支持SD-DMA技术,数据可以绕过sockert缓冲区,从内核缓冲区直接发送到网卡。内核空间缓冲区中对应的数据描述信息(内存地址、地址偏移量)记录到socket缓冲区中,由 DMA 根据内存地址、地址偏移量将数据批量地从内核缓冲区拷贝到网卡设备中(DMA gather copy),这样就又省去了内核空间中仅剩的 1 次 CPU 拷贝操作。 splice() sendfile的局限性是,只能从本地文件的fd发送到socket fd,且在一定程度上依赖硬件支持。 ...
SQL和数据库管理系统(MySQL)
为什么需要数据库? 数据太多,文件搞不定。 查询复杂 数据结构混乱难以维护 文件难以并发处理 数据库, 结构化数据管理 高效查询 并发控制 数据安全 数据库和SQL 数据库 : 关系型数据库 非关系型数据库 SQL是一种管理关系型数据库的语言。 SQL (Structured Query Language):结构化查询语言 用于操作关系型数据库:定义数据表,插入/查询/修改/删除数据 MySQL?SQLite?PostgreSQL? Database Management System. 是一种软件,不是数据库。 MySQL -> 主流服务器端 SQLite -> 嵌入式 PostgreSQL -> 支持复杂查询 MySQL安装 Ubuntu sudo apt install mysql-common mysql-client mysql-server sudo systemctl start mysql-server sudo systemctl enable mysql-server # 开机自启 Arch sudo pacman -S mariadb sudo mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql sudo systemctl start mariadb sudo systemctl enable mariadb 开始使用(Ubuntu) sudo mysql CREATE USER 'new_user'@'host_name' IDENTIFIED BY 'password115414'; host_name="localhost" 只能从本地访问 ...