什么是缓存穿透、缓存雪崩?怎么解决?

1、缓存穿透

一般的缓存系统,都是按照key去缓存库查询,如果查询不到对应的value,就去后端系统数据库查找(比如DB数据库)。

一些恶意的请求量很大,会故意查询不存在的key,在缓存数据库中查询不到数据时,就会去后端数据库查询数据,对后端系统及数据库服务器造成很大的压力,这就叫做缓存穿透。

2、怎么解决?

1)、对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert之后清理缓存。

2)、对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该Bitmap过滤。

3、缓存雪崩

当缓存服务器重启或者大量缓存集中在某一时间段失效,这样在失效的时候,发送的请求从缓存数据库中查询不数据就去后端服务数据库中查询数据,会给后端系统后数据库带来很大的压力,导致系统崩溃,服务宕机。

4、如何解决?

1.在缓存失效后,通过加锁(限制访问数据库或后端服务的线程数)或者消息队列来控制读数据库、写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其它线程等待;

2.不同的key,设置不同的过期时间,让缓存失效的时间尽量均匀;

3.做二级缓存;

怎么保证缓存和数据库数据的一致性?

1、淘汰缓存

缓存数据如果为较为复杂的数据时,进行缓存的更新操作就会变得异常复杂,因此一般推荐选择淘汰缓存,而不是更新缓存。

2、选择先淘汰缓存,再更新数据库

假如先更新数据库,再淘汰缓存,如果淘汰缓存失败,那么后面的请求都会得到脏数据,直至缓存过期。

假如先淘汰缓存再更新数据库,如果更新数据库失败,只会产生一次缓存穿透,相比较而言,后者对业务则没有本质上的影响

(缓存中数据已删除,读取不到数据,直接去数据库查询,写入从数据库中查到的新数据到缓存库中)。

3、延时双删策略

如下场景:同时有一个请求A进行更新操作,另一个请求B进行查询操作。

1.请求A进行写操作,删除缓存

2.请求B查询发现缓存不存在

3.请求B去数据库查询得到旧值

4.请求B将旧值写入缓存

5.请求A将新值写入数据库(缓存库和数据库)

出现数据不一致问题,采用延时双删策略得以解决。

public void write(String key,Object data){

redisUtils.del(key);
db.update(data);
Thread.Sleep(100);
redisUtils.del(key);
}

这么做,可以将1秒内所造成的缓存脏数据,再次删除。这个时间设定可根据俄业务场景进行一个调节。

4、数据库【读写分离】的场景

两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。

1.请求A进行写操作,删除缓存

2.请求A将数据写入数据库了,

3.请求B查询缓存发现,缓存没有值

4.请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值

5.请求B将旧值写入缓存

6.数据库完成主从同步,从库变为新值

依旧采用延时双删策略解决此问题。

Redis怎么实现分布式锁?

使用Redis实现分布式锁

redis命令:set users 10 nx ex 12 原子性命令

setnx users 10 上锁

expire users 12 设置过期时间

set users 10 nx ex 12

nx代表上锁,ex代表过期时间为12秒,此时是让上锁与过期时间设置一同进行,此时将两个步骤变成了 原子操作

//使用uuid,解决锁释放的问题
@GetMapping
public void testLock()throwsInterruptedException{

//key:lock
//value:uuid
//10秒过期
String uuid=UUID.randomUUID().toString();
//key不存在时,set一个key为lock的字符串,返回1;若key存在,则什么都不做,返回0
Boolean b_lock = redisTemplate.opsForValue().setIfAbsent(“lock”,uuid,10,TimeUnit.SECONDS);
if(b_lock){

Object value =redisTemplate.opsForValue().get(“num”);
if(StringUtils.isEmpty(value)){

return;
}
int num =Integer.parseInt(value+“”);
redisTemplate.opsForValue().set(“num”,++num);
Object lockUUID =redisTemplate.opsForValue().get(“lock”);
if(uuid.equals(lockUUID.toString())){

redisTemplate.delete(“lock”);
}
}else{

Thread.sleep(100);
testLock(); //有分布式锁
}
}

备注:可以通过lua脚本,保证分布式锁的原子性。

Redis分布式锁缺陷:

Redis分布式锁不能解决超时的问题.

分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。

Redis容易产生的几个问题:

1. 锁未被释放

2. B锁被A锁释放了

3. 数据库事务超时

4. 锁过期了,业务还没执行完

5. Redis主从复制的问题

Redis如何做内存优化?

1、缩短键值的长度

1)、缩短值的长度才是关键,如果值是一个大的业务对象,可以将对象序列化成二进制数组;

2)、首先应该在业务上进行精简,去掉不必要的属性,避免存储一些没用的数据;

3)、其次是序列化的工具选择上,应该选择更高效的序列化工具来降低字节数组大小;

4)、以JAVA为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如:protostuff,kryo等

2、共享对象池

1)、对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,

甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。

除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。

3)、字符串优化

4)、编码优化

5)、控制key的数量

Redis常用配置参数:

port 7000
cluster-enabled yes
cluster-config-file ./RedisDir/nodes-7000.conf
cluster-node-timeout 15000
appendonly yes
appendfilename “appendonly7000.aof”
protected-mode no