遥测驱动开发
摘要
Smart Rent 的 Noah 为 Elixir 提出「遥测驱动开发」:先用 OpenTelemetry 埋点,再上线,用 84.8 万台 Nerves 网关的真实数据取代拍脑袋。
<p>仓库:<a href="https://github.com/Nezteb/telemetry-driven-development" rel="ugc">https://github.com/Nezteb/telemetry-driven-development</a></p>
<p><a href="https://lobste.rs/s/bonwlu/telemetry_driven_development">评论</a></p>
查看缓存全文
缓存时间: 2026/04/22 19:04
TL;DR:用数据取代猜测——先加遥测再上线,这样你就能随时知道 Elixir 系统在生产中到底在干什么。
## 遥测驱动开发
> “系统的目的就是它实际做的事。”
Noah(GitHub/Slack NZTeb)在一次 Elixir 聚会上提出“遥测驱动开发”(TDD)。传统 TDD 是“先写测试,看它失败,再让它通过”;新的 TDD 则是“让系统告诉你它在干什么,再决定算不算完成”。下文浓缩了他的分享、现场演示,以及他在 Smart Rent 运维 84.8 万台 Nerves 网关的经验。
## 为什么传统 TDD 已不够
单元测试只能证明代码在开发者笔记本上能跑,而生产环境是分布式、并发、资源饥饿的修罗场。350 万台 IoT 设备(门锁、恒温器、漏水传感器)同时冲击平台——每秒 231 个新 TLS 连接、3600 条 MQTT 消息、7000 行 DB 写入——靠猜只会 outages。遥测成了唯一可靠的反馈闭环。
## “遥测”到底指什么
借自 1950 年代硬件,遥测字面意思是“远程测量”。想象水管上的压力表:你看不见水,但能读刻度。在软件里我们导出三类信号:
1. **追踪(Traces)**——请求级别的因果图
2. **指标(Metrics)**——随时间预聚合的数值
3. **日志(Logs)**——带上下文的离散事件
OpenTelemetry(OTel)把三者打包成厂商无关的规范。Elixir 现状:
- **追踪**——已稳定
- **指标与日志**——beta;可通过 `:telemetry` + `:telemetry_metrics` 桥接使用
## 你会遇到的库
| 包 | 用途 |
|---------|---------|
| `opentelemetry_api` | Span/指标/日志 API |
| `opentelemetry` | SDK 实现 |
| `opentelemetry_exporter` | GRPC/HTTP 导出到 OTel Collector |
| `opentelemetry_telemetry` | 把 `:telemetry` 事件桥接成 OTel span |
| `telemetry` | BEAM 原生分发库(1.0 发布于 2021-07-03) |
## 一条命令启动本地可观测栈
克隆仓库,运行:
```bash
docker compose up
```
Grafana 在 `http://localhost:3000` 启动,自带:
- **Loki**——日志
- **Tempo**——追踪
- **Mimir**——指标
缩写 LGTM 是故意玩梗——“looks good to me”,每个 PR 梦寐以求的评论。
## 演示项目走读
最小 Phoenix 应用 + GenServer 工作进程:
1. 每 10 秒调度一次,调用 `cpu_work()` 和 `io_work()`
2. 每个函数用 `OpenTelemetry.Tracer.with_span/3` 开启 OTel span
3. 属性(`cpu_ms`、`bytes_read`)挂到 span 上
4. Collector 接收,Tempo 存储,Grafana 展示瀑布图
改代码、热重载、刷新 Grafana——反馈 < 5 秒,零云成本。
## 针对遥测写测试
在 `MIX_ENV=test` 挂处理器:
```elixir
:telemetry.attach(
"test-handler",
[:my_app, :work, :stop],
fn _event, measurements, _meta, pid ->
send(pid, {:telemetry, measurements})
end,
self()
)
```
然后断言:
```elixir
assert_receive {:telemetry, %{cpu_ms: ms}} when ms > 0
```
再也不用 flaky 的 sleep 或 `assert_process` 黑科技;事件本身就是同步原语。
## 环境专属管道
| 环境 | 策略 |
|-------------|----------|
| dev | 导出到 stdout-spans,采样率 100 % |
| test | 进程内挂处理器,不走网络 |
| staging | 发到 staging collector,采样率 10 % |
| prod | 发到区域 collector,采样率 1 %,基于头部的概率采样 |
runtime.exs 读取 `OTEL_EXPORTER_ENDPOINT` 和 `OTEL_TRACES_SAMPLER_ARG`,同一容器镜像到处运行。
## 持续集成技巧
CI 任务:
```bash
docker compose -f ci.docker-compose.yml up --exit-code-from test
```
栈启动,测试在完整可观测环境下跑,栈自行销毁。产物:JUnit XML *和* 追踪 JSON,方便事后验尸。
## 证明值得投入的生产数字
Smart Rent 车队:
- 84.8 万个 Erlang 节点(Nerves 网关)
- 350 万叶子设备(每网关 4 个)
- P99 端到端延迟 350 ms
- 每秒 7000 行 DB 变更
没有 trace ID,运维会被非结构化日志淹没;有了它,支持工单变成“贴一下 trace ID,我们立刻给你看具体网关、固件版本、执行计划”。
## Elixir + OTel 当前坑点
1. 指标与日志 API 仍在变动;可能有小破环
2. 高吞吐服务需仔细调采样,否则内存爆炸
3. BEAM 调度器与 reduction→CPU 映射尚无标准语义约定
4. 跨节点传播需在 MQTT/Phoenix Channel 里手动解析 `traceparent`
社区活跃;关注 erlang-otel,日志域 PR 预计下版合并。
## 自家项目起飞清单
1. 所有应用加 `:opentelemetry_api`(只加 API,库不带 SDK)
2. 业务函数包 `with_span` 或 `:telemetry.execute`
3. 本地用 docker-compose LGTM 栈导出
4. 至少写一条测试断言遥测 payload
5. 部署到 staging,打开 Grafana,问:**“我能看到一个请求的完整故事吗?”**
6. 迭代到仪表盘比 shell 先给出答案为止
## 经验法则收尾
画不出图,就甭吐槽。先交付可观测性;指标、追踪、日志都说 OK,功能才算完。
相似文章
Elixir 应用优化之旅
一位开发者分享了优化 Elixir 应用的经验与教训,重点介绍了针对 Postgres 连接池工具 Ultravisor 的性能改进。文章涵盖了使用火焰图、调用追踪等性能分析技术,以及 eFlambè 和 tprof 等工具。
首次实现本地真实编程工作
开发者借助 Qwen3.6-35B 4-bit MLX 模型与 pi.dev 工具,在当前硬件上实现了高效的本地智能体编程,顺利完成了实际项目工单。
Gemma-4微调与部署中的挑战与磨难 [P]
一个机器学习团队记录了在微调并部署Gemma-4过程中遇到的实际挑战,包括与PEFT、SFTTrainer、DeepSpeed ZeRO-3的不兼容,以及缺乏运行时LoRA服务支持,并提供了每个问题的解决方法。
你这周在做什么?
一位开发者分享了可嵌入类型化语言 Ekto 的最新进展,该语言受 Lua、Koka 和 Erlang 启发,并讨论了为 Casper VM 实现引用计数、内存管理及有界续体时面临的挑战。
DELIGHT – 自托管AI工程自动驾驶仪:本地大模型 + 浏览器农场 + 仓库图谱 + 点对点计算
DELIGHT 是一个自托管的AI工程自动驾驶仪,它结合了本地大语言模型、浏览器农场和语义仓库图谱,可在不将数据发送到云端的情况下自动完成开发任务。