文章是本站的原创长文。每篇都保持无脚本、来源可溯,并与本站记录的系统现实一致:一个小型、静态、公网服务如何被构建、加固并保持诚实。
当多个事务形成不兼容锁请求的等待环时,就发生 InnoDB 死锁。开启死锁检测后,引擎选择一个受害者,回滚它的整个事务,使其他参与者能够继续。应用收到错误 1213(SQLSTATE 40001)。若只重试报错的那条语句,就会漏掉同一事务中更早执行、但已经一起被回滚的工作。
坏掉的样子 不能给所有锁错误套用同一种回滚规则。默认关闭超时整笔回滚时,锁等待超时只撤销正在等待的那条语句,事务内此前执行的语句仍然待决。开启对应服务端选项后,超时才会改为回滚整个事务。死锁本来就会回滚整个受害事务,不能把默认超时的语句级行为误套在死锁上。
受害者选择也不保证某个固定会话永远输。应用必须检查真实错误,再以有界次数重试完整逻辑事务。不可逆的外部副作用如果没有单独去重契约,不应跟着数据库事务自动重放。提交回复途中连接断开,也不等于已经得到死锁回滚的确定结论;错误类型与已知事务结果需要分开判断。
死锁受害者可能是任一参与者。超时对照要求关闭超时整笔回滚,并在全部会话使用可重复读;所示结果须人工检查。 快速验证 历史三会话测试保持原样,本轮没有重跑。保留的审阅记录报告当时无法使用 MySQL,且未在保留的验证文件中找到原始 MySQL 日志;下述版本与受害者选择仍归属于原文报告。
在可丢弃的 MySQL 8.4 实例上打开甲、乙和观察者三个全新管理客户端,先确认三个会话均处于自动提交模式,且隔离级别为 REPEATABLE READ;修改过的服务器默认值可能改变可见性,或让后面的读取也进入锁等待。关闭客户端自动重新连接,保持会话身份稳定。既有运行记录中的版本是 MySQL 8.4.11。观察者先创建尚未使用的实验模式,并核对死锁检测开启、超时采用默认语句级回滚:
CREATE DATABASE xbcatm_deadlock_lab; USE xbcatm_deadlock_lab; CREATE TABLE accounts (id INT PRIMARY KEY, balance INT NOT NULL) ENGINE=InnoDB; INSERT INTO accounts VALUES (1,100),(2,100); SELECT @@innodb_deadlock_detect,@@innodb_rollback_on_timeout; 两个设置应分别为一和零。甲开始一项给两个账户各增加十的操作,先锁住第一个账户:
...
dnsmasq 可以向上游转发 DNS 查询,在有界缓存中保留支持的回答,也可以提供本地配置的名称。这些是不同的数据来源。重复得到相同回答,可能来自缓存命中,也可能来自本地配置。主机名与权威数据不一致、或者清缓存后仍然解析到原地址时,先确认来源十分重要;否则容易把有效配置误认为没有清干净的缓存。
先识别回答来源。转发缓存与本地配置名称具有不同寿命:本地 TTL 零不等于删除配置。SIGHUP 清缓存并重载特定数据来源,通常不重读主配置。次数与地址复述文章历史示例,本次没有重跑。 坏掉的样子 启动 dnsmasq,不会自动让所有应用使用它。客户端必须把 DNS 查询交给预期监听,有的应用还会使用其他解析服务。同样,本地监听正常,也可能转发到错误上游或者读取了意外 hosts 配置。在增加缓存容量或重启服务前,应先确认真实查询路径。如果失败查询根本没到达这个进程,对它反复操作不会改变调用方看到的结果。
本地名称可以有意覆盖上游数据。清理转发缓存不会删除这个配置名称,因此反复清空也不能让上游地址获胜。应把本地记录当作有负责人、有用途的配置管理。即使回答的本地 TTL 为零,dnsmasq 仍可持续返回该值,因为它来自配置生成,不是靠保留某份转发回答维持。零 TTL 与删除本地配置并不是同一件事。
缓存容量和新鲜度解决不同问题。更大缓存可以保留更多合适条目,却不会延长条目的有效 TTL,也不会让上游故障期间所有名称都能查询成功。新名称与过期条目仍需要合适的解析路径。缓存大小也不直接代表可同时发送多少上游查询,更不能单独保证应用可用。应分别观察容量、寿命、并发与上游健康,而不是用一个参数代替所有约束。
快速验证 将脚本保存为 check-dnsmasq.py,准备 Docker 后执行 python3 check-dnsmasq.py。固定环境使用 dnsmasq 2.90 和 BIND 9.18.49。实验转发器忽略环境中的 resolv 和 hosts 文件,明确绑定回环监听,并只转发到自己的临时权威服务器。它不发布宿主端口,没有 DHCP 配置,结束后清理自己的实例,不影响现有服务:
...
经典 LVM 快照是在源逻辑卷上附加写时复制异常存储。lvcreate -s -L 5G -n snap vg/data 请求五 GiB 存储,实际分配还受卷组空闲容量和分配粒度约束。创建时不需要复制整份源卷,但不代表完全没有工作或必然瞬时完成。thin 快照使用另一种共享池机制,不能把经典快照的独立异常存储套到所有 LVM 快照上。
异常存储要覆盖整个保留窗口内发生变化的不同 chunk 及其元数据,包括备份复制期间的写入。突发负载可能比安静时估算更早耗尽容量。经典存储满后快照会失效,读取开始返回错误。应提前观察有效状态和增长趋势,留下扩容或结束备份的时间;只规定一个百分比告警,而没有响应能力与空闲空间,不能形成可靠恢复计划。
异常存储,不是副本 源使用 snapshot-origin 目标,snapshot 则把未变化的源 chunk 与保存的异常组合成视图。直接写快照的数据进入它自己的 COW 设备。对已保存异常的同一 chunk 反复修改,不需要为这份快照再保存一次原始内容。原文一百万次写入、十六个热块却只有十五个异常的数字,没有找回轨迹;若十六个不同 chunk 都写过,就需十六份数据异常,另加元数据。
lvs -a -o lv_name,lv_attr,origin,data_percent,lv_size 展示类型、状态和用量。swi-a-s--- 只是示意,不是所有快照固定的属性串;第五位 I 表示无效快照。告警应覆盖实际增长速率和处理时间。lvextend 需要 VG 中有可分配空间;lvchange --monitor y 只启用监控,自动扩容还依赖 dmeventd、有效扩容策略及足够容量,并不是一个开关就能保证成功。
...
Consul 服务发现提供注册端点和已经记录的健康观测,不能承诺每个返回端点都会成功响应调用方的下一次请求。实用的运维模型需要分开四件事:注册了什么、哪些检查与该实例有关、查询接受哪些状态,以及这些观测可能已经过去多久。仅仅成功解析到一个地址,并没有自动建立上述全部保证,也没有证明业务请求已经能够完成。
注册记录与健康状态是两回事 catalog 存储注册信息,健康端点再附上检查结果。不加过滤的 /v1/health/service/web 响应,可以包含检查为 critical 的实例。检查失败并不会自动抹去服务注册;显式注销、配置了持续 critical 后自动移除,属于不同的生命周期机制。ACL 也可能影响读者能看到哪些记录,所以查询里缺少实例,不能只归因于健康检查失败。
注册的检查通常从 critical 开始,除非显式提供初始状态。但这并不意味着每个注册服务都一定具有有意义的就绪检查。判断某个服务实例时,需要结合它自己的服务级检查,以及 serfHealth 等节点级检查。同一节点上,数据库服务的检查失败,不会自动让不相关的 Web 服务检查失败;节点级检查变为 critical,则会影响该节点上的服务。不要把“相关检查全部通过”误解成“这台机器上的所有服务检查都必须通过”。
检查结果也不只是布尔值。脚本退出码零表示 passing,一表示 warning,其他退出码表示 critical;HTTP 检查按重定向处理后的响应判断:2xx 为 passing,429 为 warning,其他最终响应表示失败。默认会跟随重定向,除非设置 disable_redirects。这些状态究竟证明了什么,仍取决于探测内容。TCP 握手成功或进程存在,并不证明应用能完成一次真实请求;检查实现必须与希望验证的能力对应。
DNS 并非总是只返回 passing HTTP 健康端点的 passing=true 要求相关检查全部 passing。Consul DNS 服务查询的默认策略不同:warning 实例仍可符合候选条件,critical 实例则被排除。配置 dns_config.only_passing=true 后,warning 也会被排除。因此,一个 warning 实例出现在 DNS,却不在 passing-only API 结果里,不一定意味着检查失效,也可能只是两条路径明确采用了不同策略。
...
Kubernetes 调度判断的是 Pod 在资源请求和其他放置约束下是否适合某个节点,不是简单查看当前 CPU 曲线,然后把任务塞进看起来最空闲的位置。请求为未来需求提供记账输入,实际使用率则是另一种观测。决定一个表面空闲的集群是否有能力容纳特定 Pod 之前,需要同时理解两者,不能让其中一个指标替代全部容量判断。
读取准入后的请求。API 拒绝、已创建但 Pending,以及成功放置,属于不同阶段。 以准入后的真实配置为依据 对于普通应用容器,调度器在判断节点是否容纳 Pod 时,会计算它们的请求。初始化需求、Pod 额外开销以及受支持的 Pod 级资源配置,也可能影响有效请求。最终经过准入的 Pod 才是实际输入,包括默认值和修改结果,而不只是操作者记得提交的源文件。排查时应读取保存下来的对象,确认控制面实际看到什么。
Node allocatable 也不同于硬件总容量,它表示按照节点配置可以提供给 Pod 的资源量。其他工作负载即使暂时空闲,其资源请求仍然占用调度记账中的空间。不过,请求并不表示应用已经实际分配了同样多的物理内存,也不表示独占相应数量的 CPU 核心。放置记账、实时消耗和独占策略属于不同机制,不能混用“预留”一词解释。
...
OpenTelemetry 的 Baggage 是与上下文关联的一组字符串键值,可以让请求入口已经知道的信息继续到达下游,例如部署区域或发布分组。它与跨度属性不同,主要用途是传播;它也与链路上下文不同,承载的是应用选择的信息,而不是连接跨度的身份标识和标志。这些机制可以一起出现在一个请求里,但不能因此把它们理解为同一份数据,也不能假定它们具有相同的存储和信任语义。
跨进程传播必须落实到具体边界。发送客户端把上下文注入 HTTP 请求头等载体,接收端再从载体中提取成本地上下文。配置正确的插桩库可以自动执行这些动作,手工插桩时则要明确调用。仅在一个进程中创建行李字段,并不会把信息自动送到另一个进程、任意后台任务或外部服务。判断传播是否成功,应检查真实客户端与接收端,而不是只检查入口是否调用过设置函数。
决定哪些信息可以传播、哪些需要记录 行李字段不会自动成为跨度、日志或指标的属性。下游必须显式读取并复制,或者使用配置好的处理器执行这一映射策略。团队如果只打开传播,就期待在查询后台找到对应属性,通常会遇到数据看似消失的问题。反过来,把所有入站字段无差别复制进遥测,也可能造成语义不清、数据过大或指标基数增长。传播和记录是两个独立决定,应当分别明确目的与范围。
上下文操作返回新的上下文,不会把所有既有引用同时修改。调用者需要保留返回值,并在预期的位置传递或附加它。为某个出站请求删除不合适的行李字段时,也应保留该请求仍然需要的链路上下文。出站字段允许列表和遥测属性允许列表都应当是明确策略:某个接收者需要的值,不一定适合随后经过的全部目的地,更不一定适合永久写入所有观测记录。
坏掉的样子 行李上下文本身没有内置机制,能够证明某个值确实由可信组件签发。调用方完全可以提交请求头,自称属于昂贵账户等级或拥有特权角色。解析成功只能说明数据表示可被读取,不能说明其声明具有授权效力。认证与权限判断必须依赖各自可信的输入。即使字段很短、内容看起来合理,也不能跳过信任来源判断;本地解析器接受字符串,不等于业务系统应该接受其中的身份声明。
不受限制的行李字段会增加请求头大小,也可能把敏感信息传播给原本不该接收它的对象,包括自动插桩所覆盖的第三方接口。需要限制键名与值的范围,检查实际传播目的地,并由传播器负责线上编码。两个相同 SDK 实例之间往返成功,也不等于所有语言实现和代理组合都能够互操作。真正跨服务使用时,应验证实际途经的边界,而不是把一个进程内的演示扩大成对整个部署的保证。
Baggage 传播与信任:机制与证据边界。 快速验证 建立 Python 虚拟环境,安装 opentelemetry-sdk==1.35.0,把脚本保存为 baggage.py,并使用该环境的解释器运行。作者曾报告 Python 3.14.4 运行;保留的精确记录覆盖 Python 3.12.14 与 SDK 1.35.0。本轮源码复核没有重跑。实验调用真实的 W3C 链路上下文传播器和行李传播器,并把跨度导出到内存;显式始终采样避免继承的采样设置影响结果。须启用断言,不使用 -O 或 PYTHONOPTIMIZE;force_flush 调用本身位于 assert 内。没有启动 HTTP 服务,字典代表客户端接下来将发送的请求头载体。因此结果直接证明编解码与上下文行为,不把尚未执行的跨服务通信写成已完成验证。
...
监听中的 TCP 套接字会区分未完成握手的请求,以及已经建立、等待应用 accept() 的连接。真正的参数名是 tcp_max_syn_backlog;本文保留历史 URL 中的拼写。它约束的是内核记住的半开请求,而传给 listen() 的 backlog 约束 accept 队列。先弄清压力发生在哪个阶段,再考虑处理办法,否则调大一个数字很可能没有解决真正的问题。
SYN cookies 是无法继续保存普通握手状态时的兜底机制。它不代表服务器不会过载、不保证报文送达,也不意味着 SYN 队列从此无需关注。即使启用了 cookies,丢包、过滤、资源分配失败和 accept 侧压力仍然可能让连接失败。判断时必须把握手状态、应用接收速度与最终业务响应分开。
SYN 队列保存未完成握手的请求,accept 队列保存已建立、等待 accept() 的连接。仅当 listen backlog 配置为 8192、somaxconn 为 4096 时,accept 侧上限为 4096。增加等待容量本身不会提高处理速率;原文全文仍暂缓、待审。 全文仍暂缓、待审;本图不为文中存疑论断背书。 backlog 参数管的是 accept 队列 Linux 从 2.2 起改变了 listen() backlog 的含义。配置值会受到 somaxconn 限制;只有当两者分别是 8192 和 4096 时,配置上限才是 4096。这是条件明确的配置例子,不是一次测量得到的精确接纳数量。应查看实际监听套接字与其网络命名空间,不能假定默认值,也不能根据短促突发中恰好成功的连接数反推严格容量。
...
ZFS 快照通过写时复制引用保留某一时点的数据集视图,不需要把全部数据另复制一遍。只要还有引用需要旧块,它们就继续保留。但创建仍涉及元数据与同步工作,不能承诺零分配,也不能把在提示符重画之前返回当作实测保证。本次没有运行 ZFS,保留文件中也未找到原文所称实验的原始输出,所以数值与运行效果必须单独限定来源。
这样的历史适合本地恢复,但 tank/data 的快照仍位于同一个池。让池不可用的故障,也可能让全部快照不可用。这描述的是共享故障边界,不是断言每次控制器故障都会原子地毁掉所有块。独立备份或复制还需要自己的保留策略及恢复验证,不能仅因另一处存在相同名字,就宣布已经获得完整灾难恢复能力。
为一个一致的树命名 快照原子地捕获数据集的文件系统状态;递归 -r 会让后代数据集在同一时刻建立快照。不递归时,子数据集被遗漏,并不是自动晚一点另拍一份。文件系统一致不等于应用检查点:数据库缓冲、日志以及选定数据集之外的文件,仍需遵循应用支持的备份或崩溃恢复流程。必须协调所有必要部分,才能把整组状态作为可恢复对象。
tank/data@daily-2026-09-16 这样的名字可以辅助保留策略,还应结合创建时间、属性与备份记录;名字本身不是删除资格的证明。zfs list -t snapshot -o name,used,refer,creation -s creation 用于观察快照及空间统计,真正清理之前还要检查依赖和 hold。自动命名也可以被可靠管理,关键是保留规则有明确、可追溯的依据。
空间在哪里:zfs list -o space zfs list -o space tank/data 选择 NAME、AVAIL、USED、USEDSNAP、USEDDS、USEDREFRESERV 与 USEDCHILD,不包含 REFER。USED 分解为四项 used-by 类别,不能写成 REFER 加 USEDSNAP。REFER 表示数据集可访问的数据,其中也可能有共享块。单份快照的 USED 统计其独有引用,删除其他快照还会改变这个值,所以逐份相加不等于整体可回收空间。
...