这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天
本文将介绍缓存的相关处理
原因以及解决方法
缓存穿透
缓存穿透: 缓存穿透是指,由于缓存中没有某个数据,但是请求仍然会被发送到后端数据库,导致大量无效查询,造成数据库的负担增大。缓存穿透一般出现在高并发环境下,对于不存在的数据请求,返回的值不存在的情况。
解决方法:
- 布障法:使用布障,例如在查询数据前先在缓存中查询该数据是否存在,如果不存在则直接返回,不再查询数据库。
- 设置空值:对于不存在的数据,可以设置一个空值缓存到缓存中,防止下次请求时对数据库造成负担。
- 哈希表:使用哈希表维护数据库中的所有数据的标识,如果数据不存在则直接返回,不再查询数据库。
缓存雪崩
缓存雪崩: 缓存雪崩是指,由于缓存数据在同一时刻失效,导致大量请求同时请求数据库,造成数据库负担瞬间增大,从而导致系统故障。
解决方法:
-
随机失效时间:设置缓存数据的失效时间,使失效时间不同一时刻失效,分散请求的压力。
-
容错机制:使用容错机制,例如请求缓存数据失败时,使用降级策略,不再请求数据库,而是返回预设的数据。
-
分布式缓存:使用分布式缓存,将数据存储在多个缓存服务器上,分散请求的压力。
缓存击穿
缓存击穿: 缓存击穿是指,在缓存中存储了一个数据,但是因为请求量突增,导致该数据在缓存中失效,同时请求到达数据库,造成数据库的负担瞬间增大。
解决方法:
- 布障法:使用布障,例如对于敏感数据,使用布障技术限制请求次数,防止请求量突增。
- 设置过期时间:设置缓存数据的过期时间,使数据在一定时间内失效,降低请求量的压力。
- 分布式缓存:使用分布式缓存,将数据存储在多个缓存服务器上,分散请求的压力。
以上代码可见往期文章。
缓存数据一致性
在有数据变更的地方,同时更新缓存和数据库。
对于读多写少的情况,采用事务(高隔离级别)+先更新数据库再更新缓存。
对于并发要求高的且一致性要求高,选择“先更新数据库再删除缓存,并结合删除重试 + 补偿逻辑 + 缓存过期TTL等综合手段”。
代码
对于读多写少的情况的代码实现:
func updateDBAndCache(db *sql.DB, cache *redis.Client, data []byte) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
// 更新数据库
stmt, err := tx.Prepare("UPDATE table SET data = ? WHERE id = ?")
if err != nil {
return err
}
defer stmt.Close()
if _, err := stmt.Exec(data, 1); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
// 更新缓存
err = cache.Set("cache_key", data, 0).Err()
if err != nil {
return err
}
return nil
}
对于并发要求高的且一致性要求高的代码实现:
func updateDBAndDeleteCache(db *sql.DB, cache *redis.Client, data []byte) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
// 更新数据库
stmt, err := tx.Prepare("UPDATE table SET data = ? WHERE id = ?")
if err != nil {
return err
}
defer stmt.Close()
if _, err := stmt.Exec(data, 1); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
// 删除缓存
err = cache.Del("cache_key").Err()
if err != nil {
return err
}
// 重试 + 补偿逻辑
for i := 0; i < 3; i++ {
_, err = cache.Get("cache_key").Result()
if err == redis.Nil {
break
}
// 进行补偿逻辑,比如重新更新数据库等
// ...
}
return nil
}
补充
docker安装redis
docker安装Redis
docker pull redis
#docker run -itd --name redis-test -p 6379:6379 redis
mkdir -p /root/redis/data //-p 表示递归创建 如果没有就创建
mkdir -p /root/redis/conf
vim /root/redis/conf/redis.conf //创建redis.conf 配置文件 文件内容如下
docker run --name dyredis -v /root/redis/data:/data -v /root/redis/conf/redis.conf:/etc/redis/redis.conf -d -p 6322:6379 redis redis-server /etc/redis/redis.conf
docker logs [容器ID] # 报错的时候看