分布式锁的由来、特点、及 Redis 分布式锁的实现详解
1923
2019.11.20
发布于 未知归属地

什么是分布式锁

要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。

1. 线程锁

主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一 JVM 中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如 Synchronized、Lock 等。

2. 进程锁

为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过 synchronized 等线程锁实现进程锁。

3. 分布式锁

当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

分布式锁的由来

图片说明

在传统单机部署的情况下,可以使用 Java 并发处理相关的 API(如 ReentrantLcok 或 synchronized)进行互斥控制。

但是在分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁的由来。

当多个进程不在同一个系统中,就需要用分布式锁控制多个进程对资源的访问。

分布式锁的特点
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

1、互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。

2、安全性:锁只能被持有该锁的客户端删除,不能由其它客户端删除。

3、死锁:获取锁的客户端因为某些原因(如 down 机等)而未能释放锁,其它客户端再也无法获取到该锁。

4、容错:当部分节点(redis 节点等)down 机时,客户端仍然能够获取锁和释放锁。

分布式锁的具体实现

高并发架构系列:什么是分布式锁?Redis 实现分布式锁详解

分布式锁一般有三种实现方式:

  1. 数据库乐观锁;

  2. 基于 ZooKeeper 的分布式锁;

  3. 基于 Redis 的分布式锁;

Redis 实现分布式锁

基于 Redis 命令:SET key value NX EX max-lock-time

这里补充下: 从 2.6.12 版本后, 就可以使用 set 来获取锁,Lua 脚本来释放锁。setnx 是老黄历了,set 命令 nx,xx 等参数, 是为了实现 setnx 的功能。

1. 加锁

public class RedisTool {

private static final String LOCK_SUCCESS = “OK”;

private static final String SET_IF_NOT_EXIST = “NX”;

private static final String SET_WITH_EXPIRE_TIME = “PX”;

/**

  • 尝试获取分布式锁

  • @param jedis Redis 客户端

  • @param lockKey 锁

  • @param requestId 请求标识

  • @param expireTime 超期时间

  • @return 是否获取成功

*/

public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {return true;}return false;}

}

jedis.set(String key, String value, String nxxx, String expx, int time)

这个 set()方法一共有五个形参

第一个为 key,我们使用 key 来当锁,因为 key 是唯一的。

第二个为 value,我们传的是 requestId,很多童鞋可能不明白,有 key 作为锁不就够了吗,为什么还要用到 value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给 value 赋值为 requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId 可以使用 UUID.randomUUID().toString()方法生成。

第三个为 nxxx,这个参数我们填的是 NX,意思是 SET IF NOT EXIST,即当 key 不存在时,我们进行 set 操作;若 key 已经存在,则不做任何操作;

第四个为 expx,这个参数我们传的是 PX,意思是我们要给这个 key 加一个过期的设置,具体时间由第五个参数决定。

第五个为 time,与第四个参数相呼应,代表 key 的过期时间。

总的来说,执行上面的 set()方法就只会导致两种结果:1. 当前没有锁(key 不存在),那么就进行加锁操作,并对锁设置个有效期,同时 value 表示加锁的客户端。2. 已有锁存在,不做任何操作。

2. 解锁

public class RedisTool {

private static final Long RELEASE_SUCCESS = 1L;

/**

  • 释放分布式锁

  • @param jedis Redis 客户端

  • @param lockKey 锁

  • @param requestId 请求标识

  • @return 是否释放成功

*/

public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
Object
result = jedis.eval(script,
Collections.singletonList(lockKey),Collections.singletonList(requestId));if
(RELEASE_SUCCESS.equals(result)) {return true;}return false;}

}

那么这段 Lua 代码的功能是什么呢?其实很简单,首先获取锁对应的 value 值,检查是否与 requestId 相等,如果相等则删除锁(解锁)。

评论 (0)