redis缓存问题
缓存击穿
缓存击穿问题详细介绍
缓存击穿是指在高并发场景下,一个或多个请求查询一个不存在的缓存数据(通常是热点数据)可能是由于该缓存数据已过期,由于缓存中没有该数据,导致这些请求直接穿透到数据库,造成数据库的压力过大,进而导致系统性能下降的问题。
缓存击穿的主要原因包括:
- 缓存失效:缓存中的数据由于过期或被主动删除,导致大量请求无法从缓存中获取数据,从而直接访问数据库。
- 并发请求:在高并发场景下,多个进程或线程同时查询一个不存在的缓存数据,导致多个请求同时访问数据库。
缓存击穿可能带来的问题包括:
- 数据库压力增大:大量请求直接访问数据库,导致数据库负载增大,性能下降,甚至可能发生宕机。
- 响应时间增加:由于直接访问数据库需要花费更多的时间,因此缓存击穿会导致响应时间增加,降低用户体验。
解决方案
热点数据永不过期
- 方法描述:对于频繁访问且重要的热点数据,可以设置其永不过期。同时,通过异步线程定期刷新缓存中的数据,以保持数据的实时性。
- 优势:可以避免因缓存过期而导致的缓存击穿问题,提高系统的稳定性和性能。
- 注意事项:需要确保定期刷新缓存的线程能够稳定运行,并且及时更新缓存中的数据以保持数据的准确性。
互斥锁
- 方法描述:使用分布式锁或缓存锁机制,保证只有一个请求能够从数据库中加载数据,其他请求等待并使用锁中的数据。当缓存未命中时,请求会尝试获取锁,获取到锁的请求会查询数据库并更新缓存,未获取到锁的请求则等待一段时间后重试。
- 实现方式:可以通过Redis的SETNX命令或其他分布式锁实现方式来实现。
- 优势:可以有效减少数据库的并发访问压力,避免缓存击穿。
- 注意事项:需要合理设置锁的等待时间和重试次数,以避免死锁或过度占用系统资源。
缓存雪崩
缓存雪崩问题介绍
缓存雪崩是指在高并发情况下,由于大量的缓存数据同时过期或缓存服务宕机,导致大量请求直接穿透到数据库,造成数据库压力过大,甚至引发系统崩溃的现象。缓存雪崩影响范围广泛,影响大于缓存击穿,严重时会导致服务不可用
缓存雪崩通常是由于以下原因导致的:
- 缓存集体过期:大量缓存数据被设置为同一时间过期,当这些缓存数据同时失效时,大量请求会涌入数据库。
- Redis服务宕机:Redis作为缓存服务,其稳定性直接影响到整个系统的性能。一旦Redis服务宕机,所有缓存数据将无法访问,所有请求都将直接打到数据库上。
- 大量突发请求:在某些情况下,如秒杀活动、热门事件等,可能会在短时间内产生大量的请求,这些请求如果都未能在缓存中找到数据,就会直接冲击数据库。
缓存雪崩的影响是灾难性的,它不仅会导致数据库负载过高、响应时间延长,甚至可能引发服务挂掉,影响整个系统的正常运行。
解决方案
差异化设置过期时间
- 策略描述:在设置缓存时,避免将大量数据的过期时间设置为同一时刻。可以通过给每个键的过期时间加上一个随机偏移量,使得数据的过期时间分散开来。
- 实施方式:在代码层面调整缓存的过期时间设置逻辑,确保不同数据的过期时间有所差异。
- 效果:减少缓存同时失效的概率,分散数据库压力。
自定义RedisCacheManager
对有效期时间进行随机设置。
1 | /** |
使用MyRedisCacheManager
1 |
|
服务降级
- 策略描述:在缓存失效或数据库压力过大的情况下,通过服务降级策略来减少对非核心业务的请求处理,确保核心业务的正常运行。
- 实施方式:使用限流组件(如Hystrix、Sentinel等)来限制请求量,或返回默认数据或静态页面等。
- 效果:保护系统不受极端情况的冲击,确保核心业务的服务质量。
使用Redis集群或哨兵模式
- 策略描述:通过部署Redis集群或哨兵模式来提高缓存服务的可用性。当单个Redis节点故障时,可以自动切换到其他节点继续提供服务。
- 实施方式:配置Redis集群或哨兵模式,监控Redis节点的状态,并实现故障自动转移。
- 效果:提高缓存服务的稳定性和可靠性,降低缓存雪崩的风险。
缓存穿透
缓存穿透问题介绍
缓存穿透是指用户查询一个数据库和缓存中都不存在的数据,导致每次查询都会直接打到数据库上,而数据库中也没有该数据,如果用户不断发起这样的请求,数据库压力会非常大,甚至可能拖垮数据库。这种情况通常是由于恶意的查询或者系统设计不当导致的。
危害
缓存穿透的主要危害在于,如果存在大量针对不存在的数据的查询请求,这些请求都会绕过缓存直接访问数据库,导致数据库压力剧增,影响系统性能,甚至可能引发数据库崩溃。
解决方案
针对缓存穿透问题,常见的解决方案有以下几种:
布隆过滤器(Bloom Filter)
原理:布隆过滤器是一种空间效率很高的概率型数据结构,用于判断一个元素是否在一个集合中。它通过使用位数组和多个哈希函数来减少误判率,但无法完全避免误判。
应用:在查询数据库之前,先通过布隆过滤器判断该数据是否可能存在。如果布隆过滤器判断该数据不存在,则直接返回空值或错误信息,避免对数据库的访问。
优缺点:
- 优点:内存占用少,查询速度快。
- 缺点:存在误判率,且实现相对复杂。
实现
关于布隆过滤器的使用,建议使用Google的Guava 或 Redission基于Redis实现,前者是在单体架构下比较适合,后者更适合在分布式场景下,便于多个服务节点之间共享。
Redission基于Redis,使用string类型数据,生成二进制数组进行存储,最大可用长度为:4294967294。
引入依赖
xml1
2
3
4<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>导入redisson配置、
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RedissonConfiguration {
private RedisProperties redisProperties;
public RedissonClient redissonSingle() {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort());
if (null != (redisProperties.getTimeout())) {
serverConfig.setTimeout(1000 * Convert.toInt(redisProperties.getTimeout().getSeconds()));
}
if (StrUtil.isNotEmpty(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
return Redisson.create(config);
}
}自定义布隆过滤器配置
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* 布隆过滤器相关配置
*/
public class BloomFilterConfig {
/**
* 名称,默认:sl-bloom-filter
*/
private String name;
/**
* 布隆过滤器长度,最大支持Integer.MAX_VALUE*2,即:4294967294,默认:1千万
*/
private long expectedInsertions;
/**
* 误判率,默认:0.05
*/
private double falseProbability;定义布隆过滤器接口
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 布隆过滤器服务
*/
public interface BloomFilterService {
/**
* 初始化布隆过滤器
*/
void init();
/**
* 向布隆过滤器中添加数据
*
* @param obj 待添加的数据
* @return 是否成功
*/
boolean add(Object obj);
/**
* 判断数据是否存在
*
* @param obj 数据
* @return 是否存在
*/
boolean contains(Object obj);
}编写实现类
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BloomFilterServiceImpl implements BloomFilterService {
private RedissonClient redissonClient;
private BloomFilterConfig bloomFilterConfig;
private RBloomFilter<Object> getBloomFilter() {
return this.redissonClient.getBloomFilter(this.bloomFilterConfig.getName());
}
// spring启动后进行初始化
public void init() {
RBloomFilter<Object> bloomFilter = this.getBloomFilter();
bloomFilter.tryInit(this.bloomFilterConfig.getExpectedInsertions(), this.bloomFilterConfig.getFalseProbability());
}
public boolean add(Object obj) {
return this.getBloomFilter().add(obj);
}
public boolean contains(Object obj) {
return this.getBloomFilter().contains(obj);
}
}后续在Controller直接使用布隆过滤器的contains可以直接判断该数据是否存在,从而实现过滤,在新增数据后,也需要使用add方法将数据添加到布隆过滤器,以便后面可以判断。
局限
由于布隆过滤器是采用hash算法将key值计算为一个数字存在bitMap中,所有有可能存在两个不同的键计算出的值相同有可能会造成误差,可以通过一个键使用多个hash算法,如果多个hash算法的结果均存在才说明该数据存在,即使这样,也会存在误差
缓存空对象
- 原理:当查询一个不存在的数据时,将空结果(如null或特定空值)缓存起来,并设置一个较短的过期时间。这样,在后续的查询中,如果仍然查询该不存在的数据,则可以直接从缓存中返回空结果,避免对数据库的访问。
- 应用:适用于数据变动不频繁的场景,可以减少对数据库的无效查询。
- 优缺点:
- 优点:实现简单,维护方便。
- 缺点:可能会浪费一定的缓存空间,且存在短期数据不一致的风险。
控制层校验
- 原理:在业务系统的控制层(如API接口)增加校验逻辑,对查询参数进行合法性校验。如果查询参数不合法(如查询不存在的数据ID),则直接返回错误信息,避免对缓存和数据库的访问。
- 应用:适用于可以通过校验规则提前判断查询是否有效的场景。
- 优缺点:
- 优点:能够有效减少无效查询对系统的压力。
- 缺点:需要维护校验规则,且可能无法覆盖所有无效查询。