gin和gorm进阶功能(10) | 青训营笔记

117 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天

本文将介绍缓存的相关处理

原因以及解决方法

缓存穿透

缓存穿透: 缓存穿透是指,由于缓存中没有某个数据,但是请求仍然会被发送到后端数据库,导致大量无效查询,造成数据库的负担增大。缓存穿透一般出现在高并发环境下,对于不存在的数据请求,返回的值不存在的情况。

解决方法:

  1. 布障法:使用布障,例如在查询数据前先在缓存中查询该数据是否存在,如果不存在则直接返回,不再查询数据库。
  2. 设置空值:对于不存在的数据,可以设置一个空值缓存到缓存中,防止下次请求时对数据库造成负担。
  3. 哈希表:使用哈希表维护数据库中的所有数据的标识,如果数据不存在则直接返回,不再查询数据库。

缓存雪崩

缓存雪崩: 缓存雪崩是指,由于缓存数据在同一时刻失效,导致大量请求同时请求数据库,造成数据库负担瞬间增大,从而导致系统故障。

解决方法:

  1. 随机失效时间:设置缓存数据的失效时间,使失效时间不同一时刻失效,分散请求的压力。

  2. 容错机制:使用容错机制,例如请求缓存数据失败时,使用降级策略,不再请求数据库,而是返回预设的数据。

  3. 分布式缓存:使用分布式缓存,将数据存储在多个缓存服务器上,分散请求的压力。

缓存击穿

缓存击穿: 缓存击穿是指,在缓存中存储了一个数据,但是因为请求量突增,导致该数据在缓存中失效,同时请求到达数据库,造成数据库的负担瞬间增大。

解决方法:

  1. 布障法:使用布障,例如对于敏感数据,使用布障技术限制请求次数,防止请求量突增。
  2. 设置过期时间:设置缓存数据的过期时间,使数据在一定时间内失效,降低请求量的压力。
  3. 分布式缓存:使用分布式缓存,将数据存储在多个缓存服务器上,分散请求的压力。

以上代码可见往期文章。

缓存数据一致性

在有数据变更的地方,同时更新缓存和数据库。

对于读多写少的情况,采用事务(高隔离级别)+先更新数据库再更新缓存。

对于并发要求高的且一致性要求高,选择“先更新数据库再删除缓存,并结合删除重试 + 补偿逻辑 + 缓存过期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]    # 报错的时候看