1.如果我要在分布式或者高并发的情况下避免并发竞争的问题,需要怎么实现?你们之前是怎么做的?
如果需要保证强一致性,就需要使用到锁机制,类似于java中的锁,就是在一个线程获取了锁,在没有释放锁之前,之前的线程都必须在后面排队。
Redis操作的话,我们使用过的有两种方案
一是基于redis原生的执行,SETNX进行操作,set if not exists,如果不存在对应的key,才创建。我们可以在一个线程进来的时候给他设置一个key,后面的线程进来发现key已经存在,则不能创建成功。
二是使用redisson,这是redis为java提供的客户端工具,专门用们操作分布式锁,里面提供了很多功能,包括可重入锁,redlock,读写锁,闭锁等。
2.Redis的分布式锁你们之前是怎么实现的?你说说分别都有什么优缺点?
一:基于redis原生的执行,SETNX进行操作,set if not exists,如果不存在对应的key,才创建。我们可以在一个线程进来的时候给他设置一个key,后面的线程进来发现key已经存在,则不能创建成功。
这种方式操作需要给key设置过期时间,否则如果A线程出了问题崩溃了,后面的线程都获取不到锁,并且应该在执行完成之后再finall里面手动删除锁。
设置了过期时间又带了其他问题,如果A线程卡了超时了,这时B线程进来操作了,突然A线程又恢复了,并把执行了删除锁,这时找不到锁都是小问题,最大的问题是可能会把B线程的锁给删除掉,导致后面线程又进来了。所以这个时候我们应该把value也用起来,再value中给当前线程设置一个随机数值,当要删除锁时去判断是不是这个线程的锁。
但是这样还有问题,我们再finall中执行的获取锁和删除锁都不是原子性操作,也会带来线程安全问题。
所以删除锁和获取锁应该使用lua脚本发送给redis进行操作。
这种方式太麻烦,我们之前是用这种,后来就都使用的是redisson
二:使用redisson,这是redis为java提供的客户端工具,专门用们操作分布式锁,里面提供了很多功能,包括可重入锁,redlock,读写锁,闭锁等。
redisson官方说法,是为了让开发者更专注于业务代码,所以他底层的操作帮我们封装好了lua脚本,保证了原子性,让我们操作更简单。
我们在项目中比较常用的是getLock获取可重入锁,和getReadWriteLock。用的最多的是读写锁。
读写锁可以设置对某个数据的读和写都是同一把锁,当是‘读,读’时相当于没有锁,所有线程都是同时读。当是‘写,读’时,在写操作没有完成前,读会等待。当是‘读,写’时,当读执行完之后才会执行写。当时‘写,写’时,只能一个写完了之后下一个线程才能写。
这样就避免了读到脏数据的问题,并且只有当一个写操作执行完成以后另一个才能进来,这也避免了线程竞争问题。
这里设置过期时间有两种方法,根据业务需要来。
一是设置leaseTime,超过一定时间自动过期。好处是能避免死锁,坏处是用户体验不好。
二是不设置过期时间,redisson会自动设置30秒过期,并开启看门狗功能,会自动对过期时间进行刷新,这样就避免了一个线程卡住导致超过过期时间的问题。坏处就是如果这个线程一直卡住,看门狗一直刷新时间,就会有死锁的风险。