Redis面试题,分布式锁

目录

在抢券场景下,如何解决超买超卖?

单体环境

集群部署环境

Redis分布式锁

 思考:Redis实现分布式锁如何合理的控制锁的有效时长?

根据业务执行时间预估

给锁续期(redisson实现的分布式锁)

通过expire命令

通过watch dog

思考:Redission实现的分布式锁可重入吗?

思考:Redission实现的分布式锁,可以保证主从数据一致吗?

RedLock(红锁):

Redis最终一致性的数据同步方式

面试回答

Redis的分布式锁如何实现?   

如何控制Redis实现分布式锁有效时长呢?

redisson实现的分布式锁是可重入的吗?

redisson实现的分布式锁能解决主从一致性的问题吗?

如果业务非要保证数据的强一致性,这个该怎么解决呢?


分布式锁使用的场景:

集群情况下的定时任务、抢单、幂等性场景

我们可以举个例子

在抢券场景下,如何解决超买超卖?

单体环境

Redis面试题,分布式锁_第1张图片

 Redis代码如下

public void rushToPurchase() throws InterruptedException {
    //获取优惠券数量
    Integer num = (Integer) redisTemplate.opsForValue().get(“num”);
    //判断是否抢完
    if (null == num || num <= 0) {
            throw new RuntimeException("优惠券已抢完");
       }
    //优惠券数量减一,说明抢到了优惠券    
    num = num - 1;
    //重新设置优惠券的数量
    redisTemplate.opsForValue().set("num", num);
}

但是当分布式环境下,会出现问题

Redis面试题,分布式锁_第2张图片

 如果是一台服务器的话,我们可以采用synchronized锁

Redis面试题,分布式锁_第3张图片

代码如下

public void rushToPurchase() throws InterruptedException {
    synchronized (this){
        //查询优惠券数量
        Integer num = (Integer) redisTemplate.opsForValue().get("num");
        //判断是否抢完
        if (null == num || num <= 0){ 
            throw new RuntimeException("商品已抢完");
        }
        //优惠券数量减一(减库存)
        num = num - 1;
        //重新设置优惠券的数量
        redisTemplate.opsForValue().set("num", num);
    }
}

但是我们的项目为了能支持更多的并发请求,往往是服务器集群部署

集群部署环境

Redis面试题,分布式锁_第4张图片

 这时候又会出现问题,因为synchronized锁是本地锁,是属于jvm的,每一个服务器都有自己的jvm,他只能解决同一个jvm下线程的互斥,所以在集群的环境下就会出现问题,

Redis面试题,分布式锁_第5张图片

 这个时候就用到了分布式锁

Redis面试题,分布式锁_第6张图片

Redis分布式锁

Redis分布式锁是通过Redis实现分布式锁的一种方式。当多个应用程序需要访问相同的共享资源时,可能会出现并发访问的问题,这时候可以使用分布式锁来解决。

Redis实现分布式锁主要利用Redissetnx命令。setnxSET if not exists(如果不存在,则 SET)的简写。

获取锁

NX是互斥、EX是设置超时时间

SET lock value NX EX 10

释放锁

DEL key

加锁流程

Redis面试题,分布式锁_第7张图片

 思考:Redis实现分布式锁如何合理的控制锁的有效时长?

        当我们去加锁时,假如业务执行时间太长了,已经超过了锁的失效时间,锁就会自动释放,但是业务还没有执行完成,这个时候,其他线程来获取锁,,是不是也可以获取成功,这个时候,就没法保证业务执行的原子性了,可能会影响业务数据,我们可以采用下面两种办法

根据业务执行时间预估

        例如,如果业务操作耗时较短,可以设置较短的锁过期时间,例如10秒;如果业务操作耗时较长,可以设置较长的锁过期时间,例如60秒或更长时间,以避免因为执行时间过长而导致锁被错误地释放。

        但需要注意的是,由于分布式环境中操作的不确定性,所以锁过期时间不可能完全准确地预估,可能出现锁持有时间过长或者出现死锁的情况。

给锁续期(redisson实现的分布式锁)

redission实现的分布式锁也是基于Redis的setnx命令实现的,但是做了很多的增强和优化

Redis面试题,分布式锁_第8张图片

给Redis分布式锁续期的方法一般就是在获取锁之后,在锁的有效期内不断更新锁的过期时间,使其保持持有状态。一般来说有下面两种办法

通过expire命令

一种常见的实现方式是使用Redis的expire命令(设置过期时间),例如:

EXPIRE resource_name 30

其中,resource_name是锁的key,30是锁的过期时间,表示在当前时间点再续30秒的时间来持有锁。可以通过在获取锁之后每隔一定时间调用expire命令来将锁的过期时间不断推迟,从而实现锁的自动续期。

通过watch dog

Redis中的watch dog(监视狗)是指用来监视事务执行过程的一种机制。在Redis中,watch命令可以用来监视指定的key,当事务中的某个操作修改了被监视的key时,该事务就会被回滚,从而保证事务的原子性。

watch机制的实现方式是通过在内存中记录一个key的变化情况,并将记录的版本号与执行事务的客户端绑定,如果事务执行过程中发现该key的版本号与绑定的客户端不一致时,就回滚该事务。

当一个事务执行过程中需要监视多个key时,可以使用multiview(多路监视)功能,将多个key与当前执行事务的客户端绑定,从而达到监视多个key的效果。

需要注意的是,watch命令的实现依赖于Redis的单线程架构,因此需要注意在高并发环境下watch机制的性能问题。同时,为了避免出现与预期不符的情况,watch命令一般不建议太过于频繁地使用。

虽然watch dog机制主要是用来实现Redis事务的原子性,但是也可以通过watch命令来实现给Redis分布式锁续期的功能。

具体实现方式是在获取锁之后,使用watch命令监视锁的key,然后在加锁期间使用multi命令开启一个事务,将锁的过期时间设置为需要续期到的时间,然后执行exec命令提交事务并释放对key的监视,从而实现对锁的续期操作。

下面是一个实现给Redis分布式锁续期的示例代码:

WATCH lock_key

val = GET lock_key

if val == currentLockValue:

    MULTI

    SETEX lock_key 30 _new_lock_value_

    EXEC

UNWATCH

其中,lock_key是锁的key,_new_lock_value_是新的锁值,30是续期到的时间,可以根据实际需要进行调整。

需要注意的是,在使用watch命令进行锁续期时,需要避免出现watch并发冲突的情况,也就是多个线程同时对同一个key进行watch操作的情况,否则可能会出现watch命令失效锁被错误地释放的问题。

思考:Redission实现的分布式锁可重入吗?

redis实现的分布式锁是不可重入的,但是redission实现的分布式锁是可重入的

public void add1(){
    RLock lock = redissonClient.getLock("lock");
    boolean isLock = lock.tryLock();
    add2();
    lock.unlock();
}

public void add2(){
    RLock lock = redissonClient.getLock("lock");
    boolean isLock = lock.tryLock();
    lock.unlock();
}

Redission的可重入锁实现是通过Redis的key名称和线程ID来实现的。当一个线程获取Reentrant Lock时,会将该Reentrant Lock的持有次数加1,并记录持有该锁的线程ID和持有次数等信息。当线程再次获取该锁时,会将该锁的持有次数再加1,直到持有次数为0时,才会将锁释放。

当业务比较复杂的时候,锁的密度比较稀疏的时候,我们就可以重入,同时可以避免多个锁之间产生死锁的问题

在redis中,实现是利用hash结构记录线程id重入次数

Redis面试题,分布式锁_第9张图片

思考:Redission实现的分布式锁,可以保证主从数据一致吗?

在企业开发中,通常会搭建redis的主从集群架构,主节点负责写数据,像一些增删改查的操作,从节点主要负责的是对外的读操作,当主节点发生了写数据后,就要把数据同步给从节点

Redis面试题,分布式锁_第10张图片

 假如获取到了锁之后,主进程还没来得及把数据同步给从节点,主节点宕机了,这时候,会根据Redis提供的哨兵模式,从两个从节点中选出一个节点当作主节点,

Redis面试题,分布式锁_第11张图片

 因为数据没有同步过来,所以新线程也能加锁成功,这个时候就会出现,两个线程同时出现了同一把锁,这个时候,业务就有可能出现脏数据的现象。

我们可以采用RedLock来避免

RedLock(红锁)

不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n / 2 + 1),避免在一个redis实例上加锁。

Redis面试题,分布式锁_第12张图片

RedLock是一种基于Redis的分布式锁算法,它可以在多个Redis节点之间协作来提高锁的可靠性,包括对主从同步异步延迟的情况进行处理。

RedLock的基本思想是在多个Redis节点之间获取多个锁,并控制获取锁的时间和锁的数量等参数,从而提高锁的稳定性。但要注意的是,RedLock并不能完全避免分布式锁的失效问题,

但是在高并发情况下,性能很差,实际上很少使用。

实际上,Redis集群整体的思想是AP思想,也就是高可用,所以我们可以想办法做到最终一致性, 但是如果非要做到强一致性,我们可以使用CP思想的zookeeper来实现分布式锁,他可以保证数据的强一致性。

Redis最终一致性的数据同步方式

具体地,Redis通过异步复制的方式实现节点间的数据同步。当主节点发生故障而从节点晋升为主节点时,从节点会通过主节点的RDB或AOF快照数据进行初始化。接着,从节点会通过持续复制主节点的数据来保证数据的最终一致性。这种数据复制方式的特点是异步的、非实时的,因此可能导致一定程度的数据丢失不一致性

要实现Redis的最终一致性,需要注意以下几点:

  1. Redis的主从同步采用异步方式,需要合理调整复制间隔和复制节点数量,从而保证数据同步的时效性和正确性。

  2. Redis提供了数据持久化机制,可以通过RDB和AOF两种方式在不同时间间隔下对数据进行持久化,从而保证数据的持久性和可靠性。

  3. Redis的最终一致性并不是强一致性,因此在应用中需要根据业务特点和数据重要性等因素进行合理权衡,选择适当的数据同步方式和保护措施。同时还需要考虑数据丢失和不一致性对业务的影响,并采取相应的容错和恢复措施。

面试回答

Redis的分布式锁如何实现?   

候选人:嗯,在redis中提供了一个命令setnx(SET if not exists) 它可以在键不存在的情况下设置成功并返回1,否则返回0。在设置锁的时候,可以使用SETNX命令来判定当前锁是否被占用,从而保证仅有一个线程可以获得该锁。

另一种方式是使用Redis的lua脚本,将SET命令和判定锁是否被占用的逻辑结合起来实现。在使用lua脚本时,可以使用Redis的EVAL命令来执行脚本。

如何控制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实现的分布 式锁,它是可以保证强一致性的

你可能感兴趣的:(redis,分布式,数据库)