【Redis】Redis分布式锁

【Redis】Redis分布式锁

分布式应用进行逻辑处理时经常会遇到并发问题。如果一个操作要修改用户的状态。修改状态需要先读出用户的状态,在内存里进行修改,改完了再存回去。

如果这样的操作同时进行,就会出现并发问题,因为"读取"和"保存状态"这两个操作不是原子操作。(原子操作是指不会被线程调度机制打断的操作。这种操作一旦开始,就会一直运行到结束,中间不会有任何线程切换)

这个时候就要使用到分布式锁来限制程序的并发执行。Redis 分布式锁使用得非常广泛,它是面试的重要考点之一,很多同学都知道这个知识,也大致知道分布式锁的原理,但是具体到细节的掌握上,往往并不完全正确。

分布式锁是什么?

分布式锁本质上要实现的目标就是在 Redis 里面占一个"坑",当别的进程也要来占坑时,发现那里已经有一根"大萝卜"了,就只好放弃或者稍后再试。

占坑一般使用setnx(set if not exists)指令,只允许被一个客户端占坑。先来先占用完了,再调用del指令释放"坑"

shell 复制代码
> setnx lock:codehole true
OK
... do something critical ...
> del lock:codehole
(integer) 1

但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。

于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也可以保证5s之后锁会自动释放。

shell 复制代码
> setnx lock:codehole true
OK
> expire lock:codehole 5
... do something critical ...
> del lock:codehole
(integer) 1

但是以上逻辑还有问题。如果在setnxexpire之间服务器进程突然挂掉了,可能是因为机器掉电或者是人为造成的,就会导致expire得不到执行也会造成死锁。

这种问题的根源就在于setnxexpire是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。也许你会想到用 Redis 事务来解决,但在这里不行,因为expire是依赖于setnx 的执行结果的,如果setnx没抢到锁,expire是不应该执行的。事务里没有 if-else 分支逻辑,事务的特点是一口气执行,要么全部执行,要么一个都不执行。

为了解决这个疑难,Redis 开源社区涌现了许多分布式锁的 library,专门用来解决这个问题,实现方法极为复杂,小白用户一般要费很大的精力才可以弄懂。如果你需要使用分布式锁,意味着你不能仅仅使用 Jedis 或者 redis-py,还得引入分布式锁的 library。

为了治理这个乱象,在 Redis 2.8 版本中,作者加入了 set 指令的扩展参数,使得 setnx 和expire 指令可以一起执行,彻底解决了分布式锁的乱象。从此以后所有的第三方分布式锁 library 都可以休息了。

shell 复制代码
> set lock:codehole true ex 5 nx
OK
... do something critical ...
> del lock:codehole

上面这个指令就是setnxexpire组合在一起的原子指令,它就是分布式锁的奥义所在。

超时问题

Redis 的分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻辑执行得长,以至于超出了锁的超时限制,就会出现问题。因为这时候第一个线程持有的锁过期了,临界区的逻辑还没有执行完,而同时第二个线程就提前重新持有了这把锁,导致临界区代码不能得到严格串行执行。

为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了问题,造成的数据小错乱可能需要人工介入解决。

有一个稍微安全一点的方案是将 set 指令的 value 参数设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除 key,这是为了确保当前线程占有的锁不会被其他线程释放,除非这个锁是因为过期了而被服务器自动释放的。

但是匹配 value 和删除 key 不是一个原子操作,Redis 也没有提供类似于delifequals这样的指令,这就需要使用 Lua 脚本来处理了,因为 Lua 脚本可以保证连续多个指令的原子性执行。

可重入性

可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。比如Java语言里有个 ReentrantLock就是可重入锁。Redis 分布式锁如果要支持可重入,需要对客户端的 set 方法进行包装,使用线程的Threadlocal 变量存储当前持有锁的计数。

相关推荐
冰暮流星19 分钟前
mysql之表子查询
数据库·mysql
Full Stack Developme1 小时前
CRM相关库表设计
数据库
步行cgn1 小时前
Spring 注入 Map 集合详解
数据库·python·spring
风跟我说过她2 小时前
SQL 一键转经典 Chen 风格 ER 图:开源 CLI + 在线工具 + Agent Skill
数据库·python·sql·开源·开源软件
这个DBA有点耶2 小时前
MySQL大表DDL锁表锁到崩溃?Online DDL的3个关键参数和实战避坑
数据库·mysql·架构
2302_1113 小时前
java依赖小结
java·运维·数据库
听到微笑3 小时前
Elasticsearch 如何存储与检索海量向量
数据库·elasticsearch
海浪仙人掌3 小时前
速动比率怎么分析?速动比率分析有哪些注意事项?
大数据·数据库·人工智能
企业数字化笔记3 小时前
固定资产历史数据怎么导入系统?Excel模板、字段映射和数据校验
android·java·数据库·后端
还是奇怪4 小时前
OpenAI GPT-5.6 构建指南拆解:创业公司如何用模型选择与 Responses API 降低 Agent 成本
java·数据库·人工智能·gpt