在讲 RocketMQ 消息模型时,有一个经典约束:每个队列只能串行消费

原因很直接——要保证严格顺序和消息不丢,Consumer 必须一条一条处理,确认完再拉下一条。一旦并发,就会出现”消费空洞”。

那如果我们不要求严格顺序呢?能不能在单个队列内做并行消费?

答案是可以的。而且 RocketMQ 本身已经实现了这个能力。


先搞清楚:消费空洞是什么

假设队列里有三条消息,offset 分别是 1、2、3,我们并发消费:

1
2
3
offset=1 → 处理中
offset=2 → 处理完成 ✓
offset=3 → 处理完成 ✓

此时 offset=2 和 3 都完成了,如果直接把 commitOffset 推进到 3,那 offset=1 一旦失败需要重试,就找不回来了——它”掉”进了一个洞里。

这就是消费空洞:commit 点超过了还未完成的消息,导致重启或重平衡后消息永久丢失。


放宽约束后的核心问题

串行消费能避免空洞,因为 commitOffset 只有在当前消息确认后才往前走,天然保证”已提交 = 已处理”。

并行消费的挑战在于:多条消息同时在飞,commit 点该停在哪?

答案是:停在”最低未完成 offset”的前一位


解决方案:滑动窗口

核心思路是维护一个”飞行中”的消息窗口,用类似 TCP 滑动窗口的方式管理 commit 点。

1
2
3
4
5
6
7
8
9
队列: [1][2][3][4][5][6][7]...

窗口起点 (commitOffset)

当前飞行中: {3✓, 4✓, 5✗(处理中), 6✓, 7✓}

最低未完成

commitOffset 只能推进到 4(5 还没完成)

具体实现:

  1. 用一个有序结构(TreeMap<Long offset, Status>)记录所有飞行中的消息及其状态
  2. 每条消息处理完成(无论成功或失败),更新对应 offset 的状态
  3. 每次有消息完成时,从最小 offset 开始扫描,找到连续已完成的最大 offset
  4. 将 commitOffset 推进到该位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 伪代码示意
TreeMap<Long, Boolean> inflightWindow = new TreeMap<>();

// 拉取一批消息,全部扔进线程池
for (Message msg : pullBatch) {
inflightWindow.put(msg.getOffset(), false); // false = 未完成
threadPool.submit(() -> {
process(msg);
inflightWindow.put(msg.getOffset(), true); // 标记完成
tryAdvanceCommitOffset();
});
}

void tryAdvanceCommitOffset() {
long newCommit = currentCommitOffset;
for (Map.Entry<Long, Boolean> e : inflightWindow.entrySet()) {
if (!e.getValue()) break; // 遇到未完成的,停住
newCommit = e.getKey();
}
commitOffset(newCommit);
inflightWindow.headMap(newCommit, true).clear(); // 清掉已提交部分
}

这样,即使 offset=6、7 先完成,commitOffset 也不会越过 5——直到 5 完成,窗口才整体滑动。


RocketMQ 的实现:ProcessQueue

RocketMQ 的并发消费模式(MessageListenerConcurrently)就是这套思路的工业实现。

它用 ProcessQueue 作为核心数据结构:

  • 内部是一个 TreeMap<Long offset, MessageExt>,存放所有已拉取但未提交的消息
  • 消息消费完成后,从 TreeMap 中移除对应 offset
  • 提交 offset 时,取 TreeMap.firstKey()(最小 offset)作为 commitOffset
1
2
3
4
5
6
7
8
9
10
11
// RocketMQ 源码简化版(ProcessQueue.java)
public long removeMessage(List<MessageExt> msgs) {
for (MessageExt msg : msgs) {
msgTreeMap.remove(msg.getQueueOffset());
}
// 返回当前最小 offset,即 commitOffset 的位置
if (!msgTreeMap.isEmpty()) {
return msgTreeMap.firstKey();
}
return ...;
}

换言之:TreeMap 最小 key 就是”窗口的左边界”,只有它之前的消息才是安全可提交的


失败消息怎么处理

并行消费模式下,失败不能像串行那样”原地重试再继续”,有几种策略:

策略 做法 代价
原地重试 失败后重试,完成前窗口不滑动 一条慢消息阻塞整个窗口
发回重试队列 把失败消息发到 %RETRY% topic,标记当前 offset 完成 不阻塞,但消息乱序(重试时机延后)
丢弃进死信队列 超过重试次数后写入 DLQ 最终放弃,保证主流程不被阻塞

RocketMQ 的并发消费默认走第二种:失败消息发回 %RETRY%TopicName,broker 延迟后重新投递,不影响当前队列的 offset 推进。


数据结构选型:TreeMap、ConcurrentSkipListMap、Ack Table

滑动窗口是一个思路框架,具体用什么数据结构来实现它,影响并发性能和实现复杂度。这里比较三种常见选择。


TreeMap(红黑树)

TreeMap 的底层是红黑树——一棵自平衡二叉搜索树,所有节点按 key 排序,左小右大。

1
2
3
4
5
    5
/ \
3 7
/ \ / \
2 4 6 8

红黑树通过旋转和变色来维持近似平衡(任意路径上黑节点数相同),保证树高 O(log n),从而让插入、删除、查找都是 O(log n)。

用在滑动窗口里:

  • key = offset,value = 是否已完成
  • firstKey() → 窗口左边界(最小 offset),O(log n)
  • 消息完成后 put(offset, true),找 commit 点时从头遍历

问题:TreeMap 不是线程安全的,多线程并发 mark 时必须加锁(RocketMQ 用的是 ReadWriteLock),高并发下锁竞争是瓶颈。


ConcurrentSkipListMap(跳表)

跳表(Skip List)是 TreeMap 的并发替代方案,结构上是多层有序链表

1
2
3
4
第3层:HEAD ──────────────────────────────> [7] ──> NULL
第2层:HEAD ──────────> [3] ──────────────> [7] ──> NULL
第1层:HEAD ──> [1] ──> [3] ──> [5] ──────> [7] ──> NULL
第0层:HEAD ──> [1] ──> [2] ──> [3] ──> [5] ──> [6] ──> [7] ──> NULL

查找时从最高层开始,大步前进,碰到比目标大的节点就下沉一层,逐步逼近目标。平均 O(log n),和红黑树相同。

跳表的优势在于并发:插入和删除只需要修改局部节点的 next 指针,不涉及全局旋转,天然适合用 CAS(Compare-And-Swap)实现无锁操作。java.util.concurrent.ConcurrentSkipListMap 就是这么做的——多线程并发 mark offset 时无需全局锁,只有在节点级别做 CAS。

用在滑动窗口里:

  • 直接替换 TreeMap,去掉 synchronized
  • firstKey() 返回最小未完成 offset,线程安全
  • 高并发场景下比 TreeMap + lock 吞吐量更高

代价:内存占用比 TreeMap 高(多层链表指针),且每条消息都需要独立节点,窗口大时内存不友好。


Ack Table(位图)

位图(Bitmap)是另一个维度的思路:不用树形结构按序维护节点,而是用一个定长数组的每一个比特位代表一个 offset 的完成状态。

映射关系:第 i 个 bit 对应消息 baseOffset + i,bit=1 表示已完成,bit=0 表示处理中。

1
2
3
4
5
6
baseOffset = 100

消息 offset: 100 101 102 103 104 105
对应 bit 位: 0 1 2 3 4 5
当前状态: 1 1 0 1 1 0
已完 已完 处理中 已完 已完 处理中

从 bit 0 往右,找第一个 0——这里是 bit 2(offset=102),所以 commitOffset 停在 102,只有 100 和 101 可以安全提交。即使 103、104 已完成,也不能越过这个空洞。

  • mark 操作:bitmap |= (1L << i),O(1),无内存分配
  • 找 commit 点:找从最低位起连续 1 的个数

找 commit 点用位运算加速,核心是 numberOfTrailingZeros(~word)

1
2
3
word  = 0b...00101011   (bit 0,1,3,4 为 1,bit 2 为 0)
~word = 0b...11010100 (取反,原来的 0 变成 1)
numberOfTrailingZeros(~word) = 2

~word 把 0 变成 1,再找最低位的 1,合起来就是”原 word 从低位起连续 1 的个数”——即可提交的消息数。

1
2
3
4
5
6
7
8
9
10
int committed = 0;
for (long word : bitmap) {
if (word == -1L) { // 64 位全 1,这 64 个 offset 全部完成
committed += 64;
} else {
committed += Long.numberOfTrailingZeros(~word);
break;
}
}
commitOffset = baseOffset + committed;

位图的优势:

  • **mark 是真正的 O(1)**,无内存分配,无指针跳转
  • 极度缓存友好:一个 1024 消息的窗口只占 128 字节,一个 cache line 就装下了
  • 无锁:mark 时用 AtomicLong 的 CAS 即可,不需要全局锁

位图的限制:

  • 窗口大小固定,超出窗口的消息无法追踪
  • 窗口不能滑动直到左边界的消息完成(和滑动窗口一样的语义约束)
  • 适合窗口大小可预估、消息 offset 连续的场景

三者对比

TreeMap ConcurrentSkipListMap Ack Table (Bitmap)
底层结构 红黑树 跳表 位数组
mark 复杂度 O(log n) O(log n) O(1)
找左边界 O(log n) firstKey O(1) firstKey O(n/64) 位扫描
线程安全 需加锁 内置无锁 CAS AtomicLong CAS
内存 每条消息一个节点 每条消息多个指针 每条消息 1 bit
适用场景 简单实现、消息量不大 高并发、消息量大 超高吞吐、窗口固定

RocketMQ 选择了 TreeMap + ReadWriteLock,因为消费侧的 mark 频率通常不是瓶颈(拉批处理的粒度较粗),简单可靠优先。如果是自研的高吞吐系统,Bitmap 方案在内存和 CPU 效率上都更占优。


与严格顺序消费的对比

串行消费 并行消费(滑动窗口)
吞吐量 低(单线程) 高(线程池并发)
消息顺序 严格保证 不保证(并发执行)
消息不丢 保证 保证(commitOffset 安全)
实现复杂度 简单 需要 offset 跟踪
适用场景 订单状态流转、账务 日志处理、通知推送

小结

单队列并行消费是可行的,关键是不能直接提交最新 offset,而要提交”最低未完成 offset 的前一位”。

滑动窗口(ProcessQueue)解决了这个问题:把飞行中的 offset 全部追踪起来,只有左边界连续完成时,commit 点才向前推进。

放弃严格顺序,换来的是线程池级别的并发能力——在大多数业务场景下,这笔交易是合算的。

  “日月不失其体,故蔽而复明;江汉不失其源,故穷而复通。”

  遮蔽并不等于熄灭,险阻并不等于断绝。只要本体未失,只要源头不竭,困顿终会过去,光明终将重来。这句话出自《文中子》,言简而意远。五百年来,历史一遍遍地验证着这个道理,而投资人瑞达利欧(Ray Dalio)穷数十年之力研究大国兴衰,所得出的结论,与这句古语竟有异曲同工之妙——真正能穿越周期的,从来不是一时的强盛,而是本体与源头的完好。

  大国的兴衰,是人类历史上最波澜壮阔的周期。瑞达利欧研究了过去五百年间所有主要帝国的命运,从荷兰、英国到美国,他发现一个惊人的规律:大国的崛起与衰落,遵循着几乎相同的节律——新秩序的建立、财富的积累、内部的腐化、债务的扩张、货币的贬值、外部的挑战,这六个阶段如同四季轮回,无一例外。美国今天面对的,是英国一百年前面对的;英国当年面对的,是荷兰更早之前面对的。然而,周期并不意味着终结。荷兰帝国的海上霸权衰落了,但荷兰至今仍是高度发达、秩序优良的现代国家;英国让出了”日不落帝国”的宝座,但英语文化、普通法体系、金融城的基因延续至今,深刻影响着全球秩序。它们”失其体”了吗?没有。霸权周期终结,但文明的本体——制度的韧性、社会的共识、自我更新的意志——从未真正散失。反观那些真正走向衰亡的文明,往往不是败于外敌,而是先败于内部的腐化与分裂。日月失其体,方才再不复明。由此可见,在国家层面,”本体”不是领土的大小,不是GDP的排名,而是一个社会应对变化、自我修复、凝聚共识的能力。只要这股力量不散,哪怕经历最猛烈的周期震荡,终能穿越险阻,重归通途。

  如果说大国兴衰的周期以百年为刻度,那么互联网的周期则以十年为单位,演示了同样深刻的道理。二十世纪末,互联网带着人类从未见过的乐观主义扑面而来。纳斯达克指数在五年间上涨了将近六倍,无数几乎没有商业模式的公司,估值动辄数十亿美元。所有人都相信,这一次,世界真的不同了。然后,泡沫破裂了。2000年3月,纳斯达克开始崩塌,三年间跌去将近八成,无数公司蒸发,整整一代投资人谈”网络”色变。然而,这是互联网的终结吗?二十年后回望,我们知道答案——泡沫破裂的那一刻,亚马逊股价跌到了6美元,谷歌在一片焦土中悄然成立,互联网不但没有终结,它彻底重塑了商业、社交、媒体与金融。泡沫淘汰的,是那些”失其源”的公司——没有真实用户需求,没有可持续商业模式,纯靠叙事和情绪支撑的空中楼阁。留下来的,是那些根扎得深的:亚马逊的物流基础设施,谷歌的搜索算法,腾讯的社交关系链。”江汉不失其源,故穷而复通。”险阻过后,有源者通,无源者止——泡沫破裂之后,反而是检验谁拥有真正源头的时刻。

  历史不会简单重复,但总是押着相同的韵脚。2023年,人工智能带着比当年互联网更凶猛的气势席卷全球。算力军备竞赛、估值泡沫、”AGI改变一切”的叙事……这一幕,像极了1999年。瑞达利欧的框架在这里同样适用:新技术的大周期,通常经历”创新突破—过度投机—泡沫修正—真实落地—深度重塑”五个阶段,而我们或许正站在从第二阶段走向第三阶段的边缘。这不是悲观,这是规律。真正值得追问的,不是泡沫何时破裂,而是:AI的”本体”是什么,AI的”源头”在哪里?AI的本体,是人类对智能工具的真实需求——让繁琐的工作更快完成,让复杂的决策有更好的参谋;AI的源头,是几十年来数学、信息论与神经网络研究的底层积累,以及人类生产的海量语言与知识。这些不会因为估值回调而蒸发。所以,即便寒冬重演,那些本体未失、源头不竭的技术路径,终将穿越阴霾,在更广阔的现实世界里重新通流。被淘汰的,只会是那些没有真实场景、纯靠叙事融资的空洞故事。

  从大国周期,到互联网周期,再到AI大周期,三重叙事指向同一个结论:周期是必然的,险阻是必然的,遮蔽是必然的。没有什么力量能绕开周期,但有些力量能穿越周期。区别只在于:本体是否完好,源头是否不竭。这个道理,对个人同样成立。我们每个人的一生,也都在经历各自的周期——青春的骄傲与中年的困顿,事业的顺遂与失意的低谷,时代浪潮的托举与退潮后的搁浅。不失其体者,蔽而复明——“体”是你真正相信的价值,是你持续磨砺的能力,是在最难的时候仍能让你站起来的那个理由。不失其源者,穷而复通——“源”是你不断输入、不断学习、不断与真实世界碰撞的那股活水。瑞达利欧在书的最后写道:”最重要的不是你得到了什么,而是你如何面对那些不可避免的挑战。”这与两千年前那句古语,何其相似。

  日月总有被云遮蔽的时刻,但只要它还是它,黎明就只是时间问题。江河总有被山阻断的时候,但只要源头的水仍在,它终将找到入海的路。守住本,不断源,就是在任何时代穿越任何周期的根本之道。

我向来是不惮以最坏的恶意,来推测资本家的。

但近来听说海力士的事,竟使许多正人君子们惊诧得离了谱。他们瞪圆了眼,仿佛见到了猫儿给耗子守灵,或是磨坊的驴子突然坐到了餐桌上,手里还攥着金汤匙。于是,那句憋在喉咙里的国骂,终于和着唾沫星子喷将出来:“***,你背叛了资产阶级!”

这骂声里,是有真委屈在的。

在正统的生意经里,规矩是极明白的:工人的汗是用来浇灌利润的,而利润是用来供养财阀的雪茄与游艇的。现在可好,这家公司竟劈开了一大块“营业利润”,足足百分之十,说是要塞进那三万五千个穿工服的人的兜里。这在卫道士看来,简直是成何体统!这不仅是分了钱,这是分了“命”。

然而,这种“背叛”,在我看来却大抵是有些悲凉的滑稽。

那些骂他背叛的人,大概是忘了,这世上最极端的利己,往往披着利他的皮。海力士的官儿们并不全是疯子。他们深知,在那高耸入云的HBM芯片堆里,藏着的不是砂砾,而是几万个随时准备跳槽的脑袋。在这个名为“人工智能”的竞技场上,资本早已发现,光靠皮鞭是赶不出尖端算法的。为了让这台巨大的印钱机器不至于因零件流失而散架,他们不得不从指缝里漏出一点残羹,美其名曰“分红”,实则是给那些高级壮丁们续上一条名为“忠诚”的锁链。

自然,这世上还有另一类老爷,正对着这笔赏金切齿拊心。

这类老爷是极精明的,他们眼里的员工,大约和实验室里的白鼠或拉车的骡子并无二致。在他们的算盘珠子上,从来没有“分享”这两个字。若公司亏了,那必是“降本增效”,要勒紧工人的裤腰带;若公司赚了,那必是“战略远见”,要塞满自己的钱袋。

他们最擅长的,是把“共克时艰”印在员工手册上,却把“论功行赏”藏在自家的密室里。当你去问这些老爷:“海力士都发了,我们呢?”他们便要祭起那套名为“情怀”的法宝了。先是语重心长地谈谈行业寒冬,再忆苦思甜地讲讲创业维艰,最后还要拍拍你的肩膀,语重心长地说:“年轻人,眼光要放长远,不要总盯着那点阿堵物。公司现在的积累,都是为了你们未来更大的平台。”

这逻辑是极妙的:钱是我的,而“平台”是你的。你出了汗,换来了一个可以继续出汗的机会,这难道不是莫大的恩赐么?

在这种老爷看来,一个子儿也不给员工,不仅是省了成本,更是守住了“资本的纯洁性”。他们坚信,只有让员工保持适度的饥饿,那骡子才会为了鼻尖前那颗虚幻的红萝卜,继续没命地跑下去。

他们骂海力士“背叛”,其实是在害怕。害怕这种“天价奖金”成了一面照妖镜,照出他们那满口的“福报”,背地里全是吃人不吐骨头的生意。

所以,这算什么背叛呢?这不过是资产阶级在面对更巨大的焦虑时,一次不得不为的倒戈。而那些守财奴式的谩骂,也并不显得高尚,只显出一种穷途末路的刻薄。

但我看这些老爷是大可不必担心的。只要这世上还有足够多还没被照到的角落,他们依旧可以心安理得地捂紧钱袋,继续在他们的盛宴上,优雅地剔着牙缝里的血丝。

至于那句“***”,在震耳欲聋的开香槟声中,终究不过是一阵风,吹过了也就吹过了。世界依旧冷冷地转着,有的工人在罢工,有的工人在领赏,而资本在暗处,正算计着下一场更隐秘的收买。

我只觉得这喧嚣有些吵闹。

在分布式系统里,我们最常听到的一个词是“一致性”。听起来很像管理学口号,但在真实世界里,它常常是生死攸关的问题。

“拜占庭将军问题”(Byzantine Generals Problem)就是一个经典提问:
如果参与者里有坏人,或者消息会被篡改,系统还能否做出统一决策?

先讲故事版本

想象几位将军率军围城,大家约好同时进攻或同时撤退。每位将军分散在不同地点,只能通过信使传话。

问题来了:

  • 有些信使可能在路上被拦截
  • 有些将军可能是叛徒,会故意发送假命令
  • 大家拿到的信息可能互相矛盾

如果有人进攻、有人撤退,进攻方兵力不足,会被守军集中消灭。所以关键不只是“做决定”,而是“在不可靠环境中做同一个决定”。

这和普通故障有什么不同

我们平时处理的很多故障,属于“崩溃故障”(Crash Fault):
节点要么正常工作,要么直接挂掉不说话。

拜占庭故障更难,因为它不是“沉默”,而是“乱说话”:

  • 可能给 A 说“进攻”,给 B 说“撤退”
  • 可能伪造签名、重放旧消息
  • 可能看起来在线且活跃,但行为恶意

简单说,崩溃故障是“失联”,拜占庭故障是“内鬼 + 谣言 + 伪装”。

为什么这个问题很重要

因为现实系统并不总在“理想机房”运行:

  • 跨机房网络会抖动
  • 节点可能被入侵
  • 消息可能延迟、丢失或被篡改

在这种环境下,如果没有一个抗拜占庭的共识机制,系统就可能出现“每个节点都认为自己是对的,但整体已经分裂”的状态。

一个关键结论:3f + 1

在经典异步模型下,如果你希望系统容忍 f 个拜占庭节点(任何“行为任意、不可预测”的节点),
通常至少需要 3f + 1 个总节点,才能保证安全性与活性(在特定假设下)。

为什么不是 2f + 1
因为 2f + 1 适合对付“节点宕机不说话”,不够对付“节点作恶乱说话”。

2f + 1 的规模下:

  • 诚实节点只有 f + 1
  • 恶意节点有 f
  • 恶意节点可以给不同诚实节点发送相互冲突的信息

这样就可能把诚实节点分裂成两个阵营,导致系统无法同时保证一致性和持续推进。
而在 3f + 1 下,诚实节点至少有 2f + 1,多数集合之间会有足够交集,
从而避免两份互斥决议都被通过。

举个直觉例子:

  • 想容忍 1 个恶意节点(f = 1
  • 至少要 4 个节点(3*1+1=4

为什么不是 2 或 3?
因为你必须让“诚实节点形成足够多数”,从而压过伪造信息带来的混乱。

区块链为什么总提它

很多人第一次听到拜占庭将军问题,是在区块链里。
本质原因很简单:区块链就是一个“参与者不完全可信”的分布式账本。

不同系统用不同共识机制来应对:

  • 工作量证明(PoW):用算力和成本抵御恶意行为
  • 权益证明(PoS):用质押和惩罚约束作恶
  • PBFT 类协议:通过多轮投票和签名验证达成共识

路径不同,但目标一致:
即便有人作恶,整体账本仍保持可验证的一致状态。

那 Paxos / Raft 是什么关系

讲到共识,很多工程师第一反应是 Paxos 或 Raft。
它们非常重要,但要注意:它们默认处理的主要是崩溃故障,不是拜占庭故障。

你可以把它们理解为:

  • Paxos / Raft:假设节点大体诚实,重点解决“谁是 leader、日志如何复制、节点宕机后如何恢复”
  • PBFT / BFT 类协议:假设可能有人作恶,重点解决“即使有人撒谎,系统也不分叉”

在崩溃故障模型下,Paxos / Raft 通常 2f + 1 个节点就能容忍 f 个宕机节点。
例如 3 节点容忍 1 个宕机,5 节点容忍 2 个宕机。
它依赖的是多数派(f + 1)法定人数和多数交集,而不是对恶意行为的防御。

所以不是谁“更高级”,而是防御模型不同:

  • 机房内强信任环境(数据库主从、配置中心等)常用 Raft/Paxos
  • 开放或弱信任环境(联盟链、跨机构协作)更关注 BFT 能力

一句话:
Raft/Paxos 很擅长处理‘有人掉线’,BFT 协议是为‘有人捣乱’准备的。

工程视角下的启发

拜占庭将军问题不是让我们“永远不信任任何人”,
而是提醒我们在架构里明确回答三个问题:

  1. 你要防的是“宕机”,还是“作恶”?
  2. 你的信任边界在哪里?
  3. 出现矛盾信息时,谁有最终裁决权?

如果这些问题在设计阶段没有说清楚,
上线后通常会以“偶发一致性 bug”的形式回来找你。

写在最后

很多系统故障,并不是因为算法不够高级,
而是因为默认前提太乐观:我们假设了节点诚实、网络可靠、消息真实。

拜占庭将军问题最珍贵的地方就在于它逼你承认:
在复杂系统里,不可信是常态,不是例外。

当你下次设计一个分布式流程时,不妨先问一句:
“如果其中一个参与者在撒谎,这个系统还能自洽吗?”

如果你第一次接触并发编程,很容易觉得它是“多开几个线程”这么简单。
但只要线程之间开始共享资源,事情就会迅速变得微妙:程序可能不崩溃,却永远不往前走。

“哲学家进餐问题”(Dining Philosophers Problem)正是把这种微妙,讲得最优雅的经典模型之一。

问题到底是什么

想象有五位哲学家围坐在一张圆桌旁,每两位哲学家之间放一把叉子。
每位哲学家会在两种状态之间切换:

  • 思考
  • 进餐

规则非常简单:哲学家必须同时拿到左手边和右手边两把叉子,才能吃饭;吃完再放下。

看起来很合理,对吧?问题就出在“很合理”这件事上。

为什么会死锁

最常见的朴素实现是:每位哲学家先拿左叉,再拿右叉。
如果五个人在同一时刻都拿到了左叉,会发生什么?

  • 每个人手里都有一把左叉
  • 每个人都在等右叉
  • 但每个人的右叉都在邻居手里

于是系统进入一个稳定又绝望的状态:谁也不释放,谁也吃不上。
这就是死锁(Deadlock)

并发世界里最危险的 bug 往往不是“炸掉”,而是“卡住且看起来没问题”。

不死锁就够了吗

很多人第一次修复这个问题,会优先考虑“如何避免死锁”。这当然重要,但还不够。

就算不死锁,也可能出现:

  • 饥饿(Starvation):某个哲学家长期抢不到叉子,一直吃不到饭
  • 不公平(Unfairness):总是某几位哲学家更容易拿到资源

所以一个真正好的并发策略,目标通常是三层:

  1. 不死锁
  2. 尽量避免饥饿
  3. 在吞吐与公平之间找到平衡

常见解法

1)资源有序分配

给所有叉子编号,规定每个人必须先拿编号小的,再拿编号大的。
这样可以打破“循环等待”条件,死锁自然消失。

优点:简单、好实现。
缺点:公平性不一定最佳,仍可能让少数线程长期吃亏。

2)限制同时尝试进餐的人数

比如最多只允许 4 位哲学家同时尝试拿叉子(总共 5 人)。
这相当于引入一个“服务员”(信号量)控制并发度,减少冲突环形成的可能。

优点:思路直观,工程上常见。
缺点:降低并发上限,吞吐量可能下降。

3)破坏对称性

让其中一位哲学家先拿右叉再拿左叉,其余人保持先左后右。
通过让系统“不完全一致”,避免所有线程走进同一陷阱。

优点:改动小。
缺点:更像技巧而不是通用框架,扩展到复杂系统时不够稳健。

4)使用超时与重试

拿不到第二把叉子就放下第一把,随机等待后再试。
它不追求一次成功,而是通过退避策略降低长期冲突概率。

优点:实用,适合分布式场景。
缺点:参数不好调,可能在高竞争下抖动严重。

这个模型为什么一直不过时

哲学家、叉子、餐桌只是故事皮肤。它映射到现实系统里,几乎随处可见:

  • 线程争抢锁
  • 数据库事务争抢行锁/表锁
  • 微服务争抢连接池、队列、限流配额

很多线上“偶发卡死”问题,根子都和这张餐桌差不多:
每个参与者都在按规则办事,但整体却陷入互相等待。

写在最后

哲学家进餐问题最有价值的地方,不在于背出某个标准答案,而在于它提醒我们:

  • 局部正确,不等于全局正确
  • 系统“能跑”,不等于系统“能一直跑”
  • 并发设计里,顺序、公平和退让机制同样重要

如果你正在写多线程代码,可以试着问自己一句:
“我的线程,是否也坐在一张看不见的圆桌旁?”

很多问题的答案,就在这句反问里。

本文是一篇Circle创始人Jeremy Allaire的自述,摘自Circle 招股书,讲述公司愿景、使命与上市意义,展现其重塑全球金融体系的雄心:

为了让大家更好地理解Circle这家公司及其潜力,我想和大家分享一下我的经历:这些经历是如何促使我萌生创立Circle的念头,又是如何引导我们一步步走到今天这一步的。同时,我也想和大家探讨一下:对于Circle来说,成为一家上市公司意味着什么;另外,在未来十年里,我认为Circle在全球金融体系中应该扮演怎样的角色。

成为互联网的极致主义者

我很幸运,从小家里就有一台苹果II型电脑,因此我在20世纪80年代初就能够接触到互联网出现之前的网络世界了。尽管当时的条件有限,但这对我来说意义重大。我的父母的世界观和道德观念也深受这样的理念影响:如果我们能够与人们建立更深入的联系,并将世界各地的人们紧密联系在一起,那么我们就有可能为这个世界带来积极而深刻的变革。

作为一名大学里学习政治学、哲学和经济学的学生,我的世界观建立在这样一个信念之上:那些拥有自由信息体系与自由市场,并且能够相互交流与贸易的国家,必将带来一个政治与经济更加稳定的世界。正是在1990年,我开始接触互联网——那时的互联网还处于商业化的初期阶段,充斥着命令行界面、各种奇怪的论坛,以及存储着大量随机科学数据和政府信息的各类服务器。

正是在那些时刻,我成为了互联网的坚定支持者。我清楚地认识到:通过开放的技术协议连接起来的全球计算机网络,任何人都可以在此基础上进行开发和使用;这样的网络必将引发社会与经济的全面变革。

在接下来的20年里,我全身心投入到互联网软件平台与基础设施的建设中,这些努力为实现那些构想提供了有力支持。最终,我共同创立了两家全球性的互联网技术公司,并在公司的发展中发挥了关键作用,帮助它们顺利完成了从Web 1.0到Web 2.0的转型过程。在那段时期,开放的互联网以不可逆转且难以预测的方式彻底改变了软件、媒体、通信以及零售业的发展格局。

这场严重的金融危机——一个转折点

在我投身于互联网事业近20年的时间里,世界经历了本世纪最严重的全球金融危机。20世纪90年代和21世纪初是经济快速增长和全球化进程加速的时期,而互联网无疑在推动这一经济发展过程中发挥了重要作用。然而,这一切都在2008年戛然而止了。

作为一名政治经济学的学生,我必须弄清楚到底发生了什么。全球经济体系中这个至关重要的部分——金融服务行业——已经让我们陷入了巨大的困境。在深入研究银行业、货币体系、中央银行制度以及国际货币体系的起源与发展后,我逐渐确信:我们需要为金融体系建立一种全新的架构。这种新架构应该以互联网为基础,依托开放的网络、开源软件以及全球化的整合机制;最终,通过这种架构,完全基于储备的数字货币可以与基于互联网的信贷市场相结合,从而构建出一个更加安全的金融体系。

正是在这段自我探索的时期,我在2012年偶然发现了比特币。虽然我并不认为比特币本身会成为解决所有金融问题的答案,但我在比特币中看到了技术创新与哲学理念的完美结合——这种结合围绕着建立一个基于互联网的、开放式的替代性金融系统这一核心理念展开。

Circle的成立与愿景

2013年初,在深入了解了围绕比特币和区块链形成的早期技术社区后,我和Circle的联合创始人肖恩·内维尔清楚地意识到:一个全新的技术层正在逐渐形成。

互联网正在诞生,它带来了一套全新的开放协议,这些协议将在价值交换方面发挥重要作用——就如同早期的协议在数据与通信交换领域所起的作用一样。

虽然在当时还无法实现这些目标,但作为技术专家的我们清楚地认识到:区块链技术必将不断发展,最终能够用于发行代表其他资产的数字代币,支持以智能合约的形式执行代码,并使人们能够构建更高级别的货币交易协议。这种技术的影响将会比互联网还要深远——因为它不仅能改变信息的传播方式,更有可能彻底重塑整个全球经济体系。

我们设想开发一种名为“HTTP for Money”的协议,这种协议专门用于处理美元以及其他法定数字货币的交易。该协议将构建一个开放且可编程的基础设施,从而让全球金融体系能够按照互联网的模式进行重构。

对我们两人来说,有两个关键结论是非常明确的。

首先,自然而然的结果就是区块链技术将在规模、速度和效率方面实现突破。如果互联网上的货币交易能够采用这种基于区块链的协议,那么存储和传输价值(即法定货币的价值)的过程将会被商业化。这样一来,货币的流通速度将会加快,同时也会为实体经济带来数万亿美元的收益。

其次,由于数字货币协议可以通过智能合约进行编程,这意味着货币的用途最终将会发生巨大变革。新的“货币形态”将会出现,这些新形态可能会彻底改变金融、贸易、商业以及资本市场的运作基础。

为实现公司的使命与愿景而努力奋斗

作为数字资产和区块链行业的早期先驱者,我认为Circle在众多竞争对手中脱颖而出。我们选择了一条与众不同的道路来实现我们的使命——通过让价值交换变得更加顺畅,从而推动全球经济的繁荣发展。我们深知,要实现这一目标,要构建一个能够大规模使用数字货币的世界,就必须采取与那些仅仅专注于扩大投资规模或进行投机性加密货币交易的公司截然不同的方法。

在开发 Circle 的过程中,有几个关键原则起到了指导作用。

  1. 与监管机构和政策制定者直接沟通。我们一直坚信并坚持采取这种直接与全球各地的监管机构和政策制定者进行沟通的方式。为了与他们建立良好的合作关系,并帮助政府理解这些新兴且复杂的技术,我们曾在美国及其他国家的立法机构面前作证。此外,我们还定期向监管机构和政策制定者提交关于稳定币及数字资产生态系统的意见和反馈。最终,我们坚信,继续与监管机构和政策制定者合作对于构建一个健康的互联网金融体系至关重要。

  2. 掌握以平台为核心的技术开发方法。在构建我们的核心数字货币技术栈时,我们将其视为一个供他人进一步开发的平台。这种开发方式最终促成了 USDC 的诞生及其后续的成功,同时也推动了 Circle 公司提供更广泛的平台服务。

  3. 适应当前的市场状况。在这个过程中,每一步都需要根据技术的实际发展水平以及市场的现状来进行调整——而市场的走向并不总是我们期望的那样。为那些早期采用新技术的用户开发的产品,与为全球主流金融市场和商业领域开发的产品是完全不同的。因此,我们采取了谨慎的小步推进策略,但始终保持着长远的目光。

  4. 将合规性作为竞争优势。我们的目标是将现有的金融体系与新的互联网金融体系结合起来,这意味着加密货币并非孤立存在的独立技术,而是一种可以被用来连接这两个体系的工具。为此,我们已经在合规基础设施方面投入了大量资源,并且打算继续加大投资力度。这种做法与我们“以合规为先”的经营理念是一致的;我们认为,这样的做法有助于提升我们在监管方面的参与度和影响力,同时也能增加我们对潜在合作伙伴、客户以及最终用户的吸引力。

总的来说,这些执行原则使Circle能够在市场的初期阶段保持敏捷性和竞争力,并最终让我们具备了在2018年推出USDC所需的能力和专业知识。七年后,我们关于“互联网上可编程的美元协议”的愿景终于成为了现实。尽管在过去我们遭遇过各种挑战和风险,而且未来无疑还会继续面临新的问题,但我相信,Circle完全有潜力成为新型互联网金融体系的重要组成部分。

我们所取得的成就

如今,Circle已成为一个全球范围内广受认可和尊重的品牌与企业。它以诚信与透明度著称,在构建互联网金融体系的过程中始终扮演着倡导者和领导者的角色。

自2018年推出以来,作为Circle稳定币网络的核心,USDC截至2025年3月28日已支持了超过25万亿美元的链上交易。数以百万计的终端用户使用USDC进行支付、结算,并将其视为一种数字形式的“价值储存工具”。数千家企业和开发者已经与USDC进行了集成,从而形成了一个蓬勃发展的生态系统。各大支付公司、企业技术厂商、消费互联网应用平台、金融科技企业以及数字资产公司都在利用Circle的技术来开发基于USDC的各种解决方案。

USDC一直建立在强大的市场基础设施和充足的流动性基础之上。自2021年1月1日至2024年12月31日期间,Circle共发行了超过5043亿美元的USDC,并回购了超过4644亿美元的USDC——这些回购操作始终都是以1:1的比例与美元进行兑换的。

我们确实面临过诸多挑战。例如,在2023年,USDC的流通量出现了长期下降,这背后有多种因素:美国短期利率上升、数字资产价格下跌,以及数字资产交易生态系统中杠杆率的降低;此外,2023年3月美国一些地区性银行的倒闭也导致了二级市场出现暂时性的价格波动,部分市场份额因此转移到了竞争对手手中。毫无疑问,未来我们仍会继续面临各种挑战。然而,USDC和Circle稳定币网络的持续发展展现出了强大的韧性,使我们能够在短时间内成为一家充满活力的企业。2020年时,我们的收入仅为1540万美元,到了2021年这一数字增长到了8490万美元;2022年为7.72亿美元,2023年达到15亿美元,而2024年则达到了17亿美元。2024年,我们实现了1.56亿美元的净利润和2.85亿美元的调整后EBITDA。截至2024年12月31日,我们的总流动性资金达到了10.45亿美元,其中现金及现金等价物为7.51亿美元,专门用于持有公司自有稳定币的现金及现金等价物则为2.94亿美元。在此期间,我们还组建了一支具有多元化和全球背景的团队,成员来自十多个国家和地区以及美国的35个州和地区。这些团队成员大多来自领先的互联网、科技和金融服务企业,我们认为他们具备构建这一新型互联网金融系统所需的各项技能。

Circle是一家以产品和技术为核心的公司。我们已经在产品和技术研发方面投入了大量资金,并且会继续加大投入,以期在市场中创造长期价值。

成为上市公司

在很多方面,Circle长期以来一直处于公众的严密监督之下。由于需要运营一个始终处于运行状态且受到严格监管的数字货币基础设施,Circle必须保持高度的透明度,并接受来自美国乃至全球各地政府机构的严格监管。作为一家全球性机构,我们极其重视自己作为金融资产管理者所肩负的角色与责任。

对于Circle来说,在纽约证券交易所上市,是我们继续追求最大程度透明度和责任感的体现。我们正在构建我们认为对金融体系至关重要的基础设施,并希望与全球领先的企业和政府携手合作,共同推动这一新型互联网金融体系的建立与发展。作为一家在美国上市的上市公司,我们将继续坚守透明度和责任制的原则,因为我们必须遵守纽约证券交易所对上市公司所规定的各项报告要求、公司治理规范以及其他相关规定。

但最重要的是,如今选择公开上市这一决定,恰恰表明我们正处于 Circle 公司以及整个互联网金融系统发展的重要转折点。我们认为,当前所面临的机会得益于多种有利因素的叠加:成熟的技术基础、明确的监管政策以及巨大的市场需求。这一机遇极具复杂性,它诞生于高度创新的技术与金融服务之间的交汇处。虽然我们对把握住这个机会充满信心,但我们的未来(就像过去一样)依然充满了各种不确定性和风险,我们必须成功应对这些挑战。事实上,我们在这份招股说明书中专门用大量篇幅来描述这些风险,我建议大家仔细阅读“风险因素”部分的内容。通过成为一家上市公司,我们能够让广大公众与我们共同踏上这段旅程。

一种长期性的方法与理念

如果您决定成为 Circle 的投资者,那么深入了解我们创造价值的方式与理念就显得尤为重要了。

我们是从非常长远的角度来规划未来的发展方向的,这一点从我们十多年前就明确提出的创业愿景中就能看出来。我们认为,我们有能力打造一家全新的、具有行业标杆意义的互联网平台企业,这家企业的根基将建立在互联网金融体系之上。同时,我们也坚信,这个新型金融体系的建立还仅仅处于起步阶段而已。

我们认为,我们目前所取得的成就仅仅只是冰山一角;未来依然存在着巨大的发展机遇。因此,我们会在那些具备显著长期增长潜力的领域继续进行投资。鉴于这个市场仍处于发展的初期阶段,市场机会的规模巨大,同时我们也面临着诸多复杂的问题与潜在障碍,所以我们打算在可预见的未来继续加大投资力度,以实现持续的发展。

最重要的是,我们将始终坚守自己的核心使命与价值观,以此作为基础来继续发展并引领这家公司前行。

我们无法被简单地归类到某个固定的框架中

我们总喜欢说,Circle是一家与众不同的公司。如果你去问我们的任何一位投资者或员工,甚至整个市场,我想你也会得到同样的回答。但从公开市场的角度来看,有一点非常重要需要明确——我们不属于任何传统的行业类别或模式之中。

有些人可能认为我们是一家支付公司,有些人则认为我们属于金融机构的范畴,还有人觉得我们更像一家提供消费互联网服务或平台软件的公司。事实上,我们兼具了这些公司的特点。我相信,正是这一点让我们与众不同。

我们正在打造一家互联网平台公司——Circle稳定币网络为消费者和企业提供了一个具有互联网规模覆盖范围的平台与网络服务,并为开发者提供了一套软件协议,使他们能够将这些协议集成到自己的应用程序中。我们的商业模式和技术架构受益于类似其他领先互联网平台的“网络效应”。

与此同时,在不断变化的监管环境下,我们正在建设一套完善的金融市场基础设施。我们始终坚持以合规为首要原则,优先遵守相关法规,并积极与监管机构和政策制定者沟通合作,共同为我们的行业制定合适的监管政策。

这将是一场充满冒险的旅程

如果您打算以投资者或股东的身份加入我们,那么我也想和大家分享一些内容——这些内容我之前已经向我们所有的员工和投资者讲过。

我们所从事的工作并非易事。毕竟,我们正在努力应对一个不断变化的生态系统:在这个系统中,创新技术与那些受到严格监管的传统金融服务相互交织,由此带来了诸多挑战与不确定性。构建一个新的互联网金融体系、为货币和经济活动建立新的基础设施、制定并执行相关政策,同时还要在复杂的全球宏观环境下运营这些基础设施——这一切确实非常困难。这项工作既充满复杂性,也极具挑战性,每天都会给我们带来新的考验。然而,这同时也是一次令人兴奋的金融与技术探索之旅。

我们对未来的互联网金融系统能够为世界带来什么有着清晰的愿景。如果我们能够实现这一愿景,我们认为Circle的投资者将会获得回报,同时人类整体的福祉也会得到提升。

希望你能加入我们的这次冒险之旅!

Jeremy Allaire

在日常工作中,我们经常会遇到以下需求:

  • 把多张图片转成一个 PDF(例如照片、扫描件打包)
  • 把多个 PDF 合并成一个文档

在 macOS 下,这些操作可以通过命令行工具 ImageMagick (magick) 和 pdfunite 来完成,非常高效。下面就一步步介绍。

1. 安装工具

Homebrew 安装 ImageMagick

1
brew install imagemagick

Homebrew 安装 pdfunite(属于 poppler 工具集)

1
brew install poppler

安装完成后,命令 magick 和 pdfunite 就可以使用了。

2. 图片转 PDF

单张图片转 PDF

1
magick input.jpg output.pdf

多张图片合并成一个 PDF

1
magick image1.jpg image2.png image3.tif output.pdf

批量转换当前目录下所有图片

1
magick *.jpg output.pdf

这样,所有图片会按文件名顺序合并为多页 PDF。

💡 如果需要控制清晰度或文件大小,可以加参数:

1
magick -quality 85 -resize 1024x1024 input.jpg output.pdf

3. 合并 PDF

方法一:用 ImageMagick(适合临时需求)

1
magick file1.pdf file2.pdf merged.pdf

不过要注意:ImageMagick 会把 PDF 渲染为图片后再导出,这意味着:

  • 文本会变成不可复制的图片
  • 文件体积可能变大
  • 清晰度依赖 -density 参数(默认 72 DPI,推荐 150 或 300)

例如:

1
magick -density 150 file1.pdf file2.pdf merged.pdf

方法二:用 pdfunite(推荐,保留文本/矢量)

1
pdfunite file1.pdf file2.pdf file3.pdf merged.pdf

特点:

  • 无损合并,保留原有的文字、链接、矢量图形
  • 速度快,文件大小几乎不变
  • 更适合正式文档处理

4. 总结

图片转 PDF:用 magick,简单高效

PDF 合并:

  • 临时/不在乎文本 → magick
  • 正式/保留可编辑文本 → pdfunite

两者配合,可以解决绝大多数日常 PDF 处理需求。

N多年前,一个设计师选址的败笔,成就了世界建筑史上的奇观——比萨斜塔。曾经不能正常使用的建筑原有功能,在N多年后因飞来之笔而附加值大增,在旅游收入中名胜大噪,成为当地的支柱产业。于是大地对斜塔说,看看,不是我的地基的问题吧,我早就说过,如果是作为建筑的话,你早就淹没在哥特式、巴特式的建筑群中名不见经传了。

N+N年后,一个科学巨檠在比萨斜塔上玩康乐球,一不小心一个康乐球从手中滑脱,径直落下塔底,哇塞,科学史上的伟大定律诞生了!定格,闪光灯、镁光灯、碘钨灯闪烁,帅呆!斜塔也因此独获殊荣、大红大紫。

N+N+N年后,一群跛子入主斜塔,手里拿着铅球、康乐球、波波球、乒乓球,也试图诞生一个伟大的定律或别的什么哲理。或由于底子太薄,或由于悟性太差,没玩出什么高科技、高品位来,闲的无聊,改玩梭哈。你输我赢之间,难免东家长西家短、口舌是非多。

一日,跛子甲说,不知你们发现没有,这块宝地,是我有生以来走过的最平坦的一块福地?跛子乙说,纯粹胡说八道,这个地方是我走过的最崎岖的山路。争论未果,曲终人散。第二天跛子甲与跛子乙因先来后到的原因调换了座位,跛子甲说,今天运气真背,这块地方是我走过的最凹凸不平的糟糕之地。跛子乙说,你脑子又进水了,这块地方才真正是我走过的最幸福的康庄大道。

跛子,就是跛子。不会因穿上什么贵族的华丽霓裳,而变得贵族和有文化;也不会因为练就了拜仑的体魄,就具备了诗人的斗志和豪情;更不会因占据了什么福地、站在了巨人的肩膀上,就成就什么伟大的定律。

于是乎,斜塔对大地说,我是正的,你才是斜的。跛子说,梭哈不成,我们改玩麻将,看看谁笑到最后。

我们站在地平线,遥望曾经壮观的比萨斜塔,看着它一点点的倒下……

0%