news 2026/10/19 4:53:35

Atlas:MySQL读写分离与分库分表中间件实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Atlas:MySQL读写分离与分库分表中间件实战解析

做后端时间长了,拆过库、分过表、抗过千万级QPS的人,几乎都绕不过一个名字——Atlas。如果你手头的"atlas"指的是阿里开源的那个MySQL数据库中间件,那这篇文章就是给你写的。如果你以为它是地图册、是希腊神话里扛天的泰坦,那也不冲突,因为在数据库世界里,它确实替应用扛住了来自底层的读写压力,把成千上万条SQL合理分配到不同的MySQL实例上,让业务方不用关心背后有几台库、哪台挂了怎么办。

Atlas是360开源的一个基于MySQL官方版改造的数据库中间件方案,核心能力是读写分离、多从库负载均衡、分库分表,以及对业务透明地屏蔽底层主从切换。它解决的问题非常明确:当单库撑不住流量、需要把读流量分散到多个从库时,应用层不可能每出一个新需求就去改一遍连接串,更不能容忍某台从库宕机后整个服务直接雪崩。Atlas相当于在应用和MySQL之间加一层"交通调度",把读SQL分发给健康的从库,把写SQL固定到主库,遇到故障还能自动摘除节点,整个过程对业务代码近乎无感。

这篇文章我会从架构原理讲到部署配置,再讲踩坑和排错,全程基于线上真实场景,尽量让同样在做中间件选型或已经在用Atlas的同行能直接抄作业。适合谁看?后端开发、DBA、架构师,以及刚接触数据库中间件但不想只看PPT的人。

1. 内容整体设计与思路拆解

1.1 Atlas到底属于哪一类中间件

先明确一个概念:Atlas不是存储引擎,也不是数据库本身,它是介于应用和MySQL之间的透明代理层。你把它理解为"数据库网关"可能更准确。应用连接Atlas就像连接一台MySQL,发过去的SQL会被Atlas解析、改写、路由,然后转发给后端的真实MySQL实例,最后把结果原样返回给应用。

从网关的思路出发,你就能理解Atlas为什么能实现读写分离而不需要改应用代码。它会在SQL解析阶段主动判断这条语句是SELECT还是INSERT/UPDATE/DELETE,SELECT默认走从库,写操作走主库,遇到事务中的语句会特殊处理,保证同一事务内的所有SQL都路由到同一个连接。这种设计跟直接在应用层做多数据源切分相比,最大的优势是侵入性为零,业务方只需要改一个JDBC地址,剩下的全部交给中间件。

很多人会问,既然有MyCAT、ShardingSphere这些"后起之秀",为什么还要聊Atlas?我的看法是,Atlas的定位跟它们不一样。MyCAT和ShardingSphere侧重分库分表的全链路治理,ShardingSphere甚至支持分布式事务、数据脱敏、影子库等功能。而Atlas更纯粹,它把读写分离和从库负载均衡做到极致,在360内部大规模生产环境验证过,稳定性和性能都经得起考验。如果业务形态是典型的"读多写少"——比如内容平台、商品详情、订单查询——Atlas这种轻量方案反而更合适,部署简单、运维成本低,没有那么多心智负担。

1.2 读写分离解决了什么核心痛点

在没有中间件之前,最常见的读写分离方案有两种。第一种是在应用代码里维护两个数据源,写操作走主库,读操作走从库,靠AOP或者手动指定;第二种是让DNS或VIP指向主库,从库自己搭SRC复制,业务完全感知不到从库的存在,自然也无法分流读流量。

方案一的问题是所有读请求都要开发显式做标注,一旦某个查询漏标或者标错,要么把压力全打给主库,要么把写操作发到从库直接报错。方案二的问题更致命,从库只负责备份,压力完全没分担,主库该扛多少还是扛多少,架构上等于没有做读写分离。Atlas把这两类问题一次性解决了,读请求通过统一入口自动负载均衡到多台从库,并且支持权重分配、故障自动剔除、平滑增删节点,这些能力在高峰期扩从库时尤其好用。

这里要特别提一下读写分离真正要解决的不是"把读分走",而是"让应用层彻底不用关心主从拓扑"。比如某天你想临时加一台物理从库来扛双十一流量,用Atlas只需要改配置reload,不用动任何应用,也不用发版;反过来,想下线一台低配从库,直接摘节点即可。这种灵活性在业务高速迭代期几乎等于救命,因为你永远无法预判流量会在哪一天突然翻倍。

1.3 为什么选Atlas而不是自己写代理

我见过不少团队想自己用Netty写一个数据库代理,从协议解析开始翻源码,最后基本都翻了车。MySQL客户端/服务端协议本身不复杂,但要处理多版本认证、预编译语句、事务状态、字符集、结果集压缩等等细节,工作量远超想象。更麻烦的是高并发下的连接管理、慢SQL拦截、后端连接池复用,每一个点都足够做好几周。

Atlas官方源码基于MySQL 5.6的proxy分支深度定制,等于站在Oracle官方团队的肩上看网络层和协议层,再把360自身在搜索引擎场景下积累的高并发运维经验注入其中。它的线程模型是经典的I/O多路复用,前端接入和与后端的交互分离,内存管理上做了大量优化以应对长连接场景。自己写的代理通常在高并发和连接风暴下暴露问题,而Atlas在这些场景已经被人反复打磨过,选它多少有点"站在巨人肩膀上"的意思。

2. 核心细节解析与实操要点

2.1 Atlas的关键模块:前端连接管理、SQL解析、路由分发、后端连接池

拿一个真实请求走一遍完整链路,你就能理解Atlas内部是怎么协作的。假设某个订单查询请求发出了一条SQL:SELECT * FROM order_info WHERE order_id = 123,应用通过JDBC把这条SQL发到Atlas的监听端口。

第一步,Atlas的前端连接器会把它包装成一个内部事件,放进I/O线程池处理;第二步,SQL解析模块会对语句做词法分析,判断它是查询还是写操作,同时提取表名、条件表达式、是否包含特定分表键;第三步是路由决策,判断要不要走从库、走哪台从库、需不需要改表名;最后一步,从后端连接池里取出一个到目标MySQL的空闲连接,把SQL转发过去,拿到结果集再做协议转换返回给应用。

这里面每个模块都有细节。比如SQL解析如果命中事务状态,连接上已经执行过SET autocommit=0或者BEGIN,那后续SQL就不能再走从库了,必须全部路由到主库,否则会出现同一事务里先写后读但读不到自己刚提交的数据。这属于读写分离中间件最容易踩的坑,Atlas的实现方式是按照连接粒度跟踪事务状态,一旦开启事务就把该连接在后端绑定到主库,直到COMMIT或ROLLBACK。

2.2 从库负载均衡的真实策略:不只是轮询

Atlas默认的负载均衡策略是加权轮询,权重在配置文件的lt_master和lt_slave里设置。不要小看权重这个参数,它决定了流量分配是否符合后端机器真实能力。举例来说,标配的8核16G从库和16核32G从库放在同一个组里,如果权重同样是100,低配机器可能比高配机器更早打满CPU,导致延迟升高、主从复制位点差距拉大。

经验做法是低配机器权重设置在40~60之间,高配机器设置在100以上,然后压测观察两台的QPS和CPU曲线。注意从库的负载还受复制线程影响,如果你在主库上跑了大量批量UPDATE,从库的SQL线程也可能成为瓶颈,这时候即使读流量没有完全打满CPU,从库也可能会积压复制延迟,需要结合Seconds_Behind_Master一起判断。

此外,Atlas在负载均衡中还考虑了后端连接的"健康状态",这比普通的轮询高一个等级。中间件会定期对每个后端MySQL节点发起心跳检测,如果某个从库连不上或者响应超时,Atlas会把它从可用节点池里踢掉,后续读流量自动打到其他健康节点上,这个过程对应用完全透明。恢复后如果想自动加回,需要配置dynamic_remove相关的检测项,但我个人更推荐用脚本主动检测再reload,可控性更高。

2.3 分库分表规则:如何做到对业务透明

Atlas支持的分库分表不是那种自动路由到多个物理库的"全自动分片",而是按照配置的分表键做映射。简单理解就是,你在配置文件里定义一张逻辑表order_info,并指定分表键order_id,Atlas会根据order_id的哈希值计算它应该落到哪一张物理分表,比如order_info_0到order_info_99共100张表,分布在多个MySQL实例上。

这里有个关键点:分表键必须出现在SQL的WHERE条件里,否则Atlas无法定位到具体表,只能把请求广播到全部分表再汇总。如果SQL是SELECT * FROM order_info WHERE user_id = 456,没有包含order_id,Atlas会做全表扫描式的路由,把所有分表都查一遍,性能惨不忍睹。设计上一定要避免这种查询,如果业务确实需要按用户维度查订单,那就用ES或者宽表做二次索引。

对于INSERT语句,分表键直接决定数据落在哪张表,所以无论如何都不能为空;UPDATE和DELETE需要带上完整的分表键,否则会有跨分表更新的风险。我刚接手时最头疼的就是那些历史遗留的慢SQL,WHERE条件里没有分表键,上线后直接把所有分片库打满,排查半天才发现是Atlas广播导致的全库扫描。

3. 实操过程与核心环节实现

3.1 环境准备:从源码编译到二进制部署

还是那句话,如果不是做源码级二次开发,我强烈建议直接用官方Release包,别从源码编。通常生产环境是CentOS 7.x或兼容Linux发行版,需要提前装好依赖库:libffi-devel、glib2-devel、mysql-devel等。如果执意要编,./configure && make && make install一条龙走完大概十几分钟,但中间会碰到各种依赖缺失问题,踩坑成本完全不值得。

我自己的标准部署流程是这样的:先规划好Atlas实例所在的服务器资源,建议独立的4核8G以上机器,不要和业务应用混部,因为高并发下Atlas本身会有CPU和内存消耗;然后从官方源下载对应版本的二进制包,解压后目录结构通常包含conf、bin、lib、log等文件夹;最关键的一步是修改conf下的atlas.conf以及test.cnf,配置后端地址、用户名密码和读写分离规则。

启动时有一个特别容易忽略的点:Atlas启动用户权限。如果你用root启动,后续reload、关闭脚本可能因为文件属主问题报错。我的习惯是创建一个专门的atlas用户,把整个目录chown给它,然后用su - atlas -c '/usr/local/mysql-proxy/bin/mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/test.cnf'启动。这样日志和pid文件的权限不会乱,后期维护省心不少。

3.2 核心配置逐项拆解:test.cnf里的每一项分别干嘛

以最常见的配置为例,我逐段解释一下关键参数。

[mysql-proxy] admin-username=admin admin-password=admin123 admin-lua-script=/usr/local/mysql-proxy/lib/mysql-proxy/lua/admin.lua proxy-backend-addresses=192.168.1.10:3306 proxy-read-only-backend-addresses=192.168.1.11:3306,192.168.1.12:3306 proxy-address=0.0.0.0:3307 proxy-lua-script=/usr/local/mysql-proxy/lib/mysql-proxy/lua/atlas.lua log-level=message log-path=/usr/local/mysql-proxy/log

proxy-backend-addresses是主库地址,proxy-read-only-backend-addresses是从库地址,多个从库用逗号分隔。注意主库可以配置多台,但如果做了主主复制,Atlas并不能自动识别哪台才是可写的主库,需要靠外部脚本保证配置里写的永远是正确的可写节点。proxy-address是Atlas对外开放的监听地址,应用侧连的就是这个IP和端口,跟直接连MySQL的3306区分开。

这里还要提一下proxy-lua-script。Atlas的不少功能逻辑是用Lua脚本实现的,默认的atlas.lua包含了读写分离和分表的处理流程。如果你想进行定制,比如某些特定表的查询强制走主库,可以写自定义Lua脚本挂在这个参数下。当然,改Lua之前强烈建议先在测试环境充分验证,因为这里一旦报错,流量入口会直接失效。

还有一个容易被忽略的配置项是pwds相关文件,用于管理MySQL后端账号的加密密码。Atlas不会把你的明文密码直接写进test.cnf,而是通过bin/encrypt工具生成加密串,再填到配置文件里。这个设计不错,至少不会因为查看配置文件的同事一眼就看到数据库密码。

3.3 启动验证与读写分离效果检查

启动完成后,先在Atlas所在机器上用mysql -h127.0.0.1 -P3307 -u用户名 -p密码连接,能连上说明前端网络正常。接着查看管理端口,Atlas默认还开了一个admin接口,通常用mysql -h127.0.0.1 -P2345 -uadmin -padmin123连上去,可以执行SELECT * FROM backends;查看后端节点状态。

执行类似下面的命令:

SELECT * FROM backends;

结果里会显示每台后端MySQL的角色(rw或ro)、地址、类型和状态。up状态代表节点健康可用,如果是down说明Atlas已经把它摘除了。这一步能直观看到读写分组的实际节点情况,排查问题时第一件事就是来这里看状态。

验证读写分离是否生效,有个土办法:在后端MySQL的general_log里观察SQL到底落在哪台实例上。先在从库上打开general_log,然后通过Atlas执行一条SELECT SLEEP(1);这样的查询,如果从库日志出现了这条SQL,说明读流量确实被路由到了从库。注意生产环境别开general_log太久,磁盘很快会被撑爆,验证完记得关掉。

3.4 分表场景下的配置与压力测试记录

分表配置的核心逻辑在atlas.lua里,通过set_shard_interval之类的函数设定分表规则。举例来说,假如我把订单表分成64张表,分布在两台MySQL上:

set_shard_interval("order_info", 64) set_shard_key("order_info", "order_id")

这意味着Atlas会对order_id做哈希后映射到0~63号表。实际使用时,你还需要在每台MySQL上手工创建order_info_0到order_info_31(或者按需分布)这些物理表,Atlas不会自动建表。

做完这些之后,我习惯用一套写好的脚本模拟分表键查询,分别测试命中、不命中、广播三种场景的响应时间。实测下来,未命中分表键的广播查询耗时通常是命中查询的3~5倍,这还只是两台分片机器,如果分片更多,差距呈指数增长。所以上线前一定要在评审环节卡死:所有业务SQL的WHERE条件必须包含分表键。

压测环节建议用sysbench或者自研的压测脚本,直接压Atlas的监听端口,而不是压后端MySQL。压测关注两个指标:Atlas所在机器的CPU使用率和后端各节点的QPS分布是否均匀。如果某台从库的QPS明显偏高,检查一下权重配置和后端连接数是否出现了"倾斜"——这种现象通常是因为长连接被固定到了某台旧连接上,重启Atlas或者调整连接池参数可以缓解。

4. 常见问题与排查技巧实录

4.1 主从切换后应用还在写旧主库怎么办

这是用中间件后最常遇到的问题。数据库主从切换后,旧主库可能变成只读或者已经被隔离,但Atlas配置里proxy-backend-addresses还指向旧IP,新写入的SQL全部报错。我见过多个团队的解决方案是"在Atlas里再配一个新后端",这是错的,因为真实的主库角色已经变了,配置也要跟着变。

正确处理方式是:修改test.cnf里的主库地址为新主库IP,然后执行mysql-proxy reload重载配置。这里有个细节,reload不会自动清理旧的连接池连接,已经处于Sleep状态的旧连接到下一次被复用之前依然存在。如果不想等连接自然老化,可以直接重启Atlas进程,代价是瞬间断开所有应用连接,需要评估业务容忍度。

更优雅的方案是让主从切换工具(比如MHA或者自研的哨兵脚本)在完成切换后直接回调一个脚本,脚本自动修改Atlas配置并reload。我们在生产上就是这么做的,切换全过程业务基本无感知,唯一的代价是需要保证Atlas所在机器对新的主库地址网络可达,并且账号权限都已经在新主库上建好。

4.2 Atlas后端节点频繁自动摘除又恢复

如果你发现某个从库在后端状态里一会up一会down,不要急着怀疑Atlas,先去看这台从库的负载和连接数。我踩过的一个坑是同一台从库上跑了多个业务,某个定时任务瞬间打满CPU,Atlas的健康检测超时后直接把节点摘除了,定时任务结束CPU回落,Atlas又把它加回来,整个周期反复横跳。

这种抖动在业务高峰期会造成读流量在剩余几台从库之间反复重新分配,严重时引发雪崩。建议监控方面不要只看Atlas的backends状态,还要关联后端MySQL的CPU、负载和线程数。如果从库经常性高负载,要么给它扩容,要么降低该节点在Atlas里的权重,而不是盲目依赖自动摘除机制。

另外一个容易被忽略的原因是后端MySQL的max_connections设置过小。Atlas后端连接池会在高并发下创建大量连接到MySQL,如果超过max_connections,新连接会被拒绝,健康检测失败后节点被摘除。排查时登录MySQL执行SHOW STATUS LIKE 'Threads_connected';对比一下当前连接数和上限就清楚了。

4.3 分表场景下的跨分片查询与LIMIT陷阱

Atlas做广播查询时,会把原始SQL下发到每个分片,然后把所有结果集合并返回。这里有一个很隐蔽的坑:如果你执行SELECT * FROM order_info ORDER BY create_time LIMIT 10,而且没有带分表键,每个分片都会各自查出10条,然后在Atlas层合并时再取前10条,结果并不是所有数据真正排完序后的前10条。

这意味着涉及排序+分页的查询,如果条件里不带分表键,结果可能错误。最直接的规避方案是业务SQL全部强制要求带分表键,让查询落在一个分片上。如果确实需要全局排序,那就不适合用Atlas的分表功能,建议引入ES或专门的统计宽表,用"分表存明细+汇总查宽表"的思路解决。

同理,INSERT语句如果不带分表键,Atlas通常会直接拒绝,这点设计得比某些中间件要严格。DELETE和UPDATE不带分表键时,有些版本会广播执行,这个风险极大,操作前一定要反复确认SQL语义,最好在测试环境先跑一遍EXPLAIN看看Atlas会路由到哪几张表。

4.4 连接数被打满和TCP TIME_WAIT堆积

应用侧每发起一次新连接,Atlas就要建立一条到后端MySQL的连接,高并发短连接场景下TIME_WAIT会暴涨,严重时端口耗尽导致新连接失败。解决方案有两层:应用侧尽量使用连接池,保持长连接,而不是每次都新建连接;Atlas侧可以调大后端连接池的最大连接数,避免频繁建连和断连。

判断连接是否健康的命令很简单,在Atlas机器上执行ss -ant | grep 3306 | wc -l看与后端MySQL的ESTABLISHED连接数。如果连接数持续走高且CPU飙升,很可能是某个连接一直没释放,注意排查wait_timeout和interactive_timeout参数。调优时需要结合实际业务,不是连接池越大越好,过大反而会在主库切换时触发连爆。

4.5 常见问题快速排查表

现象可能原因排查命令 / 处理方式
通过Atlas连接MySQL报Access denied加密密码配置错误或后端账号权限不足重新执行bin/encrypt生成密码串,检查后端账号授权
读写分离未生效,读流量全走主库配置里没写从库地址或从库全部downSELECT * FROM backends;确认ro节点状态
事务内SELECT被路由到从库未开启事务跟踪或连接处于非事务状态检查应用是否通过连接池复用连接且未正确提交
分表查询报"no shard found"SQL未包含分表键或分表规则未加载检查Lua脚本、确认查询条件包含分表键
Atlas进程CPU 100%SQL量过大或广播查询过多抓慢日志,重点排查未命中分表键的SQL
后端MySQL连接数居高不下连接池参数过大或应用连接泄漏检查Threads_connected,调整后逐步压测验证

5. 后续扩展与设计层面的经验沉淀

5.1 从Atlas出发理解所有数据库中间件

Atlas我用了几年之后,最大的收获是理解了所有数据库中间件的设计共性:前端协议解析、后端连接池管理、路由规则引擎、健康检查与故障转移。后来再看ShardingSphere、MyCAT、Vitess这些项目,你会发现核心思路都是相通的,只是功能边界和扩展方式有差异。可以说,把Atlas的源码和配置吃透,就是打开数据库中间件领域的一把钥匙。

如果你现在要做的项目是业务快速迭代、团队没有专门的DBA人力、又不希望引入太重的基础设施,Atlas依然值得优先考虑。相反,如果你的业务已经复杂到需要分布式事务、跨库关联查询、数据脱敏这些能力,那Atlas的定位会显得不太够,得往更上层的分布式数据库中间件方向调研。

5.2 中间件层引入前必须想清楚的三件事

第一件,监控能力。中间件一旦接管所有SQL流量,它就变成了核心依赖,如果它自身没有指标暴露(QPS、延迟、连接池使用率、节点健康状态),出了问题你会两眼一抹黑。我们当时的做法是写脚本定时拉取Atlas的admin接口状态,推送到监控系统并配置告警。

第二件,容量评估。Atlas本质上是多了一层代理,链路变长,单次请求延迟理论上会增加,虽然局域网内通常只是百微秒级别的额外开销,但跨机房场景下必须实测。还有内存占用,每一条前端连接和后端连接都要占内存,如果应用连接数非常多,你给Atlas配的内存就要相应调大。

第三件,降级方案。中间件不是万能的,万一Atlas进程崩溃、主机宕机、网络分区,你的应用必须有一个"绕过中间件直连数据库"的降级预案,哪怕只是配置中心里临时切一个地址,也能在关键时刻救命。这个问题很多团队直到故障发生才意识到,到时候手忙脚乱改配置上线,成本极其高昂。

5.3 我个人在实操中体会最深的一个坑

最后分享一个我自己踩过很多次的坑。Atlas的连接是有状态的,尤其是事务状态和临时表状态,真实场景里一旦应用侧的连接池开了druid.testWhileIdle之类的探测,空闲连接会被自动新建或回收,如果Atlas和后端之间的连接因为网络原因被重置,应用感知到的却是连接超时或者通信链路异常,这时候应用日志往往不会直接指向Atlas,排查起来非常绕。

后来我养成了一个习惯:所有通过Atlas的数据库账号,都单独设置一个,不要跟直连数据库的账号混用。这样当问题出现时,我可以通过账号的general_log快速区分流量到底是走Atlas还是走了其他通道,省下大量排查时间。这个习惯谈不上多高级,但在跨团队协作、多环境混合部署的场景下,真的能救命。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 4:53:35

本地化Git代码审查CLI工具:集成LLM的静态分析增强器

1. 项目概述:一个真正能落地的开源代码审查 CLI 工具“open-code-review”这个名字乍一听像某个 GitHub 上刚建的空仓库,但如果你在终端里敲下open-code-review --help后看到的不是报错而是清晰的子命令列表,还带实时调用本地 LLM 分析 Git d…

作者头像 李华
网站建设 2026/10/19 4:52:20

5G与低轨卫星互联网:密度与边界的对决

1. 从“密度”与“边界”两个词,重新理解5G和星链我做通信网络规划这些年,被问过最多的问题不是“5G怎么建”,而是“5G和星链到底谁厉害”。每次听到这个问法,我都觉得大家把方向搞偏了。5G和低轨卫星互联网(大家习惯叫…

作者头像 李华
网站建设 2026/10/19 4:51:50

信息安全概论考点梳理:密码学、PKI、访问控制与防火墙易错点详解

简介:面向高校信息安全课程学习者及备考人员,《信息安全概论》试题文档适用于期末复习、考研基础梳理或教师出题参考。内容覆盖密码学与认证、公钥基础设施体系、访问控制模型、防火墙与安全协议、安全评价标准、网络攻击流程等核心考点,并具…

作者头像 李华
网站建设 2026/10/19 4:50:56

分布式电源接入配电网故障定位:挑战、仿真与算法实现

分布式电源(DG)接入配电网之后,原来那套“单侧电源、辐射状供电”的故障定位逻辑基本就失效了。短路电流的幅值、方向,以及故障后暂态分量的分布,都会因为DG的位置和容量发生明显改变,传统阻抗测距和方向判…

作者头像 李华
网站建设 2026/10/19 4:50:49

开源代码审查方法论:CLI+Git+LLM 三位一体实践

1. 项目概述:这不是一个“工具”,而是一套可落地的开源代码审查方法论open-code-review 这个名字乍看像某个 GitHub 仓库或 CLI 工具,但实际它代表的是一类正在快速成型的新型协作范式——以开源精神驱动、以 CLI 为入口、以 LLM 为智能引擎、…

作者头像 李华
网站建设 2026/10/19 4:49:45

开源本地化代码审查工具 open-code-review 实战指南

1. 这不是又一个“AI写代码”玩具,而是一套能嵌入真实开发流水线的开源代码审查工作流我第一次在团队内部试运行open-code-review的时候,没敢直接推到主分支,而是先拉了个feat/open-cr-test分支,把上周刚合并的三个 PR 全部回滚重…

作者头像 李华