分布式事务2PC详解:原理、实现与生产环境避坑指南

1. 从一个真实的数据不一致说起

去年帮一个做电商的朋友排查线上问题,场景很典型:用户下单后,订单库写入了记录,但库存库扣减失败,结果订单显示"已支付"却发不出货。查日志发现,订单服务和库存服务各自连了独立的数据库,下单逻辑里先调订单服务、再调库存服务,中间任何一步网络抖动或者服务重启,整个链路就断了。这不是代码写得烂,而是典型的跨库操作缺少事务保障。

单库单表的时候,我们靠数据库自身的ACID就能搞定,一个BEGIN到COMMIT之间要么全成功要么全回滚。可一旦业务拆成了多个独立服务、每个服务有自己的库,本地事务就管不到别人家的库了。这时候就需要分布式事务出场,而2PC(Two-Phase Commit,两阶段提交)是其中最经典、最基础的一种解决方案。

这篇内容适合谁看?如果你正在做微服务拆分、遇到过跨服务数据不一致、或者面试被问到分布式事务答不上来,那这篇就是写给你的。我会从2PC的核心原理讲起,拆解它的两个阶段到底在干什么,然后给出可落地的实现思路和代码骨架,最后重点聊聊它在生产环境里那些"文档不会告诉你"的坑。全程用大白话加实际场景,不堆砌学术词汇。

需要先说明一点:2PC不是一个具体的框架或中间件,它是一种协议规范,数据库层面(如XA规范)、消息队列、以及各种分布式事务框架都实现了它。理解协议本身,比记住某个框架的API重要得多。

2. 2PC的两个阶段到底在做什么

2.1 角色划分:协调者与参与者

2PC里有两个核心角色。协调者(Coordinator)是整个事务的"总指挥",负责发起投票、收集结果、下达最终指令。参与者(Participant)是真正执行数据操作的那些节点,比如订单库、库存库、账户库。

打个比方,协调者就像婚礼的司仪,参与者是新郎新娘双方家长。司仪先问"都准备好了吗",所有人回答"准备好了",司仪才宣布"礼成";只要有一个人说"不行",司仪就宣布"取消"。这个"先问再定"的过程,就是2PC的精髓。

协调者本身可以是一个独立的服务,也可以是发起事务的那个业务服务。参与者就是各个资源管理器(数据库、消息中间件等)。关键点在于:协调者必须能可靠地记录事务状态,否则它自己挂了,整个事务就悬空了。

2.2 第一阶段:准备阶段(投票阶段)

准备阶段的核心动作是:协调者向所有参与者发送prepare请求,每个参与者执行事务操作但不提交,把数据写到undo/redo日志里,锁住相关资源,然后回复"我可以提交"或者"我不行"。

这里有个特别容易被忽略的细节:参与者回复"可以提交"之后,它其实已经把该做的活都干完了,只是差最后一步commit。数据已经写进了日志,资源已经锁住了,就等协调者一声令下。这意味着如果参与者回复yes之后协调者挂了,这个参与者会一直持有锁,其他事务碰这些数据就会被阻塞。

我用一个简化的时序来说明:

协调者 -> 参与者A: prepare 协调者 -> 参与者B: prepare 参与者A: 执行操作,写日志,锁资源,回复 yes 参与者B: 执行操作,写日志,锁资源,回复 yes

注意,参与者执行的操作是"预执行",比如扣库存这个动作,在准备阶段就已经把库存数从100改成了99,只是这个修改还没真正落盘生效(取决于具体实现,XA里是写入了但未提交)。

2.3 第二阶段:提交阶段(执行阶段)

如果所有参与者都回复yes,协调者向所有人发送commit,参与者正式提交,释放锁,回复ack。如果任何一个参与者回复no,或者超时没回复,协调者向所有人发送rollback,参与者用undo日志回滚,释放锁。

第二阶段是"一锤定音"的阶段,没有商量余地。协调者根据第一阶段的投票结果做决定,这个决定一旦做出就不可撤销。参与者必须无条件执行,哪怕它自己觉得不该提交。

这里有个反直觉的点:如果协调者在第二阶段发送commit的过程中挂了,那些已经收到commit的参与者提交了,没收到commit的参与者还锁着资源等指令。等协调者恢复后重新发送,最终大家还是会一致。但如果协调者永久性挂了,没收到指令的参与者就会一直等下去,这就是2PC最被人诟病的阻塞问题。

3. 为什么2PC能保证一致性:从日志和锁说起

3.1 undo/redo日志是2PC的命根子

参与者能在准备阶段"先干活不提交",靠的就是日志。以数据库为例,当执行UPDATE inventory SET count = count - 1 WHERE id = 1时,数据库会先把"原值100"写进undo日志,把"新值99"写进redo日志,然后修改内存中的数据页。此时事务处于"已准备但未提交"状态。

如果协调者说commit,数据库就把redo日志刷盘,标记事务已提交。如果协调者说rollback,数据库就用undo日志把数据改回100。整个过程,日志保证了无论发生什么,数据都能恢复到某个确定状态。

提示:不同数据库对XA的支持程度差异很大。有些数据库在prepare阶段就把redo日志刷盘了,有些则延迟到commit。这直接影响崩溃恢复的行为,选型时一定要查清楚。

3.2 锁机制带来的隔离性代价

准备阶段锁住资源,是为了防止其他事务读到"中间状态"。比如库存已经预扣成99,但事务还没提交,这时候另一个查询进来,它应该看到100还是99?2PC通过锁让其他事务等待,保证它们看到的要么是事务前的100,要么是事务后的99,绝不会是"99但可能回滚"的脏数据。

但这个锁的代价非常大。假设一个事务涉及5个库,准备阶段锁了5个库的相关行,然后协调者因为网络问题卡了30秒,这30秒里所有想操作这些行的事务全部排队。在高并发场景下,这几乎是灾难性的。我见过一个案例,一个大促活动因为一个跨库事务卡住,导致库存表的行锁等待队列飙到几千,整个下单链路雪崩。

3.3 协调者的单点问题

协调者要记录全局事务的状态,通常需要一张transaction_log表或者一个高可用的存储。如果协调者本身是单点,它挂了整个系统就瘫了。所以生产级的2PC实现,协调者必须做高可用,比如用Raft或Paxos选主,或者把状态存到可靠的分布式存储里。

但即便协调者高可用,它和参与者之间的网络分区仍然可能导致问题。经典的"协调者发出commit后网络分区,部分参与者收到部分没收到",这时候系统处于不一致状态,直到网络恢复。2PC本身不解决网络分区,它假设网络最终会恢复。

4. 手写一个2PC骨架:从接口设计到落地

4.1 核心接口定义

要理解2PC,最好的方式是自己实现一个最小骨架。我用Java风格伪代码来演示,其他语言思路一致。

// 参与者接口 public interface Participant { // 准备阶段:执行操作,返回是否就绪 boolean prepare(TransactionContext ctx); // 提交阶段 void commit(TransactionContext ctx); // 回滚阶段 void rollback(TransactionContext ctx); } // 协调者 public class Coordinator { private List<Participant> participants; private TransactionLog log; // 持久化事务状态 public boolean execute(TransactionContext ctx) { // 第一阶段:准备 List<Participant> ready = new ArrayList<>(); for (Participant p : participants) { try { if (p.prepare(ctx)) { ready.add(p); } else { // 有人说不,全部回滚 rollbackAll(ready, ctx); return false; } } catch (Exception e) { rollbackAll(ready, ctx); return false; } } // 记录全局状态为"准备提交" log.write(ctx.txId, "PREPARED"); // 第二阶段:提交 for (Participant p : ready) { p.commit(ctx); } log.write(ctx.txId, "COMMITTED"); return true; } }

这个骨架看起来简单,但魔鬼在细节里。比如prepare返回true之后,参与者必须保证即使自己重启,也能在收到commit时正确提交。这就要求参与者把"已准备"状态持久化,而不是放在内存里。

4.2 参与者如何实现prepare的持久化

以库存服务为例,prepare阶段不能直接UPDATE inventory SET count = count - 1,因为这样改了数据但事务没提交,其他事务会读到脏数据(除非用行锁)。更稳妥的做法是引入一张事务中间表:

-- 事务记录表 CREATE TABLE tx_record ( tx_id VARCHAR(64) PRIMARY KEY, status VARCHAR(16), -- PREPARED / COMMITTED / ROLLED_BACK created_at TIMESTAMP ); -- 库存预扣表 CREATE TABLE inventory_freeze ( tx_id VARCHAR(64), product_id BIGINT, freeze_count INT, PRIMARY KEY (tx_id, product_id) );

prepare时,先往tx_record插入一条PREPARED记录,再往inventory_freeze插入冻结数量,同时扣减可用库存。commit时,把tx_record状态改成COMMITTED,删除冻结记录。rollback时,把冻结的数量加回可用库存,删除冻结记录,状态改成ROLLED_BACK。

这种"冻结"思路比直接锁行更可控,因为它把锁的粒度从数据库行锁转移到了业务层面的记录,减少了数据库层面的锁竞争。很多实际生产系统就是这么干的。

4.3 协调者的状态机与恢复逻辑

协调者需要一张全局事务表:

CREATE TABLE global_tx ( tx_id VARCHAR(64) PRIMARY KEY, status VARCHAR(16), -- BEGIN / PREPARING / PREPARED / COMMITTING / COMMITTED / ROLLING_BACK / ROLLED_BACK created_at TIMESTAMP, updated_at TIMESTAMP );

协调者启动时,要扫描global_tx表,找出所有处于中间状态(PREPARING、PREPARED、COMMITTING、ROLLING_BACK)的事务,根据状态决定是继续提交还是继续回滚。这就是崩溃恢复。

比如一个事务状态是PREPARED,说明所有参与者都准备好了,协调者应该继续发commit。如果状态是PREPARING,说明还没收集完投票,协调者应该发rollback(因为不确定是否所有人都准备好了,保守回滚)。

注意:恢复逻辑里有个坑——如果协调者在PREPARED状态挂了,恢复后继续commit,但某个参与者其实在prepare时回复的是no(只是协调者还没记录),那就会导致部分提交。所以协调者必须在收到所有yes之后才写PREPARED状态,这个写入顺序不能错。

5. 生产环境里2PC的那些坑

5.1 性能问题:为什么大厂都在逃离2PC

2PC的性能瓶颈主要来自三个方面:锁持有时间长、网络往返多、协调者吞吐受限。

锁持有时间从prepare开始到commit结束,中间包含网络传输、协调者决策、日志写入。一个跨3个库的事务,锁持有时间可能是单库事务的5到10倍。网络往返方面,2PC至少需要两轮RPC(prepare一轮,commit一轮),如果加上超时重试,可能更多。协调者吞吐方面,所有事务都要经过协调者,它很容易成为瓶颈。

我做过一个粗略的压测:单库事务QPS能到8000,换成2PC跨两个库,QPS直接掉到800左右,差了10倍。这还是在网络状况良好的情况下。所以2PC适合低频、短事务、对一致性要求极高的场景,比如金融转账、订单创建,不适合高并发秒杀。

5.2 超时与重试:最容易被忽略的细节

2PC里超时处理极其关键。协调者发prepare后等多久?参与者回复慢怎么办?commit阶段参与者没响应怎么办?

我的经验是:prepare超时时间要短(比如3秒),因为参与者如果正常,准备动作很快;commit超时时间可以长一些(比如10秒),因为commit是最终决定,必须确保送达。超时后不能简单重试,因为重试可能导致重复提交。

正确的做法是:协调者记录每次请求的发送状态,超时后先查询参与者的实际状态(通过tx_id查tx_record表),如果参与者已经commit了,就跳过;如果还是PREPARED,就重发commit。这叫幂等重试。

参与者这边也要做幂等:收到commit时,先查tx_record,如果已经是COMMITTED,直接返回成功,不重复执行。

5.3 空回滚与悬挂:两个隐蔽的陷阱

空回滚是指:参与者在prepare阶段因为网络问题没收到请求,但协调者超时后发了rollback,参与者收到rollback时发现自己根本没执行过prepare,这时候如果直接回滚,可能把其他事务的数据搞乱。解决办法是:参与者收到rollback时,先查tx_record,如果不存在该tx_id,就插入一条ROLLED_BACK记录,表示"这个事务我虽然没参与,但我知道它回滚了"。

悬挂是指:参与者在prepare超时后,协调者发了rollback,参与者执行了回滚。但之后那个延迟的prepare请求才到达,参与者又执行了prepare,导致数据被锁定但永远不会被提交。解决办法是:参与者在执行prepare前,先查tx_record,如果发现该tx_id已经是ROLLED_BACK,就拒绝prepare。

这两个问题在文档里经常一笔带过,但实际生产中一旦出现,排查起来非常痛苦。我建议在tx_record表里加一个status字段,把PREPARED、COMMITTED、ROLLED_BACK都记下来,所有操作前先查状态。

6. 2PC的替代方案与选型建议

6.1 TCC:把2PC的业务逻辑显式化

TCC(Try-Confirm-Cancel)本质上是2PC的业务层实现。Try对应prepare,Confirm对应commit,Cancel对应rollback。区别在于,TCC的Try阶段不是简单锁资源,而是执行一个"预留"操作,比如冻结库存而不是扣减库存。

TCC的好处是把控制权交给业务,性能比XA好,因为不需要数据库层面的长事务。坏处是开发成本高,每个参与方都要实现三个方法,而且要考虑幂等、空回滚、悬挂。适合对性能有要求、业务逻辑相对清晰的场景。

6.2 本地消息表与事务消息

本地消息表的核心思路是:把分布式事务拆成多个本地事务,用消息表来保证最终一致性。比如订单服务在本地事务里同时写订单和一条"扣库存"消息,然后异步发消息给库存服务,库存服务消费消息后扣库存。如果扣库存失败,消息会重试。

这种方案不保证强一致性,但保证最终一致性,性能好,实现简单。缺点是消息可能重复消费,需要业务做幂等。事务消息(如RocketMQ的事务消息)是本地消息表的进阶版,把消息表的维护交给消息队列。

6.3 选型对比表

方案一致性性能开发成本适用场景
2PC/XA强一致低低(框架支持)低频金融交易
TCC强一致中高高并发、业务可控
本地消息表最终一致高中异步解耦场景
事务消息最终一致高中消息驱动架构
Saga最终一致高中长流程、多步骤

选型的核心原则是:不要为了强一致而强一致。很多业务场景其实可以接受最终一致,比如发优惠券、积分累计,晚几秒到账用户根本感知不到。只有涉及钱、库存这种不能出错的,才需要强一致方案。

7. 我在实际项目中的几点体会

第一,能不用分布式事务就不用。最好的分布式事务是没有分布式事务。通过合理的业务拆分,把需要事务的操作收敛到同一个服务、同一个库里,从根源上避免跨库。比如订单和库存,能不能合并成一个"交易服务"?很多团队拆得太细,反而给自己挖坑。

第二,如果一定要用2PC,把事务范围压到最小。一个事务涉及的服务不要超过3个,参与的操作要尽可能快。我见过一个事务里包含了调用外部API、发短信、写日志,这种绝对不能用2PC,因为外部API的延迟不可控,会把整个事务拖死。

第三,监控和告警必须到位。2PC的中间状态(PREPARED、COMMITTING)如果长时间不结束,就是异常信号。我一般会设置一个阈值,比如PREPARED状态超过30秒就告警,然后人工介入或者自动触发回滚。同时要监控协调者的队列积压、参与者的锁等待时间。

第四,测试要覆盖异常场景。正常流程谁都能跑通,关键是网络抖动、服务重启、超时这些情况。我习惯用工具模拟网络延迟和丢包,专门测prepare阶段断网、commit阶段断网、协调者重启这些case。很多bug都是在这种极端情况下暴露的。

最后分享一个小技巧:在tx_record表里加一个expire_at字段,记录事务的过期时间。后台起一个定时任务,扫描所有超过过期时间还是PREPARED状态的事务,自动触发回滚。这能兜住大部分因为协调者崩溃导致的悬挂事务,虽然不能保证100%正确,但比人工排查快得多。这个机制我们叫它"事务兜底清理",在几个项目里都救过场。

相关新闻

USB存储设备识别:从描述符到系统块设备的完整判定指南

USB存储设备识别:从描述符到系统块设备的完整判定指南

判断一个USB设备到底是不是存储设备&#xff0c;这个需求在驱动开发、系统工具、安全管控软件里太常见了。你会遇到各种怪事&#xff1a;一个U盘插上去&#xff0c;文件管理器能正常打开&#xff0c;但你写脚本枚举USB设备&#xff0c;按网上教程只看bDeviceClass&#xff0c;结…

2026/10/20 5:35:11 阅读更多 →
OpenClaw 云服务器一键部署指南:5分钟内跑通 AI 代理

OpenClaw 云服务器一键部署指南:5分钟内跑通 AI 代理

不知道你有没有过这种经历&#xff1a;想试一个 AI 自动化工具&#xff0c;先装环境&#xff0c;再配依赖&#xff0c;最后卡在模型接口上&#xff0c;折腾一下午连控制台页面都没见到。所以我最近几乎把所有需要长时间运行的任务都搬到云服务器上做&#xff0c;像 OpenClaw 这…

2026/10/20 5:35:11 阅读更多 →
从听到用:播客下载、转写与摘要的AI知识管理指南

从听到用:播客下载、转写与摘要的AI知识管理指南

我第一次意识到播客这个形态适合用来追踪 AI 领域&#xff0c;是在某次通勤路上。当时我刚结束一个项目的复盘&#xff0c;正好听到一档节目的某期内容&#xff0c;讲的是多模态模型如何改变内容审核的工作流。那一期的观点并不算深&#xff0c;但胜在信息密度很高&#xff0c;…

2026/10/20 5:34:11 阅读更多 →

最新新闻

STM32 bxCAN实战:从物理层到过滤器配置与调试排障

STM32 bxCAN实战:从物理层到过滤器配置与调试排障

做嵌入式这几年&#xff0c;UART、I2C、SPI都是老朋友&#xff0c;但真正让我觉得“像个通信工程师”的&#xff0c;是开始啃STM32的CAN外设之后。原因很简单&#xff1a;在汽车、机器人和工业现场&#xff0c;CAN总线是默认的“老大哥”&#xff0c;而它在微控制器上的硬件实现…

2026/10/20 6:26:33 阅读更多 →
ReST-MCTS精读:用蒙特卡洛树搜索与过程奖励实现LLM自我训练

ReST-MCTS精读:用蒙特卡洛树搜索与过程奖励实现LLM自我训练

这篇是论文精读系列的第九期。选题的时候我特意挑了一个既讲方法、又讲工程的文章&#xff1a;ReST-MCTS&#xff0c;标题直译过来就是“基于过程奖励引导的树搜索做LLM自我训练”。一句话概括它的核心思想&#xff1a;让大模型不要只想着“快答”&#xff0c;而是像下棋一样&a…

2026/10/20 6:26:33 阅读更多 →
实测,ToClaw为什么是最优秀的OpenClaw龙虾:从 ToDesk 远程到 TaoToken 统一 Key 的配置复盘

实测,ToClaw为什么是最优秀的OpenClaw龙虾:从 ToDesk 远程到 TaoToken 统一 Key 的配置复盘

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 6:26:33 阅读更多 →
diagram-design:从图形类型到布局配色的技术图设计指南

diagram-design:从图形类型到布局配色的技术图设计指南

1. 从"diagram-design"这个标题说起&#xff1a;它到底在解决什么问题第一次看到"diagram-design"这个标题&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;这大概率不是一个单纯的"画图工具"&#xff0c;而是一套关于"如何把复杂信息…

2026/10/20 6:26:33 阅读更多 →
CTF Misc入门:DOCX隐写与ZIP伪加密的底层原理及010 Editor实操

CTF Misc入门:DOCX隐写与ZIP伪加密的底层原理及010 Editor实操

CTF新手村常客——Misc题型里的DOCX隐写和伪加密&#xff0c;对很多刚入坑的朋友来说&#xff0c;看WriteUp觉得挺简单&#xff0c;真上手时又容易卡壳。我最初玩PolarCTF这类靶场时也是这样&#xff0c;面对一个打不开的docx文件&#xff0c;第一反应是直接改后缀名丢进解压工…

2026/10/20 6:26:33 阅读更多 →
HTTP POST表单提交全解析:Content-Type、编码格式与实战避坑指南

HTTP POST表单提交全解析:Content-Type、编码格式与实战避坑指南

1. 从一个被忽略的细节说起&#xff1a;为什么表单提交远不止填个action很多人第一次接触Web开发&#xff0c;接触到的第一个"能跑起来"的功能&#xff0c;大概率就是一个HTML表单。写一个<form>标签&#xff0c;配上action和method"post"&#xff0…

2026/10/20 6:25:32 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →