ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Colibri轻量虚拟化:KSM去重与写时复制实现秒级克隆

Colibri轻量虚拟化:KSM去重与写时复制实现秒级克隆 在云平台和虚拟化圈子里问一句“colibri 是什么”大概率会得到三个截然不同的答案有人会想到蜂鸟有人会想到那款常见的 ARM 计算机模块系列还有人会立刻反应过来说的是那套以轻量虚拟化为核心、把虚拟机启动时间从分钟级压到秒级的方案。我这次要拆的就是最后这一个。colibri 这套轻量虚拟化思路核心目标非常朴素让虚拟机克隆不再等于“复制一份完整磁盘再冷启动一遍”而是靠内存页面共享加上写时复制让新实例几乎在你还盯着进度条的瞬间就已经跑起来了。它解决的是高密度部署场景下最烦人的两件事——启动慢、内存吃不满。适合谁看做私有云平台虚拟化层的、维护大规模测试环境的、每天被“再给我开十台机器”这种需求追着跑的运维和平台开发都能从里面捞到能直接用的东西。1. Colibri 到底解决什么问题1.1 名字背后的多义与本文的聚焦范围先把语义这层薄膜捅破不然沟通起来容易鸡同鸭讲。colibri 本意是蜂鸟体型极小、振翅频率极高、能在空中悬停——这三个特征被不同领域的人分别借用了硬件厂商拿它命名超小尺寸的计算机模块虚拟化圈则拿它命名一套“轻量化到极致”的实例创建机制。因为蜂鸟的特性恰好对应了这套方案的两个卖点单实例开销极小、状态切换极快。我这次聚焦的是虚拟化方向。它在早期最广为人知的一种落地形态是作为 OpenStack Nova 的一个虚拟化驱动出现的负责接管实例的创建、启动、暂停与恢复。后来这套思路被拆解、被复现作为独立组件服务纯 libvirt/KVM 环境的情况也不少。这里需要提前说明因为不同版本、不同分支在命令与配置项上存在差异本文涉及的具体操作我尽量用 KVM/libvirt 生态里通用且可验证的等价命令来演示涉及平台专属参数的地方会明确标出来你按自己手上的版本文档对照着改。这个取舍是有意的——比起贴一堆你可能根本跑不起来的专属命令我更愿意把底层机制讲透让你在任何一套封装里都能自己找到对应的开关。为什么要费这个劲因为 colibri 这类方案最容易被误用的地方恰恰不是命令而是对机制的理解。很多人以为“克隆快”就等于“随便开”结果内存被慢慢吸干、磁盘链越拉越长、宿主机在凌晨三点开始剧烈抖动。理解了页面共享和写时复制的边界你才知道哪些实例可以放心超分、哪些必须老老实实独占资源。1.2 传统虚拟机克隆的三个痛点要理解 colibri 的价值先得看清传统做法卡在哪。假设你有一个 20GB 的系统盘、内存分配 4GB 的标准模板机现在要基于它开出十台测试机。第一个痛点是磁盘复制。哪怕用最省事的 qcow2 全量复制十台就是 200GB 的写入量如果是 raw 格式写放大更夸张。磁盘 IO 被打满的时候宿主机上其他正在跑的实例会跟着一起卡这种连带伤害在生产环境里非常致命。第二个痛点是冷启动。操作系统从引导加载器开始跑完整套启动流程加载内核、初始化服务、等待网络就绪稳定状态通常在 30 到 90 秒之间。十台串行启动就是十几分钟并行启动又会把 CPU 和磁盘 IO 顶到天花板启动时间反而更长。做大规模测试环境的人对这段等待时间应该深恶痛绝。第三个痛点是内存浪费。十台实例各占 4GB物理内存上就是 40GB 的硬占用但真实情况是这些实例的内存里有大量完全相同的页面——同一个内核镜像、同一套动态库、同一份被 mmap 进来的只读数据。传统方案对这些重复页面的处理方式是放任不管各存各的物理内存被白白吃掉。colibri 这套思路就是同时对这三个痛点下手磁盘走写时复制的链式继承内存走页面去重与共享启动状态走快照恢复。三条路径合起来才有了“秒级起一台机器”这个结果。2. 核心原理拆解页面共享与写时复制2.1 内存页面去重是怎么生效的内存页面去重的内核实现KVM 环境下依赖的是 KSM也就是内核同页合并机制。它的工作方式很像一个后台巡检工周期性扫描进程的匿名内存页对内容做哈希比对一旦发现两页内容完全一致就把它们合并到同一个物理页上并把原来那些虚拟页的映射改成只读、指向这个共享物理页。此后任何一方要写这个页面触发缺页异常内核给它单独复制一份出来写操作落在副本上共享关系断开。这解释了一个关键问题为什么去重不会导致数据串台。因为共享的前提是只读一旦有人要改立刻分家。所以去重率高的场景通常是“大量实例跑同一套镜像、同一批服务、同一份只读配置”这种模式在测试集群、教学环境、CI 流水线里极其常见去重率做到 50% 到 70% 都不算稀奇。理解这一点之后很多参数该怎么调就有了依据。KSM 默认扫描节奏非常保守pages_to_scan默认 100sleep_millisecs默认 20换算一下就是每 20 毫秒扫 100 页每秒约 5000 页按 4KB 一页算扫描吞吐只有约 20MB/s。一台 4GB 内存的实例有大约 100 万个页面想让宿主机把这一整套页面扫完并完成合并按默认参数得等上好几分钟这段时间里实例已经全跑起来了去重根本来不及生效内存早就被吃满。所以第一步调优就是把扫描速率拉起来。把pages_to_scan调到 5000、sleep_millisecs调到 10扫描吞吐变成每秒 50 万页、约 2GB/s一台实例的全量页面一轮扫描基本在秒级完成。这个调整会让单核 CPU 占用明显上升所以别一上来就拉到极限先用监控观察 CPU 的 st 占比再逐步加。另外一个容易忽略的点是merge_across_nodes单 NUMA 节点上可以设为 1让跨节点的等价页面也合并多 NUMA 节点机器建议保持 0因为跨节点内存访问的延迟代价可能比省下来的内存更贵。2.2 写时复制与 qcow2 磁盘链内存之外另一半是磁盘。qcow2 格式天生支持后备文件链也就是 backing file 机制这正好是写时复制在块设备层的对应实现。做法是先做一个只读的基准镜像把它当作所有派生实例的公共底层然后每个新实例只创建一个极小的空壳 qcow2 文件通过-b指向那个基准。创建命令长这样qemu-img create -f qcow2 \ -b /var/lib/libvirt/images/base-win10.qcow2 \ -F qcow2 \ /var/lib/libvirt/images/clone-01.qcow2 40G这里的-F qcow2是在显式声明后备文件的格式别省略省略之后在某些版本上会触发格式探测既慢又可能出问题。新文件创建出来的实际占用只有几百 KB实例启动后读操作全部穿透到底层基准镜像只有写操作才会在当前层落盘。这就是为什么十台实例的初始磁盘成本接近于零。代价也很明确。第一基准镜像是共享的只读数据它一旦被修改或删除所有派生实例全部报废所以基准文件的权限应该是只读最好再做一层文件系统级的保护。第二派生层的读性能会略低于全量镜像因为多了一层查找写入量大的实例qcow2 文件会持续膨胀需要盯着容量。第三链不能太长两层已经是常规上限三层以上元数据开销和性能衰减都会变得不可接受。注意基准镜像必须处于关机状态且文件系统一致直接从一台正在运行的机器上拷贝磁盘文件做基准十有八九会因为日志未落盘而在克隆实例上出现文件系统检查。2.3 为什么启动能被压到秒级有了内存共享和磁盘写时复制还差最后一块拼图状态恢复。冷启动那几十秒本质上是在重复执行一套完全确定的初始化流程既然流程确定就可以把执行完的结果存下来复用——这就是快照恢复的思路。具体路径是把一台已经完成初始化、进入稳定状态的实例把它的 CPU 寄存器、设备状态和全部内存内容整体保存下来。之后创建新实例时直接把这份保存好的状态灌进去CPU 从保存点继续执行跳过整个引导过程。这个过程通常在几百毫秒到两秒之间完成因为它不做任何 IO 密集的初始化只做状态写入和映射建立。这三层机制叠加起来才是完整的 colibri 效果磁盘层零成本继承内存层靠去重把重复页面压掉状态层靠快照跳过启动。三者缺一不可。只做磁盘写时复制你得到的是“省空间的慢启动”只做内存去重你得到的是“高密度但启动照旧慢”三个都做才是“快速且高密度”。这也是我在实际环境里反复强调的一点——评估这类方案时别只看单点指标要看三条路径是否都打通了。3. 环境准备与部署前的关键决策3.1 硬件与内核侧的硬性要求基础环境这块我按最小可用集来列你可以对照自己的机器核一遍。CPU 必须支持硬件虚拟化扩展这是底线没有它连 KVM 侧的门都进不去。内存方面单机做验证实验 32GB 起步做小规模生产 128GB 会更从容因为页面共享需要“先有重复、后能合并”内存太小的话实例数量上不去去重收益也看不出来。内核需要开启 KSM 支持绝大多数主流发行版的默认内核都带确认方式是看/sys/kernel/mm/ksm/这个目录是否存在。如果目录不存在说明内核编译时没打开该选项只能换内核。这块不需要额外装驱动属于内核自带能力比其他一些需要打补丁的方案友好得多。存储上建议基准镜像和派生层放在同一块 SSD 上别跨设备。原因是派生层的读操作要穿透到底层文件跨设备会让每次随机读多一次网络或总线跳转实测延迟差异在高并发启动时会被放大得很明显。如果你用的是分布式存储务必确认底层对随机小块读的延迟表现否则秒级启动的收益会被存储层吃掉。网络这块有个容易踩的坑如果实例在快照保存时持有已建立的 TCP 连接恢复出来的实例会带着一堆过期连接状态对端可能已经把这个连接回收了导致恢复后服务看起来在跑但实际不可用。常见做法是在保存前先把长连接断掉或者依赖上层服务自带的重连机制。这一点在文档里往往一笔带过但实际踩过的人都知道有多麻烦。3.2 三种落地形态的选型对比不同团队的环境差异很大我把常见的三种形态列出来对比你按自己的情况挑。落地形态适用场景部署复杂度快速启动支持主要限制平台内置驱动已建成 OpenStack 等私有云节点规模较大中跟随平台版本升级完整支持版本依赖强升级窗口受限独立组件 libvirt自建 KVM 集群追求可控性较高需自行维护完整支持需要自己写运维脚本和监控手工组合 KSM qcow2测试环境、小规模验证、教学低几乎零额外部署部分支持无状态快照无统一管理靠人肉维护我个人的建议是这样如果只是想把启动速度提上去、把测试环境密度做起来第三种手工组合已经能带来七八成的收益投入产出比最高如果你管理的是几十上百个节点的生产集群需要统一调度和资源核算那第一种平台内置驱动更合适虽然版本升级时有摩擦但省下来的人力很可观独立组件这条路适合那种有虚拟化专职团队、又不想被平台版本绑架的组织灵活性最高维护成本也最高。这里有个反直觉的结论不要一上来就选功能最全的方案。我见过太多团队为了用上快照恢复先花两个月搭了一套组件结果发现自己的业务场景根本不需要秒级启动——他们的实例生命周期是几天甚至几周启动那几十秒完全不是瓶颈。先算清楚瓶颈在哪再选形态顺序不能反。3.3 内存超分比到底怎么估这是最常被问到的问题我用一个具体算例走一遍。假设宿主机物理内存 128GB系统和其他服务预留 8GB可用于实例的内存是 120GB。单实例分配 4GB不开启去重时理论最大实例数是 120 ÷ 4 30 台实际留点余量按 28 台算。开启页面去重后关键变量是去重率。这个值不能拍脑袋得实测。测法是在目标负载下跑一批典型实例等稳定运行一段时间后读取 KSM 的统计文件cat /sys/kernel/mm/ksm/pages_shared cat /sys/kernel/mm/ksm/pages_sharing cat /sys/kernel/mm/ksm/pages_sharing /sys/kernel/mm/ksm/pages_volatilepages_sharing表示当前被共享出去的页面总数乘以 4KB 就是实际省下来的内存。用它除以所有实例的匿名内存总量得到的就是真实去重率。同构程度极高的环境——比如所有实例跑同一版本的操作系统和同一套运行时——我实测能到 60% 以上如果是每个实例服务不同、数据各异的环境去重率可能只有 15% 到 25%这时候超分就要格外谨慎。按去重率 60% 重算单实例的实际物理占用约等于 4GB ×(1-0.6)1.6GB120GB ÷ 1.6GB 75 台相比 28 台翻了将近三倍。但这个数字有个前提——它假设所有实例在同一时刻的内存活跃度接近。真实的负载有波峰波谷去重率也会随负载波动。所以我的做法是在理论值上再打个七折作为安全水位也就是按 52 台左右规划剩下的余量留给突发。提示超分比例一旦定下来必须配套内存压力监控。去重是动态的某台实例突然加载了一个大文件、内存活跃页激增去重率会瞬间下降物理占用随之抬升。没有监控的超分等于把宿主机交给运气。4. 实操从模板机到秒级克隆4.1 制作一个真正“干净”的基准镜像基准镜像的质量决定了后面所有实例的稳定性这一步值得多花时间。流程是全新安装一台最小化系统装好必备的运行时和工具做完全部系统更新然后执行清理动作——清空日志文件、清空临时目录、清除 machine-id 之类的机器唯一标识、清空 SSH 主机密钥、把磁盘上未使用的块用零填充一遍。最后这个零填充动作经常被省略但它对写时复制的影响非常直接。文件系统里已删除文件留下的空洞在镜像里可能是残留的旧数据qcow2 层在读取时会把这些块当成有效数据读出来导致派生实例实际读到的内容比预期多。用零填充之后这些块变成真正可被压缩和跳过的内容镜像体积更小读取路径也更短。接着把镜像转成 qcow2 并做一次压缩qemu-img convert -p -O qcow2 -c \ /var/lib/libvirt/images/base.raw \ /var/lib/libvirt/images/base.qcow2-c会启用压缩代价是读取时需要解压CPU 开销略高。对纯读基准镜像来说这个代价可以接受因为它只在派生层未命中时被读到。转完之后把文件设成只读并记录下它的校验值后续任何实例异常第一件事就是校验基准镜像有没有被意外改动。基准镜像做好之后先单独启动一次确认能正常进系统、网络正常、服务正常。这一步别嫌麻烦基准镜像里的任何问题都会被复制到几十台实例上排查成本是几何级上升的。确认无误后再进入克隆环节。4.2 宿主机参数配置与去重开关宿主机侧的配置分两组。第一组是 KSM 参数第二组是 libvirt 层面的存储与内存策略。KSM 的启动和调参可以这样操作# 开启 KSM echo 1 /sys/kernel/mm/ksm/run # 提高扫描速率 echo 5000 /sys/kernel/mm/ksm/pages_to_scan echo 10 /sys/kernel/mm/ksm/sleep_millisecs # 单 NUMA 节点允许跨节点合并 echo 1 /sys/kernel/mm/ksm/merge_across_nodes要注意merge_across_nodes的修改需要先把run置 0改完再置 1否则会写入失败。这些值是运行时参数重启就丢需要写进开机脚本或者 systemd 服务里做持久化。我一般会写一个简单的 unit 文件把这个配置固化下来避免哪次意外重启之后性能莫名回落却找不到原因。libvirt 层面重点是实例的 XML 定义。磁盘部分要把后备文件关系写清楚内存部分建议显式声明memballoon modelvirtio并启用自动气球回收这样在宿主机内存吃紧时气球驱动可以向实例要回一部分未使用的内存给去重争取时间。另外建议给每个实例加上cpu modehost-passthrough让实例看到宿主机真实的 CPU 特性避免因为 CPU 型号暴露不一致导致某些依赖指令集的应用出问题。配置完别忘了验证。开一台实例跑起来然后观察pages_sharing是否开始增长。如果长时间为 0八成是扫描速率不够或者实例之间的内存差异实在太大没有可合并的页面。前者调参数后者只能接受现实别硬凑。4.3 克隆流程与启动验证克隆本身很短但顺序很重要。完整流程是先定义实例的 XML 配置文件把磁盘指向新建的派生层再用virsh define注册最后启动。XML 里的磁盘段大概长这样disk typefile devicedisk driver nameqemu typeqcow2 cachenone/ source file/var/lib/libvirt/images/clone-01.qcow2/ target devvda busvirtio/ /diskcachenone这个设置值得单独说一句。它让 IO 绕过宿主机页缓存直接走 qcow2 层的处理逻辑。在写时复制场景下绕过页缓存能避免宿主机缓存和 qcow2 元数据之间出现不一致同时让后备文件的读取更可预测。代价是失去一层缓存加速如果你的基准镜像已经在存储侧有缓存这个损失基本可忽略。启动之后立刻做三件事的验证网络是否通、主机名和机器标识是否已重新生成、磁盘写入是否正常落到了派生层。第三项可以用qemu-img info看派生层文件的实际占用是否在增长。如果启动后发现派生层大小一直不变说明写入没有落到派生层很可能是后备文件路径写错了实例实际读到了一个全量的旧镜像。如果你用的是带状态快照的方案恢复环节还要多一步确认检查保存的状态文件和对应的内存文件是否完整、时间戳是否匹配。状态文件和内存文件是一对缺一个都恢复不了而且它们必须在同一时刻生成混用不同批次的文件会导致恢复后的实例行为诡异最典型的表现是进程莫名其妙崩溃或者网络栈卡死。4.4 监控指标与数据采集没有监控的高密度部署就是在裸奔我列几个必须盯的指标。宿主机侧pages_sharing和pages_volatile的比值、cpu 的 st 占比、整体内存可用量、qcow2 派生层的容量增速。实例侧启动耗时分布、内存实际活跃页数量、磁盘写入速率。采集可以很轻量用一段定时脚本把 KSM 的统计文件读出来打到日志或者监控系统里就够了。关键是建立基线——在正常负载下记录一组值之后所有异常判断都以这组值为参照。我遇到过的最典型的异常是去重率在某个时间点突然从 60% 掉到 20%查了半天发现是某个应用的自动更新把一批实例的运行时全换成了不同版本页面直接失去了可合并性。这种问题只有靠持续采集对比才能发现靠感觉是发现不了的。采集频率建议一分钟一次去重率这类指标变化相对缓慢频率再高意义不大反而增加监控系统负担。启动耗时则建议逐实例记录这个指标是判断整体方案是否还健康的先行指标一旦开始变慢往往意味着宿主机侧某个资源已经开始紧张了。5. 常见问题与排查实录5.1 克隆之后启动反而变慢了这个现象很少见但确实存在我遇到过两次。第一次的原因是后备文件链太长某次批量操作时不小心基于派生层又创建了一层派生链路变成三层每次随机读都要穿透三层元数据查找。排查方式是用qemu-img info --backing-chain看完整的链看到超过两层就说明有问题处理方式是把中间层合并掉再重建。合并用qemu-img commit把派生层内容回写到底层之后重新建立干净的两层结构。第二次的原因更有意思基准镜像所在存储的读延迟在批量启动时飙升。因为所有实例的冷数据都要从这个文件读它变成了单点热点。解决办法是把常用的只读数据在实例启动前预热进宿主机页缓存或者把基准镜像放到读性能更强的层上。这个问题的隐蔽之处在于单台克隆启动时完全看不出问题只有并发上到十台以上才会暴露所以评估阶段一定要做并发压力测试别只测单台。5.2 去重率上不去怎么办去重率不达预期是最常见的困扰。我按可能性从高到低给个排查顺序。先确认 KSM 的扫描参数是不是还停在默认值这个原因占了我在实际环境里见到的六成以上。再确认实例之间是否真的同构如果每台实例都在跑不同的服务、加载不同的数据集去重率低是正常现象不是故障。接下来看时序。去重是一个持续过程实例刚启动的前几分钟去重率一定偏低如果实例生命周期只有几分钟那去重根本没时间生效这时候追求高去重率意义不大。最后看内存类型KSM 只处理匿名页也就是进程的堆栈和堆内存这类文件页缓存不属于它的处理范围如果你的负载大部分内存都花在文件缓存上去重率天然就上不去。这里补充一个实操技巧可以给实例加上透明大页的开关控制。透明大页的页面大小是 2MBKSM 对 2MB 页的处理效率比 4KB 页低而且合并粒度更粗容易出现“部分内容相同但整页不同”而无法合并的情况。在高密度共享场景下把透明大页关掉往往能明显提升去重率代价是页表项变多、TLB 命中率下降。这个取舍需要按负载实测没有通用答案。5.3 派生层容量告警与写放大派生层膨胀是另一个高频问题。理论上派生层只存增量但实际使用中一次大规模的软件升级、一次数据库重建、一次日志暴涨都可能让派生层在短时间内长大。更麻烦的是 qcow2 的块分配机制——写入一个块时按簇分配如果写入模式很随机可能出现大量“部分使用”的簇空间利用率下降。处理思路有三条。第一条是给派生层设置监控阈值比如基准镜像大小的 30%超过就告警别等到磁盘满。第二条是对写入量确实很大的实例定期做一次镜像合并把它转成全量镜像独立出去不再依赖后备文件。第三条是在创建派生层时就指定合适的簇大小工作负载以顺序写为主时用大簇随机写为主时用小簇这个参数需要在创建时定死后期改不了所以要在模板阶段就想清楚。注意qemu-img commit操作会把派生层数据合并到底层执行期间实例必须处于关机状态而且在合并完成前不要中断。我见过一次因为合并中途强杀进程导致底层镜像结构损坏最终只能从备份恢复损失了两天的环境重建时间。5.4 问题速查表把上面这些整理成表方便你直接对照使用。现象高概率原因优先排查动作启动耗时回升后备文件链超过两层检查链深度必要时做合并重建去重率长期低于 20%KSM 扫描参数未调优提高扫描速率并观察 CPU 开销派生层容量激增实例写入量大或随机写严重检查写入模式考虑独立成全量镜像恢复后实例网络不通快照保留了过期的连接状态恢复前断开长连接或依赖重连机制宿主机内存突然吃紧负载变化导致去重率骤降核对活跃页数量及时启动气球回收克隆实例文件系统异常基准镜像制作时未落盘或未清空重新制作基准镜像并校验6. 适用场景与收益评估6.1 什么样的场景真正吃得下这套方案不是所有环境都适合。最适合的是那种“大量同构、生命周期短、对启动时间敏感”的场景持续集成流水线里临时起的构建机、培训或演示环境里每人一台的实验机、功能测试里需要频繁重置状态的验证机。这些场景的共同点是实例之间高度相似去重收益直接拉满而且快速启动带来的体验提升能被用户直接感知。不太适合的是“异构、数据密集、生命周期长”的场景。比如每台实例都在跑独立数据库、各自持有大量私有数据、一跑就是几个月。这种情况下内存去重几乎没收益快速启动带来的那几十秒节省相对于几周的运行时间可以忽略而引入的额外复杂度——后备文件链管理、备份策略调整、监控体系扩展——全都是实打实的成本。还有一种中间形态需要特别注意批处理任务。这类任务启动快、跑完就销毁看起来完美契合但如果任务本身对 IO 吞吐要求很高写时复制的穿透读可能成为瓶颈。这类场景建议先做小规模灰度用真实任务跑一轮对比开启前后的总耗时再决定是否推广。6.2 收益该怎么算才不被自己骗收益评估最容易犯的错误是只算资源账、不算复杂度账。资源账很好算磁盘节省等于全量镜像大小减去派生层初始大小乘以实例数内存节省等于共享页面数乘以页大小。这两项用前面提到的统计文件就能读出来很直观。复杂度账就隐蔽多了。后备文件链引入了一个新的依赖关系基准镜像成了所有实例的共同上游它的变更需要走一套新的发布流程不能再像以前那样随便改。备份策略要调整因为派生实例单独备份意义不大必须连同基准一起规划。监控要扩展多出来的这组指标需要有明确的告警阈值和处置预案。这些都是持续投入不会随着规模扩大而被摊薄反而会随实例数量增长而增长。所以我的算法是先算资源节省的绝对值再估这套新机制带来的额外人力投入两者相减看净收益。如果实例规模在几十台的量级节省的内存和磁盘成本本身就不大很可能被额外投入抵消掉这时候更值得关注的是运维效率的提升和用户体验的改善别只盯着成本表。如果规模到了几百上千台资源节省的绝对值会变得非常显著这时候投入是划算的。最后分享一个我在实际环境里坚持的小习惯每次做这类架构变更前先在一个隔离的小集群里跑满一个完整的生命周期——从创建、运行、扩容、缩容到销毁把每个环节的耗时和资源曲线都记下来。这套基线数据在之后半年里的价值会超出你的想象因为它能让你在问题刚冒头的时候就判断出是正常波动还是真的出了状况而不是等到用户报障才开始翻日志。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进