第一章:头像服务SLA跌穿99.9%的根因诊断与业务影响全景分析
头像服务作为用户身份识别的核心链路,其可用性直接关联登录、社交互动、消息推送等关键场景。当监控系统连续3分钟上报HTTP 5xx错误率突破0.15%,且P99响应延迟跃升至2.8s(正常值≤120ms),SLA正式跌破99.9%阈值——这不仅是技术指标异常,更是业务信任的裂痕。
根因定位路径
采用“流量-资源-依赖”三维归因法:
- 流量侧:通过
kubectl top pods -n avatar-prod发现avatar-resizer实例CPU持续满载(>95%),但QPS仅达设计容量的62%,排除单纯流量洪峰; - 资源侧:执行
strace -p $(pgrep -f 'imagemagick') -e trace=epoll_wait,read,write捕获到大量EAGAIN系统调用失败,指向内核socket缓冲区耗尽; - 依赖侧:
curl -v http://storage-gateway:8080/health返回503,结合kubectl describe pod storage-gateway发现其InitContainer因ConfigMap挂载超时(MountVolume.SetUp failed: timed out waiting for condition)导致启动卡死。
关键故障点验证
# 检查存储网关配置热加载状态(暴露ConfigMap版本不一致问题)
kubectl get cm storage-config -o jsonpath='{.metadata.resourceVersion}' # 返回"184723"
kubectl get pod storage-gateway -o jsonpath='{.spec.volumes[?(@.name=="config")].configMap.items[0].key}' # 返回"config.yaml"
# 对比发现ConfigMap实际内容版本为184722,引发热加载失败回退至旧版配置,触发连接池泄漏
业务影响量化清单
| 场景 | 受影响范围 | 用户感知表现 |
|---|---|---|
| 新用户注册 | 100% | 头像上传后始终显示默认图标 |
| 私信对话 | 73%会话中断 | 接收方头像加载失败,误判为账号异常 |
| 直播打赏弹幕 | P99延迟+1.4s | 礼物特效与头像不同步,引发投诉激增 |
此次故障暴露了配置管理与容器生命周期耦合过深的问题——当ConfigMap更新未同步触发Pod滚动更新时,服务健康检查机制未能拦截带缺陷配置的实例上线。
第二章:Golang熔断器实战——基于go-zero circuitbreaker的动态阈值设计与压测验证
2.1 熔断状态机原理与go-zero内置熔断器源码级剖析
熔断器本质是三态有限状态机:Closed(正常通行)、Open(拒绝请求)、Half-Open(试探性放行)。
状态跃迁核心逻辑
// circuitbreaker/state.go 片段
func (cb *circuitBreaker) allow() error {
switch cb.state.Load() {
case stateClosed:
if cb.shouldOpen() { // 错误率超阈值且窗口内请求数达标
cb.setState(stateOpen)
cb.openStart = time.Now()
}
case stateOpen:
if time.Since(cb.openStart) >= cb.timeout {
cb.setState(stateHalfOpen) // 超时自动进入半开
}
}
return cb.checkAllowed()
}
shouldOpen() 基于滑动窗口统计错误率;timeout 默认 60s,可配置;setState() 使用原子操作保障并发安全。
状态迁移条件对比
| 状态 | 触发条件 | 后续动作 |
|---|---|---|
| Closed | 错误率 ≥ 50% 且请求数 ≥ 20 | 切换至 Open |
| Open | 持续时间 ≥ timeout | 自动切换至 Half-Open |
| Half-Open | 成功请求数 ≥ 1 且无失败 | 切换回 Closed |
graph TD
A[Closed] -->|错误率超标| B[Open]
B -->|超时| C[Half-Open]
C -->|成功| A
C -->|失败| B
2.2 自定义错误分类策略:区分网络超时、服务端5xx与客户端4xx熔断触发逻辑
熔断器需依据错误语义差异化响应,而非仅依赖失败率阈值。
错误类型判定优先级
- 网络超时(
SocketTimeoutException/ConnectException)→ 立即触发半开状态 - HTTP 5xx(500, 502, 503, 504)→ 归入服务端故障,计入熔断计数
- HTTP 4xx(除401/403外)→ 多为客户端逻辑错误,不计入熔断统计
熔断判定核心逻辑(Java示例)
public boolean shouldTrip(Throwable t) {
if (t instanceof TimeoutException) return true; // 超时强熔断
if (t instanceof WebClientResponseException e) {
int status = e.getStatusCode().value();
return status >= 500 && status < 600; // 仅5xx计入
}
return false;
}
TimeoutException 表示下游不可达,必须阻断;WebClientResponseException 携带真实HTTP状态码,仅5xx反映服务端异常,避免将参数校验失败(400)误判为系统故障。
错误分类决策表
| 错误类型 | 是否计入熔断 | 原因说明 |
|---|---|---|
TimeoutException |
是 | 下游无响应,链路失效 |
HttpStatus.503 |
是 | 服务端过载或宕机 |
HttpStatus.400 |
否 | 客户端请求非法,非服务问题 |
graph TD
A[异常发生] --> B{是否超时?}
B -->|是| C[立即熔断]
B -->|否| D{是否5xx响应?}
D -->|是| E[累加熔断计数]
D -->|否| F[忽略,不干预]
2.3 动态恢复窗口配置:基于滑动时间窗+失败率双指标的自适应重试机制
传统固定重试策略在流量突增或依赖抖动时易陷入“重试风暴”。本机制融合滑动时间窗计数与实时失败率阈值,实现窗口大小与重试间隔的协同调节。
核心决策逻辑
- 每10秒滚动统计最近60秒内请求总数与失败数
- 当失败率 > 30% 且连续2个窗口达标 → 自动延长恢复窗口至原值1.5倍
- 失败率
配置参数表
| 参数 | 默认值 | 说明 |
|---|---|---|
windowSizeSec |
60 | 滑动窗口总时长(秒) |
bucketCount |
6 | 时间分桶数(每桶10秒) |
failureThreshold |
0.3 | 触发扩容的失败率阈值 |
# 动态窗口计算示例(伪代码)
def calculate_adaptive_window(failure_rate, current_window):
if failure_rate > 0.3 and consecutive_violations >= 2:
return min(current_window * 1.5, 300) # 上限5分钟
elif failure_rate < 0.1 and stable_windows >= 3:
return max(current_window / 1.5, 30) # 下限30秒
return current_window
该函数确保窗口在30–300秒区间内平滑伸缩,避免激进跳变;consecutive_violations和stable_windows由环形缓冲区维护,保障状态一致性。
graph TD
A[采集60s内各10s桶失败数] --> B{失败率>0.3?}
B -->|是| C[检查连续违规次数]
B -->|否| D[检查稳定窗口数]
C -->|≥2| E[窗口×1.5]
D -->|≥3| F[窗口÷1.5]
2.4 熔断指标埋点与Prometheus集成:实时观测熔断触发频次与持续时长
为精准捕获熔断状态变化,需在熔断器核心路径注入可观测性埋点:
// 在 CircuitBreaker.transition() 中添加指标上报
counter.labels("state", "OPEN").inc(); // 触发次数
timer.startTimer().observeDuration(); // 记录单次熔断持续时长(秒)
该埋点将熔断事件映射为 Prometheus 的 circuit_breaker_state_total(计数器)与 circuit_breaker_duration_seconds(直方图),支持按服务、实例、异常类型多维下钻。
关键指标语义说明
circuit_breaker_state_total{state="OPEN",service="payment"}:累计触发次数circuit_breaker_duration_seconds_sum{state="OPEN"}:总熔断时长(秒)
Prometheus 配置片段
| job_name | scrape_interval | metrics_path |
|---|---|---|
circuit-breaker |
5s |
/actuator/prometheus |
graph TD
A[熔断状态变更] --> B[埋点采集]
B --> C[暴露为/metrics]
C --> D[Prometheus拉取]
D --> E[Grafana可视化]
2.5 压测验证闭环:使用ghz模拟高并发头像请求,验证熔断生效延迟与恢复稳定性
为精准捕获熔断器在真实流量下的响应行为,选用轻量级gRPC压测工具 ghz 构建阶梯式并发模型:
ghz --insecure \
-c 200 -n 10000 \
-call pb.UserService/GetAvatar \
--proto avatar.proto \
--call-timeout 2s \
--connect-timeout 1s \
localhost:9090
参数说明:
-c 200模拟200并发连接,--call-timeout 2s触发熔断阈值(服务端配置为连续5次超时即熔断),--connect-timeout隔离网络抖动干扰。该组合可复现“超时→熔断→半开→恢复”全生命周期。
熔断状态观测维度
- ✅ 请求成功率(%)
- ✅ 平均延迟(ms)
- ❌ 错误类型分布(gRPC status code)
| 阶段 | 成功率 | P99延迟 | 熔断状态 |
|---|---|---|---|
| 正常期 | 99.8% | 120ms | CLOSED |
| 熔断触发点 | 42.1% | 2150ms | OPEN |
| 半开探测期 | 76.3% | 890ms | HALF_OPEN |
状态流转验证流程
graph TD
A[正常请求] -->|连续5次Timeout| B[OPEN]
B -->|休眠期结束| C[HALF_OPEN]
C -->|试探请求成功| D[CLOSED]
C -->|试探失败| B
第三章:降级兜底体系构建——多级Fallback策略与业务语义化兜底实践
3.1 默认头像降级链路设计:CDN静态资源兜底 + 本地fallback图谱预加载
当用户无显式头像时,需保障头像加载的高可用性与低延迟。我们构建三级降级链路:远程 CDN → 本地 assets → 内存预加载图谱。
降级优先级与触发条件
- CDN 图片(
https://cdn.example.com/avatars/default-{id}.png)超时 >800ms 或 HTTP 404 时触发降级 - 本地 fallback 图(
/static/avatars/fallback-{mod16}.png)缺失则启用内存图谱 - 图谱预加载在应用启动时完成,覆盖 16 类语义化默认头像(职业、兴趣、地域等)
预加载图谱结构
| ID (mod 16) | 语义类别 | 文件路径 |
|---|---|---|
| 0 | 工程师 | assets/ico/engineer.svg |
| 7 | 设计师 | assets/ico/designer.svg |
| 15 | 学生 | assets/ico/student.svg |
降级逻辑代码(前端)
async function loadAvatar(userId) {
const cdnUrl = `https://cdn.example.com/avatars/default-${userId}.png`;
try {
const res = await fetch(cdnUrl, { cache: 'force-cache', mode: 'no-cors' });
if (res.ok) return cdnUrl;
} catch (e) { /* 忽略网络错误 */ }
// 降级至本地资源
const localId = userId % 16;
const localPath = `/static/avatars/fallback-${localId}.png`;
// 若本地也失败,则返回预加载的 SVG 图谱(已注入 DOM)
return avatarMap.get(localId) || localPath;
}
该函数实现非阻塞异步降级:CDN 请求不阻塞渲染,失败后立即切换至本地路径;avatarMap 是启动时预加载的 <svg> 元素 Map,避免重复 DOM 查询。
graph TD
A[请求头像] --> B{CDN 可用?}
B -- 是 --> C[返回 CDN 图]
B -- 否 --> D{本地文件存在?}
D -- 是 --> E[返回本地 PNG]
D -- 否 --> F[返回预加载 SVG 图谱]
3.2 用户画像驱动的智能降级:基于活跃度/会员等级动态切换降级粒度
传统降级策略常采用全局统一开关,忽略用户价值差异。本方案将降级决策与实时用户画像深度耦合,实现“高价值用户保核心、低活跃用户限范围”。
动态降级策略路由逻辑
def get_degrade_level(user_profile: dict) -> str:
# 根据活跃度(近7日登录频次)和会员等级映射降级粒度
activity = user_profile.get("activity_7d", 0)
level = user_profile.get("vip_level", 0)
if level >= 4 or activity >= 15: # 黄金/钻石会员或高频活跃
return "minimal" # 仅降级非关键路径(如推荐浮层)
elif level >= 2 or activity >= 5:
return "moderate" # 降级次要服务(如评论热榜、头图轮播)
else:
return "aggressive" # 全量非核心降级(保留登录、主帖流)
逻辑说明:
activity_7d单位为次/周,vip_level为整数等级(0–6);minimal策略确保支付、消息等核心链路100%可用,aggressive下仅保障HTTP 200状态与基础数据加载。
降级粒度对照表
| 用户分群 | 活跃度阈值 | 会员等级 | 允许降级模块 |
|---|---|---|---|
| 高价值用户 | ≥15 | ≥4 | 仅UI动效、埋点上报 |
| 中价值用户 | 5–14 | 2–3 | 评论区、社交关系链、广告位 |
| 低价值用户 | 0–1 | 头条推荐、视频自动播放、消息红点 |
决策流程示意
graph TD
A[实时获取用户画像] --> B{VIP等级 ≥ 4?}
B -->|是| C[启用 minimal 降级]
B -->|否| D{活跃度 ≥15?}
D -->|是| C
D -->|否| E{VIP ≥2 或 活跃≥5?}
E -->|是| F[启用 moderate 降级]
E -->|否| G[启用 aggressive 降级]
3.3 降级开关治理:通过etcd动态配置+go-zero配置中心实现灰度降级能力
核心架构设计
采用「配置中心双写 + 客户端监听」模式:go-zero 内置 conf 模块监听 etcd 路径 /feature/switches/{service},支持毫秒级变更感知。
数据同步机制
// 初始化带 etcd watch 的配置管理器
c := conf.NewConfigCenter(
conf.WithEtcdEndpoints([]string{"http://etcd:2379"}),
conf.WithWatchPath("/feature/switches/order-svc"),
)
// 自动反序列化 JSON 配置为结构体
type SwitchConfig struct {
PaymentTimeout bool `json:"payment_timeout"` // 是否启用支付超时降级
InventoryMock bool `json:"inventory_mock"` // 是否启用库存模拟兜底
}
该代码启动长连接监听 etcd 中指定路径,当配置变更时触发回调并热更新内存中的 SwitchConfig 实例,避免重启服务。
灰度控制粒度对比
| 维度 | 全量开关 | 标签路由灰度 | 请求级动态权重 |
|---|---|---|---|
| 生效范围 | 全集群 | 用户标签 | traceID/UID |
| 变更延迟 | |||
| 运维复杂度 | 低 | 中 | 高 |
降级决策流程
graph TD
A[HTTP请求] --> B{读取本地SwitchConfig}
B -->|payment_timeout=true| C[跳过真实支付网关]
B -->|inventory_mock=true| D[返回预设库存JSON]
C & D --> E[记录降级日志+指标打标]
第四章:本地缓存三级防御——LRU+TTL+一致性哈希在头像场景的深度优化
4.1 go-zero cache组件二次封装:支持头像URL哈希分片与内存容量自适应驱逐
设计动机
头像URL高频访问且分布不均,原生cache.Cache易因单热点Key导致内存倾斜与OOM。需在不侵入go-zero核心的前提下,实现URL感知的分片与弹性驱逐。
核心能力
- 基于MD5前两位做16路哈希分片(
shardID = md5(url)[0:2] % 16) - 内存用量超阈值(默认80%)时,自动触发LRU+大小加权混合淘汰
分片缓存结构
type AvatarCache struct {
shards [16]*cache.Cache // 预分配16个独立cache实例
memMonitor *MemMonitor // 实时监控RSS
}
shards隔离不同URL段的内存压力;MemMonitor每5s采样runtime.ReadMemStats().Sys,动态调整各shard的MaxEntries上限。
驱逐策略权重表
| 指标 | 权重 | 说明 |
|---|---|---|
| 对象大小 | 0.6 | 优先淘汰大尺寸头像二进制 |
| 最近访问时间 | 0.4 | 避免频繁冷数据驻留 |
流程示意
graph TD
A[Get avatar URL] --> B{Hash URL → shardID}
B --> C[Shard-local Get]
C --> D{Miss?}
D -->|Yes| E[Load from OSS]
D -->|No| F[Return cached bytes]
E --> G[Auto-evict by weight]
4.2 多级缓存协同策略:内存缓存(groupcache)→ 本地磁盘缓存(badger)→ 远程Redis兜底
缓存层级职责划分
- GroupCache:无中心化、LRU淘汰的分布式内存缓存,用于高频热点数据(如用户会话元信息),毫秒级响应;
- Badger:基于LSM-tree的嵌入式KV引擎,持久化冷热过渡数据(如配置快照),兼顾读性能与断电安全;
- Redis:作为最终一致性兜底层,承载全局共享状态(如分布式锁、计数器),支持高并发写入与Pub/Sub通知。
数据流向与降级逻辑
func Get(key string) (value []byte, err error) {
// 1. 尝试内存缓存(groupcache)
if val, ok := groupCache.Get(key); ok {
return val, nil
}
// 2. 回源本地磁盘(badger)
if val, err := badgerDB.Get([]byte(key)); err == nil {
groupCache.Set(key, val) // 回填上层
return val, nil
}
// 3. 最终降级至Redis
return redisClient.Get(ctx, key).Bytes()
}
逻辑分析:该函数实现“穿透式”三级回源。groupCache.Get 为无网络调用的本地内存查;badgerDB.Get 使用 []byte 键避免序列化开销;redisClient.Get 后未做回填,因Redis已为权威源,避免写放大。
缓存一致性对比
| 层级 | 一致性模型 | TTL机制 | 典型延迟 |
|---|---|---|---|
| GroupCache | 最终一致 | LRU + TTL | |
| Badger | 强一致(本地) | 自定义过期字段 | ~1ms |
| Redis | 线性一致 | 原生EXPIRE | ~2ms |
流程图:请求生命周期
graph TD
A[Client Request] --> B{GroupCache Hit?}
B -->|Yes| C[Return from memory]
B -->|No| D{Badger Hit?}
D -->|Yes| E[Load & cache to GroupCache]
D -->|No| F[Fetch from Redis]
F --> G[Write-through to Badger?]
G -->|Optional| H[Async persist]
4.3 缓存穿透防护:布隆过滤器预检 + 空值缓存+随机TTL防雪崩
缓存穿透指大量非法或不存在的 key(如恶意构造的 ID)绕过缓存直接打到数据库,造成 DB 压力激增。
布隆过滤器预检
在请求进入缓存前,先查布隆过滤器判断 key 是否可能存在:
// 初始化布隆过滤器(m=2^20, k=3)
BloomFilter<String> bloom = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1_048_576, // 预期容量
0.01 // 误判率 ≤1%
);
逻辑分析:布隆过滤器空间高效、查询 O(1),但存在可控误判(返回 true 不代表一定存在);若返回 false,则 key 绝对不存在,可立即拦截。参数 1_048_576 保证内存约 1MB,0.01 控制漏放率。
三重防护协同机制
| 防护层 | 作用 | 关键特性 |
|---|---|---|
| 布隆过滤器 | 拦截 99% 无效请求 | 无状态、低延迟 |
| 空值缓存 | 对确认不存在的 key 缓存 null | TTL 随机化(如 60±20s) |
| 随机 TTL | 避免空值集体过期引发雪崩 | 分散失效时间窗口 |
graph TD
A[客户端请求] --> B{布隆过滤器检查}
B -- 不存在 --> C[直接返回]
B -- 可能存在 --> D[查 Redis]
D -- 命中 --> E[返回数据]
D -- 未命中 --> F[查 DB]
F -- 存在 --> G[写入缓存+布隆]
F -- 不存在 --> H[写入空值+随机TTL]
4.4 缓存一致性保障:基于Redis Pub/Sub的头像更新事件广播与本地缓存主动失效
数据同步机制
当用户更新头像时,服务端发布事件至 Redis 频道 avatar:updated,所有订阅节点实时接收并清除对应用户的本地 Guava Cache 条目。
// 发布事件(更新后触发)
redisTemplate.convertAndSend("avatar:updated",
new AvatarUpdateEvent(userId, timestamp));
AvatarUpdateEvent 包含 userId(主键)、timestamp(用于幂等校验),确保仅处理最新更新。
事件消费与失效策略
// 订阅端监听并失效本地缓存
redisMessageListenerContainer.addMessageListener(
(message, pattern) -> {
AvatarUpdateEvent event = json.decode(message.getBody(), AvatarUpdateEvent.class);
localCache.invalidate(event.getUserId()); // 主动驱逐,避免脏读
}, new PatternTopic("avatar:updated"));
该逻辑绕过被动 TTL 过期,实现毫秒级一致性,消除多实例间缓存不一致窗口。
关键参数对比
| 参数 | 生产推荐值 | 说明 |
|---|---|---|
cache.maxSize |
10,000 | 平衡内存与命中率 |
redis.pubsub.timeout |
3000ms | 防止连接阻塞 |
event.idempotency.window |
5s | 基于 timestamp 去重 |
graph TD
A[用户上传新头像] --> B[写DB + 生成CDN URL]
B --> C[Pub avatar:updated 事件]
C --> D[集群各节点 Sub 并 invalidate localCache]
D --> E[后续请求命中最新CDN地址]
第五章:Go-zero头像服务高可用配置模板与SLO监控看板交付
高可用配置模板设计原则
Go-zero头像服务采用多可用区部署模型,基于 Kubernetes StatefulSet 管理无状态处理节点,配合 etcd 作为分布式配置中心。核心配置模板分离环境变量(如 AVATAR_BUCKET_NAME, REDIS_ADDR)与静态参数(如 max_upload_size: 5M, cache_ttl: 3600s),通过 Helm Chart 的 values-prod.yaml 实现生产环境差异化注入。所有配置项均启用 Schema 校验,使用 go-zero 内置的 zrpc.MustNewConfig() 进行启动时强校验,避免运行时因配置缺失导致 panic。
SLO 定义与可观测性锚点
头像服务定义三项关键 SLO:
- 上传成功率 ≥99.95%(窗口:1 小时,错误码 4xx/5xx 分类统计)
- 缩略图生成 P95 延迟 ≤320ms(采样路径
/api/v1/avatar/thumbnail) - CDN 回源失败率 ≤0.1%(基于边缘日志中
X-Cache: MISS+status=502联合过滤)
Prometheus 指标采集通过 go-zero 自带的 stat 组件暴露 /debug/metrics,并集成 OpenTelemetry SDK 上报 trace 数据至 Jaeger。
Grafana 看板结构与告警联动
| 交付的 SLO 监控看板包含四个核心面板: | 面板名称 | 数据源 | 关键指标表达式 |
|---|---|---|---|
| SLO 达成热力图 | Prometheus | rate(http_server_requests_total{job="avatar-svc",code=~"4..|5.."}[1h]) / rate(http_server_requests_total{job="avatar-svc"}[1h]) |
|
| 缩略图延迟分布 | Prometheus + Histogram | histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{handler="thumbnail"}[1h])) by (le)) |
|
| CDN 回源健康度 | Loki + LogQL | count_over_time({job="cdn-edge"} \|~X-Cache: MISS\|~502[1h]) / count_over_time({job="cdn-edge"}[1h]) |
|
| Redis 连接池水位 | Prometheus | redis_exporter_connected_clients{job="redis-exporter"} / redis_exporter_config_maxclients{job="redis-exporter"} |
自动化交付流水线
CI/CD 流水线通过 GitHub Actions 触发,执行以下步骤:
- 使用
go-zero/tools/goctl生成新版config.yaml模板并校验语法; - 运行
helm lint --strict avatar-chart/验证 Chart 结构完整性; - 执行
terraform apply -auto-approve -target=module.monitoring同步更新 Grafana dashboard JSON 和 Alertmanager 规则; - 最终将渲染后的
values-prod.yaml和dashboard.json推送至内部 GitOps 仓库infra-configs/main的avatar-slo/目录。
# values-prod.yaml 片段示例(含健康检查增强)
livenessProbe:
httpGet:
path: /healthz
port: 8888
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["/bin/sh", "-c", "curl -f http://localhost:8888/readyz && test $(redis-cli -h $REDIS_ADDR info | grep 'used_memory_human' | awk '{print $2}' | sed 's/M//') -lt 1500"]
多集群灾备策略落地
在华东1(杭州)、华北2(北京)双集群部署头像服务,通过 Istio Gateway 的 DestinationRule 设置权重路由(主集群 95%,备用集群 5%),当 Prometheus 检测到主集群 http_server_requests_total{code=~"5.."} > 100 持续 5 分钟,触发 Argo Rollouts 自动切流。同时,OSS Bucket 开启跨区域复制(CRR),确保用户头像对象在异地具备秒级一致性。
graph LR
A[用户请求] --> B{Istio Ingress}
B -->|95%流量| C[杭州集群]
B -->|5%流量| D[北京集群]
C --> E[OSS杭州Bucket]
D --> F[OSS北京Bucket]
E <-->|CRR同步| F
C --> G[Redis杭州集群]
D --> H[Redis北京集群]
G -->|Sentinel哨兵| I[自动故障转移]
H -->|Sentinel哨兵| I
日志归档与审计追踪
所有上传请求日志经 Filebeat 采集后,按 trace_id 聚合写入 Elasticsearch,并设置 ILM 策略:热数据保留 7 天(SSD 存储),温数据压缩至冷存储(OSS 归档)保留 180 天。审计事件(如 DELETE /api/v1/avatar/{id})额外写入独立 Kafka Topic audit-avatar-delete,由 Flink 实时消费并落库至 MySQL 审计表,字段包括 operator_id, ip, user_agent, affected_count。
