跳转至

545_负载均衡与缓存策略


一句话说明

负载均衡分摊请求到多台服务器,缓存减少重复计算,两者结合是高并发系统的核心优化手段。


核心知识点

负载均衡算法

算法工作原理适用场景
轮询(Round Robin)依次分配服务器配置相同
加权轮询按权重分配服务器配置不同
最少连接分到当前连接最少的请求处理时间不均
IP Hash同IP固定服务器需要Session粘滞
一致性哈希哈希环分配缓存分布式场景
随机随机选择简单场景

缓存层次结构(从快到慢)

L1: 应用内存缓存(最快,≤1ms)
    - Python: functools.lru_cache
    - Java: Guava Cache
    - 特点:进程级,服务重启丢失

L2: 分布式缓存(快,1-5ms)
    - Redis / Memcached
    - 特点:多服务共享,持久化可选

L3: CDN缓存(快,取决于节点距离)
    - 静态资源:HTML/CSS/JS/图片

L4: 数据库查询缓存(慢,>10ms)
    - MySQL Query Cache(已废弃)
    - 推荐:在应用层用Redis缓存查询结果

L5: 磁盘缓存(最慢,>1ms)
    - 操作系统页缓存

缓存淘汰策略

LRU (Least Recently Used):最近最少使用 → 最常用,Redis默认
LFU (Least Frequently Used):最不常用 → 适合访问频率差异大的场景
TTL (Time To Live):按时间过期 → 最简单,数据有时效性时用
FIFO:先进先出 → 极少使用

实战代码/设计图/模板

负载均衡架构图

Internet
[DNS] → www.example.com → 多个IP(DNS轮询)
[四层LB(LVS/HAProxy)]  ← 处理TCP连接
[七层LB(Nginx/Envoy)]  ← 处理HTTP请求,可做路由、限流
┌───┼───┐
▼   ▼   ▼
[Web服务1][Web服务2][Web服务3]
    │         │         │
    └─────────┴─────────┘
         [后端服务]

Nginx 负载均衡配置

upstream backend_pool {
    # 轮询(默认)
    server 192.168.1.10:8000;
    server 192.168.1.11:8000;
    server 192.168.1.12:8000;

    # 加权轮询
    # server 192.168.1.10:8000 weight=3;
    # server 192.168.1.11:8000 weight=1;

    # 健康检查
    keepalive 32;
}

server {
    listen 80;

    location /api/ {
        proxy_pass http://backend_pool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # 超时设置
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
    }
}

Redis 缓存使用示例(生信场景)

import redis
import json
import hashlib
from functools import wraps

r = redis.Redis(host='localhost', port=6379, db=0)

def cache_result(ttl=3600):
    """
    缓存装饰器
    ttl:缓存过期时间(秒),默认1小时
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存 key
            key_data = f"{func.__name__}:{args}:{sorted(kwargs.items())}"
            cache_key = "biocache:" + hashlib.md5(key_data.encode()).hexdigest()

            # 查缓存
            cached = r.get(cache_key)
            if cached:
                return json.loads(cached)  # 缓存命中,直接返回

            # 缓存未命中,执行函数
            result = func(*args, **kwargs)

            # 写入缓存
            r.setex(cache_key, ttl, json.dumps(result))
            return result
        return wrapper
    return decorator

@cache_result(ttl=7200)  # 缓存2小时
def get_sample_diversity(sample_id: str) -> dict:
    """获取样本多样性指数(计算耗时,适合缓存)"""
    # 实际计算逻辑...
    return {"shannon": 3.2, "chao1": 450}

# 缓存失效(样本数据更新后主动清理)
def invalidate_sample_cache(sample_id: str):
    pattern = f"biocache:*{sample_id}*"
    keys = r.keys(pattern)
    if keys:
        r.delete(*keys)

缓存穿透/击穿/雪崩防护

import time
import random

# 防缓存穿透:布隆过滤器或缓存空值
def get_with_null_cache(key: str):
    value = r.get(key)
    if value == b"NULL":
        return None  # 空值也缓存,防止穿透
    if value:
        return json.loads(value)

    result = db.query(key)
    if result is None:
        r.setex(key, 60, "NULL")  # 缓存空结果60秒
    else:
        r.setex(key, 3600, json.dumps(result))
    return result

# 防缓存雪崩:TTL加随机抖动
def set_with_jitter(key: str, value, base_ttl=3600):
    jitter = random.randint(0, 300)  # 0-5分钟随机偏移
    r.setex(key, base_ttl + jitter, json.dumps(value))

面试常问点

问题参考答案
缓存穿透、击穿、雪崩区别?穿透=查不存在的key;击穿=热key过期;雪崩=大量key同时过期
Redis vs Memcached 选哪个?Redis功能丰富(数据结构多、持久化),推荐Redis
如何保证缓存和DB一致性?先写DB再删缓存(Cache-Aside),或延迟双删
一致性哈希解决什么问题?节点增删时减少缓存迁移量
如何做缓存预热?系统启动时从DB批量加载热数据到Redis

速查表

Redis 常用命令:
  SET key value EX 3600   # 设置值+过期时间
  GET key                  # 获取值
  DEL key                  # 删除
  EXISTS key               # 是否存在
  TTL key                  # 剩余过期时间
  INCR key                 # 原子自增(计数器)
  EXPIRE key 3600          # 设置过期时间
  KEYS pattern             # 查找匹配的key(生产慎用)
  SCAN cursor MATCH pattern # 安全的key扫描

缓存命中率参考:
  > 95% 优秀
  80-95% 良好
  < 80%  需要优化