首页 > 数据库 >redis缓存穿透和 缓存雪崩

redis缓存穿透和 缓存雪崩

时间:2024-12-27 15:18:58浏览次数:6  
标签:缓存 redis await userId redisCache 雪崩 null user

在使用Redis作为缓存系统时,缓存穿透(Cache Penetration) 和 缓存雪崩(Cache Avalanche) 是两种常见的问题。它们会影响缓存系统的性能和稳定性。以下是这两种问题的详细解释及其解决方法。

缓存穿透(Cache Penetration)

缓存穿透是指查询一个在缓存和数据库中都不存在的数据,导致请求直接穿透到数据库,增加了数据库的负载。

成因

  1. 恶意查询:
    • 攻击者故意查询一些在缓存和数据库中都不存在的数据。
  2. 误操作:
    • 用户或系统误操作查询了不存在的数据。
      示例
      假设有一个用户查询接口,用户ID 123456 不存在于缓存和数据库中。

        public async Task<User> GetUserAsync(int userId)
        {
        	// 尝试从缓存中获取用户
        	string userJson = await _redisCache.GetStringAsync($"user:{userId}");
        	if (userJson != null)
        	{
        		return JsonSerializer.Deserialize<User>(userJson);
        	}
      
        	// 从数据库中获取用户
        	User user = await _userRepository.GetByIdAsync(userId);
        	if (user != null)
        	{
        		// 将用户存入缓存
        		await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user));
        	}
      
        	return user;
        }
      

如果查询的用户ID 123456 不存在,每次查询都会直接穿透到数据库,增加数据库负载。

解决缓存穿透的方法

  1. 缓存空值:
    • 将查询结果为空的数据也缓存起来,设置一个较短的过期时间。
  2. 布隆过滤器(Bloom Filter):
    • 使用布隆过滤器来预先判断一个请求是否可能命中缓存,减少对数据库的无效查询。
  3. 参数校验:
    • 在查询缓存之前,对查询参数进行校验,确保参数的有效性。
  4. 限流和熔断:
    • 使用限流(Rate Limiting)和熔断(Circuit Breaking)机制来控制对数据库的请求。

示例:缓存空值

		public async Task<User> GetUserAsync(int userId)
		{
			// 尝试从缓存中获取用户
			string userJson = await _redisCache.GetStringAsync($"user:{userId}");
			if (userJson != null)
			{
				if (userJson == "null")
				{
					return null;
				}
				return JsonSerializer.Deserialize<User>(userJson);
			}

			// 从数据库中获取用户
			User user = await _userRepository.GetByIdAsync(userId);
			if (user != null)
			{
				// 将用户存入缓存
				await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(10));
			}
			else
			{
				// 缓存空值
				await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
			}

			return user;
		}

示例:布隆过滤器

		public class BloomFilterExample
		{
			private readonly BloomFilter _bloomFilter;
			private readonly IRedisCache _redisCache;
			private readonly IUserRepository _userRepository;

			public BloomFilterExample(BloomFilter bloomFilter, IRedisCache redisCache, IUserRepository userRepository)
			{
				_bloomFilter = bloomFilter;
				_redisCache = redisCache;
				_userRepository = userRepository;
			}

			public async Task<User> GetUserAsync(int userId)
			{
				// 使用布隆过滤器预先判断用户ID是否存在
				if (!_bloomFilter.Contains(userId))
				{
					return null;
				}

				// 尝试从缓存中获取用户
				string userJson = await _redisCache.GetStringAsync($"user:{userId}");
				if (userJson != null)
				{
					return JsonSerializer.Deserialize<User>(userJson);
				}

				// 从数据库中获取用户
				User user = await _userRepository.GetByIdAsync(userId);
				if (user != null)
				{
					// 将用户存入缓存
					await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(10));
				}
				else
				{
					// 缓存空值
					await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
				}

				return user;
			}
		}

缓存雪崩(Cache Avalanche)

缓存雪崩是指在某个时间点,大量的缓存数据同时过期,导致大量请求直接穿透到数据库,增加数据库负载,甚至可能导致数据库崩溃。

成因

  1. 缓存过期时间一致:
    • 大量缓存数据设置相同的过期时间,导致同时过期。
  2. 缓存预热失败:
    • 缓存预热(Cache Warm-up)机制失败,导致缓存数据在短时间内大量过期。
  3. 系统重启或故障:
    • 系统重启或故障导致缓存数据被清除,大量请求直接穿透到数据库。

解决缓存雪崩的方法

  1. 设置不同的过期时间:
    • 将缓存数据的过期时间设置为不同的随机值,避免同时过期。
  2. 分批过期:
    • 将缓存数据分批设置不同的过期时间,减少短时间内大量数据过期的情况。
  3. 缓存预热:
    • 系统启动时或定期预热缓存,确保缓存中有足够的数据,减少缓存数据被清除后的压力。
  4. 限流和熔断:
    • 使用限流和熔断机制来控制对数据库的请求,防止数据库过载。
  5. 本地缓存:
    • 使用本地缓存(如内存缓存)来临时存储数据,减少对数据库的直接访问。
  6. 双缓存:
    • 使用两级缓存,如Redis和本地缓存,提高缓存的稳定性和可靠性。

示例:设置不同的过期时间

		public async Task<User> GetUserAsync(int userId)
		{
			// 尝试从缓存中获取用户
			string userJson = await _redisCache.GetStringAsync($"user:{userId}");
			if (userJson != null)
			{
				return JsonSerializer.Deserialize<User>(userJson);
			}

			// 从数据库中获取用户
			User user = await _userRepository.GetByIdAsync(userId);
			if (user != null)
			{
				// 设置随机的过期时间
				Random random = new Random();
				int randomMinutes = random.Next(5, 15); // 随机过期时间在5到15分钟之间
				await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(randomMinutes));
			}
			else
			{
				// 缓存空值
				await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
			}

			return user;
		}

示例:缓存预热

		public class CacheWarmupExample
		{
			private readonly IRedisCache _redisCache;
			private readonly IUserRepository _userRepository;

			public CacheWarmupExample(IRedisCache redisCache, IUserRepository userRepository)
			{
				_redisCache = redisCache;
				_userRepository = userRepository;
			}

			public async Task WarmupCacheAsync()
			{
				// 获取所有用户ID
				List<int> userIds = await _userRepository.GetAllUserIdsAsync();

				foreach (int userId in userIds)
				{
					User user = await _userRepository.GetByIdAsync(userId);
					if (user != null)
					{
						// 设置随机的过期时间
						Random random = new Random();
						int randomMinutes = random.Next(5, 15); // 随机过期时间在5到15分钟之间
						await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(randomMinutes));
					}
					else
					{
						// 缓存空值
						await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
					}
				}
			}

			public async Task<User> GetUserAsync(int userId)
			{
				// 尝试从缓存中获取用户
				string userJson = await _redisCache.GetStringAsync($"user:{userId}");
				if (userJson != null)
				{
					if (userJson == "null")
					{
						return null;
					}
					return JsonSerializer.Deserialize<User>(userJson);
				}

				// 从数据库中获取用户
				User user = await _userRepository.GetByIdAsync(userId);
				if (user != null)
				{
					// 设置随机的过期时间
					Random random = new Random();
					int randomMinutes = random.Next(5, 15); // 随机过期时间在5到15分钟之间
					await _redisCache.SetStringAsync($"user:{userId}", JsonSerializer.Serialize(user), TimeSpan.FromMinutes(randomMinutes));
				}
				else
				{
					// 缓存空值
					await _redisCache.SetStringAsync($"user:{userId}", "null", TimeSpan.FromMinutes(1));
				}

				return user;
			}
		}

总结

  1. 缓存穿透(Cache Penetration):
    成因:查询在缓存和数据库中都不存在的数据。
    解决方法:
    • 缓存空值
    • 布隆过滤器
    • 参数校验
    • 限流和熔断
  2. 缓存雪崩(Cache Avalanche):
    成因:大量缓存数据同时过期,导致大量请求直接穿透到数据库。
    解决方法:
    • 设置不同的过期时间
    • 分批过期
    • 缓存预热
    • 限流和熔断
    • 本地缓存
    • 双缓存

参考资源

标签:缓存,redis,await,userId,redisCache,雪崩,null,user
From: https://www.cnblogs.com/chenshibao/p/18635870

相关文章

  • 【Redis Zset】Redis Zset多字段排序方案设计
    背景最近拿到多个排行榜相关的需求,按财富值,魅力值等单个或多个字段进行排序默认取前N条数据,考虑使用Redis进行排行榜实现,数据结构使用zset,本文对财富值和魅力值二个或多个字段排序的思路进行说明; 需求背景排行榜,按财富值和魅力值进行倒序排序,优先财富值排序,财富值相同则取魅......
  • 日志文件爆满_开发脚本每小时自动检测日志大小_定期清理日志_生产环境redis宕机_无法
     今天日志数据占用磁盘爆满,正常运行的系统发生,redis无法写入的报错,导致共用的redis服务器,瘫痪了,很多系统都进不去了. 最后查了一下才知道,是因为磁盘上一个日志文件170多GB了,都是日志.看看怎么处理:首先编写一个脚本,用来循环检测,每一个小时检测文件大小,如果超过......
  • Redis篇--应用篇1--会话存储(session共享)
    1、概述实现Session共享是构建分布式Web应用时的一个重要需求,尤其是在水平扩展和高可用性要求较高的场景下。在分布式服务或集群服务中往往会出现这样一个问题:用户登录A服务后可以正常访问A服务中的接口。但是我们知道,分布式服务通常都是有多个微服务一起构建形成的。如果......
  • (九).NET6.0搭建基于Redis的Hangfire定时器
    1.首先创建新的类库项目Wsk.Core.Hangfire,然后在Wsk.Core.Package包项目下引用hangfire有关的组件,包括Hangfire、Hangfire.Core、Hangfire.Redis、Hangfire.Redis.StaskExchange2.在配置文件新增基于redis的hangfire的数据库连接3.在Wsk.Core.Hangfire项目下,新增Hangfire连......
  • (八).NET6.0添加通用的Redis功能
    1.添加包:StackExchange.Redis2.在配置文件里面,新建Redis的有关配置信息Name是别名,可以任意起。Ip是Redis的服务端地址,例如安装本地,就是127.0.0.1,端口号Port默认是6379,密码可以通过Redis安装的根目录下的配置文件进行设置,Timeout是连接的超时时间,Db是使用Redis的DB区,一般Redis......
  • PHP语言laravel框架中基于Redis的异步队列使用实践与原理
    在Laravel中,基于Redis的异步队列是通过Laravel的队列系统与Redis服务结合来实现的。这种队列机制允许你将任务推送到队列中,并由后台工作进程异步处理这些任务。这样,你就可以将耗时的操作(如发送邮件、处理视频、数据同步等)推迟到后台处理,从而提高应用的响应速度。###1......
  • Redis 性能优化策略
    一、引言在当今数字化时代,Redis作为一款高性能的键值对存储数据库,在众多领域中发挥着关键作用。无论是应对高并发的Web应用场景,还是满足大数据量下的快速读写需求,Redis都展现出了卓越的性能优势。然而,随着业务的不断拓展和数据量的持续增长,如何进一步优化Redis的性能,使......
  • 中型项目中 Redis 的关键作用
    一、引言在中型项目的开发与运维过程中,随着业务量的增长和数据复杂度的提升,数据处理和存储面临着诸多挑战。例如,高并发场景下的数据读写压力、海量数据的快速查询需求以及数据一致性的保障等问题,都对项目的性能和稳定性提出了更高的要求。而Redis作为一款高性能的内存数据库......
  • Redis是什么,怎么安装使用
    ###Redis是什么?Redis(**RE**mote**DI**ctionary**S**erver)是一个开源的、基于内存的高性能键值数据库(key-valuestore)。它不仅支持简单的键值对,还支持多种复杂的数据结构,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(SortedSet)等。Redis常被用作缓存、中间件......
  • Redis缓存数据库
    1、介绍redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库redis的官网:redis.io注:域名后缀io属于国家域名,是britishIndianOceanterritory,即英属印度洋领地1、redis的特点:1.丰富的数据结构 -----string,list,set,zset,ha......