# Redis緩存穿透、緩存擊穿、緩存雪崩、熱點Key的示例分析
## 一、緩存穿透(Cache Penetration)
### 1.1 概念與場景
緩存穿透指**查詢不存在的數據**,導致請求直接穿透緩存層到達數據庫。常見場景:
- 惡意攻擊:故意請求不存在的ID(如負值、超大數值)
- 業務誤操作:前端傳遞無效參數(如已刪除的商品ID)
### 1.2 示例代碼
```java
// 錯誤示例:未做空值校驗
public Product getProduct(Long id) {
// 1. 先查緩存
Product product = redisTemplate.opsForValue().get("product:" + id);
if (product == null) {
// 2. 緩存未命中,直接查庫
product = productMapper.selectById(id); // 可能返回null
redisTemplate.opsForValue().set("product:" + id, product);
}
return product;
}
// 改進代碼
if (product == null) {
product = productMapper.selectById(id);
if (product == null) {
redisTemplate.opsForValue().set("product:" + id, "NULL", 5, TimeUnit.MINUTES);
return null;
}
redisTemplate.opsForValue().set("product:" + id, product);
}
# Python示例
from pybloom_live import ScalableBloomFilter
bf = ScalableBloomFilter(initial_capacity=1000)
# 預熱階段加載所有有效ID
for id in valid_ids:
bf.add(id)
# 查詢前校驗
if not id in bf:
return None
某個熱點Key突然過期時,大量并發請求直接擊穿到數據庫。典型場景: - 秒殺商品緩存過期 - 首頁熱門資訊緩存失效
public Product getProductWithLock(Long id) {
String lockKey = "lock:product:" + id;
try {
// 嘗試獲取分布式鎖
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (locked) {
Product product = productMapper.selectById(id);
redisTemplate.opsForValue().set("product:" + id, product, 1, TimeUnit.HOURS);
return product;
} else {
Thread.sleep(100); // 短暫等待后重試
return getProductWithLock(id);
}
} finally {
redisTemplate.delete(lockKey);
}
}
// 緩存數據結構
{
"value": "真實數據",
"expire_time": 1672502400 // 邏輯過期時間戳
}
大量Key同時過期或Redis集群宕機,導致請求全部涌向數據庫。典型案例: - 凌晨批量任務刷新所有緩存 - Redis主從切換期間
// 設置基礎過期時間+隨機偏移量
int baseExpire = 3600;
int randomExpire = new Random().nextInt(300);
redisTemplate.opsForValue().set(key, value, baseExpire + randomExpire, TimeUnit.SECONDS);
請求 → CDN → 本地緩存(Caffeine) → Redis → DB
# 偽代碼示例
def get_data(key):
if circuit_breaker.is_open():
return get_data_from_backup()
try:
data = redis.get(key)
if not data:
data = db.query(key)
redis.setex(key, ttl, data)
return data
except Exception as e:
circuit_breaker.record_failure()
raise e
// Guava本地緩存
LoadingCache<String, Product> localCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.SECONDS)
.build(new CacheLoader<String, Product>() {
@Override
public Product load(String key) {
return getFromRedis(key); // 從Redis集群讀取
}
});
# 將hot_key拆分為hot_key_1, hot_key_2...
shard_id = hash(user_id) % 4
real_key = f"hot_key_{shard_id}"
redis.get(real_key)
問題類型 | 核心特征 | 典型解決方案 |
---|---|---|
緩存穿透 | 查詢不存在數據 | 布隆過濾器、空值緩存 |
緩存擊穿 | 單熱點Key失效 | 互斥鎖、邏輯過期 |
緩存雪崩 | 大量Key同時失效 | 差異化TTL、多級緩存 |
熱點Key | 單Key訪問量極高 | 本地緩存、Key分片 |
實際生產環境中,建議結合監控系統(如Prometheus)和動態配置中心實現策略的實時調整。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。