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% 需要优化