当前位置: 首页 > news >正文

Redis 分布式锁如何保证同一时间只有一个客户端持有锁

一、核心原理

Redis 通过 原子性操作唯一标识 来保证同一时间只有一个客户端持有锁。

关键点:

  1. 原子性:加锁操作必须是不可分割的,一次执行完成,不能被其他命令插入。
  2. 唯一性:锁的 value 必须能唯一标识持有者(通常是 UUID + 线程 ID)。
  3. 互斥性:只有当锁不存在时才能加锁,防止多个客户端同时持有。

二、加锁流程(保证互斥的关键步骤)

1. 使用 SET NX EX

SET lock_key unique_value NX EX expire_time
  • NX(Not eXists):只有当 lock_key 不存在时才设置成功
  • EX expire_time:设置过期时间,防止死锁
  • unique_value:唯一标识锁持有者

原子性保证

  • Redis 是单线程执行命令的,SET NX EX 是一个单条命令,在执行过程中不会被其他命令打断
  • 这意味着多个客户端同时执行加锁命令时,Redis 会按顺序处理,只有第一个执行的客户端能成功

2. 成功与失败的判断

  • 成功 → 返回 "OK",表示当前客户端持有锁
  • 失败 → 返回 null,表示锁已被其他客户端持有

3. 释放锁时的安全性

释放锁必须保证只能删除自己加的锁

if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
  • 先判断锁的 value 是否等于当前客户端的唯一标识
  • 如果相等 → 删除锁
  • 如果不相等 → 不删除(防止误删其他客户端的锁)

三、底层机制保证互斥性

  1. Redis 单线程模型

    • Redis 处理命令是单线程的,命令之间不会并发执行
    • 多个客户端同时请求加锁时,Redis 会按顺序处理,只有第一个满足条件的请求能成功
  2. SET NX EX 的原子性

    • SET 命令带 NX 和 EX 参数时,是一个原子操作
    • 不会出现“先判断再设置”这种可能被其他命令插入的情况
  3. 唯一标识防误删

    • 即使锁过期被其他客户端抢到,旧客户端也无法删除新客户端的锁,因为 value 不匹配

四、边界情况与优化

1. 锁过期导致并发

  • 如果业务执行时间超过锁的过期时间,锁会提前释放,其他客户端可能加锁成功,导致多个客户端同时执行临界区代码
  • 优化:使用自动续期(Watchdog)机制,在锁快过期时延长过期时间

2. Redis 主从延迟

  • 如果使用 Redis 主从架构,主节点加锁成功,但从节点延迟同步,可能导致其他客户端在从节点加锁成功
  • 优化:使用 RedLock 算法,在多个独立 Redis 节点加锁,必须多数节点成功才算加锁成功

五、面试回答示例

Redis 分布式锁通过 SET key value NX EX expire_time 保证同一时间只有一个客户端持有锁。
其中 NX 保证只有当锁不存在时才能加锁,EX 设置过期时间防止死锁,value 用唯一标识防止误删。
Redis 是单线程执行命令的,SET NX EX 是原子操作,多个客户端同时加锁时,只有第一个执行的客户端能成功。
释放锁时用 Lua 脚本判断 value 是否匹配,保证只能删除自己加的锁。
如果业务执行时间可能超过锁的过期时间,可以用自动续期机制避免锁提前释放。

Java 代码示例:Redis 分布式锁应用

import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.UUID;public class RedisDistributedLockExample {// Redis 连接信息private static final String REDIS_HOST = "127.0.0.1";private static final int REDIS_PORT = 6379;// 锁的 keyprivate static final String LOCK_KEY = "my_distributed_lock";// 锁的过期时间(秒)private static final int EXPIRE_TIME = 10;// Jedis 客户端private Jedis jedis;public RedisDistributedLockExample() {this.jedis = new Jedis(REDIS_HOST, REDIS_PORT);}/*** 尝试获取分布式锁* @param lockValue 锁的唯一标识(UUID)* @return 是否加锁成功*/public boolean tryLock(String lockValue) {String result = jedis.set(LOCK_KEY, lockValue, "NX", "EX", EXPIRE_TIME);return "OK".equals(result);}/*** 释放分布式锁(Lua 脚本保证原子性)* @param lockValue 锁的唯一标识* @return 是否释放成功*/public boolean unlock(String lockValue) {String luaScript ="if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('del', KEYS[1]) " +"else " +"   return 0 " +"end";Object result = jedis.eval(luaScript,Collections.singletonList(LOCK_KEY),Collections.singletonList(lockValue));return Long.valueOf(1).equals(result);}/*** 模拟业务逻辑*/public void doBusiness() {System.out.println(Thread.currentThread().getName() + " 正在执行业务逻辑...");try {Thread.sleep(5000); // 模拟耗时操作} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println(Thread.currentThread().getName() + " 业务逻辑执行完成");}public static void main(String[] args) {RedisDistributedLockExample lockExample = new RedisDistributedLockExample();// 每个线程使用不同的锁标识String lockValue = UUID.randomUUID().toString();if (lockExample.tryLock(lockValue)) {try {System.out.println(Thread.currentThread().getName() + " 获取锁成功");lockExample.doBusiness();} finally {if (lockExample.unlock(lockValue)) {System.out.println(Thread.currentThread().getName() + " 释放锁成功");} else {System.out.println(Thread.currentThread().getName() + " 释放锁失败(可能锁已过期或被其他线程持有)");}}} else {System.out.println(Thread.currentThread().getName() + " 获取锁失败,稍后重试");}}
}

代码说明

  1. 加锁

    • 使用 SET key value NX EX expire_time 保证原子性
    • NX:只有当 key 不存在时才设置成功
    • EX:设置过期时间,防止死锁
    • value:唯一标识锁持有者(UUID)
  2. 释放锁

    • 使用 Lua 脚本保证判断 + 删除的原子性
    • 只有锁的持有者才能删除锁,防止误删
  3. 业务逻辑

    • 在持有锁的情况下执行,确保同一时间只有一个线程执行临界区代码
http://www.dtcms.com/a/554286.html

相关文章:

  • 做网站编辑工作好不好如何设计好的网页
  • U-Net笔记
  • 海力士DDR差异性对比--H9HCNNNCPMMLXR-NEE H9HCNNNCPMMLXR-NEI
  • bfs|红黑树multiset
  • 伊利集团的网站建设水平评价成都做网站做的好的公司
  • 论文阅读:arxiv 2025 Safety in Large Reasoning Models: A Survey
  • 选择手机网站建设医疗网站织梦
  • 蓝牙体重秤方案:硬件设计需要注意什么
  • 张家港建网站的公司住房和城乡建设部网站
  • 【AIGC】HPS v2:评估人类对文本到图像合成偏好的可靠基准
  • Download from your IP address is not allowed(qt下载教程)
  • 出海东南亚无忧:腾讯云如何凭借本地合作与全球节点,保障游戏和电商业务合规流畅?
  • Jmeter的自动化测试实施方案详解
  • 共享自行车与电动共享自行车使用中建成环境影响的对比研究:基于合肥数据的时空机器学习分析
  • 如何使用Jmeter做接口测试?
  • 网站用哪个软件做企业官网建设费用
  • 重庆网站设计找重庆最佳科技蛋糕网站源码
  • 东莞建设网官方网站小程序怎么赚钱的
  • 石家庄网站开发公司电话佛山新网站建设渠道
  • Golang多goroutine求解1000万和1亿以内的素数
  • 【开题答辩过程】以《基于协同过滤算法的彩妆商城系统的设计与实现》为例,不会开题答辩的可以进来看看
  • 一级a做爰片i免费网站横沥镇仿做网站
  • 怎么做能上谷歌网站优化营商环境心得体会
  • 学习C#调用OpenXml操作word文档的基本用法(4:Style类分析-2)
  • 河北建设厅注册中心网站首页网站头页
  • Vue3教程简介
  • Excel天气查询插件开发指南(★)
  • GO语言-->Gin 框架 HTTP 路由
  • Android EDLA项目导入mainline包后蓝牙签名报错分析解决
  • 保定网站建设找谁建设部四库一平台查询