diff --git a/docs/algorithm/bloom-filter.md b/docs/algorithm/bloom-filter.md index 7cd5cd8..7fd30eb 100644 --- a/docs/algorithm/bloom-filter.md +++ b/docs/algorithm/bloom-filter.md @@ -18,21 +18,48 @@ ### 工作原理 +```mermaid +graph LR + subgraph 插入元素x["插入元素 x"] + X["x"] --> H1["h₁(x) = 3"] + X --> H2["h₂(x) = 7"] + X --> H3["h₃(x) = 11"] + H1 --> B3["bit[3] = 1"] + H2 --> B7["bit[7] = 1"] + H3 --> B11["bit[11] = 1"] + end ``` -插入元素 x: - h1(x) = 3 → bit[3] = 1 - h2(x) = 7 → bit[7] = 1 - h3(x) = 11 → bit[11] = 1 -查询元素 y: - h1(y) = 3 → bit[3] = 1 ✓ - h2(y) = 5 → bit[5] = 0 ✗ → y 一定不存在 +```mermaid +graph LR + subgraph 查询y["查询元素 y — 一定不存在"] + Y["y"] --> YH1["h₁(y) = 3 → bit[3]=1 ✓"] + Y --> YH2["h₂(y) = 5 → bit[5]=0 ✗"] + YH1 --> YR["结论:一定不存在"] + YH2 --> YR + end -查询元素 z: - h1(z) = 3 → bit[3] = 1 ✓ (被 x 置的) - h2(z) = 7 → bit[7] = 1 ✓ (被 x 置的) - h3(z) = 11 → bit[11] = 1 ✓ (被 x 置的) - → z 可能存在(假阳性!实际未插入过) + subgraph 查询z["查询元素 z — 假阳性"] + Z["z"] --> ZH1["h₁(z)=3 → bit[3]=1 ✓"] + Z --> ZH2["h₂(z)=7 → bit[7]=1 ✓"] + Z --> ZH3["h₃(z)=11 → bit[11]=1 ✓"] + ZH1 --> ZR["结论:可能存在
⚠️ 实际未插入(假阳性)"] + ZH2 --> ZR + ZH3 --> ZR + end +``` + +```mermaid +graph LR + subgraph 位数组["位数组 (m=16)"] + direction LR + b0["0"] --- b1["0"] --- b2["0"] --- b3["1"] --- b4["0"] --- b5["0"] --- b6["0"] --- b7["1"] + b7 --- b8["0"] --- b9["0"] --- b10["0"] --- b11["1"] --- b12["0"] --- b13["0"] --- b14["0"] --- b15["0"] + end + + 插入元素x -.->|h₁ h₂ h₃| b3 + 插入元素x -.-> b7 + 插入元素x -.-> b11 ``` ### 假阳性率 @@ -52,6 +79,36 @@ $$p \approx \left(1 - e^{-kn/m}\right)^k$$ ### 布隆过滤器 vs 其他结构 +```mermaid +graph TB + subgraph BF["布隆过滤器 — 极致省空间"] + B1["位数组 m bit"] --> B2["k 个哈希函数"] + B2 --> B3["插入: O(k)"] + B2 --> B4["查询: O(k)"] + B2 --> B5["删除: ❌"] + B2 --> B6["假阳性: 有"] + end + + subgraph HS["HashSet — 精确但占空间"] + HS1["存完整元素"] --> HS2["哈希表"] + HS2 --> HS3["插入: O(1)"] + HS2 --> HS4["查询: O(1)"] + HS2 --> HS5["删除: ✅"] + HS2 --> HS6["假阳性: 无"] + end + + subgraph CBF["Counting Bloom — 可删除"] + C1["计数器数组 4m bit"] --> C2["k 个哈希函数"] + C2 --> C3["插入: O(k)"] + C2 --> C4["查询: O(k)"] + C2 --> C5["删除: ✅"] + C2 --> C6["假阳性: 有"] + end + + BF -.->|"约 4x 空间差"| CBF + HS -.->|"约 4~10x 空间差"| BF +``` + | | 布隆过滤器 | HashSet | Counting Bloom Filter | |---|-----------|---------|----------------------| | 空间 | ★★★★★ 极省 | ★★ 需存完整元素 | ★★★ 约为布隆 4x | @@ -142,6 +199,29 @@ func (bf *BloomFilter) hash(data []byte, i int) uint { ### 使用示例:缓存穿透防护 +```mermaid +sequenceDiagram + participant C as 客户端 + participant BF as 布隆过滤器 + participant R as Redis + participant DB as DB + + C->>BF: 查询 user:50 + BF-->>C: 一定不存在 ✗ + C-->>C: 直接返回,不查缓存/DB + + C->>BF: 查询 user:100 + BF-->>C: 可能存在 ✓ + C->>R: GET user:100 + alt 缓存命中 + R-->>C: 返回数据 ✅ + else 缓存 miss + C->>DB: SELECT * FROM users WHERE id=100 + DB-->>C: 返回数据 + C->>R: SET user:100 + TTL + end +``` + ```go package main diff --git a/docs/algorithm/heavykeeper.md b/docs/algorithm/heavykeeper.md index e517644..f7a430b 100644 --- a/docs/algorithm/heavykeeper.md +++ b/docs/algorithm/heavykeeper.md @@ -15,6 +15,30 @@ ## 📝 详细说明 +### 大象流 vs 老鼠流 + +```mermaid +graph LR + subgraph 数据流["高速数据流"] + F1["流 A: 10000次 🐘"] + F2["流 B: 8000次 🐘"] + F3["流 C: 1次 🐭"] + F4["流 D: 2次 🐭"] + F5["流 E: 1次 🐭"] + F6["流 ...: 1次 🐭"] + end + + F1 --> HK["HeavyKeeper"] + F2 --> HK + F3 --> HK + F4 --> HK + F5 --> HK + F6 --> HK + + HK --> TOP["Top-K 结果:
流 A: ~10000
流 B: ~8000"] + HK -.->|老鼠流被衰减淘汰| GONE["流 C~E: 归零 🗑️"] +``` + ### 背景:为什么需要 HeavyKeeper | 方案 | 优势 | 劣势 | @@ -26,6 +50,53 @@ ### 算法流程 +```mermaid +graph TB + Input["插入流 x"] --> Loop["遍历 d 行"] + + Loop --> Hash["pos = hᵢ(x) mod w"] + Hash --> Check{"bucket[pos]?"} + + Check -->|"flowID == x"| Hit["count++"] + Check -->|"flowID == 空"| Empty["写入 (x, 1)"] + Check -->|"flowID ≠ x"| Conflict{"random() < p?"} + + Conflict -->|"否"| Skip["跳过"] + Conflict -->|"是"| Decay["count--"] + + Decay --> Zero{"count == 0?"} + Zero -->|"是"| Replace["替换为 (x, 1)"] + Zero -->|"否"| Skip2["保留原流"] + + Hit --> NextRow["下一行"] + Empty --> NextRow + Skip --> NextRow + Replace --> NextRow + Skip2 --> NextRow + NextRow --> Loop + + style Hit fill:#4caf50,color:#fff + style Empty fill:#2196f3,color:#fff + style Decay fill:#ff9800,color:#fff + style Replace fill:#f44336,color:#fff +``` + +```mermaid +graph TB + subgraph 数据结构["HeavyKeeper 结构 (d=3, w=8)"] + direction TB + Row0["行 0: h₀(x)"] + Row1["行 1: h₁(x)"] + Row2["行 2: h₂(x)"] + + Row0 --- B00["流C:3"] --- B01["流A:10000"] --- B02[" "] --- B03["流D:1"] --- B04["流A:9500"] --- B05[" "] --- B06["流B:8000"] --- B07["流A:9800"] + Row1 --- B10["流B:7500"] --- B11[" "] --- B12["流A:10200"] --- B13["流E:1"] --- B14[" "] --- B15["流B:8200"] --- B16[" "] --- B17["流A:9000"] + Row2 --- B20[" "] --- B21["流A:10100"] --- B22["流C:2"] --- B23["流B:7800"] --- B24[" "] --- B25["流A:9900"] --- B26[" "] --- B27["流D:0🗑️"] + end + + Query["查询流 A"] --> Max["取各行最大 count = 10200"] +``` + ``` 数据结构:d × w 的二维表 每个桶:(flowID, count) @@ -53,6 +124,24 @@ Top-k 查询: ### 衰减概率的影响 +```mermaid +graph LR + subgraph p0["p = 0 — 无衰减"] + P0E["老鼠流永不清除
噪声累积,Top-K 精度低"] + end + + subgraph pok["p 适中 (0.01~0.1) — 最佳"] + POKE["老鼠流: count=1 → 一次衰减→0 🗑️
大象流: count=10000 → -1 无感 ✅
Top-K 精度高"] + end + + subgraph pbig["p 过大 (>0.3)"] + PBIGE["大象流也被严重衰减
估计偏低,Top-K 不准"] + end + + p0 -->|"增大 p"| pok + pok -->|"继续增大 p"| pbig +``` + | 衰减概率 p | 效果 | |-----------|------| | p = 0 | 无衰减,退化为 Count-Min Sketch 变体 | @@ -75,6 +164,8 @@ Top-k 查询: ### 基础实现 +```go + ```go package heavykeeper @@ -211,6 +302,18 @@ func sortDesc(items []Item) { ### 实战:Nginx 访问日志 Top-K 热门 IP +```mermaid +graph LR + Log["Nginx Access Log
stdin 流式读取"] --> Parse["提取 Client IP"] + Parse --> HK["HeavyKeeper
4行 × 65536桶 × 5%衰减
≈ 2MB"] + HK --> TopK["Top-10 热门 IP"] + + subgraph 流量模型 + Elephant["192.168.1.100 🐘
10.0.0.5 🐘"] -->|"高频"| HK + Mouse["172.16.x.x 🐭
大量低频 IP"] -->|"衰减淘汰"| HK + end +``` + ```go package main diff --git a/docs/architecture/cache/cache-breakdown-avalanche-penetration.md b/docs/architecture/cache/cache-breakdown-avalanche-penetration.md index 0eb9d63..9ec6354 100644 --- a/docs/architecture/cache/cache-breakdown-avalanche-penetration.md +++ b/docs/architecture/cache/cache-breakdown-avalanche-penetration.md @@ -13,6 +13,29 @@ --- +## 📊 三者发生机制 + +```mermaid +graph TB + subgraph 击穿["🔓 缓存击穿 — 单热点 key 过期"] + B1["热 key 过期"] --> B2["大量并发同时 miss"] + B2 --> B3["同时回源 DB"] + B3 --> B4["DB 瞬时尖峰 💥"] + end + + subgraph 雪崩["❄️ 缓存雪崩 — 大面积 key 集中失效"] + A1["大量 key 同时刻过期
或缓存节点宕机"] --> A2["请求大面积 miss"] + A2 --> A3["全部涌向 DB"] + A3 --> A4["DB 持续高压 💥"] + end + + subgraph 穿透["🕳️ 缓存穿透 — 查不存在的数据"] + C1["请求查询不存在的 key"] --> C2["缓存永远 miss"] + C2 --> C3["每次直达 DB"] + C3 --> C4["DB 持续高压 💥"] + end +``` + ## 📝 三者对比 | | 缓存击穿 | 缓存雪崩 | 缓存穿透 | @@ -24,6 +47,30 @@ --- +## 🛡️ 解决方案总览 + +```mermaid +graph LR + subgraph 击穿["击穿"] + A1["A1 永不过期"] + A2["A2 加锁排队
singleflight"] + end + + subgraph 雪崩["雪崩"] + B1["B1 加锁/限流"] + B2["B2 随机失效"] + B3["B3 Redis 高可用
多级缓存"] + end + + subgraph 穿透["穿透"] + C1["C1 参数校验"] + C2["C2 缓存空对象"] + C3["C3 布隆过滤器"] + end +``` + +--- + ## 🛡️ 解决方案 ### A. 缓存击穿 @@ -32,6 +79,26 @@ 逻辑过期而非 TTL 过期:缓存中存一个过期时间字段,由后台异步刷新,请求始终命中缓存。 +```mermaid +sequenceDiagram + participant C as 客户端 + participant R as Redis + participant W as 后台 Worker + participant DB as DB + + C->>R: GET key + R-->>C: 返回数据 + expireAt + alt 未过期 + C-->>C: 直接使用 ✅ + else 已过期(逻辑过期) + C-->>C: 仍返回旧数据 ✅(可用性优先) + C->>W: 触发异步刷新 + W->>DB: 查询最新数据 + DB-->>W: 返回新数据 + W->>R: SET key + 新 expireAt + end +``` + ```go type CacheItem struct { Data any @@ -62,6 +129,32 @@ func (s *Service) Get(ctx context.Context, key string) (any, error) { 只放一个请求去 DB 加载,其余请求等锁释放后读缓存。 +```mermaid +sequenceDiagram + participant C1 as 请求1 + participant C2 as 请求2 + participant C3 as 请求3 + participant R as Redis + participant Lock as 分布式锁 + participant DB as DB + + C1->>R: GET key → miss + C2->>R: GET key → miss + C3->>R: GET key → miss + + C1->>Lock: 尝试获锁 ✅ + C2->>Lock: 尝试获锁 ❌ 等待 + C3->>Lock: 尝试获锁 ❌ 等待 + + C1->>DB: SELECT ... + DB-->>C1: 返回数据 + C1->>R: SET key + TTL + C1->>Lock: 释放锁 + + C2->>R: GET key → hit ✅ + C3->>R: GET key → hit ✅ +``` + ```go var mu sync.Mutex @@ -150,6 +243,27 @@ func (s *Service) GetWithRateLimit(ctx context.Context, key string) (any, error) 给 TTL 加随机偏移量,避免大量 key 在同一时刻集中过期。 +```mermaid +graph LR + subgraph 无 Jitter["❌ 无随机偏移"] + T1["key_a TTL=30min"] --> E1["同时过期"] + T2["key_b TTL=30min"] --> E1 + T3["key_c TTL=30min"] --> E1 + E1 --> CRASH["DB 雪崩 💥"] + end + + subgraph 有 Jitter["✅ 有随机偏移"] + J1["key_a TTL=30m+2m"] --> D1["32min 过期"] + J2["key_b TTL=30m+7m"] --> D2["37min 过期"] + J3["key_c TTL=30m+4m"] --> D3["34min 过期"] + D1 --> SAFE["DB 压力分散 ✅"] + D2 --> SAFE + D3 --> SAFE + end + + 无 Jitter -.->|加 jitter| 有 Jitter +``` + ```go func (s *Service) SetWithJitter(ctx context.Context, key string, data any, baseTTL time.Duration) { jitter := time.Duration(rand.Intn(300)) * time.Second // 0~300s 随机偏移 @@ -168,6 +282,24 @@ for _, item := range items { 缓存节点宕机 = 所有 key 同时"失效",必须依赖高可用架构: +```mermaid +graph TB + subgraph 多级缓存架构 + Client["客户端请求"] + L1["L1 本地缓存
bigcache / ristretto"] + L2["L2 Redis
Sentinel / Cluster"] + L3["L3 DB"] + + Client --> L1 + L1 -->|miss| L2 + L2 -->|miss| L3 + L3 --> L2 + L2 --> L1 + + L1 -.->|Redis 不可用时
降级到本地| Client + end +``` + | 方案 | 说明 | |------|------| | **Redis Sentinel** | 自动故障转移,主节点挂掉时从节点升主 | @@ -256,6 +388,17 @@ func (s *Service) GetWithNullCache(ctx context.Context, key string) (any, error) 在缓存之前加一层布隆过滤器,不存在的 key 直接拦截。 +```mermaid +graph LR + Req["请求"] --> BF{"布隆过滤器"} + BF -->|"一定不存在 ✗"| Reject["直接拒绝
不查缓存/DB"] + BF -->|"可能存在 ✓"| Cache{"Redis 缓存"} + Cache -->|hit| Return["返回数据"] + Cache -->|miss| DB["查询 DB"] + DB -->|有数据| Cache + DB -->|无数据| Null["缓存空对象
短 TTL"] +``` + ```go import "github.com/bits-and-blooms/bloom/v3"