redis 缓存雪崩、穿透、击穿
·
giftia
缓存的使用
func GetData(key string) (string, error) {
// 先查询缓存
value, err := redisClient.Get(key).Result()
if err == nil {
return value, nil
} else if err == redis.Nil {
// 缓存失效,从数据库查询
data, err := db.GetData(key)
if err != nil {
return "", err
}
// 缓存到redis
redisClient.Set(key, data, 30*time.Minute)
return data, nil
} else {
return "", err
}
}
三个问题
缓存击穿
- 热key突然过期,大量请求打到数据库
- 冷key突然变热key,过期后大量请求打到数据库
缓存雪崩
- 大量key集中过期,请求全部打到数据库
- redis 服务器宕机,大量请求打到数据库
缓存穿透
- 缓存和数据库中都没有,大量请求打到数据库(恶意攻击)(比如id为负或id巨大)
解决方案
缓存击穿
- 永不过期
- 热key永不过期
- 冷key先定时过期,监控访问量到某个数量时,设置永不过期
- 加锁排队
- 缓存过期后,对于大量请求,使用分布式锁,保证只有一个线程去查询数据库然后重置缓存,其他线程等待从缓存中查询。
func GetData(key string) (string, error) {
// 先查询缓存
value, err := redisClient.Get(key).Result()
if err == nil {
return value, nil
} else if err == redis.Nil {
// 缓存失效,加锁排队
redisClient.Lock()
defer redisClient.Unlock()
// 再次查询缓存(防止上一个抢到锁的线程已经重置了缓存)
value, err = redisClient.Get(key).Result()
if err == nil {
return value, nil
} else {
// 缓存失效,从数据库查询
data, err := db.GetData(key)
if err != nil {
return "", err
}
// 缓存到redis
redisClient.Set(key, data, 30*time.Minute)
return data, nil
}
} else {
return "", err
}
}
缓存雪崩
- 随机过期时间
- 随机设置缓存过期时间,避免集中过期。
func GetData(key string) (string, error) {
// 先查询缓存
value, err := redisClient.Get(key).Result()
if err == nil {
return value, nil
} else if err == redis.Nil {
// 缓存失效,加锁排队
redisClient.Lock()
defer redisClient.Unlock()
// 再次查询缓存(防止上一个抢到锁的线程已经重置了缓存)
value, err = redisClient.Get(key).Result()
if err == nil {
return value, nil
} else {
// 缓存失效,从数据库查询
data, err := db.GetData(key)
if err != nil {
return "", err
}
// 缓存到redis, 随机过期时间
expire := time.Duration(rand.Intn(30*60)) * time.Second
redisClient.Set(key, data, expire)
return data, nil
}
} else {
return "", err
}
}
- redis 高可用(解决服务器宕机)
- 集群、哨兵模式、主从模式、容灾备份
缓存穿透
- 参数校验(不能完全杜绝)
- 对于查询参数,做必要的校验,比如id是否合法,长度是否合法等。
- 缓存空值
- 对于查询不到的数据,设置一个空值到缓存,避免频繁查询数据库。
func GetData(key string) (string, error) {
// 先查询缓存
value, err := redisClient.Get(key).Result()
if err == nil {
return value, nil
} else if err == redis.Nil {
// 缓存失效,加锁排队
redisClient.Lock()
defer redisClient.Unlock()
// 再次查询缓存(防止上一个抢到锁的线程已经重置了缓存)
value, err = redisClient.Get(key).Result()
if err == nil {
return value, nil
} else {
// 缓存失效,从数据库查询
data, err := db.GetData(key)
if err != nil {
if err == db.ErrDataNotFound {
// 缓存空值
redisClient.Set(key, "", 30*time.Minute)
return "", nil
}
return "", err
}
// 缓存到redis, 随机过期时间
expire := time.Duration(rand.Intn(30*60)) * time.Second
redisClient.Set(key, data, expire)
return data, nil
}
} else {
return "", err
}
}
- 布隆过滤器(黑白名单)
- 对于查询的不存在id,放到布隆过滤器中,黑名单,不通过。
- 对于查询的存在id,放到布隆过滤器中,白名单,通过。