每天一道面试题--什么是分布式锁?

166 阅读3分钟
20211118233202_eec9d.jpeg 看完美女,咱们直接进入正题~

面试官:讲一下你对分布式锁的理解,以及使用场景。

回答开始:

先看图 C64AE2AC-51A8-4E9C-B71F-E5759E01E56E.png

首先我们要聊下一下本地锁,本地锁只能在同一个进程中,避免多个线程(所有线程共享进程的内存)对于进程中同一个资源的并发访问,如果是单机环境,本地锁就可以满足。
但是在分布式环境中,本地锁无法保证多个进程之前的资源争夺,于是就需要使用分布式锁。

B8EEDA20-63B3-4509-AC3D-5C672BE4BDFF.png 解决方案很简单:核心是引入一个中间件,设置互斥锁,便可以实现。

常见的实现方案:

  1. 利用数据库主键的唯一特性,单独创建一张表,每次insert id=1 的记录,插入成功即抢占,失败说明正在被使用,释放锁就是删除记录。

  2. redis 的setNx命令,SETNX是 set If not exist的简写。意思就是当 key 不存在时,设置 key 的值,存在时,什么都不做。释放锁就是del key。

  3. zookeeper,创建一个有序的临时节点来实现。

数据库的性能差,zookeeper用的则比较少维护成本高,redis便成为了最常用的方案,主要讨论下redis的分布式锁实现。

Redis分布式锁

根据上述使用 setNx 命令抢占锁,执行成功后,del 删除来释放锁,从技术的角度看:setnx 占锁成功,业务代码出现异常或者服务器宕机,没有执行删除锁的逻辑,就造成了死锁

1、那如何规避死锁这个风险呢?

设置锁的自动过期时间,过一段时间后,自动删除锁,这样其他线程就能获取到锁了。

但是又出现了新的问题,过期时间到期后,任务还没执行完,便会有问题。

2、如何保证抢占锁和设置超时时间的原子性呢?

Redis 正好支持这种操作:设置某个 key 的值并设置多少毫秒或秒 过期。 

set  key value PX <多少毫秒> NX  或   set  key value EX <多少秒> NX

3、如果提前过期删除了其他线程的锁怎么办?

这个问题还是要提一下,不过并没有解决提前过期的问题。可以给锁的值value设置为当前线程ID,删除时比较,如果是本线程抢占的锁,才可以执行删除,可以用LUA脚本,来保证 获取锁的值、删除锁 这两步的原子性。

if redis.call("get",KEYS[1]) == ARGV[1]  then      return redis.call("del",KEYS[1])  else      return 0  end

一般来说只要把锁的过期时间设置远远大于业务代码的执行时间,便可以支持99.99%的场景,但是在系统异常、STW、GC时,进程阻塞的场景,虽然极少出现,这时无法保证业务代码的执行时间。

4、如果防止锁提前过期?

终极方案

使用如 Redission(java)、Redsync(go)等开源框架,实现了锁续期、保证原子性等。

redis官方推荐的分布式锁实现:redis.cn/topics/dist…