监控告警
完善的监控和告警是保证分布式 ID 系统稳定运行的关键。
1. 核心指标
QPS(每秒查询数):
QPS = 每秒生成的 ID 数量
阈值:
- 正常:> 10,000
- 警告:< 10,000
- 严重:< 1,000
延迟(Latency):
P50:50% 的请求延迟
P99:99% 的请求延迟
P999:99.9% 的请求延迟
阈值:
- 优秀:P99 < 1ms
- 良好:P99 < 10ms
- 警告:P99 < 100ms
- 严重:P99 ≥ 100ms
错误率(Error Rate):
错误率 = 失败请求数 / 总请求数 × 100%
阈值:
- 优秀:< 0.01%
- 良好:< 0.1%
- 警告:< 1%
- 严重:≥ 1%
可用性(Availability):
可用性 = (总时间 - 停机时间) / 总时间 × 100%
阈值:
- 优秀:≥ 99.99%
- 良好:≥ 99.9%
- 警告:≥ 99%
- 严重:< 99%
2. 资源指标
CPU 使用率:
阈值:
- 正常:< 70%
- 警告:70-90%
- 严重:> 90%
内存使用率:
阈值:
- 正常:< 70%
- 警告:70-90%
- 严重:> 90%
网络带宽:
阈值:
- 正常:< 70%
- 警告:70-90%
- 严重:> 90%
3. 业务指标
Buffer 使用率:
Buffer 使用率 = 已分配 ID / Buffer 总容量 × 100%
阈值:
- 正常:< 50%
- 警告:50-80%
- 严重:> 80%
号段获取频率:
号段获取频率 = 每秒获取号段次数
阈值:
- 正常:< 10 次/秒
- 警告:10-50 次/秒
- 严重:> 50 次/秒
1. 使用 Prometheus
配置:
配置要点
- 配置表达的是环境差异和运行参数,不是业务规则本身。
Java 实现:
2. 使用 Micrometer
Micrometer 是 Spring 生态的指标门面:应用只面向 Micrometer 的 API 写指标,具体落到 Prometheus、Datadog 还是别的后端由配置决定。
配置:
management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
tags:
application: id-generator
distribution:
percentiles-histogram:
id.generate: true
percentiles:
id.generate: 0.5, 0.99, 0.999
Java 实现:
/**
* 用 Micrometer 把发号过程的关键指标暴露出去。
*/
@Component
public class IdMetrics {
private final Counter generatedCounter;
private final Counter failedCounter;
private final Timer generateTimer;
/** 号段剩余量交给 Gauge 采集。 */
private final AtomicLong segmentRemaining = new AtomicLong();
public IdMetrics(MeterRegistry registry) {
this.generatedCounter = Counter.builder("id.generated.total")
.description("成功生成的 ID 数量")
.register(registry);
this.failedCounter = Counter.builder("id.failed.total")
.description("生成失败的次数")
.register(registry);
this.generateTimer = Timer.builder("id.generate")
.description("生成一个 ID 的耗时")
.publishPercentiles(0.5, 0.99, 0.999)
.register(registry);
// 瞬时状态用 Gauge:采集时回调取值
Gauge.builder("id.segment.remaining", segmentRemaining, AtomicLong::get)
.description("本地号段剩余量")
.register(registry);
}
/** 记录一次成功的发号。 */
public void recordSuccess(long costNanos) {
generatedCounter.increment();
generateTimer.record(costNanos, TimeUnit.NANOSECONDS);
}
/** 记录一次失败。 */
public void recordFailure() {
failedCounter.increment();
}
/** 续期完成后更新号段剩余量。 */
public void updateSegmentRemaining(long remaining) {
segmentRemaining.set(remaining);
}
}
指标要挑这几个:
| 指标 | 类型 | 说明 |
|---|---|---|
| id.generated.total | Counter | 发号总量,除以时间就是 QPS |
| id.failed.total | Counter | 失败量,和总量一起算出错误率 |
| id.generate | Timer | 发号耗时,P99 对应监控告警里的延迟阈值 |
| id.segment.remaining | Gauge | 本地号段剩余量,反映续期是否跟得上 |
| id.degraded | Gauge | 当前生效的降级方案等级,0 表示主方案 |
号段剩余量、降级等级这类「瞬时状态」只能做成 Gauge,每次采集时回调取值;写成 Counter 就没法表达「现在还剩多少」。
指标名要稳定。指标名一旦上线就很难改,Prometheus 里的历史序列和告警规则都绑在上面,命名规范要在项目开始时定好。
1. 告警级别
P0 - 严重告警:
- 系统完全不可用
- QPS = 0
- 错误率 > 10%
响应时间:立即处理(< 5分钟)
P1 - 高级告警:
- 系统部分故障
- QPS < 1,000
- 错误率 > 1%
响应时间:1小时内处理
P2 - 中级告警:
- 性能下降
- QPS < 10,000
- 延迟 P99 > 100ms
响应时间:24小时内处理
P3 - 低级告警:
- 资源使用率偏高
- Buffer 使用率 > 50%
响应时间:持续观察
2. 告警规则
Prometheus 告警规则:
配置要点
- 配置表达的是环境差异和运行参数,不是业务规则本身。
1. 通知方式
邮件:
短信:
钉钉/企业微信:
2. 告警聚合
聚合策略:
同一类型的告警在 5 分钟内只发送一次
严重告警立即发送
其他告警批量发送
1. 结构化日志
发号链路横跨接入层、服务层、数据层,出了问题要靠日志把一个请求的各段串起来。结构化日志就是每条日志都输出成机器可读的 JSON,字段固定,方便日志系统按字段检索和聚合。
输出格式:
{
"timestamp": "2024-04-10 00:00:01",
"level": "INFO",
"service": "id-generator",
"traceId": "9f1c2d3e4a5b6c7d",
"bizType": "order",
"segmentId": 1024,
"costMs": 1,
"message": "号段获取成功"
}
字段设计有三条原则:
- 必备字段固定:时间、级别、服务名、链路 ID、业务类型。字段名不要随场景变化,否则日志系统里的字段索引会碎掉。
- 上下文用 MDC 传递。traceId 这类每个请求都有的值,在入口写进 MDC,之后任何一行日志都能自动带上,不用一层层往下传。
- 敏感信息不进日志。手机号、身份证、完整的请求体都不能进日志;ID 本身可以进,它的作用就是定位。
实现:
/**
* 用 MDC 把链路上下文带进每一行日志。
*/
@Component
public class TraceIdFilter extends OncePerRequestFilter {
private static final String TRACE_ID = "traceId";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String traceId = request.getHeader("X-Trace-Id");
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
MDC.put(TRACE_ID, traceId);
// 回写给下游服务,整条链路才能串起来
response.setHeader("X-Trace-Id", traceId);
try {
filterChain.doFilter(request, response);
} finally {
// 线程来自线程池,清理必须放在 finally 里
MDC.remove(TRACE_ID);
}
}
}
日志格式用 logback 的 JSON 编码器配置:
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<customFields>{"service":"id-generator"}</customFields>
</encoder>
</appender>
清理必须放在 finally 里:线程来自线程池,不清理的话下一个请求会带上上一个请求的 traceId,看起来像是串行污染。
2. 分布式追踪
日志能告诉你「发生了什么」,分布式追踪能告诉你「在哪一步花的时间」。一个发号请求经过接入层、服务层、数据库三层,每一层都记一笔 span,串起来就是一条完整的时间线。
使用 Sleuth + Zipkin:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
配置:
spring:
sleuth:
sampler:
probability: 1.0
zipkin:
base-url: http://zipkin:9411
实现:
/**
* 在发号的关键节点上打点,耗时和结果都会进入 Zipkin。
*/
@Service
public class TracedIdService {
private final SegmentIdGenerator idGenerator;
private final Tracer tracer;
public TracedIdService(SegmentIdGenerator idGenerator, Tracer tracer) {
this.idGenerator = idGenerator;
this.tracer = tracer;
}
/** 生成一个 ID,并记录这次发号落在哪个 span 里。 */
public long nextId() {
Span span = tracer.nextSpan().name("id.generate").start();
try (Tracer.SpanInScope scope = tracer.withSpan(span)) {
long id = idGenerator.nextId();
span.tag("id", Long.toString(id));
return id;
} catch (RuntimeException e) {
span.error(e);
throw e;
} finally {
span.end();
}
}
}
接入之后,一个发号请求在 Zipkin 上会呈现成这样:
客户端 ── POST /api/id ──▶ id-generator ── SELECT id_segment ──▶ MySQL
12ms 10ms 8ms 2ms
注意事项:
- 采样率要按流量调。
probability是 1.0 表示全采样,压测或者线上大流量时改成 0.1 甚至更低,否则追踪本身会成为负担。 - Sleuth 在 Spring Boot 3 之后已经停止演进,新项目改用 Micrometer Tracing,API 不同但思路一致:上下文都放在 ThreadLocal 里,跨线程要显式传递。
- span 的粒度要合适。给每一条 SQL 都建 span 会把链路图撑爆,发号这种关键路径上留 2 到 3 个 span 就够了。
1. Grafana 面板
关键指标:
- QPS 趋势图
- 延迟分布图
- 错误率趋势图
- 资源使用率图
- Buffer 使用率图
2. 自定义面板
示例:
配置要点
- 配置表达的是环境差异和运行参数,不是业务规则本身。
- 队列、缓存、存储和服务参数决定系统在高峰期的缓冲能力。
1. 完善的监控
✅ 监控所有核心指标
✅ 设置合理的阈值
✅ 实现多级告警
✅ 支持多种通知方式
2. 及时的告警
✅ P0 告警立即处理
✅ P1 告警 1 小时内处理
✅ P2 告警 24 小时内处理
✅ P3 告警持续观察
3. 完整的日志
✅ 结构化日志
✅ 分布式追踪
✅ 日志聚合
✅ 日志分析
完善的监控和告警系统是保证分布式 ID 系统稳定运行的关键。
恭喜你完成了《系统设计 - 分布式 ID 生成器》课程!
你学到了: ✅ 分布式 ID 的多种方案 ✅ 各方案的优缺点和适用场景 ✅ 生产级系统的设计原则 ✅ 完整的系统架构 ✅ 监控和告警策略
下一步
建议继续学习:
- 系统设计其他主题
- 分布式系统设计
- 微服务架构
- 高可用系统设计
感谢学习!🎉
发现内容有问题?
告诉我们哪里需要改进。
