go-backend-handbook 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/11-monitoring.md +269 -7
  2. package/package.json +1 -1
package/11-monitoring.md CHANGED
@@ -135,15 +135,277 @@ groups:
135
135
  - **有 `for`**:避免瞬时抖动误报;
136
136
  - **定期清理**:长期不响的告警要复核。
137
137
 
138
- ## 11.6 看板(Grafana)怎么摆
138
+ ## 11.6 看板怎么摆:不是凑五个标题,而是按排障顺序组织
139
139
 
140
- 第一行:总览(QPS、错误率、P99、实例数)
141
- 第二行:按接口拆分的延迟和错误
142
- 第三行:依赖(MySQL/Redis/MQ)健康
143
- 第四行:资源(CPU/内存/GC/goroutine)
144
- 第五行:业务指标
140
+ Grafana 里的 **Row** 是“折叠分组”,不是一种特殊图表。你可以把一行理解成一组面板:
145
141
 
146
- **看板要给值班人看**:一眼看出"是不是挂了、挂了多久、影响哪些接口"。
142
+ ```text
143
+ Dashboard
144
+ ├── Row 1:总览(先判断有没有事故)
145
+ ├── Row 2:接口(定位受影响的路由)
146
+ ├── Row 3:依赖(查找外部瓶颈)
147
+ ├── Row 4:资源(查找进程/机器瓶颈)
148
+ └── Row 5:业务(确认用户结果和损失)
149
+ ```
150
+
151
+ 这五行的顺序是值班人的阅读顺序,不是随便排的:
152
+
153
+ ```text
154
+ 用户有没有受影响
155
+ -> 哪个接口受影响
156
+ -> 是不是数据库/Redis/MQ 的问题
157
+ -> 是不是 CPU/内存/GC/协程的问题
158
+ -> 最终影响多少业务结果
159
+ ```
160
+
161
+ ### 11.6.1 第一行:总览,10 秒内判断“是不是挂了”
162
+
163
+ 第一行只放 4 个核心面板,不要塞十几个图。
164
+
165
+ | 面板 | 图类型 | PromQL 示例 | 单位 | 含义 |
166
+ |---|---|---|---|---|
167
+ | 总 QPS | Stat / Time series | `sum(rate(http_requests_total{job="minicoin"}[$__rate_interval]))` | req/s | 当前服务每秒处理多少请求 |
168
+ | 5xx 错误率 | Stat / Time series | `sum(rate(http_requests_total{job="minicoin",status=~"5.."}[$__rate_interval])) / sum(rate(http_requests_total{job="minicoin"}[$__rate_interval]))` | percent | 用户看到服务端错误的比例 |
169
+ | P99 延迟 | Stat / Time series | `histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="minicoin"}[$__rate_interval])))` | seconds | 99% 请求都在这个时间内完成;最慢的 1% 可能更慢 |
170
+ | 存活实例数 | Stat | `count(up{job="minicoin"} == 1)` | 个 | 当前有多少实例在线 |
171
+
172
+ **为什么总览先放这四个?**
173
+
174
+ - QPS 突然变成 0:可能入口挂了、流量没进来、服务完全不可用;
175
+ - 错误率上升:用户已经开始失败;
176
+ - P99 上升:用户开始变慢;
177
+ - 实例数下降:先查发布、崩溃、探针和资源。
178
+
179
+ **面板要看趋势,不要只看一个数字。** Grafana 的 Stat 面板要开启 sparkline 或同时放一个 Time series,否则你只能看到“现在 300ms”,不知道它是从 100ms 涨上来的,还是一直如此。
180
+
181
+ #### 错误率为什么不是“错误数 / 请求数”
182
+
183
+ `http_requests_total` 是 Counter,只增不减。要在时间窗口内先算增长率:
184
+
185
+ ```promql
186
+ sum(rate(http_requests_total{status=~"5.."}[5m]))
187
+ /
188
+ sum(rate(http_requests_total[5m]))
189
+ ```
190
+
191
+ 分子和分母必须是同一时间范围、同一组实例。否则得到的是“历史累计错误数 / 当前请求数”,没有意义。
192
+
193
+ #### P99 为什么不是平均值
194
+
195
+ 假设 100 个请求:
196
+
197
+ ```text
198
+ 99 个请求 20ms
199
+ 1 个请求 5s
200
+ ```
201
+
202
+ 平均值大约 `69.8ms`,看起来还能接受;但 P99 是 5s,说明有真实用户在等 5 秒。**平均值会掩盖长尾,后端看延迟必须看 P95/P99,必要时看 P999。**
203
+
204
+ ### 11.6.2 第二行:按接口拆,定位“到底哪个接口出问题”
205
+
206
+ 第一行告诉你“系统有问题”,第二行告诉你“问题在哪条路由”。
207
+
208
+ 至少放:
209
+
210
+ 1. **各接口 QPS**:`sum by (route) (rate(http_requests_total[$__rate_interval]))`
211
+ 2. **各接口 5xx 错误率**:按 `route` 分组;
212
+ 3. **各接口 P95/P99**:按 `route` 分组;
213
+ 4. **最慢接口表格**:按 P99 排序,显示 route、实例、当前值。
214
+
215
+ ```promql
216
+ # 每个接口的 P99
217
+ histogram_quantile(0.99,
218
+ sum by (le, route) (
219
+ rate(http_request_duration_seconds_bucket{job="minicoin"}[$__rate_interval])
220
+ )
221
+ )
222
+ ```
223
+
224
+ ```promql
225
+ # 每个接口的 5xx 错误率
226
+ sum by (route) (
227
+ rate(http_requests_total{job="minicoin",status=~"5.."}[$__rate_interval])
228
+ )
229
+ /
230
+ sum by (route) (
231
+ rate(http_requests_total{job="minicoin"}[$__rate_interval])
232
+ )
233
+ ```
234
+
235
+ #### `route` 必须归一化
236
+
237
+ 正确:
238
+
239
+ ```text
240
+ /v1/accounts/:uid/balance
241
+ /v1/recharges
242
+ ```
243
+
244
+ 错误:
245
+
246
+ ```text
247
+ /v1/accounts/u-1001/balance
248
+ /v1/accounts/u-1002/balance
249
+ /v1/accounts/u-1003/balance
250
+ ```
251
+
252
+ 如果把真实用户 ID 放进 label,每个用户都会变成一条时间序列,Prometheus 内存和磁盘会被撑爆。这叫**高基数(high cardinality)**。
253
+
254
+ #### 加一个 `$route` 变量
255
+
256
+ 在 Grafana 顶部加变量:
257
+
258
+ ```text
259
+ Name: route
260
+ Type: Query
261
+ Query: label_values(http_requests_total{job="minicoin"}, route)
262
+ ```
263
+
264
+ 然后面板查询使用:
265
+
266
+ ```promql
267
+ http_request_duration_seconds_bucket{job="minicoin",route="$route"}
268
+ ```
269
+
270
+ 这样值班人可以先看全局,再下钻某个接口。
271
+
272
+ ### 11.6.3 第三行:依赖健康,判断瓶颈是不是数据库/Redis/MQ
273
+
274
+ Go 服务本身可能只有 20ms,但请求总共 3 秒,通常慢在下游。依赖行至少要有:
275
+
276
+ | 依赖 | 面板 | 关键指标 |
277
+ |---|---|---|
278
+ | MySQL | 连接池使用率 | `db_pool_in_use / db_pool_max` |
279
+ | MySQL | 查询延迟 | `histogram_quantile(0.99, rate(db_query_duration_seconds_bucket[...]))` |
280
+ | MySQL | 错误/超时/死锁 | `db_errors_total{type="timeout"}` 等 |
281
+ | Redis | 命令延迟 | Redis exporter 或应用侧直方图 |
282
+ | Redis | 错误率 | `redis_errors_total` |
283
+ | Kafka/RabbitMQ | 消费积压 | `kafka_consumergroup_lag` 或应用侧 lag |
284
+ | MQ | 发布失败 | `mq_publish_failures_total` |
285
+
286
+ 示例:
287
+
288
+ ```promql
289
+ # 数据库连接池使用率
290
+ db_pool_in_use{job="minicoin"}
291
+ /
292
+ db_pool_max{job="minicoin"}
293
+ ```
294
+
295
+ ```promql
296
+ # 数据库查询 P99
297
+ histogram_quantile(0.99,
298
+ sum by (le) (rate(db_query_duration_seconds_bucket{job="minicoin"}[$__rate_interval]))
299
+ )
300
+ ```
301
+
302
+ ```promql
303
+ # MQ 消费积压持续增长
304
+ max by (topic, group) (kafka_consumergroup_lag)
305
+ ```
306
+
307
+ **看依赖不要只看“有没有错误”,还要看延迟和饱和度。** 连接池 95% 使用率、查询 P99 从 10ms 涨到 2s,即使暂时没有错误,也已经在走向故障。
308
+
309
+ ### 11.6.4 第四行:资源,判断是不是进程或实例自身扛不住
310
+
311
+ 资源行建议按实例展示,图例写 `{{instance}}`,否则你只能看到总和,不知道哪台机器有问题。
312
+
313
+ | 面板 | PromQL 示例 | 说明 |
314
+ |---|---|---|
315
+ | CPU | `rate(process_cpu_seconds_total[$__rate_interval])` | 单位约等于“核数”,不是百分比 |
316
+ | 内存 RSS | `process_resident_memory_bytes` | 进程实际占用的物理内存 |
317
+ | Heap | `go_memstats_heap_inuse_bytes` | Go 正在使用的堆内存 |
318
+ | GC 平均暂停 | `rate(go_gc_duration_seconds_sum[$__rate_interval]) / rate(go_gc_duration_seconds_count[$__rate_interval])` | 不是 GC 总时间 |
319
+ | Goroutine | `go_goroutines` | 持续上涨可能是泄漏 |
320
+ | 磁盘/文件描述符 | `process_open_fds` | 句柄泄漏也要看 |
321
+
322
+ 资源图要和流量图对照。CPU 高但 QPS 也高,可能只是正常扩容;QPS 没变但 CPU 持续上涨,才更像死循环、GC、泄漏或异常重试。
323
+
324
+ ### 11.6.5 第五行:业务指标,确认用户结果
325
+
326
+ 技术指标正常,不代表业务成功。业务行至少放:
327
+
328
+ - 充值成功数;
329
+ - 充值成功率;
330
+ - 充值失败按错误码分布;
331
+ - 重复请求数;
332
+ - 冻结账户拒绝数;
333
+ - 数据库余额变化与订单金额是否一致(离线对账指标)。
334
+
335
+ ```promql
336
+ # 每秒成功充值数
337
+ sum(rate(mini_coin_recharge_total{result="success"}[$__rate_interval]))
338
+
339
+ # 充值结果分布
340
+ sum by (result) (rate(mini_coin_recharge_total[$__rate_interval]))
341
+ ```
342
+
343
+ 业务指标同样不能放 `uid`、`req_id`、订单号这类高基数字段。需要追单个请求时查日志和 trace,不要把它塞进 Prometheus label。
344
+
345
+ ### 11.6.6 一个实际看板的排布
346
+
347
+ 一个 12 列宽的 Grafana Dashboard 可以这样排:
348
+
349
+ ```text
350
+ Row 1 总览
351
+ [QPS 3列] [错误率 3列] [P99 3列] [实例数 3列]
352
+
353
+ Row 2 接口
354
+ [各接口 P99 6列] [各接口错误率 6列]
355
+ [最慢接口表格 12列]
356
+
357
+ Row 3 依赖
358
+ [MySQL 池 4列] [MySQL 延迟 4列] [Redis 延迟 4列]
359
+ [Kafka lag 6列] [MQ 发布失败 6列]
360
+
361
+ Row 4 资源
362
+ [CPU 3列] [内存 3列] [GC 3列] [Goroutine 3列]
363
+
364
+ Row 5 业务
365
+ [成功数 3列] [成功率 3列] [失败码 3列] [重复请求 3列]
366
+ ```
367
+
368
+ ### 11.6.7 真的出问题时,怎么按看板走
369
+
370
+ 假设值班人收到“充值接口 P99 > 1s”告警:
371
+
372
+ ```text
373
+ 1. 看总览:QPS 正常、5xx 1.8%、P99 1.6s、实例数没少
374
+ 2. 看接口:只有 /v1/recharges 慢,余额查询正常
375
+ 3. 看依赖:MySQL 连接池 98%,查询 P99 2.4s,Redis 正常
376
+ 4. 看资源:Go CPU 只有 20%,goroutine 数量持续上涨
377
+ 5. 看业务:充值成功数下降,错误码 ACCOUNT_FROZEN 没有异常
378
+ 6. 看日志/trace:大量请求卡在 SELECT ... FOR UPDATE
379
+ 7. 结论:数据库行锁等待,不是 Go 计算慢;继续查长事务和最近发布
380
+ ```
381
+
382
+ 这就是五行的意义:**用指标逐层缩小范围,最后用日志和 trace 证明根因。**
383
+
384
+ ### 11.6.8 看板必须配的细节
385
+
386
+ - 时间范围默认最近 15 分钟,支持 1h/6h/24h;
387
+ - 每个面板写清单位:req/s、%、秒、字节、个;
388
+ - 延迟面板同时显示 P50/P95/P99,不要只看平均值;
389
+ - 关键面板设置红色阈值;
390
+ - 图例显示 `instance`/`route`/`result`,不能全是一堆无名线;
391
+ - 仪表盘顶部放服务名、环境和时间范围;
392
+ - 发布事件用 annotation 标在图上;
393
+ - 点击告警能跳到看板,点击看板能跳到日志/trace;
394
+ - 看板本身要版本化,不能只在某个人账号里手工改。
395
+
396
+ ### 11.6.9 不要这样摆
397
+
398
+ - 一个面板放 20 条无明显图例的线;
399
+ - 用用户 ID、请求 ID、订单号做 label;
400
+ - 只展示平均值;
401
+ - 只有 CPU/内存,没有错误率和业务指标;
402
+ - 图表没有单位,不知道 0.3 是秒还是毫秒;
403
+ - 告警和看板使用不同口径;
404
+ - 看板堆了 50 个图,值班人不知道先看哪个。
405
+
406
+ **记住:看板不是“数据展示墙”,而是排障流程图。第一行回答能不能用,第二行回答哪里坏,第三行回答外部依赖是否健康,第四行回答实例是否饱和,第五行回答业务损失。**
407
+
408
+ ---
147
409
 
148
410
  ## 11.7 日志、指标、追踪分工
149
411
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-backend-handbook",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Go 后端开发全景讲义:语言、并发、数据库、缓存、消息队列、gRPC、监控告警、测试、性能、安全、部署与完整开发流程",
5
5
  "keywords": [
6
6
  "go",