欢迎访问

专注系统底层与高性能服务开发,持续记录 Go / Rust / C++ / 云原生的一线实践。

从源码细节到线上治理,尽量少空话,多代码。

Go Worker Pool:饱和控制与降级策略

背景 很多 Go 服务都会做 worker pool,但只做“固定 worker 数量”还不够。 真正决定稳定性的,是池子打满后怎么办。 基础模型 type Job func(context.Context) error type Pool struct { jobs chan Job } func NewPool(workerN, queueN int) *Pool { p := &Pool{jobs: make(chan Job, queueN)} for i := 0; i < workerN; i++ { go func() { for job := range p.jobs { _ = job(context.Background()) } }() } return p } 关键点:拒绝策略 func (p *Pool) TrySubmit(job Job) error { select { case p.jobs <- job: return nil default: return errors.New("worker pool saturated") } } 当队列已满时,快速失败通常比无限排队更可控。 总结 worker pool 的目标不是“永不拒绝”,而是让系统在高峰时有可预测行为。 配合监控和限流,才能构成完整保护链路。 可用系统的关键是边界清晰,而不是无限扛压。

2026年4月27日 · 1 分钟 · BvBeJ

Go 服务零停机数据库 Schema 迁移实战

问题画像 线上最危险的不是“改表”,而是“业务和改表耦合发布”:应用已读新字段,但 DDL 还没完成;或者 DDL 成功了,回滚却读不懂新结构。 迁移模型:Expand -> Migrate -> Contract Expand:只做向后兼容变更(加列、加索引、双写入口)。 Migrate:后台回填与数据校验,逐步把读流量切向新字段。 Contract:确认没有旧路径后,再删旧列/旧索引。 Go 侧发布顺序 // 第一步发布:双写 + 旧读优先 func SaveOrder(ctx context.Context, o Order) error { row := model.OrderRow{ ID: o.ID, // old_total 保持兼容 OldTotal: o.Total, // new_total 为新字段 NewTotal: decimalPtr(o.Total), } return repo.Upsert(ctx, row) } // 第二步发布:读新字段,失败回退旧字段 func LoadOrderTotal(r model.OrderRow) decimal.Decimal { if r.NewTotal != nil { return *r.NewTotal } return r.OldTotal } 回填策略 按主键区间分页,避免大事务长时间占锁。 每批记录校验 checksum,把异常写入死信表。 回填作业限速,和在线业务共享数据库 QPS 预算。 观测与止损 指标:回填进度、回填错误率、慢 SQL、锁等待时间。 开关:双写开关、读路径开关、回填暂停开关。 预案:任意阶段都能回到“旧读+旧写”。 常见坑 在 Expand 阶段做非兼容 DDL(例如直接改列类型)。 回填任务不幂等,重跑会污染数据。 只关注“DDL 成功”,忽略“业务一致性成功”。 小结 零停机迁移不是一条 SQL,而是一条发布流水线。把数据库变更当成可灰度、可观测、可回滚的工程流程,风险会从“不可控事故”变成“可管理演进”。

2026年4月27日 · 1 分钟 · BvBeJ

Rust Axum 中间件分层:认证、限流、追踪怎么排

背景 Axum 用起来很顺手,但真实项目里经常出现一个隐性问题: 中间件越加越多 顺序靠经验调整 出故障时不知道是哪个层拦住了请求 推荐分层 从外到内通常建议: request id / tracing panic recover 全局限流 认证鉴权 业务路由 let app = Router::new() .route("/api/user", get(get_user)) .layer(TraceLayer::new_for_http()) .layer(RequestIdLayer::new()) .layer(TimeoutLayer::new(Duration::from_secs(2))); 总结 中间件不是“越多越安全”,而是“每层职责清晰、顺序可解释”。 发布前做一次链路压测,往往能提前发现大部分分层问题。 架构清晰的系统,异常路径也应该清晰。

2026年4月27日 · 1 分钟 · BvBeJ

Kubernetes 资源配额:性能、稳定性与成本平衡

背景 很多集群的问题,最后都落在资源配置上: requests 太高,调度不进去 requests 太低,服务被抢占导致抖动 limits 太严,CPU 被频繁 throttling 基本策略 先测业务基线,再填 requests CPU limits 结合业务特性决定是否设置 内存 limits 必须有,否则容易把节点拖垮 resources: requests: cpu: "300m" memory: "512Mi" limits: cpu: "1000m" memory: "1Gi" 观测指标 建议持续看: container_cpu_cfs_throttled_seconds_total container_memory_working_set_bytes Pod OOMKilled 次数 总结 资源参数不是一次性配置,而是持续调优过程。 用监控数据驱动参数调整,比凭经验拍数值更靠谱。 资源治理本质是容量治理,最终影响的是可用性和成本。

2026年4月26日 · 1 分钟 · BvBeJ

Vue3 大表单工程化:状态拆分与校验治理

背景 后台系统里最难维护的页面之一,就是大表单: 字段多 联动多 校验规则多 草稿和提交逻辑分叉 如果状态设计不清晰,后期改一个字段就会牵动全局。 实用拆分 表单值与 UI 状态分离 同步校验与异步校验分离 页面状态按分区拆 composable const formValue = reactive({ name: '', email: '', company: '', }) const uiState = reactive({ submitting: false, dirty: false, activeTab: 'basic', }) 防止无效重渲染 大对象不要全量深监听 使用按字段 watch 拆分子组件隔离更新范围 watch( [() => formValue.email, () => formValue.company], () => { validateContactFields() } ) 总结 大表单的关键不是“怎么写更快”,而是“怎么改不炸”。 前期做好状态边界,后期迭代成本会低很多。 复杂页面最终拼的是可维护性,不是首版速度。

2026年4月26日 · 1 分钟 · BvBeJ

C++ 网络模型:Reactor 与 Proactor 怎么选

背景 高并发网络服务里,Reactor 和 Proactor 是两个绕不开的模型。 很多争论其实都在混淆一个问题: 你是在等“就绪事件” 还是在等“异步操作完成事件” 核心差异 Reactor 内核告诉你哪个 fd 就绪 业务线程自己 read/write Proactor 业务先发起异步 IO 内核完成后通知你结果 在 Linux 常规栈里,很多框架本质上还是 Reactor;在 io_uring 这类能力更强的接口下,Proactor 风格更容易落地。 简化示意 // Reactor 风格:事件到达后主动读 void onReadable(int fd) { char buf[4096]; ssize_t n = ::read(fd, buf, sizeof(buf)); if (n > 0) { handleRequest(buf, n); } } // Proactor 风格:提交后等待完成回调 void submitRead(Connection* c) { io_uring_prep_read(c->sqe, c->fd, c->buf, c->cap, 0); } void onReadComplete(Connection* c, int res) { if (res > 0) { handleRequest(c->buf, res); } } 选择建议 现有生态以 epoll 为主:优先 Reactor 追求极致吞吐且能接受复杂度:考虑 Proactor/io_uring 团队经验不足时,别为“更先进”盲目迁移 总结 模型没有绝对优劣,只有场景匹配。 ...

2026年4月25日 · 1 分钟 · BvBeJ

Docker + BuildKit:CI 构建提速实战

背景 CI 最容易浪费时间的阶段之一,就是镜像构建。 常见慢点: 每次都重新下载依赖 layer 顺序不合理导致缓存失效 多架构构建没有缓存复用 BuildKit 的关键能力 cache mount 并行构建 远程缓存导入导出 # syntax=docker/dockerfile:1.7 FROM golang:1.22-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod go mod download COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux go build -o app . GitHub Actions 示例 - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v6 with: context: . push: true tags: ghcr.io/org/app:latest cache-from: type=registry,ref=ghcr.io/org/app:buildcache cache-to: type=registry,ref=ghcr.io/org/app:buildcache,mode=max 总结 提速的核心不是换机器,而是设计好缓存路径。 构建系统越早做缓存治理,团队长期效率越高。 CI 慢不是宿命,很多时候只是缓存没被认真设计。

2026年4月25日 · 1 分钟 · BvBeJ

Linux 网络 IO:epoll 与 io_uring 选型笔记

先说结论 多数业务服务里,epoll 依然是稳妥选择。io_uring 在某些高并发 IO 场景能更快,但复杂度也更高。 epoll 的优势 生态成熟,排障资料多 编程模型稳定 与现有网络库兼容性好 对于大部分 API 服务,epoll 足够用。 io_uring 的价值点 减少系统调用次数 提升批量提交与完成处理效率 在特定负载下降低延迟 但它要求你理解提交队列、完成队列、内核版本差异等细节。 选型建议 先压测当前 epoll 方案,定位真实瓶颈 若瓶颈明确在 IO 提交/完成路径,再评估 io_uring 预留回退方案,避免一次性全量切换 团队视角 技术选型不只看 benchmark,还要看: 团队掌握程度 线上问题可定位性 长期维护成本 小结 新能力值得关注,但架构决策应以稳定收益为中心。对大多数团队来说,逐步引入比激进替换更现实。

2026年4月25日 · 1 分钟 · BvBeJ

OpenTelemetry 指标治理:控制基数比盲目埋点更重要

指标系统最怕什么 很多团队一开始埋点很积极,过一阵就发现存储暴涨、查询变慢。高基数标签通常是罪魁祸首。 典型高危标签: user_id request_id 完整 URL 路径 动态错误信息 设计原则 指标看聚合趋势,不看单请求细节 单请求细节放到 trace 或日志 标签值集合必须可控 一个可行做法 http.route 用模板路由,如 /users/:id 错误类型归类为固定枚举 把业务自定义标签做白名单审核 工程措施 在 SDK 层做标签拦截 给 metric pipeline 增加 cardinality 预算告警 定期扫描 top N label pairs 小结 可观测性不是“埋得越多越好”。指标、日志、追踪各司其职,系统才能长期稳定运行。

2026年4月25日 · 1 分钟 · BvBeJ

C++ 协程与 IO 调度:从回调地狱到结构化异步

为什么协程不是魔法 C++20 协程让异步代码写起来像同步,但它只解决语法组织,不自动提供高性能调度。 真正决定上限的是: 任务队列策略 IO 事件分发 唤醒与线程绑定方式 一个简化示意 task<int> fetch_and_parse(socket& s) { auto buf = co_await async_read(s); co_return parse(buf); } 这段很优雅,但背后必须有 executor 驱动 co_await 的挂起与恢复。 调度层常见坑 所有任务丢进一个全局队列,热点锁竞争严重 IO 线程和计算线程混跑,尾延迟放大 协程对象生命周期管理混乱 实践建议 IO 与 CPU 密集任务分池 减少跨线程恢复,尽量本地唤醒 在高频路径避免动态分配 结语 协程把异步写法变自然,但高性能系统仍然遵循老规律:调度、内存、锁竞争。语法升级不能替代架构设计。

2026年4月24日 · 1 分钟 · BvBeJ