分布式锁🔒是个啥❓ 其实就这么点事
聊聊分布式锁,本文将以redis 为实现方式
Q:为什么会存在分布式锁
不同的进程需要用互斥的方式对共享的资源进行访问。

Q:加锁有什么好处
效率:加锁可以避免我们不必要的重复执行一个操作。比如说同一个计数器触发两次。
正确性:加锁可以防止数据被并发的修改,避免了数据的损坏、丢失、不一致之类的情况。
Q:实现分布锁可以使用什么
Q:实现锁的时候需要注意什么
Q:redis实现加锁🔐
SETNX : 如果 key 不存在,就通过key 来设置value保存字符串,操作返回结果, 在这种情况下操作等同于。但是如果 key 存在时再进行 操作,value 不会改变,操作返回结果。

当操作需要加锁的时,通过 进行赋值,如果返回,说明加锁成功,执行完操作之后再进行操作,以便其他进程使用。
@Test
Boolean distributedLocks() {
if (redisCli.setnx("hello","word") != 1) {
log.info("==加锁失败==");
return false;
}
redisCli.expire("hello",2); // 设置过期时间,防止死锁
// do something
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
}
redisCli.del("hello"); // 执行完毕,删除锁
return true;
}
但是这样写是存在问题的,如果在 设置时,客户端崩溃代码还没来得及设置过期时间,就会产生死锁。
所以需要改进加锁的代码,可以通过 命令的第三个参数进行过期时间和不可更改的设置。
SetParams params = SetParams.setParams();
params.nx(); // 当key不存在时才能设置
params.ex(2); // 过期时间。second
if (!redisCli.set("hello","word",params).equals("OK")) {
log.info("==加锁失败==");
return false;
}
但是这样还是存在问题,如果进程a加锁成功,但是执行具体业务逻辑的代码超过了设置的过期时间,这时候锁过期失效,进程b就可以加锁成功。如果这时进程a执行完成也是可以删锁的,尽管现在锁属于进程b。所以需要对应每个进程加锁时进行区分,防止这种误删的操作出现。
可以通过设置一个唯一的uuid为value,保证删除时的准确性。
@Test
Boolean distributedLocks() {
// 一个唯一标示
String uuid = UUID.randomUUID().toString();
SetParams params = SetParams.setParams();
params.nx(); // 当key不存在时才能设置
params.ex(2); // 过期时间。second
if (!redisCli.set("hello",uuid,params).equals("OK")) {
log.info("==加锁失败==");
return false;
}
// do something
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
}
if (redisCli.get("hello").equals(uuid)) {
redisCli.del("hello"); // 执行完毕,删除锁
return true;
}
return false;
}
如果这么写的话,还是不完美的,在删除锁的时候是两步操作,不符合原子性。像这种操作可以根据实际需要决定是不是可以容忍的,毕竟就算是删除没有执行成功,锁还是会自动过期的。如果不能容忍的话,可以通过一段Lua代码根据key和value决定要不要删除,保证客户端调用的原子性。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
关于加锁的失败的逻辑,你可以加锁失败后直接返回,也可以通过一个for 循环设置重试次数,当然也可以用一个while 循环直至加锁成功,具体实现可以根据业务逻辑自行判断。
Q:这样实现就是完美的了吗🤔️
以上的节点在单机的redis上是适用的,但是如果存在redis主从节点就会有问题。如果在集群中master节点由于某种原因发生了切换,可能会出现锁丢失的情况。试想这种情况:
Q:什么是 redLock
实现redLock 的前提是我们拥有N个master 节点,这些节点是相互独立的,互不干扰、独立运行
以同时拥有5个节点为例,为了获取锁,客户端需要进行:

对接redLock 的理解可以是:既然单节点不可靠,那我就多放几个节点,各个节点相互独立,没有从属关系,即使偶尔几个节点挂掉了,只要保证大多数节点能请求成功,那么加锁流程就没有问题。这样的5个节点,即使4和5 都挂了,只要123能请求成功,满足大多数条件,没有超时,加锁还是可以成功的。
Q:关于redLock 的爱恨纠葛⚔️
Martin Kleppmann(《设计数据密集型应用》的作者)曾经写文章 分析了redLock的缺点,主要集中在这几点:
redis的作者 antirez 也写文章回击,就几个问题提出了解决方案,想了解的同学可以自行阅读。
Q:简单聊聊Redisson
redLock的加锁、解锁也是通过Lua代码实现的,保证了原子性。
redisson 实现了一个机制,主要是负责对锁进行监控,可以延长锁的有效期。可以用来避免由于Redisson客户端节点宕机或者或者其他原因造成的死锁情况。
Q:说在最后
redis 的分布式锁只看谁能加锁成功,如果不成功,要么进行重试,要么直接返回。如果希望所有的请求都以排队的形式进行等待,按照顺序一个一个处理,zookeeper无疑是最适合的。