目录
- Redis的租约问题
- Redis租约问题的想法
- Redis租约问题的解决方案
Redis的租约问题
首先我们先来说一说什么是Redis的租约问题。
在我们实现Redis分布式锁的时候,我们会出现Redis锁的时间<业务执行执行时间,这其实就是一个典型的租约问题,那么什么是租约问题呢?我们让用户线程获取锁之后,同时为了防止用户进程产生错误而无法释放锁,导致其他用户再也无法获取不到锁产生的死锁现象,所以我们为每一个锁设定一个Expire Time,这样即使在用户进程不能正常释放锁的情况下,过期时间到了之后,Redis会自动释放掉锁来让别的用户来获取到锁。
这就是典型的租约机制,用户申请了一个租约时长为lock_timeout的锁,用户可以在租约期间使用完之后正常释放锁,如果说了租约时间,即使用户没有释放锁,Redis也会自动释放锁。
Redis租约问题的想法
我们来设想一下,如果用户可以很清晰的知道自己将要使用锁的时间,那么我们设置lock_timeout就很容易了,但是如果当用户并不知道自己用锁的时间,设置租约就会是一个困难,当然,一般都不知道。我们设置租约时间
- 如果我们设置的时间过短,那么可以用户还没用完锁,锁就被Redis释放掉了,之后多个用户访问一个共享资源产生错误。
- 如果我们设置的时间过长,那么当用户如果产生错误不能正常释放锁的时候,其他用户线程就需要等待较长的时间才能获取到锁。
所以,我们就萌生出一个想法:当用户并不知道自己会用多久的时间,那么我们为该锁设置一个较小的lock_timeout,同时在该锁的过期之前,就自动的向服务器延长该锁的lifetime.
Redis租约问题的解决方案
Redis租约问题一般有两个解决方案。
- 业务调研
我们需要大量测试我们业务的执行时间,然后可以将我们所的过期时间设置为业务时间的1.5倍,给他充分的冗余时间。但是如果我们考虑更多的异常情况发生的话,那么我们这种方法可能不太靠谱。所以就有了第二种方法。 - 锁的续约
锁的续约,我们在去加锁的时候,一般都会去启动一个线程,而在开启这个启动线程的时候,我们同样去启动一个守护线程,这个守护线程,我们就可以用来作为锁的续命。过程:
假设我们用户拿到了我们的锁,但是我们的业务时间远远大于我们加锁的时间,如果我们的锁快到我们的过期时间的时候,比如过期时间的前5s,这个时候我们就可以用我们的守护线程去扫它,看看我们的业务有没有执行完,如果没有执行完,那么续约同样的lock_timeout,以此类推,确保我们当前的加锁时间大于我们的业务执行时间,这个是锁续约的核心思想。
那么我们这么做可能会出现什么问题?
在我们进行业务调研后,我们得出了我们业务时间的平均值,如果我们的用户线程在执行过程中出现了问题,那么业务执行时间就会无限长,那么我们的岂不是会进行无限的续约?所以简单的设计有点不太合理。
那么怎么解决呢?
我们的解决方案是使用重试次数(这个重试次数是重点)
假设我们的业务的执行时间平均是10s,那么我们最多容忍它执行50s如果他还没有执行完成,那么我们就认为的服务器或者代码有问题,我们需要进行人为干预。这里的话,我们需要重试四次即可。
注意:我们的守护线程是依赖于我们的用户线程而生的,用户线程死,我们的守护线程就死,所以我们守护线程来进行续命,用户线程在,守护线程就在,就可以续命。
这里使用了Lua脚本,Lua脚本的作用在于保证原子性,它能确保Lua脚本里面的内容要么同时执行,要么同时不执行。