TGViewer
开发者工具箱|编程·开发工具·资源 开发者工具箱|编程·开发工具·资源 @devtoolboxhub · 780 subscribers
Post #1582 4
Optimux 开源:按需图片视频优化服务

Optimux 是一个 Go 服务,可即时调整图片和视频大小、重新编码并流式传输——通过一个 URL 参数即可完成。该项目已在内部运行一段时间,现以 AGPL-3.0 协议开源。开源的核心动机是底层 worker 池:决定任意时刻有多少 goroutine 在处理图片的部分,作者认为这是代码库中最有趣的部分,值得从私有仓库中拿出来。

作者从经典的 Job/Dispatcher 模式起步,该模式源自 Marcio Castilho 的《用 Golang 处理每分钟 100 万请求》一文。该模式用有界池替代无界 goroutine 生成,曾将系统从约 100 台 EC2 实例缩减到 4 台 c4.Large,处理接近每分钟百万请求。但那个工作负载是向 S3 上传 JSON——几乎全是 I/O 等待,几乎不占 CPU。换成图片缩放后,模式失效了。

pprof 阻塞分析显示 67.99% 的时间花在 chanrecv1 上——worker 在等任务,dispatcher 在等 worker 归还通道。当单个任务变成数百毫秒的 CPU 密集型图片处理时,通道编排开销不再隐形。目标吞吐 8.6rps,该模式只能到 2.0xrps。作者直接推倒重来。

从同步基线逐变量重建:无 worker 内联处理 2.9rps,已超过 dispatcher 模式;单通道单 worker 2.1rps,比不并发还差;单队列 4 worker 3.15rps,平均延迟 1.37s;双队列(拉取/处理分离)各 4 worker 3.07rps,但延迟分布明显平滑——拉取源图 40-100ms,libvips 处理约 700ms,两阶段服务时间差异巨大,共享队列存在阻抗失配。加权 2 拉取/4 处理反而更差。另尝试了需求驱动型生产者/消费者(类似 Elixir GenStage),协调成本高于收益,未采用。独立于 worker 数量的优化:流式输出编码字节而非整体缓冲,吞吐 3.28rps,延迟降至 1.31s。

核心结论:Job/Dispatcher 模式并非错误,而是适用于外部资源(S3、网络、数据库)受限、自身 CPU 基本空闲的工作负载。libvips 图片处理则相反:受限资源是自身 CPU 和内存,固定 MAX_WORKERS 无法跟踪突发与空闲交替的队列。EC2 上 iostat 显示 CPU 在 80%+ 与 40% 间摆动,%steal 峰值达 8.74%。worker 数量本身必须是活变量。

结构差异:dispatcher 模式用 N+1 个通道(池通道加每 worker 通道)和 dispatcher goroutine 表达可用性;DynamicScaler 所有 worker 直接从一个共享 Queue chan T 拉取,无 dispatcher、无每 worker 通道、无显式"空闲"消息——可用性即"当前阻塞在 Queue 接收上",由 Go 运行时仲裁。另一差异:dispatcher 的 MAX_WORKERS 启动时读一次环境变量,永不变化;DynamicScaler 的每个 WorkerSlot 可标记 Retiring,需有竞态安全的退出路径,因为该工作负载下"当前正确 worker 数"本身就是被争用的资源。


DynamicScaler[T] 是基于队列压力伸缩的泛型 worker 池,类型参数为任务类型,同一 scaler 可独立驱动图片、视频、批处理任务。注册表是单一有序切片,按创建顺序追加,尾部即最新 worker,LIFO 退休策略因此是确定性扫描。活跃数通过遍历计算得出,无独立计数器,不存在状态失同步问题。退休发生在空闲边界:worker 在 select 中阻塞等待取消、退休请求或任务队列,无 default 分支;退休请求在 worker 空闲瞬间到达。默认 2 秒宽限期,超时则取消 context 兜底,但注册表移除只发生在真实退出通知时。伸缩决策有冷却期(默认 30 秒),因瞬时队列长度在突发流量下噪声大;唯一绕过冷却的情况是活跃 worker 低于 MinWorkers 时立即补充,防止崩溃风暴导致池长时间空转。

未测试项:Fiber 的 prefork 模式(SO_REUSEPORT 运行 N 个 OS 进程,内核负载均衡连接)。作者判断不会提升吞吐——所有池形态实验吞吐仅在 2.1 到 3.15rps 间波动,真实成本是每图约 700ms 的 libvips CPU 工作,非调度或通道开销。prefork 改变的是进程数而非核心数。预期收益在尾部延迟和故障隔离:单进程 GC 暂停或 OOM 不会拖垮其他 fork。

后续计划:等待 govips 合并原生流式支持(PR davidbyttow/govips#539),当前每阶段都整体缓冲为 []byte;HTTP/2 跨流优先级调度器——Go 的 x/net/http2.WriteScheduler 已被维护者弃用,nginx 的优先级树私有,fasthttp 解析 PRIORITY 帧后不再使用,真正先例是 H2O(O(1) 但不公平)、nghttp2(WFQ 但 O(n))、Tempesta FW(WFQ + HAProxy ebtree),计划基于 x/net/http2.Framer 从零实现;扩容信号需区分真实突发与 libvips 线程池饱和——govips 的 ConcurrencyLevel 实际只写不读,RuntimeStats 只统计累计操作数,可行方案是原子 in-flight 计数器配合 VipsConcurrency 门控。


仓库:github.com/go-batteries/optimux

#开发者 #工具 #Optimux #Go #Golang #WorkerPool #libvips #图片处理 #视频处理 #AGPL
@DevToolboxHub
More from @devtoolboxhub
  1. Sep 26, 2026PostgreSQL 复制槽上限参数 max_replication_slots 解析 max_replication_slots 决定共享内存中复制槽数组的长度,仅此而已。它不决…
  2. Sep 26, 2026五仓库架构踩坑:Gitlink 与双 CI Flude 团队复盘了多仓库架构的实践代价。项目从第一分钟起就选择拆分成独立仓库,Pipeline、engine、design-docs…
  3. Sep 26, 2026Xeno Core:TypeScript 后端架构框架 Node.js 复杂系统的架构选型往往决定代码的长期命运。开发者常在两条路之间纠结:要么依赖重度使用实验性装饰器和反射(如…
  4. Sep 25, 2026用 SLO 给 AI Agent 的行为定个预算 Grafana Labs 提出把可靠性工程里的错误预算(error budget)思路用到 AI Agent 上。延迟、token…
  5. Sep 25, 2026Xcode 27.2 改用 JSON 项目格式 Xcode 27.2 用基于 JSON 的 project.xcproj 取代了沿用多年的 project.pbxproj。新建项目…
  6. Sep 25, 2026PostgreSQL 的 max_prepared_transactions 该不该开 prepared transaction 是脱离会话独立存在的两阶段提交事务。执行 PREP…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →