☰
3PC三阶段提交协议详解:从2PC痛点、超时状态机到脑裂兜底实践
2026/10/17 4:27:39 网站建设 项目流程

做分布式系统这几年,事务一致性这块我算是反复踩坑。3PC(Three-Phase Commit Protocol,三阶段提交协议)是分布式事务经典协议里绕不开的一个,网上讲原理的文章很多,但大多停在“三个阶段画个图”的层面,看完只知道大概,一旦想落地就发现细节全是坑。这篇就顺着我自己从2PC到3PC的理解过程,把这个协议掰开揉碎讲清楚,也把那些只看图容易忽略的问题补上:超时机制、状态机、脑裂风险、参数配置,该讲的都讲一遍。

先说这个内容适合谁看。如果你在做分布式数据库、微服务事务中间件,或者正在准备分布式系统相关的技术面试,那这篇就是给你写的。不需要你之前对3PC有多深的理解,会2PC就够用了。我尽量用大白话把协议的设计逻辑讲明白,也尽量把我实操中踩过的坑说清楚。

1. 为什么需要3PC:先看2PC到底哪里不靠谱

1.1 2PC的流程回顾

2PC全称Two-Phase Commit Protocol,翻译过来叫二阶段提交协议。整个流程就是“一阶段准备,一阶段提交”:

第一阶段,协调者向所有参与者发送prepare请求。参与者收到后,在本地执行事务操作,把undo日志(用于回滚)和redo日志(用于故障恢复)写到磁盘,但不会真正提交,也不会对外部释放锁资源。一切就绪后,参与者回复yes或者no。

第二阶段,协调者收集所有参与者的回复。如果所有人都回了yes,协调者就发送commit指令,参与者收到后正式提交并释放资源。但凡有一个人回了no,或者协调者没能在超时时间内收齐回复,协调者就发送abort指令,所有参与者回滚事务并释放资源。

这个流程用饭局来类比特别贴切:第一阶段是问所有人“晚上能不能来吃饭”,每个人都说能来之后开始备菜,但大家约好先不上桌;第二阶段是确认人数到齐后统一喊“开席”,或者人不够就直接“散伙”。2PC的直观好处是简单,理解门槛低,实现也相对容易。但它的问题恰恰出在这个看似简单的流程里。

1.2 2PC的三大痛点

第一个痛点是同步阻塞。参与者执行完事务操作后,会一直持有行锁、表锁或者其他资源锁,等待协调者的最终指令。如果协调者这个时候宕机了,参与者根本不知道事务到底该提交还是该回滚,只能继续等待。锁不释放,后续所有访问这些数据的事务都会被卡住,整个系统的吞吐量可能直接掉到接近零。这不是理论上的风险,现实中我见过协调者进程异常退出后,底层数据库锁一堆事务全部阻塞的情况,处理起来非常狼狈。

第二个痛点是协调者单点。协调者在2PC里是唯一的决策者,整个事务能否提交完全取决于它是否正常。协调者一旦宕机,没有其他节点能取而代之做出决策,参与者就陷入无限等待。虽然可以对协调者做高可用部署,但主备切换本身也需要时间,而且在切换过程中事务的最终状态依然是不确定的。

第三个痛点是数据不一致窗口。协调者在第二阶段发出commit指令之后、参与者还没全部收到之前,如果协调者宕机了,就会有一部分参与者收不到commit指令,导致部分库已经提交、部分库没有提交,全局数据处于不一致状态。更麻烦的是,协调者宕机后,它之前发出的commit决策可能没有持久化,重启后连自己都不知道当时发过什么指令,不一致问题就变成了悬案。

这三个痛点加起来,意味着2PC在追求强一致的同时,牺牲了可用性和容错性。分布式系统里“协调者挂了全链路卡死”这种场景,用2PC是没法根治的,只能靠外部人工介入或者额外的高可用机制去缓解。

1.3 3PC的设计思路

3PC就是针对2PC的上述痛点做改进的。3PC全称Three-Phase Commit Protocol,三阶段提交协议。设计者的思路很明确:既然2PC第二阶段“一锤定音”太脆弱,那就把提交过程拆得更细,让每个参与者提前表达意向、提前做好准备,再给参与者也加上超时机制,让它们不至于无限死等。

具体做了两件事。第一件事是在2PC之前加了一个CanCommit探测阶段,先问所有参与者“这个事务你们能不能提交”,如果能,再进入预提交阶段;如果不能,整个事务直接终止,省掉了无谓的日志写入和锁操作。第二件事是引入超时机制和状态机,协调者和参与者都有各自的超时处理策略,尤其是参与者在收到预提交指令之后如果超时未收到最终指令,默认执行提交。这两件事组合起来,就是为了解决2PC的阻塞和单点问题。

但注意,3PC并没有完全消除不一致风险。它在消除阻塞的同时换了另一种风险形式,也就是所谓的脑裂问题。这个后面单独拎出来讲,因为这是3PC最容易被忽略也最关键的地方。

2. 3PC协议完整流程拆解

2.1 第一阶段:CanCommit询问阶段

CanCommit阶段是3PC相对2PC最直观的变化。协调者向所有参与者发送canCommit消息,消息里带上事务ID和事务内容。参与者收到canCommit后,不会立即执行事务操作,只是检查一下自己当前的状态:本地数据库是否正常、是否有足够的锁资源、事务内容是否可以被执行等等。检查通过就回复yes,不行就回复no。

这个设计的好处很实际。2PC的第一阶段会让参与者直接执行事务并写日志,但如果事务本身注定要失败(比如某个参与者已经处于异常状态),这些操作全部都是白做的,还占用了锁资源和日志空间。3PC先在CanCommit阶段用最小的代价探明所有参与者的状态,能提交再往下走,不能提交就直接终止,整体开销小很多。

协调者在这个阶段如果收到的全是yes,就进入预提交阶段;如果收到了任何一个no,或者等待超时,就发送abort指令结束事务,此时参与者的本地事务还什么都没做,回滚代价极低。

这个阶段可以说是在“动手干活前先开个碰头会”,确认大家都有意愿和时间,再去准备饭菜。从资源利用率角度看,比2PC那种“先折腾半天再散伙”要合理得多。

2.2 第二阶段:PreCommit预提交阶段

PreCommit是3PC真正开始干活的地方。协调者收到所有参与者的yes回复后,向参与者发送preCommit指令。参与者收到preCommit后,开始执行本地事务操作:写入undo日志、redo日志、加锁、做数据变更,但这些变更对外部仍然不可见,不会真正提交。

参与者完成本地操作后,需要把日志落盘,然后向协调者回复ACK。注意,这个ACK必须是在日志真正持久化之后才能发出。如果先回ACK再写日志,参与者一旦在回ACK之后宕机,重启后可能丢失事务状态,后面就无法决定到底该提交还是该回滚。日志落盘是3PC能够从故障中恢复的基石,这个顺序在工程实现里绝对不能反。

协调者在PreCommit阶段等待所有参与者的ACK。如果全部收到,协调者会进入最终提交阶段;如果超时没有收齐,协调者会发送abort指令,回滚整个事务。这里有一个和参与者的超时行为配合着看才有意义的设计:协调者在PreCommit超时后选择abort,而参与者在PreCommit超时后却可能选择提交,两者之间有一个微妙的风险点,放在后面“脑裂问题”一节细讲。

2.3 第三阶段:DoCommit最终提交阶段

当协调者收到所有参与者的preCommit ACK之后,事务在逻辑上已经具备了提交条件,协调者向所有参与者发送doCommit指令。参与者收到doCommit后,正式提交本地事务,释放锁资源,让数据变更对外部可见,并向协调者回复最终确认。

协调者收到所有参与者的确认后,整个事务宣告完成。如果某个参与者没回确认,协调者通常会重试doCommit,因为此时网络上可能只是暂时丢包,参与者只要状态还在,重发一次指令就能让它完成提交。

注意一个细节:DoCommit阶段发出之后,协调者正常情况下不会反过来再发abort了。因为在PreCommit阶段所有参与者都已经确认了各自能提交,此时全局决策已经偏向提交,强行中止反而更容易制造不一致。

2.4 中止路径与消息全景

把3PC的消息路径完整过一遍,你会对协议的设计有一个全局观:

  1. 协调者发canCommit给所有参与者。
  2. 参与者返回yes或no。
  3. 若有人返回no,协调者发abort,事务中止。
  4. 若全部返回yes,协调者发preCommit给所有参与者。
  5. 参与者执行本地事务操作,日志落盘后返回ACK。
  6. 协调者若超时未收齐ACK,发abort,事务中止。
  7. 协调者若收齐ACK,发doCommit给所有参与者。
  8. 参与者收到doCommit后提交事务,并返回提交确认。

中止路径主要有三条:CanCommit阶段有人明确拒绝、CanCommit阶段超时、PreCommit阶段协调者等待ACK超时。每条中止路径的清理代价不一样,CanCommit阶段的中止几乎不消耗资源,PreCommit阶段的中止则要走一遍undo日志回滚。理解了这些路径,就能明白3PC在“尽量避免无谓事务操作”这件事上确实比2PC做得更好。

3. 超时机制与状态机:3PC真正的改进所在

3.1 协调者的超时策略

3PC给协调者也加了超时状态,这是很多人读协议时容易漏掉的部分。协调者在CanCommit阶段等待所有yes/no回复时,如果超时,直接abort。这好理解,因为还没进入具体执行阶段,中止代价最低。

更有意思的是协调者在PreCommit阶段的超时策略。协调者向参与者发出preCommit之后,开始等待ACK。如果超时没有收齐ACK,协调者会发送abort指令。为什么这里不是继续等待,也不是直接doCommit?因为参与者可能有一部分根本没收到preCommit,或者收到了但本地执行失败,这几类情况都意味着“我不确定所有参与者都准备好了”。此时如果协调者强行doCommit,那部分没准备好的参与者就只能在超时后自行决定去向,反而更容易产生不一致。所以协调者在PreCommit超时后abort,是为了把决策权收在自己手里,尽量减少参与者“自由发挥”的概率。

但这里注意,协调者发abort是尽了全力去通知,通知不到的地方依然可能出问题。超时机制降低了网络故障的影响范围,却没有彻底消除它。

3.2 参与者的超时策略

参与者的超时策略是3PC最核心的改进,甚至可以这么说:没有参与者的超时决策,3PC就只是多了一个阶段的2PC而已。

参与者在不同阶段等待时,超时后的动作不一样:

在等待canCommit结果时超时,参与者本地事务还没开始执行,唯一要做的就是清理掉事务的临时状态,然后中止,这是安全的。

在收到preCommit之后、等待doCommit或abort指令时超时,参与者默认执行提交。这个设计是3PC消除同步阻塞的关键:参与者不再无限等协调者,它自己在一定条件下可以做出提交决策。2PC的参与者被锁住就是因为没有这个能力,3PC给了这个能力,同时把决策条件卡在“收到了预提交指令”这一道门槛上。

参与者在收到canCommit但还没收到preCommit时超时,也会中止。所以在参与者状态机里,超时行为不是一个动作,而是两套动作,关键在于当前处于哪个状态。

3.3 参与者状态与协调者状态总览

把3PC的状态机拉通看会更清楚。协调者状态大致如下:

协调者状态触发动作超时结果
INIT发起canCommitabort
WAITING_CAN_COMMIT收集yes/noabort
WAITING_PRE_COMMIT_ACK等待ACKabort
WAITING_DO_COMMIT_ACK等待提交确认重试doCommit

参与者状态则更复杂一些:

参与者状态触发动作超时结果
INIT收到canCommit中止
CAN_COMMIT_RESPONDED收到preCommitabort
PRECOMMITTED等待doCommit或abort本地提交
COMMITTED事务完成无需处理

注意参与者状态里,从PRECOMMITTED开始,超时结果就是“本地提交”。这正是3PC和2PC在行为上的分水岭。收到preCommit意味着参与者已经执行完本地事务并且日志落盘,此时如果一直等不到协调者指令,继续等待只会白白占用锁资源,协议设计决定让它提交,把锁释放掉,让整个系统继续往前走。

3.4 一个容易蒙圈的问题:参与者超时后为什么默认提交

初学者经常会问一个问题:参与者超时后默认提交,那万一协调者本来想abort怎么办?这不是更容易不一致吗?

要回答这个,得站在协议设计者的视角去理解。参与者在收到preCommit之前,它是不会主动提交的。而能进入PreCommit阶段,说明协调者已经收到了所有参与者在CanCommit阶段的yes回复,也就是说,所有参与者都确认过“这个事务我可以执行并且愿意执行”。到了PreCommit阶段,全局的行事逻辑已经强烈偏向“提交”了。

这就像饭局里所有人已经明确说了“我会来”,此时主办方逐一通知“准备开席了”,如果你迟迟没等到饭桌上菜的通知,你是默认大家都在等你开吃呢,还是默认大家散伙了?在协议设计的语境里,提交是更有希望的选项。从可用性角度看,如果超时后默认abort,那么一次临时网络抖动就可能导致已经执行完的事务全部回滚,之前写日志、加锁、做数据变更的开销全部白费,系统的可用性会大打折扣。

所以3PC在PreCommit后的超时决策是“偏向提交”的,拿一致性上的潜在风险换可用性和实时性。这个取舍没有绝对对错,属于典型的CAP权衡。但代价就是脑裂问题,这是3PC的固有缺陷,只能在工程层面想办法兜底。

4. 工程实现、参数选型与适用场景

4.1 状态机伪代码实现

为了让你能直接上手理解,我写一个简化版的协调者状态机伪代码。实际工程里肯定要复杂得多,但核心骨架就是这样:

// 协调者状态机(伪代码,使用异步消息总线) class Coordinator { constructor(participants, transactionId) { this.participants = participants; this.transactionId = transactionId; this.state = 'INIT'; } async run() { // Phase 1: CanCommit this.state = 'WAITING_CAN_COMMIT'; let canCommitResults = await this.broadcast('canCommit'); if (canCommitResults.includes('no') || canCommitResults.length < this.participants.length) { await this.broadcast('abort'); this.state = 'ABORTED'; return 'aborted'; } // Phase 2: PreCommit this.state = 'WAITING_PRE_COMMIT_ACK'; let preCommitAcks = await this.broadcast('preCommit'); if (preCommitAcks.length < this.participants.length) { await this.broadcast('abort'); this.state = 'ABORTED'; return 'aborted'; } // Phase 3: DoCommit this.state = 'WAITING_DO_COMMIT_ACK'; await this.broadcast('doCommit'); this.state = 'COMMITTED'; return 'committed'; } async broadcast(messageType) { let acks = []; for (let participant of this.participants) { try { let ack = await this.sendWithRetry(participant, messageType); acks.push(ack); } catch (e) { // 记录异常,等待超时统一处理 console.error(`participant ${participant} failed on ${messageType}`, e); } } return acks; } }

参与者这边,超时状态机更需要仔细设计。核心是:收到preCommit之后进入PRECOMMITTED状态,同时启动一个自提交定时器,超时未收到doCommit就自动提交。

// 参与者状态核心逻辑(伪代码) class Participant { constructor() { this.state = 'INIT'; this.commitTimer = null; } handleMessage(message) { switch (message.type) { case 'canCommit': this.state = 'CAN_COMMIT_RESPONDED'; // 检查本地事务可行性 this.reply('yes'); break; case 'preCommit': // 写undo/redo日志,先落盘再回复 this.writeTransactionLog(); this.reply('ack'); this.state = 'PRECOMMITTED'; // 关键:启动超时自提交定时器 this.commitTimer = setTimeout(() => { this.commitLocally(); }, this.preCommitTimeout); break; case 'doCommit': this.commitLocally(); break; case 'abort': this.rollbackLocally(); break; } } }

这里要特别强调:参与者在收到preCommit后,必须立即启动超时定时器,而不是等“感觉超时了”再去判断。工程实现上,定时器要与消息收到时间绑定,最好用单调时钟(monotonic clock)而不是系统墙上时钟,避免系统时间调整导致超时误判。

4.2 超时参数怎么配

超时参数是3PC落地里最容易踩坑的地方。我给一套基于实际经验的配置思路,不是绝对标准,但至少不会犯大错。

第一层是RPC超时。协调者调用参与者接口的底层超时时间,一般设500ms到1s,并且配合2到3次重试。这个值如果设得太短,正常的业务高峰期RPC延迟稍微一抖动就会触发重试甚至失败;设得太长,又会让整体事务的响应时间被一个慢节点拖死。

第二层是阶段超时。CanCommit和PreCommit阶段等待所有参与者响应的总超时,一般设3s到10s。这个值必须比底层RPC超时加上重试次数的总和要大,否则会出现“RPC还在重试,阶段已经超时”的错乱情况。同时,也要比事务本身可能执行的SQL耗时大几倍。如果业务SQL要跑2s,阶段超时至少要留到5s以上。

第三层是全局事务超时。协调者从发起canCommit到最终拿到所有确认的总时间上限,一般设30s到60s。超过这个时间,无论当前处于哪个阶段,都要强制中止事务并触发补偿逻辑。这是为了防止某个参与者长时间卡死导致事务上下文不释放。

实践里有个重要的前提:先测清楚全网99%的RTT延迟,再定超时参数。我记得有一次把阶段超时设成1s,结果高峰期RPC偶尔超过1s,大量事务在PreCommit阶段被误判abort,业务方疯狂反馈“事务莫名其妙回滚”。后来把阶段超时调成5s,加上重试熔断,情况才平稳。超时不是越小越“灵敏”,而是越大越“容忍”,要根据实际延迟分布来做。

4.3 3PC适合用在哪些场景

3PC在实际生产里的应用范围没有2PC那么广,但它有自己适合的土壤。

适合场景是参与者数量少、网络相对稳定、业务方对可用性要求高的内部系统。参与者数量少意味着broadcast的开销可控;网络相对稳定意味着脑裂发生的概率低;可用性要求高意味着参与者超时后自动提交的“偏向提交”策略是有价值的。

反过来,如果需要严格强一致,比如金融对账、订单扣款这类不能接受任何不一致的业务,3PC就不合适。这种场景宁可选择2PC加人工补偿,或者直接用共识协议来保证一致性。共识协议能做到超过半数节点存活就能继续决策,容错性比3PC的“全体确认再提交”要好。

另外,如果你的系统已经有稳定可靠的协调者高可用方案,那3PC的优势会被削弱,因为它的核心价值在于“不依赖协调者单点也能让参与者超时自救”。协调者已经高可用了,2PC配合协调者切换可能更省事。

4.4 与2PC和共识协议的横向对比

把3PC放在分布式一致性方案的坐标系里看,它的位置更清晰。我做了一张简表:

维度2PC3PC共识协议(如Paxos/Raft)
阶段数23通常2轮选举
参与者自主决策不具备PreCommit后超时可自提交具备,按多数派提交
阻塞问题严重基本消除基本消除
协调者单点严重有缓解有Leader选举机制
脑裂风险有有有,但通过多数派降低
一致性强度可能不一致可能不一致通常被认为更稳定
工程复杂度低中高

这张表能看出一个事实:3PC不属于“用更复杂的方式解决一切问题”的银弹,它只是把2PC的同步阻塞和单点问题做了缓解,代价是状态机更复杂、脑裂风险需要额外兜底。

为什么实际系统里3PC用得少?一是因为状态机复杂,每一步都有超时分支,排查问题的难度大;二是因为Paxos/Raft这类共识协议在多数派容错、Leader选举方面的表现更成熟,能提供比3PC更扎实的一致性保证。3PC更像是一个承上启下的理论节点,帮我们理解“为什么引入多数派”“为什么需要超时决策”这些问题。

5. 常见问题与踩坑记录

5.1 脑裂问题不是传说

3PC的脑裂问题值得单独拿出来讲,因为它是协议最经典也最难处理的地方。

先描述一个具体场景:协调者C在PreCommit阶段向参与者A和B都发送了preCommit指令。A收到了,B没有收到。协调者C在等待ACK时发现B的ACK迟迟不来,超时后决定abort,并把abort发给了A。A收到abort后回滚了事务。但B由于没收到preCommit,一直处于等待状态,直到超时之后,B依据协议规则默认提交。结果就变成了A回滚、B提交,全局数据不一致。

这就是脑裂。本质上,3PC赋予参与者“超时自提交”的能力之后,参与者在网络分区中可能无法感知协调者已经决定abort,于是做出和协调者不同的决策。这个风险在2PC里是不存在的,因为2PC参与者根本没有自主决策能力;但2PC的代价是参与者会无限阻塞。

工程上能做哪些兜底?我实践下来有几种思路:

一是收紧参与者范围,让参与者和协调者都在同一个可靠网络域内,降低网络分区概率。跨地域部署时尽量不要直接跑3PC,把跨地域的部分拆成别的机制。

二是引入第三方仲裁。参与者超时后不自作主张提交,而是去询问一个仲裁节点(比如基于共识协议的元数据服务)确认协调者是否还活着、是否已经发出过abort。如果仲裁节点判定协调者失联,参与者就可以按照协议提交。

三是使用提交令牌。协调者在发出preCommit时附带一个递增的提交令牌,参与者只有确认令牌是最新的才允许提交。协调者在发出abort后再发一个新的吊销令牌,这样超时的参与者如果发现自己持有的令牌已过期,就不会提交。

这几种手段都不能百分之百消除脑裂,但能把窗口缩小到工程上可接受的范围。设计分布式事务时,脑裂是绕不开的底层问题,提前想清楚应急预案比临时救火重要百倍。

5.2 日志和持久化必须提前做

3PC能不能从故障中恢复,完全取决于日志和持久化做得够不够扎实。

参与者在收到preCommit之后、回复ACK之前,必须先把undo和redo日志落盘。如果参与者先回了ACK再写日志,一旦它在这个间隙宕机,重启后日志丢失,不知道该提交什么,该回滚什么,整个事务就变成了悬案。协调者在发出doCommit之前,也要把当前事务状态持久化到本地存储。否则协调者宕机重启后,它自己都不知道事务进行到了哪个阶段,更没法去引导参与者完成后续操作。

我见过一个项目因为日志没有先落盘,在参与者宕机重启后出现了部分数据丢失。排查了很久才发现,日志写入和ACK回复的顺序反了。这个顺序在文档里就是一句话,但落地时一旦搞反,代价非常高。

另外,日志要带完整的事务ID、阶段序号、参与者列表和最终决策。恢复时,协调者通过事务ID能找到这个事务到底处于哪个状态,再决定对参与者发送doCommit还是abort。如果日志里连参与者列表都没存,恢复时想通知都不知道通知谁。

5.3 超时设太短的代价

超时参数不是越小越好,这个坑我前面提过,但值得单独展开。

有一段时间我在一个内部系统里用3PC跑跨节点数据同步,因为担心故障恢复太慢,把阶段超时设成了1s。结果业务高峰期RPC延迟偶尔超过1s,大量事务在PreCommit阶段被误判abort。业务方反馈“事务无规律回滚”,一开始我还不相信是超时问题,因为从协调者日志看,确实有很多参与者没回ACK。后来抓了参与者的延迟数据,发现99%延迟只有200ms,但99.9%延迟超过2s。也就是说,超时设成1s,等于把0.1%的正常慢请求全部当成故障处理了。

后来我调整策略:先统计全网99%延迟,把超时设为这个值的5到10倍,再给业务SQL的执行时间留出余量。比如99%延迟是200ms,业务SQL最慢2s,阶段超时就设成5s左右。这样既不会因为小抖动误杀事务,也不会让普通故障拖太久。

还有一点,超时参数最好做成动态可调,通过配置中心下发,而不是写死在代码里。因为你永远不知道线上流量比你预期的大多少,动态调整比重启发布快得多。

5.4 高频问题速查

最后整理一个高频问题速查表,面试和自测都能用:

问题回答要点
3PC和2PC最大区别是什么?多了一个CanCommit探测阶段,且参与者具备PreCommit后的超时自主决策能力
PreCommit后参与者超时怎么办?按协议默认提交,这是3PC消除阻塞的关键
3PC能保证强一致吗?不能,存在脑裂导致的不一致窗口
协调者在PreCommit超时后会怎么做?发送abort,放弃本次事务
3PC为什么实际用得少?状态机复杂,脑裂问题处理成本高,多数场景用共识协议或2PC加补偿
3PC和共识协议的核心区别?3PC偏重在全体确认后提交,共识协议按多数派决策,容错能力更强

看到这里你会发现,3PC的难点不在三个阶段的流程本身,而在超时和状态机怎么设计,以及脑裂问题怎么兜底。这些才是实际工程里会花大量时间的地方。

我自己在实际研究时的感受是:3PC的价值不在“拿来主义”,而在于它把分布式事务的权衡讲透了——加一个探测阶段可以降低无谓开销,引入超时机制可以消除阻塞,但给参与者自主决策权的同时也必然引入新的不一致风险。如果现在让我设计一个分布式事务中间件,我一定会把3PC的状态机、日志、超时分离的设计思路拿过来,再用共识协议或者补偿机制去填脑裂的坑。至少在做技术选型和技术面试时,能把2PC、3PC、共识协议放在同一条线上比较,整个思路会特别清晰,这个收益比单纯背协议流程大得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询