第一章:SLO文档背景与核心目标定义
在现代云原生系统与平台工程实践中,SLO(Service Level Objective)文档已超越传统SLA的法律约束角色,成为团队对可靠性的共同语言和协作契约。它不是运维部门单方面承诺的指标清单,而是产品、开发、SRE与业务方在可观测性数据基础上持续对齐的“可靠性北极星”。
SLO为何必须以文档形式显式定义
口头共识或监控看板上的孤立指标极易随人员流动而失真。SLO文档强制要求明确回答:服务对谁关键?用户真实感知的失败是什么?可接受的故障预算消耗速率是多少?例如,一个面向全球用户的API网关,其核心SLO不能仅写“99.9%可用”,而需明确定义为:“在滚动30天窗口内,HTTP 5xx错误率 ≤ 0.1%,测量依据为所有/v1/*路径的env=prod流量,采样率100%,由Prometheus中sum(rate(http_request_total{code=~"5..", env="prod"}[5m])) / sum(rate(http_request_total{env="prod"}[5m]))实时计算”。
核心目标的三重锚定
- 用户价值锚定:SLO必须映射到可观察的用户行为,如“搜索结果返回延迟 > 2s 的请求占比
- 决策可操作锚定:每个SLO需绑定明确的响应规则,例如“当本周错误预算剩余
- 演进可持续锚定:文档须包含版本号、最后评审日期及下次评审触发条件(如“每季度或当核心用户旅程变更后”)。
典型SLO文档结构要素
| 要素 | 说明 | 示例 |
|---|---|---|
| 服务标识 | 唯一名称与上下文 | payment-gateway-prod(支撑订单支付主流程) |
| 目标指标 | 可测量、可聚合的黄金信号 | error_rate_5m(5分钟错误率) |
| 目标值 | 明确阈值与时间窗口 | ≤ 0.1%(滚动30天) |
| 测量方法 | 查询语句与数据源 | Prometheus查询 + Grafana数据源ID ds-prom-prod |
| 预算规则 | 初始预算与补给逻辑 | 每周100分,按实际错误率线性扣减,每周一00:00重置 |
SLO文档的生命力源于持续验证——建议每月执行一次“SLO健康快照”脚本,自动校验指标定义一致性与数据链路连通性:
# 检查Prometheus中SLO指标是否存在且有最近数据
curl -s "http://prometheus.example.com/api/v1/query?query=count_over_time(http_request_total{code=~'5..',env='prod'}[1h])" \
| jq '.data.result[0].value[1]' # 应返回非零数值,否则需排查采集配置
第二章:Golang商品服务P99延迟
2.1 基于Go runtime调度器的低延迟协程治理实践
在高吞吐、低延迟场景下,goroutine泛滥易引发调度器争抢与GC压力。关键在于主动节制而非被动回收。
协程生命周期管控策略
- 使用
sync.Pool复用轻量级任务结构体,避免高频堆分配 - 通过
runtime.Gosched()主动让出时间片,防止长循环独占P - 设置
GOMAXPROCS=1配合GODEBUG=schedtrace=1000定期观测调度延迟
自适应工作协程池(代码示例)
type AdaptivePool struct {
ch chan func()
limit int32
}
func (p *AdaptivePool) Submit(task func()) {
select {
case p.ch <- task:
default:
// 超载时降级:同步执行或丢弃(依SLA策略)
task()
}
}
逻辑分析:
select非阻塞提交保障毫秒级响应;ch容量动态绑定atomic.LoadInt32(&p.limit),该值由runtime.NumGoroutine()与 P 队列长度联合反馈调节。参数limit初始设为GOMAXPROCS * 2,上限不超512,避免M频繁切换。
调度行为对比(单位:μs)
| 场景 | 平均调度延迟 | P 队列积压 |
|---|---|---|
| 默认 runtime | 84 | 高 |
| 节制型 Pool + Gosched | 12 | 极低 |
graph TD
A[新任务到达] --> B{是否超限?}
B -->|是| C[同步执行/降级]
B -->|否| D[投递至无锁chan]
D --> E[Worker goroutine 拉取]
E --> F[执行前 runtime.Gosched]
2.2 商品读写路径的零拷贝序列化与内存池复用设计
零拷贝序列化核心逻辑
避免 JVM 堆内 byte[] 多次复制,直接操作堆外内存(DirectByteBuffer)与 Unsafe 偏移写入:
// 商品结构体紧凑布局:id(8B) + price(4B) + stock(4B) + version(4B)
public void serialize(Product p, ByteBuffer buf) {
buf.putLong(p.id); // offset 0
buf.putInt(p.price); // offset 8
buf.putInt(p.stock); // offset 12
buf.putInt(p.version); // offset 16 → 总长20B,无对象头、无引用跳转
}
逻辑分析:ByteBuffer 指向预分配堆外内存,putXxx() 调用 Unsafe.putXxx() 绕过 GC 和数组边界检查;参数 buf 由内存池统一供给,生命周期受请求上下文管理。
内存池复用策略
| 池类型 | 初始大小 | 最大容量 | 回收触发条件 |
|---|---|---|---|
| 20B小块池 | 1024 | 8192 | 请求完成即归还 |
| 1KB中块池 | 64 | 512 | 异步批量刷盘后释放 |
数据流转视图
graph TD
A[HTTP请求] --> B[Netty ByteBuf]
B --> C[PoolThreadLocalCache]
C --> D[DirectByteBuffer:20B]
D --> E[Serialize → 堆外写入]
E --> F[网卡DMA直传]
2.3 分布式缓存穿透防护与多级缓存一致性状态机实现
缓存穿透防护:布隆过滤器 + 空值缓存双策略
为拦截恶意或错误的不存在 key 查询,采用布隆过滤器前置校验,并对确认不存在的 key 写入短期空值(NULL + TTL=60s):
// 布隆过滤器校验 + 空值回写
if (!bloomFilter.mightContain(key)) {
return null; // 必定不存在,直接返回
}
Object value = redis.get(key);
if (value == null) {
redis.setex("empty:" + key, 60, "1"); // 防穿透空标记
return null;
}
逻辑说明:
bloomFilter.mightContain()误判率可控(empty: 便于批量清理;TTL 避免长期占用内存。
多级缓存一致性状态机
采用三态机驱动本地缓存(Caffeine)、分布式缓存(Redis)、数据库协同更新:
| 状态 | 触发条件 | 动作 |
|---|---|---|
STALE |
Redis 过期/DB 更新后 | 清除本地缓存,异步加载 Redis |
FRESH |
本地缓存命中且未过期 | 直接返回 |
PENDING |
本地缺失且 Redis 未就绪 | 加锁加载,防止击穿 |
graph TD
A[请求到达] --> B{本地缓存命中?}
B -->|是| C[返回 FRESH]
B -->|否| D{Redis 是否存在?}
D -->|是| E[加载至本地 → FRESH]
D -->|否| F[查 DB → 写 Redis+本地 → STALE→FRESH]
2.4 Go HTTP Server超时链路对齐与Context传播深度优化
超时链路不一致的典型表现
当 http.Server.ReadTimeout 与 context.WithTimeout() 独立设置时,请求可能在读取阶段被 net.Conn 关闭,但 handler 仍持有未取消的 ctx,导致 goroutine 泄漏与逻辑错乱。
Context 传播的三层对齐策略
- 底层:
http.Server的BaseContext和ConnContext钩子注入统一 timeout context - 中层:
http.TimeoutHandler仅适用于顶层 handler,无法穿透中间件链 - 上层:所有中间件/业务 handler 必须接收并传递
r.Context(),禁用context.Background()
关键代码:服务端上下文对齐实现
srv := &http.Server{
Addr: ":8080",
BaseContext: func(_ net.Listener) context.Context {
return context.WithTimeout(context.Background(), 30*time.Second)
},
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
// 对每个连接绑定读写超时
return context.WithDeadline(ctx, time.Now().Add(30*time.Second))
},
}
此处
BaseContext提供全局生命周期 context,ConnContext为每个连接注入带 deadline 的子 context;二者协同确保r.Context()在ServeHTTP全流程中始终携带一致的超时信号,避免中间件自行创建 context 导致 cancel 丢失。
超时传播效果对比表
| 场景 | r.Context().Done() 触发时机 |
是否可中断阻塞 I/O |
|---|---|---|
仅设 ReadTimeout |
连接关闭后才触发 | ❌(io.Read 不感知) |
仅设 context.WithTimeout |
定时器到期即触发 | ✅(需显式 select ctx.Done()) |
| 双重对齐(本节方案) | 任一条件满足即触发 | ✅(标准库 http 自动 propagate) |
graph TD
A[Client Request] --> B[net.Listener.Accept]
B --> C[ConnContext 注入 deadline context]
C --> D[HTTP Parser + ServeHTTP]
D --> E[r.Context 透传至所有 Handler]
E --> F{select {<br>case <-ctx.Done():<br> return<br>case data := <-dbChan:<br> handle}}
2.5 基于pprof+ebpf的实时延迟归因分析与热区定位方法论
传统采样仅捕获用户态调用栈,难以关联内核调度、I/O阻塞与锁竞争。pprof 提供高精度用户态火焰图,而 eBPF 实现零侵入内核事件追踪(如 sched:sched_blocked_reason、block:block_rq_issue),二者通过统一时间戳与 PID/TID 关联,构建跨栈延迟归因链。
数据融合机制
使用 perf script -F +brstack --no-children 导出带分支栈的 perf 数据,再通过 pprof --tag=ebpf 注入 eBPF 事件标记:
# 启动延迟感知型 eBPF 探针(基于 BCC)
sudo /usr/share/bcc/tools/biolatency -m -D 10 # 毫秒级 I/O 延迟直方图
此命令启用内核
block_rq_issue/block_rq_complete事件监听,-m输出毫秒单位,-D 10每10秒刷新;输出直接映射至 pprof 的--tags字段,实现 I/O 延迟与 Go runtime block profile 的语义对齐。
归因路径建模
graph TD
A[pprof CPU Profile] --> B[goroutine 状态切换点]
C[eBPF sched:sched_switch] --> B
D[eBPF tcp:tcp_sendmsg] --> E[网络写阻塞]
B --> E
| 维度 | pprof 覆盖范围 | eBPF 补充能力 |
|---|---|---|
| 时间精度 | ~10ms 定时采样 | 微秒级事件触发 |
| 栈深度 | 用户态完整调用链 | 内核态上下文 + 返回地址 |
| 阻塞根因 | 仅显示阻塞 goroutine | 显式标注锁持有者/PID |
该方法论已在 Kubernetes Node 上实现
第三章:17项硬指标的技术分解与SLI映射逻辑
3.1 商品查询类指标(SKU检索、类目树加载、属性聚合)的SLI建模与采样策略
商品查询链路的SLI需精准反映终端用户体验,核心聚焦三类场景:SKU实时检索延迟(P95 ≤ 300ms)、类目树首屏加载成功率(≥99.95%)、属性聚合响应完整性(100%字段覆盖+无截断)。
SLI定义与关键维度
- 检索延迟:按
query_type(模糊/精确/拼写纠错)、device_type(APP/Web)、user_tier(新客/高价值)多维切分 - 类目树:以
tree_depth≤4且node_count≥50为有效加载样本 - 属性聚合:校验
aggregation_key与facet_values长度一致性
采样策略设计
# 动态采样器:兼顾覆盖率与可观测性开销
def should_sample(trace_id: str, biz_type: str) -> bool:
base_rate = {"sku_search": 0.05, "cat_tree": 0.02, "attr_agg": 0.01}
# 对错误链路强制100%采样
if trace.get("error") or trace.get("http_status") >= 400:
return True
return int(trace_id[-4:], 16) % 100 < base_rate[biz_type] * 100
逻辑分析:采用trace_id末4位十六进制哈希实现无状态均匀采样;错误请求全量捕获保障故障归因;各业务类型差异化基线率控制数据体积。
| 指标类型 | 采样率 | 日均样本量 | SLI阈值 |
|---|---|---|---|
| SKU检索 | 5% | 2.1亿 | P95 ≤ 300ms |
| 类目树加载 | 2% | 8400万 | 成功率 ≥ 99.95% |
| 属性聚合 | 1% | 4200万 | 完整率 = 100% |
数据同步机制
graph TD
A[前端埋点] –>|HTTP上报| B(边缘日志网关)
B –> C{按biz_type路由}
C –> D[SKU检索指标流]
C –> E[类目树指标流]
C –> F[属性聚合指标流]
D & E & F –> G[实时Flink作业
SLI计算+异常检测]
3.2 商品写入类指标(上架/下架/库存变更)的幂等性保障与事务边界收敛
数据同步机制
商品状态变更需跨库存、价格、搜索、推荐多系统同步,但各子系统更新延迟与失败概率不同。核心矛盾在于:单次业务操作必须产生全局一致的最终状态,且重复请求不可导致状态漂移。
幂等令牌设计
采用 biz_type:sku_id:timestamp:nonce 复合键生成唯一 idempotency_key,写入前校验 Redis 中是否存在已成功标记:
// 基于 Lua 脚本保证原子性
String script = "if redis.call('exists', KEYS[1]) == 1 then " +
" return tonumber(redis.call('hget', KEYS[1], 'status')) " +
"else " +
" redis.call('hmset', KEYS[1], 'status', ARGV[1], 'ts', ARGV[2]) " +
" redis.call('expire', KEYS[1], 86400) " +
" return 0 " +
"end";
Long result = jedis.eval(script, Arrays.asList("idemp:up_1001"), Arrays.asList("success", String.valueOf(System.currentTimeMillis())));
逻辑说明:
KEYS[1]为幂等键;ARGV[1]表示预期状态(如"online");返回表示首次执行,非表示已存在且返回历史状态码。Redis 过期时间设为 24 小时,覆盖最长业务重试窗口。
事务边界收敛策略
| 边界类型 | 是否纳入本地事务 | 原因说明 |
|---|---|---|
| 库存扣减 | ✅ | 强一致性要求,DB 行锁保障 |
| 搜索索引更新 | ❌ | 最终一致性,走异步消息队列 |
| 推荐标签刷新 | ❌ | 非核心路径,允许延迟 5 分钟 |
状态机驱动流程
graph TD
A[接收SKU上架请求] --> B{幂等键已存在?}
B -- 是 --> C[返回历史状态码]
B -- 否 --> D[开启本地事务]
D --> E[更新商品主表状态]
D --> F[扣减预留库存]
D --> G[提交事务]
G --> H[投递MQ:search.up, rec.refresh]
3.3 混合负载场景下SLO守门员(SLO Guardian)的动态阈值熔断机制
在微服务集群中,混合负载(如突发读请求 + 周期性批处理写入)易导致静态SLO阈值频繁误熔断。SLO Guardian 采用基于滑动窗口分位数与负载特征向量的双因子动态阈值生成器。
动态阈值计算核心逻辑
def compute_dynamic_threshold(latencies_ms: List[float],
load_ratio: float,
base_p95: float = 200.0) -> float:
# 负载感知缩放:当CPU+IO综合负载>0.7时,允许p95上浮至1.8倍基线
scale_factor = 1.0 + max(0, load_ratio - 0.7) * 0.8
window_p95 = np.percentile(latencies_ms[-500:], 95)
return max(base_p95, window_p95 * scale_factor) # 保底不跌破基线
该函数融合实时延迟分布(滑动窗口p95)与系统负载强度(load_ratio),避免低负载时过度保守、高负载时过早熔断。
熔断决策流程
graph TD
A[采集最近10s延迟+资源指标] --> B{p95 < 动态阈值?}
B -->|是| C[维持服务开放]
B -->|否| D[触发分级降级:限流→只读→熔断]
关键参数对照表
| 参数 | 含义 | 典型值 | 调整依据 |
|---|---|---|---|
window_size |
滑动窗口采样点数 | 500 | 保障统计稳定性 |
load_ratio |
CPU+磁盘IO加权归一化负载 | [0.0, 1.0] | 防止资源瓶颈被掩盖 |
scale_cap |
最大缩放倍数 | 1.8x | 避免阈值漂移失控 |
第四章:生产环境SLO持续验证与故障注入实战
4.1 基于Chaos Mesh的gRPC延迟注入与P99漂移敏感度测试
为量化服务网格中gRPC调用对网络抖动的敏感性,我们使用Chaos Mesh注入可控延迟并观测P99响应时间漂移。
延迟实验配置
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: grpc-latency
spec:
action: delay
mode: one
selector:
labels:
app: payment-service
delay:
latency: "100ms" # 基线延迟
correlation: "25" # 模拟抖动相关性(0–100)
duration: "30s"
该配置在payment-service Pod出向流量中注入均值100ms、标准差约25ms的延迟,correlation参数控制延迟序列的时间自相关性,更贴近真实网络抖动特征。
P99漂移观测结果
| 延迟注入 | 原始P99 (ms) | 注入后P99 (ms) | 漂移幅度 |
|---|---|---|---|
| 0ms | 42 | 42 | — |
| 100ms | 42 | 218 | +419% |
敏感度归因分析
graph TD
A[gRPC客户端] -->|Unary RPC| B[服务端处理]
B --> C[HTTP/2流复用]
C --> D[队列积压放大效应]
D --> E[P99指数级上升]
gRPC的流控机制与底层TCP重传叠加,在延迟注入下引发请求排队雪崩,导致尾部延迟非线性放大。
4.2 Prometheus+Thanos多维标签下SLO Burn Rate的实时告警闭环
SLO Burn Rate核心计算逻辑
Burn Rate = (错误事件数 / 总请求数) ÷ SLO目标值,需在多维标签(service, region, canary)下逐组聚合。
Thanos Query层标签下推优化
# prometheus.rules.yml —— 基于多维标签的SLO窗口聚合
- record: slo:burn_rate5m:ratio_rate30d
expr: |
sum by (service, region, canary) (
rate(http_requests_total{code=~"5.."}[5m])
)
/
sum by (service, region, canary) (
rate(http_requests_total[5m])
)
/ 0.999 # SLO=99.9%
该表达式在Thanos Query中自动下推至各Prometheus sidecar,避免全局笛卡尔积;by (...)保留业务关键维度,支撑细粒度告警路由。
告警触发与闭环路径
graph TD
A[Prometheus采集] --> B[Thanos Query聚合]
B --> C{BurnRate > 10?}
C -->|是| D[Alertmanager按service+region分派]
D --> E[Webhook调用自愈Pipeline]
| 维度组合 | 告警阈值 | 自愈动作 |
|---|---|---|
auth, us-east, prod |
BurnRate > 8 | 自动扩容API节点 |
payment, eu-west, canary |
BurnRate > 3 | 回滚灰度版本 |
4.3 商品服务灰度发布期间SLO偏差的自动化回滚决策树
当商品服务灰度流量中错误率(error_rate_5m)持续超阈值(>0.8%)且P95延迟上升>20%,触发决策树评估:
核心判定逻辑
# 回滚触发条件:SLO双指标联合校验(15秒粒度采样)
if error_rate_5m > 0.008 and latency_p95_delta_pct > 20:
if traffic_ratio > 0.3: # 灰度比例 >30% → 立即回滚
trigger_rollback("CRITICAL")
elif is_canary_metric_stable(): # 新版本指标连续3周期稳定 → 观察
delay_action(300) # 延迟5分钟再判
else:
trigger_rollback("WARNING") # 其他情况降级回滚
逻辑分析:error_rate_5m为5分钟滑动窗口错误率,latency_p95_delta_pct是相较基线P95延迟的百分比增幅;traffic_ratio由配置中心实时注入,避免硬编码。
决策路径优先级
- 高危场景(高流量+双指标恶化)→ 强制立即回滚
- 低流量但指标异常 → 启动熔断隔离而非全量回滚
- 指标抖动但趋势收敛 → 自动延长观察窗口
SLO偏差响应等级映射表
| 偏差组合 | 响应动作 | 冷却期 |
|---|---|---|
| 错误率↑ + 延迟↑ | 全量回滚 | 300s |
| 错误率↑ + 延迟正常 | 降级非核心链路 | 120s |
| 错误率正常 + 延迟↑ | 扩容+限流 | 60s |
graph TD
A[SLO采集] --> B{error_rate>0.8%?}
B -->|否| C[继续灰度]
B -->|是| D{latency_p95_delta>20%?}
D -->|否| E[告警+人工确认]
D -->|是| F{traffic_ratio>0.3?}
F -->|是| G[自动全量回滚]
F -->|否| H[启动隔离策略]
4.4 基于OpenTelemetry TraceID的端到端延迟追踪与根因聚类分析
OpenTelemetry 的 TraceID 是贯穿请求全链路的唯一标识符,为跨服务、跨进程、跨语言的延迟归因提供原子锚点。
数据同步机制
后端服务通过 OpenTelemetry SDK 自动注入 traceparent HTTP 头,并将 TraceID 与日志、指标、数据库慢查询记录关联:
# 在 Flask 中注入 TraceID 到结构化日志
from opentelemetry import trace
import logging
logger = logging.getLogger(__name__)
tracer = trace.get_tracer(__name__)
@tracer.start_as_current_span("user_profile_fetch")
def get_user_profile(user_id: str):
span = trace.get_current_span()
logger.info("Fetching profile", extra={"trace_id": span.context.trace_id})
逻辑说明:
span.context.trace_id返回 128 位十六进制整数(如0x4bf92f3577b34da6a3ce929d0e0e4736),需转为 32 位小写字符串用于日志/存储索引;该 ID 在整个分布式调用树中保持不变,是后续关联分析的唯一键。
根因聚类流程
使用 TraceID 对采样 Span 数据按 service.name + http.status_code + duration 三元组聚类,识别高频异常模式:
| Cluster ID | Services Involved | Avg Latency (ms) | Error Rate | Dominant Span Kind |
|---|---|---|---|---|
| C-7a2f | auth → api → db | 1240 | 92% | CLIENT |
graph TD
A[Incoming Request] --> B[Inject TraceID & SpanID]
B --> C[Propagate via W3C TraceContext]
C --> D[Collect Spans from All Services]
D --> E[Group by TraceID + Duration Quantile]
E --> F[Cluster Anomalous Traces via DBSCAN]
第五章:SLO演进路线与跨团队协同规范
SLO从单点监控到服务契约的演进阶段
某大型电商中台在2021年Q3启动SLO体系建设,初期仅在订单服务中定义了“P99响应延迟 ≤ 800ms(99.5%达标率)”,依赖Zabbix告警触发人工介入。至2022年Q2,升级为基于Prometheus+Thanos的多维SLI采集体系,覆盖HTTP成功率、gRPC错误码分布、DB查询P95耗时三类核心指标,并通过Service Level Indicator Registry(SLIR)实现指标元数据统一注册。2023年Q4起,SLO正式写入API网关契约——所有调用方在接入前必须签署《SLO兼容性承诺书》,明确下游可容忍的错误预算消耗速率(如:每小时最多消耗0.3%错误预算)。
跨团队SLO对齐会议机制
为解决支付团队与风控团队在“交易验签超时”归责争议,建立双周SLO对齐会议(SLO Sync-up),采用结构化议程:
| 环节 | 时长 | 主导方 | 输出物 |
|---|---|---|---|
| SLI数据复盘 | 15min | SRE | 误差预算消耗热力图(按服务/时段/错误类型) |
| 归因分析陈述 | 20min | 故障方 | 根因时间线(含链路追踪TraceID锚点) |
| 预算重分配提案 | 10min | 双方TL | 修订后的季度错误预算配额表 |
2023年该机制使跨团队SLO争议平均解决周期从7.2天压缩至1.8天。
错误预算自动化熔断实践
某金融级账户服务实施三级错误预算熔断策略,通过OpenFeature动态配置开关:
# feature-flag-config.yaml
flags:
budget-circuit-breaker:
variations:
"level-1": { enabled: true, threshold: "0.5%" }
"level-2": { enabled: true, threshold: "1.2%" }
"level-3": { enabled: true, threshold: "2.0%" }
targeting:
- context: "prod-us-east"
variation: "level-2"
当错误预算消耗达阈值时,自动触发API网关限流规则更新(Envoy xDS动态下发),并同步向Slack #slo-alerts频道推送结构化告警:
⚠️ ACCOUNT-SERVICE 错误预算剩余 0.8%(阈值1.2%)
🔗 Dashboard | 📜 Budget Runbook
SLO文档即代码工作流
所有SLO声明均以YAML格式存于Git仓库 /slo-specs/ 目录下,通过CI流水线强制校验:
slo-validator检查SLI表达式语法(如rate(http_request_errors_total{job="account-api"}[5m]) / rate(http_requests_total{job="account-api"}[5m]))budget-calculator自动推导季度错误预算基线(基于历史90天P50/P90/P99分位数加权计算)
2024年Q1审计显示,SLO变更合并前平均阻断率23%,其中76%为SLI指标标签不一致(如env=prod vs environment=production)。
flowchart LR
A[Git Push slo-specs/v2/account-slo.yaml] --> B[CI触发slo-validator]
B --> C{语法校验通过?}
C -->|Yes| D[执行budget-calculator]
C -->|No| E[Reject PR with line-specific error]
D --> F[生成SLO Dashboard JSON]
F --> G[Deploy to Grafana via Terraform] 