数据过期删除策略
Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用
惰性删除
惰性删除:设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key
优点 :对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查
缺点 :对内存不友好,如果一个key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放。
定期删除
定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key)。
定期清理有两种模式:
SLOW模式是定时任务,执行频率可以通过修改配置文件redis.conf 来调整。
FAST模式执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms。
优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。
缺点:难以确定删除操作执行的时长和频率。
面试回答模板
Redis的数据过期策略有哪些 ?
背熟以下答案,大概用时1min。
在redis中提供了两种数据过期删除策略,第一种是惰性删除,在设置该key过期时间后,我们不去管它,当需要该key时,我们再检查其是否过期,如果过期,我们就删掉它,反之返回该key。
第二种是定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除里面过期的key。
定期删除存在两种模式:
SLOW模式是定时任务,任务执行频率可以通过修改配置文件redis.conf 来调整。
FAST模式执行频率不固定,但两次之间的间隔和每次耗时都有固定值。
Redis的过期删除策略实际是惰性删除和定期删除两种策略配合使用的。
分布式锁
Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则 SET)的简写。
获取锁
命令:
# 添加锁,NX是互斥、EX是设置超时时间
SET lock value NX EX 10
释放锁
命令:
# 释放锁,删除即可
DEL key
基于redisson看门狗机制(锁的续期)
redis加锁时,如果不自己定义参数中的leaseTime(锁自动释放时间),或者定义的leaseTime为-1,那么就会开启看门狗机制。(不定义的话,这个线程默认的leaseTime就是30s)
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit);
看门狗机制就是当一个线程A加锁成功,且达到看门狗机制的触发条件时,会额外开一个线程,一般命名为Watch dog,这个线程每隔(releaseTime/3)的时间就为A续期时间,即重置过期时间。一般这个续期时间为锁的有效时间的一半,也可以自己额外设置。
在该锁有效期间,如果有其他线程想要试图加锁,那么就会不断while循环重试。
而采用了看门狗机制的锁的释放,是需要手动完成的,释放完成后还需要通知看门狗线程。
需要注意的是,加锁、设置过期时间等操作都是基于lua脚本完成的,因为能保证操作的原子性
代码模板
public void redisLock() throws InterruptedException{
RLock lock = redissonClient.getLock("heimalock");
try{
boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
if(isLock){
System.out.println("执行业务……");
}
}finally{
lock.unlock();
}
}
可重入性
锁的可重入性指的是同一个线程在持有锁的情况下,能够多次获取该锁而不会发生死锁或阻塞的情况。可重入锁的一个典型应用场景是在一个方法中调用另一个加锁的方法,譬如以下代码。如果方法A在获取锁后调用了方法B,而方法B也需要获取同一个锁,那么如果锁是可重入的,方法B可以直接获取到锁,而不会因为锁已被方法A持有而发生死锁。
public void add1(){
RLock lock = redissonClient.getLock(“heimalock");
boolean isLock = lock.tryLock();
//执行业务
add2();
//释放锁
lock.unlock();
}
public void add2(){
RLock lock = redissonClient.getLock(“heimalock");
boolean isLock = lock.tryLock();
//执行业务
//释放锁
lock.unlock();
}
基于redisson实现的分布式锁是拥有可重入性的。
其中的KEY一般称为大键,可以根据自己的业务来命名。
field一般称为小键,是当前线程的唯一标识。
value记录着当前线程重入的次数。
主从一致性
对于多线程的主从一致性,实际上redisson没办法很好地保障。
像是原本如下运行,一个主库,两个从库,且java应用程序对主库进行加锁。
因为某种原因,主库短暂宕机,此时根据哨兵模式选举了下面的从库为新的主库。
此时又有个新的java应用来对这个新的主库进行加锁。那么就会出现两个线程同时持有一把锁的情况。
redlock红锁
为此,redisson提出了一个叫RedLock,红锁。
RedLock(红锁):不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n / 2 + 1),避免在一个redis实例上加锁。即,在超过一半的实例上进行加锁。但是这种机制实现复杂,性能较差,运维起来也很繁琐。所以实际开发中很少用。
AP思想和CP思想
综上,对于多线程的主从一致性,实际上redis没办法很好地保障。因为redis本身是AP思想。
AP思想(可用性与分区容忍):
AP思想注重系统的可用性和分区容忍性,即在遇到网络分区或节点故障的情况下,系统仍然能够提供服务并保持可用性。这意味着系统可以容忍一定程度的数据不一致或冲突,以保证用户的访问体验。
如果想要强一致性,建议使用CP思想的zookeeper。
CP思想(一致性与分区容忍):
CP思想注重系统的一致性和分区容忍性,即在分布式环境中保持数据的一致性,并且即使发生网络分区,系统也能保持一致性。这种设计思想追求强一致性,确保所有节点中的数据都是一致的,但可能会导致一些节点不可用。
面试回答模板
Redis分布式锁如何实现 ?
候选人:嗯,在redis中提供了一个命令setnx(SET if not exists)
由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候是其他客户端是不能设置这个key的
面试官:好的,那你如何控制Redis实现分布式锁有效时长呢?
候选人:嗯,的确,redis的setnx指令不好控制这个问题,我们当时采用的redis的一个框架redisson实现的。
在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了
还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自旋不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。
面试官:好的,redisson实现的分布式锁是可重入的吗?
候选人:嗯,是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数
面试官:redisson实现的分布式锁能解决主从一致性的问题吗
候选人:这个是不能的,比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。
我们可以利用redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,并且要求在大多数redis节点上都成功创建锁,红锁中要求是redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。
但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变的很低了,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁
面试官:好的,如果业务非要保证数据的强一致性,这个该怎么解决呢?
**候选人:**嗯~,redis本身就是支持高可用的,做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用zookeeper实现的分布式锁,它是可以保证强一致性的。