这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排


这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
什么是分布式锁?大家好,我是jack xu,今天跟大家聊一聊分布式锁 。首先说下什么是分布式锁,当我们在进行下订单减库存,抢票,选课,抢红包这些业务场景时,如果在此处没有锁的控制,会导致很严重的问题 。学过多线程的小伙们知道,为了防止多个线程同时执行同一段代码,我们可以用 synchronized 关键字或 JUC 里面的 ReentrantLock 类来控制,但是目前几乎任何一个系统都是部署多台机器的,单机部署的应用很少,synchronized 和 ReentrantLock 发挥不出任何作用,此时就需要一把全局的锁,来代替 JAVA 中的 synchronized 和 ReentrantLock 。
分布式锁的实现方式流行的主要有三种,分别是基于缓存 redis 的实现方式,基于 zk 临时顺序节点的实现以及基于数据库行锁的实现 。我们先来说下用 Jedis 中的 setnx 命令来构建这把锁 。
Jedis写法使用 Redis 做分布式锁的思路是,在 redis 中设置一个值表示加了锁,然后释放锁的时候就把这个 key 删除 。思路是很简单,但是在使用过程中要避免一些坑,我们先看下加锁的代码:
/*** 尝试获取分布式锁** @param jedisRedis客户端* @param lockKey锁* @param requestId请求标识* @param expireTime 超期时间* @return 是否获取成功*/public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {// set支持多个参数 NX(not exist) XX(exist) EX(seconds) PX(million seconds)String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;}这段代码很简单,主要说下这里用的命令是 SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL],而没有使用 SETNX+EXPIRE 的命令,原因是 SETNX+EXPIRE 是两条命令无法保证原子性,而 SET 是原子操作 。那这里为什么要设置超时时间呢?原因是当一个客户端获得了锁在执行任务的过程中挂掉了,来不及显式地释放锁,这块资源将会永远被锁住,这将会导致死锁,所以必须设置一个超时时间 。
释放锁的代码如下:
/*** 释放分布式锁** @param jedisRedis客户端* @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;}这里也有两点注意的地方,第一是解铃还须系铃人,怎么理解呢,就是 A 加的锁 B 不能去 del 掉吧,不然岂不是全乱套了,谁加的锁就谁去解,我们一般把 value 设为当前线程的 Id,Thread.currentThread().getId(),然后在删的时候判断下是不是当前线程 。第二点是验证和释放锁是两个独立操作,不是原子性,这个怎么解决呢?使用 Lua 脚本,即 if redis.call('get', KEYS[1]) == ARGV[1] then returnredis.call('del', KEYS[1]) else return 0 end,它能给我们保证原子性 。
Redisson写法Redisson 是 Java 的 Redis 客户端之一,提供了一些 API 方便操作 Redis 。但是 Redisson 这个客户端可有点厉害,我们先打开官网看下 github.com/redisson/re…
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
这个目录里面有很多的功能,Redisson 跟 Jedis 定位不同,它不是一个单纯的 Redis 客户端,而是基于 Redis 实现的分布式的服务,我们可以看到还有 JUC 包下面的类名,Redisson 帮我们搞了分布式的版本,比如 AtomicLong,直接用 RedissonAtomicLong 就行了 。锁只是它的冰山一角,并且它对主从,哨兵,集群等模式都支持,当然了,单节点模式肯定是支持的 。
在 Redisson 里面提供了更加简单的分布式锁的实现,我们来看下它的用法,相当的简单,两行代码搞定,比 Jedis 要简单的多,而且在 Jedis 里需要考虑的问题,它都已经帮我们封装好了 。
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
我们来看下,这里获取锁有很多种的方式,有公平锁有读写锁,我们使用的是 redissonClient.getLock,这是一个可重入锁 。
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
现在我把程序启动一下
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排


推荐阅读