写点什么

Redis「7」实现分布式锁

作者:Samson
  • 2022 年 5 月 18 日
  • 本文字数:1638 字

    阅读完需:约 5 分钟

Redis「7」实现分布式锁

01-基于 Redis 的分布式锁

在同一进程中,为确保不同线程之间的同步操作,一般使用的synchronized关键字或ReentrantLock等锁机制。但对不同服务器上的多个进程来说,如果要达到同步操作的效果,就需要借助分布式锁来实现。


一般来说,实现分布式锁有几种方式:1)基于数据库;2)基于 Redis;3)基于 ZooKeeper。本文我们将讨论一下基于 Redis 如何实现分布式锁功能。

01.1-setnx 和 expire

Redis 中的setnx只有当键不存在时才可设置键的值,可用来表示成功获取锁。未避免进程在持锁期间异常宕机,而导致锁无法及时被释放,可用expire命令为锁添加一个过期时间,可以在超时后自动释放。


Redis 2.6.12 及更高版本中,set命令添加了对”set iff not exist”、”set iff exist”和”expire timeout”语义的支持:


set key value [EX seconds | PX milliseconds] [NX | XX]
复制代码

01.2-Redis 实现分布式锁的基本逻辑

Redis 中实现分布式锁的基本逻辑如下:


  1. 在 Redis 中指定一个键作为锁,指定唯一标识作为值

  2. 当 Redis 不包含锁时,意味着无任何进程持锁,此时进程可以请求获取锁,成功后将“锁-自身标识”键值对写入 Redis。同时只有一个进程可以拥有锁,满足互斥性

  3. 防止死锁,即为避免某个进程异常导致锁无法被释放,需要设置过期时间。

  4. 在有效时间内,释放锁时,需要校验唯一标识,避免错误地释放他人的锁。这个过程要保证原子性,一般采用 Lua 脚本实现。


使用 Redis 原生功能实现分布式锁的细节,可以参考 [1]。


[1] Redis 分布式锁|从青铜到钻石的五种演进方案

02-Redisson

Redisson 是一个供 Java 开发者使用的 redis-cli 实现。它提供了分布式锁和同步机制的实现。Redisson 中提供了多种锁实现,例如可重入锁RLock,读写锁RReadWriteLock,以供不同场景中使用。

02.1-可重入锁

使用可重入锁的方式非常简单:


final RLock anyLock = this.redisson.getLock("anyLock");anyLock.lock();// 业务逻辑anyLock.unlock();
复制代码


当获取锁以后,在 Redis 中会存在一个键为 anyLock,值类型为 hash,field 为 “a5112130-6121-4fa9-98f6-271a7c724c00:88”(每次都可能会不一样),value 为 1,指重入次数。

02.2-读写锁

使用读写锁的方式如下:


final RReadWriteLock lock = this.redisson.getReadWriteLock("read:write:lock");
lock.readLock().lock();// 业务逻辑,读lock.readLock().unlock();
lock.writeLock().lock();// 业务逻辑,写lock.writeLock().unlock();
复制代码


进程获取读锁后,Redis 中会存在一个键为 {read:write:lock}:a5112130-6121-4fa9-98f6-271a7c724c00:90:rwlock_timeout:1,值类型为 string,值为 1,超时时间为 30s。


进程获取写锁后,Redis 中会存在一个键为 read:write:lock,值类型为 hash,包含两个 field: “a5112130-6121-4fa9-98f6-271a7c724c00:89:write”(可能每次都不一样),其值为 1;”mode”,其值为 write / read


读锁与读锁之间互补影响


读-写锁、写-写锁、写-读锁之间互斥,同步

02.3-信号量

当临界资源数为 1 时,使用锁可以控制多进程之间同步访问。当临界资源有多个时,锁的表达能力有限,这时候可以使用信号量来控制多个进程之间的同步。


final RSemaphore semaphore = this.redisson.getSemaphore("park");// 获取信号量semaphore.acquire();// 释放信号量semaphore.release();
复制代码


在使用信号量之前,需要在 Redis 中存储一个键 park,其值为临界资源的数量,例如 3,表示连接资源数量为 3。


每当进程通过 acquire 获取一个信号量,park 的值将减小 1。当信号量为 0 时,尝试获取信号量将失败。进程通过 release 释放信号量时,park 的值将增加 1。


注:这里有个小坑,如果重复调用 release,将导致型号量的值不断增加,使用时需要特别注意。


上述使用示例的完整代码可以在我的 gitee 中找到。


[1] 分布式锁中的王者方案 - Redisson


历史文章推荐

Redis「6」实现消息队列

Redis「5」事件处理模型与键过期策略

Redis「4」Redis 在秒杀系统中的应用

Redis「3」持久化

Redis「2」缓存一致性与异常处理

Redis「1」流水线、事务、Lua 脚本

发布于: 刚刚阅读数: 2
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

还未添加个人简介

评论

发布
暂无评论
Redis「7」实现分布式锁_redis_Samson_InfoQ写作社区