专注系统底层与高性能服务开发,持续记录 Go / Rust / C++ / 云原生的一线实践。
从源码细节到线上治理,尽量少空话,多代码。
专注系统底层与高性能服务开发,持续记录 Go / Rust / C++ / 云原生的一线实践。
从源码细节到线上治理,尽量少空话,多代码。
背景 大表格卡顿通常来自渲染数量、响应式开销或频繁派生计算。先区分瓶颈,再决定虚拟列表、分页还是数据模型调整。 核心判断 只渲染可见窗口并保留适当 overscan。 对不可变大数组使用 shallowRef,替换数组触发更新。 行 key 稳定,昂贵格式化与计算按实际依赖缓存。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 const rows = shallowRef<Row[]>([]); const visibleRows = computed(() => rows.value.slice(start.value, end.value)); 边界与误区 切片只是虚拟列表的局部逻辑,还要维护滚动占位、行高和焦点。shallowRef 的深层原地修改不会自动更新,必须遵守不可变替换约定。 验证方式 在目标移动设备测试大数据滚动、排序和选中状态;记录长任务与内存,同时验证键盘导航、可访问性和屏幕尺寸变化。 小结 使用虚拟列表、稳定 key 和服务端筛选,避免把整份深层对象传给每个单元格。
背景 一个函数看起来复杂,不代表它是热点。优化之前需要固定请求模型,让采样结果可以解释和复现。 核心判断 记录二进制版本、构建参数、负载与 CPU 拓扑。 先看调用树和时间占比,再按假设做一次最小改动。 同步观察吞吐、尾延迟与分配,避免局部收益换来整体退化。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 perf record -F 99 -g -- ./service --scenario=steady; perf report --stdio | head -80 边界与误区 无符号或不可靠的栈展开会让火焰图失真。perf 权限、架构和事件支持不同,容器采样前要确认访问范围。 验证方式 同样场景至少重复多次,比较稳定区间;保留原始 perf.data 与符号,让他人能复查,而不是只留下截图。 小结 固定负载、编译选项和采样窗口,先用系统指标定位再用 profiler 解释热点。
背景 解析网络头时返回输入切片可以避免复制,但切片也会绑定输入缓冲区的生命周期。跨队列长期保存时,零拷贝可能变成资源占用。 核心判断 短生命周期解析返回借用切片,长期保存显式转 owned。 按原始协议长度检查边界,未找到分隔符不能无限积累缓冲。 同时测 CPU、分配次数和保留内存,而不是只统计 memcpy。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 fn parse_header(buf: &[u8]) -> Option<&[u8]> { let end = buf.iter().position(|&b| b == b'\n')?; Some(&buf[..end]) } 边界与误区 一个小切片若依赖大型 backing buffer,可能阻止整块缓冲释放。示例只定位换行,不执行编码校验,也不保证内容是合法完整协议头。 验证方式 覆盖无换行、空头、大包和跨读取边界,比较复制方案与借用方案的峰值内存;让生命周期约束在编译期保持可见。 小结 先建立拥有数据的正确性基线,再在测量证明有收益的局部引入切片和共享缓冲。
背景 依赖变慢时每层都重试三次,原本一次请求可能被放大成几十次。重试需要全链路预算,而不是固定循环次数。 核心判断 仅重试明确的暂时失败,并确认操作幂等。 退避加入随机抖动,同时受入口 deadline 限制。 只在一层负责重试,记录尝试次数与预算耗尽。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 var lastErr error for attempt := 0; attempt < 3; attempt++ { if err := ctx.Err(); err != nil { return err } lastErr = call(ctx) if lastErr == nil || !retryable(lastErr) { return lastErr } if attempt < 2 { if err := sleep(ctx, backoff(attempt)); err != nil { return err } } } return lastErr 边界与误区 示例中 call、retryable、sleep 和 backoff 是业务适配函数,sleep 必须响应取消。最后一次失败后不再等待,错误要保留最后一次调用结果。 验证方式 注入超时、永久拒绝和短暂故障,核对实际调用数;多实例同时失败时,检查抖动是否减少同步重试峰值。 小结 以请求 deadline 为总边界,对不可重试错误立即返回,并限制链路放大倍数。
背景 Monorepo 解决了代码放在哪里,却不会自动解决谁可以依赖谁。一个工具包反向依赖业务层,很快会让整个仓库无法独立演进。 核心判断 画出允许的依赖方向,公共入口仅导出稳定契约。 项目引用描述编译依赖,包 exports 与 lint 管理实际导入路径。 构建缓存键包含源码、依赖版本和配置输入。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 { "references": [{ "path": "../domain" }], "compilerOptions": { "composite": true, "declaration": true } } 边界与误区 project references 不等于访问控制,也不会自动阻止跨包深层导入。composite 项目的输出目录和包解析必须与运行方式匹配。 验证方式 新增一个故意违规的导入,确认 lint 能拦截;修改底层包,检查受影响包重建,干净环境仍能按依赖顺序完成构建。 小结 每个包只暴露稳定入口,规定依赖方向并阻止通过深层路径绕过边界。
背景 表持续更新之后,查询变慢和空间增长不一定靠加索引解决。旧版本清理、统计信息和长事务都可能影响维护效果。 核心判断 观察死元组估计、autovacuum 时间、事务年龄与维护进度。 区分 VACUUM 清理旧版本和 ANALYZE 更新统计信息的职责。 优先缩短长事务,再按高变化表调整阈值与维护预算。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 SELECT relname, n_live_tup, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 20; 边界与误区 n_dead_tup 是估计值,不是精确计数。普通 VACUUM 通常不把空间归还操作系统;VACUUM FULL 会重写并需要强锁,不能当作无风险清理。 验证方式 对持续更新表观察维护前后的大小、计划和延迟;检查被长事务阻挡的清理效果,并评估维护对磁盘 I/O 的影响。 小结 区分 autovacuum 未跟上、长事务阻塞和表设计放大,再按表配置阈值。
背景 半夜响起一条“CPU 高”告警,如果不知道用户是否受影响、应该做什么,它只会增加疲劳。告警必须对应可执行动作。 核心判断 定义影响范围、持续时间和责任团队。 优先使用错误、延迟与预算消耗等用户相关信号。 绑定 runbook,说明排查入口、临时缓解、升级条件和恢复标准。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 - alert: ApiHighErrorRate expr: error_rate > 0.02 for: 10m labels: { severity: page } annotations: { runbook: "runbooks/api-5xx" } 边界与误区 示例的 error_rate 是需要预先定义的 recording rule,不是内置指标。低流量分母与缺失数据策略要明确,阈值不能直接照搬。 验证方式 回放真实或合成事故,核对触发、恢复与通知路由。让未参与编写的人只依据 runbook 操作一次,检查能否完成有效缓解。 小结 告警必须说明症状、影响、负责人、时间窗口和排查入口,避免只堆数量。
背景 监听端口存在,却仍有客户端连接超时,问题可能出在握手或应用接收速度。不同 TCP 队列反映不同阶段,不能混为一谈。 核心判断 区分握手中的请求队列与等待 accept 的连接队列。 把 ss 的监听队列与 ListenOverflows、ListenDrops 对照。 同时检查应用 accept 速度、FD 限额和调度延迟。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 ss -lntp; ss -s; nstat -az | grep -E 'Listen|Syncookies|Retrans'; cat /proc/net/netstat | grep Listen 边界与误区 提高 backlog 只是增加缓冲,不能解决长期接收能力不足。Syncookies 也不是业务容量扩展机制,队列含义要按 socket 状态解读。 验证方式 在受控环境制造连接突发,观察计数器增量和客户端连接耗时;若队列稳定增长,先查服务线程和资源限额,再逐项调参。 小结 把 SYN 队列、accept 队列、socket 状态和应用 accept 速率放在同一时间轴。
背景 CPU 很低但请求队列越来越长时,仅靠 CPU 扩容可能不够。HPA 指标必须与实际瓶颈和单副本处理能力建立关系。 核心判断 CPU utilization 依赖合理的 requests,先确认分母与指标可用性。 队列型任务考虑 backlog 或处理延迟等业务信号。 设定扩缩容范围、稳定窗口和依赖容量,避免副本数增长压垮数据库。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 metrics: - type: Resource resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } } behavior: { scaleDown: { stabilizationWindowSeconds: 300 } } 边界与误区 配置是 HPA spec 片段,还需 scaleTargetRef 与副本范围。扩容不会即时完成,镜像拉取和启动预热都属于响应延迟。 验证方式 用阶梯流量观察期望副本、实际就绪数、队列与 p99;再撤掉负载,确认缩容没有导致反复抖动。 小结 选择与瓶颈和 SLO 相关的指标,设置稳定窗口和最大副本避免扩缩容震荡。
背景 同一镜像如果因环境不同而重新构建,就很难证明测试与生产运行的是同一个产物。应把部署配置与不可变应用分离。 核心判断 镜像只包含程序和非敏感静态资源,环境参数在启动时注入。 配置加载后统一校验,必要值缺失应直接失败。 记录镜像 digest 和配置版本,回滚时同时检查二者兼容性。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 FROM gcr.io/distroless/static-debian12 COPY app /app ENTRYPOINT ["/app"] 边界与误区 示例假定 Go 等静态二进制可在该运行镜像中执行;动态链接、CA、时区和文件权限需单独确认。不要把密钥 COPY 进镜像层。 验证方式 同一个 digest 分别在测试与生产配置下启动,核对生效值和日志脱敏。验证配置缺失、不可写目录以及运行用户权限。 小结 镜像只包含程序和安全默认值,环境差异通过运行时配置注入并在启动时校验。