Orien Daily

Redis 跳表:一个被低估的数据结构是怎么变成排序集合心脏的
很多人提到跳表,第一反应是"随机化的平衡树替代品",然后就不再深想了。但如果你读 Redis 的t_zset.c,会发现跳表在那里的用法远比教科书上的演示精巧得多。
先说跳表本身的直觉。普通链表查找是 O(n),这没人想用。跳表的核心操作是"往上搭快车道"——每个节点以 50% 概率决定是否出现在上一层,这样第 k 层的节点数大约是第 k-1 层的一半,形成一种概率性的"二分查找"结构。查找时从最高层往右走,走不动就往下跳一层,平均 O(log n)。这个概率构造意味着不需要像红黑树那样做旋转维护,插入删除简单得多。
但 Redis 没有简单照搬 Pugh 的原始论文。如果你看zslInsert的源码,会发现它在插入时维护了一个rank数组——记录每一层从起点到插入位置跳了多少步。这不是跳表标准操作,而是 Redis 自己加的。目的很明确:ZRANK命令需要 O(log n) 地返回元素排名,而不是遍历计数。这个 rank 信息在节点删除时也要更新,维护成本不高,但功能价值极大。这就是读源码比读论文多出来的东西——工程场景会倒逼数据结构变形。
再比如,Redis 的跳表每个节点存了分值(score)和成员对象(obj),但比较排序的优先级是先 score 后 obj 指针地址。为什么要用指针地址而不是字符串字典序?因为指针比较是 O(1),而字符串比较可能很长。在内存数据库的热路径上,这种选择不是微优化,是设计决策。
还有个容易忽略的细节:跳表的最大层数 Redis 设成了 32,而不是理论上足够的 ceil(log₂(n))。因为 2^32 已经远超任何合理的集合大小,留余量比动态计算层数更划算。这种"够用就行"的常数选择贯穿整个 Redis 源码。

🌅 Orien Daily 朝刊【5.16】
1️⃣ Gutenberg 持续进化 — 免费数字图书馆越做越好
2️⃣ Wikipedia 变 XP 桌面 — 复古浏览器体验走红
3️⃣ 公司已陷入 AI 精神病 — Mitchellh 引行业反思
4️⃣ 酒店泄露百万护照驾照 — 云存储误设公开
5️⃣ Linux 提权 0-day — 无特权访问 root 文件
6️⃣ Claude Code Routines — Anthropic 自动化编码工作流
7️⃣ 煤污染削弱太阳能 — 颗粒物降低面板效率
8️⃣ 特朗普称习会成果丰硕 — 台湾问题悬而未决
频道:@OrienDaily


🌃 Orien Daily 晚刊【5.15】
1️⃣ RAV4 车主实测拆除调制解调器与 GPS — 隐私硬改造
2️⃣ Musk vs Altman 陪审团要判什么 — 年度最大科技案
3️⃣ SpaceXAI 合并后已流失50+人 — 人才留存堪忧
4️⃣ arXiv 新规:幻觉引用禁投一年 — 学术界抵制 AI 造假
5️⃣ 中国短剧如何沦为 AI 内容工厂 — 低成本量产真相
6️⃣ Linux 0-day:无特权读 root 文件 — ssh-keysign 提权
7️⃣ 美中首脑会谈结束特朗普返美 — 台湾问题成焦点
8️⃣ SolidJS 2.0 Beta 原生异步 — Suspense 重做
频道:@OrienDaily
検証 vs 実証:论文里用错一个字,意思差很远
写研究计划书的时候,脑子里想说"验证假设",手底下写出来的到底是検証还是実証?这两个词在学术界出现频率极高,但混在一起用的话,导师读起来会觉得概念不清。
検証(けんしょう)侧重点在于"检查、确认是否正确"。已有的理论也好,某个主张也好,你拿数据去对照它、检验它,这个动作叫検証。换句话说,検証的对象是别人已经提出来的东西,你的角色是审核者。论文里常见的「仮説を検証する」就是这么回事——假设不是你定的,你只是拿来验。
実証(じっしょう)则偏向"用事实来证明、使……成立"。它更强调你通过实验或案例,正面地把某个观点支撑起来。实证研究叫「実証研究」,实证分析叫「実証分析」,这些词背后的逻辑是:我要拿出实在的证据,让这个论点站得住脚。
一句话区分:検証是"查它对不对",実証是"证它站得住"。好比法庭上,検証像交叉质询,実証像呈堂证供。
最常见翻车场景:写「実証的アプローチ」,结果整篇论文都在做统计检验,没有任何实地案例或实验数据支撑——这在日语学术语境下会被认为用词不当,统计检验应该用検証的アプローチ。
那么问题来了:如果你要做一个既有假设检验又有实地数据分析的研究,该用哪个词?
JWT 的算法协商陷阱:当服务器说"你选算法"
一个常见的 JWT 实现细节,看着无害,实际能直接提权。场景是这样的——很多 JWT 库在验证签名时,会从 token 头部的alg字段读取算法,然后据此选择验证方式。问题在于,攻击者可以把alg从RS256(非对称)改成HS256(对称),然后拿服务器公开的 RSA 公钥当 HMAC 密钥来签名。服务器拿到 token,看到alg: HS256,就用公钥做 HMAC 验证——而攻击者刚刚就是用这把公钥签的名,验证自然通过。整条攻击链只需要一个公开的公钥端点和一个不做算法白名单的服务端。
根因不是 JWT 设计本身,而是验证方把"对方声明的算法"当成了可信输入。修复说起来简单:服务端硬编码只接受哪些算法,不从 token 头取;如果用的是 RS256,就永远只走非对称验证路径,拒绝降级成 HMAC。但现实中老旧代码和默认配置常常忽略这一步,扫描 GitHub 上 populist JWT 库的 issue 列表,算法混乱类的 bug 至今还在冒头。
eBPF 验证器:内核里的迷你编译器
Linux 内核里藏着一个不太起眼却极其精巧的组件——eBPF 验证器。当你往内核加载一段 BPF 程序时,它并不会直接执行,而是先被这个验证器逐指令扫描。验证器要做的事情本质上就是编译原理里的程序分析:它模拟执行所有可能的路径,追踪每个寄存器和栈槽的类型与范围,确保程序不会越界访问内存、不会无限循环、不会使用未初始化的变量。最有趣的部分是它对循环的处理——eBPF 早期干脆禁止循环,后来才允许有限循环,验证器通过限制总指令数(上限一百万条)和跟踪状态来保证终止。它还会做死代码消除,把不可能到达的分支直接剪掉,这在某种意义上就是在做编译优化。如果你去看kernel/bpf/verifier.c,会发现这个文件有三万多行,占了整个 eBPF 子系统代码量的大头,比虚拟机解释器本身还大得多。换句话说,内核团队把最多的工程精力不是花在"怎么运行 BPF 程序",而是花在"怎么确保 BPF 程序不会搞崩内核"上。这个取舍很说明问题:在一个特权执行环境中,安全验证的复杂度永远高过执行本身。


