当前位置:

首页 > 编程开发 > Redis分布式锁实现的方法是什么

Redis分布式锁实现的方法是什么

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

一、分布式锁是什么分布式锁是满足分布式系统或集群模式下多进程可见并且互斥的锁。基于Redis实现分布式锁:1、获取锁互斥:确保只能有一个线程获取锁;非阻塞:尝试获取锁,成功返回true,失败返回false;添加锁过期时间,避免服务宕机引起死锁。SETlockthread1NXEX102、释放锁手动释放;DELkey1超时释放,获取锁时添加一个超时锁;二、代码实例packagecom.guor.utils;importorg.springframework.data.redis.core.StringRed

本文目录

      一、分布式锁是什么

      分布式锁是 满足分布式系统或集群模式下多进程可见并且互斥的锁。

      基于Redis实现分布式锁:

      1、获取锁

      • 互斥:确保只能有一个线程获取锁;

      • 非阻塞:尝试获取锁,成功返回true,失败返回false;

      添加锁过期时间,避免服务宕机引起死锁。

      SET lock thread1 NX EX 10

      2、释放锁

      • 手动释放;DEL key1

      • 超时释放,获取锁时添加一个超时锁;

      二、代码实例

      package com.guor.utils;
      
      import org.springframework.data.redis.core.StringRedisTemplate;
      
      import java.util.concurrent.TimeUnit;
      
      public class RedisLock implements ILock{
      
          private String name;
          private StringRedisTemplate stringRedisTemplate;
      
          public RedisLock(String name, StringRedisTemplate stringRedisTemplate) {
              this.name = name;
              this.stringRedisTemplate = stringRedisTemplate;
          }
      
          private static final String KEY_PREFIX = "lock:";
      
          @Override
          public boolean tryLock(long timeout) {
              // 获取线程唯一标识
              long threadId = Thread.currentThread().getId();
              // 获取锁
              Boolean success = stringRedisTemplate.opsForValue()
                      .setIfAbsent(KEY_PREFIX + name, threadId+"", timeout, TimeUnit.SECONDS);
              // 防止拆箱的空指针异常
              return Boolean.TRUE.equals(success);
          }
      
          @Override
          public void unlock() {
              stringRedisTemplate.delete(KEY_PREFIX + name);
          }
      }

      上面代码存在锁误删问题:

      1. 如果线程1获取锁,但线程1发生了阻塞,导致Redis超时释放锁;

      2. 此时,线程2尝试获取锁,成功,并执行业务;

      3. 此时,线程1重新开始执行任务,并执行完毕,执行释放锁(即删除锁);

      4. 但是,线程1删除的锁,和线程2的锁是同一把锁,这就是分布式锁误删问题;

      在释放锁时,释放线程自己的分布式锁,就可以解决这个问题。

      package com.guor.utils;
      
      import cn.hutool.core.lang.UUID;
      import org.springframework.data.redis.core.StringRedisTemplate;
      
      import java.util.concurrent.TimeUnit;
      
      public class RedisLock implements ILock{
      
          private String name;
          private StringRedisTemplate stringRedisTemplate;
      
          public RedisLock(String name, StringRedisTemplate stringRedisTemplate) {
              this.name = name;
              this.stringRedisTemplate = stringRedisTemplate;
          }
      
          private static final String KEY_PREFIX = "lock:";
          private static final String UUID_PREFIX = UUID.randomUUID().toString(true) + "-";
      
          @Override
          public boolean tryLock(long timeout) {
              // 获取线程唯一标识
              String threadId = UUID_PREFIX + Thread.currentThread().getId();
              // 获取锁
              Boolean success = stringRedisTemplate.opsForValue()
                      .setIfAbsent(KEY_PREFIX + name, threadId, timeout, TimeUnit.SECONDS);
              // 防止拆箱的空指针异常
              return Boolean.TRUE.equals(success);
          }
      
          @Override
          public void unlock() {
              // 获取线程唯一标识
              String threadId = UUID_PREFIX + Thread.currentThread().getId();
              // 获取锁中的标识
              String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
              // 判断标示是否一致
              if(threadId.equals(id)) {
                  // 释放锁
                  stringRedisTemplate.delete(KEY_PREFIX + name);
              }
          }
      }

      三、基于SETNX实现的分布式锁存在下面几个问题

      1、不可重入

      同一个线程无法多次获取同一把锁。

      2、不可重试

      获取锁只尝试一次就返回false,没有重试机制。

      3、超时释放

      锁的超时释放虽然可以避免死锁,但如果业务执行耗时较长,也会导致锁释放,存在安全隐患。

      4、主从一致性

      如果Redis是集群部署的,主从同步存在延迟,当主机宕机时,此时会选一个从作为主机,但是此时的从没有锁标识,此时,其它线程可能会获取到锁,导致安全问题。

      四、Redisson实现分布式锁

      Redisson是一个在Redis的基础上实现的Java驻内存数据网格。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中包含各种分布式锁的实现。

      1、pom

      
      
          org.redisson
          redisson
          3.13.6
      

      2、配置类

      package com.guor.config;
      
      import org.redisson.Redisson;
      import org.redisson.api.RedissonClient;
      import org.redisson.config.Config;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class RedissonConfig {
      
          @Bean
          public RedissonClient redissonClient(){
              // 配置
              Config config = new Config();
      
              /**
               * 单点地址useSingleServer,集群地址useClusterServers
               */
              config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456");
              // 创建RedissonClient对象
              return Redisson.create(config);
          }
      }

      3、测试类

      package com.guor;
      
      import lombok.extern.slf4j.Slf4j;
      import org.junit.jupiter.api.BeforeEach;
      import org.junit.jupiter.api.Test;
      import org.redisson.api.RLock;
      import org.redisson.api.RedissonClient;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import javax.annotation.Resource;
      import java.util.concurrent.TimeUnit;
      
      @Slf4j
      @SpringBootTest
      class RedissonTest {
      
          @Resource
          private RedissonClient redissonClient;
      
          private RLock lock;
      
          @BeforeEach
          void setUp() {
          	// 获取指定名称的锁
              lock = redissonClient.getLock("nezha");
          }
      
          @Test
          void test() throws InterruptedException {
              // 尝试获取锁
              boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);
              if (!isLock) {
                  log.error("获取锁失败");
                  return;
              }
              try {
                  log.info("哪吒最帅,哈哈哈");
              } finally {
                  // 释放锁
                  lock.unlock();
              }
          }
      }

      五、探索tryLock源码

      1、tryLock源码

      尝试获取锁
      public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
      	// 最大等待时间
      	long time = unit.toMillis(waitTime);
      	long current = System.currentTimeMillis();
      	long threadId = Thread.currentThread().getId();
      	Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
      	if (ttl == null) {
      		return true;
      	} else {
      		// 剩余等待时间 = 最大等待时间 - 获取锁失败消耗的时间
      		time -= System.currentTimeMillis() - current;
      		if (time <= 0L) {// 获取锁失败
      			this.acquireFailed(waitTime, unit, threadId);
      			return false;
      		} else {
      			// 再次尝试获取锁
      			current = System.currentTimeMillis();
      			// subscribe订阅其它释放锁的信号
      			RFuture subscribeFuture = this.subscribe(threadId);
      			// 当Future在等待指定时间time内完成时,返回true
      			if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
      				if (!subscribeFuture.cancel(false)) {
      					subscribeFuture.onComplete((res, e) -> {
      						if (e == null) {
      							// 取消订阅
      							this.unsubscribe(subscribeFuture, threadId);
      						}
      
      					});
      				}
      
      				this.acquireFailed(waitTime, unit, threadId);
      				return false;// 获取锁失败
      			} else {
      				try {
      					// 剩余等待时间 = 剩余等待时间 - 获取锁失败消耗的时间
      					time -= System.currentTimeMillis() - current;
      					if (time <= 0L) {
      						this.acquireFailed(waitTime, unit, threadId);
      						boolean var20 = false;
      						return var20;
      					} else {
      						boolean var16;
      						do {
      							long currentTime = System.currentTimeMillis();
      							// 重试获取锁
      							ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
      							if (ttl == null) {
      								var16 = true;
      								return var16;
      							}
      							// 再次失败了,再看一下剩余时间
      							time -= System.currentTimeMillis() - currentTime;
      							if (time <= 0L) {
      								this.acquireFailed(waitTime, unit, threadId);
      								var16 = false;
      								return var16;
      							}
      							// 再重试获取锁
      							currentTime = System.currentTimeMillis();
      							if (ttl >= 0L && ttl < time) {
      								// 通过信号量的方式尝试获取信号,如果等待时间内,依然没有结果,会返回false
      								((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
      							} else {
      								((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
      							}
      							time -= System.currentTimeMillis() - currentTime;
      						} while(time > 0L);
      
      						this.acquireFailed(waitTime, unit, threadId);
      						var16 = false;
      						return var16;
      					}
      				} finally {
      					this.unsubscribe(subscribeFuture, threadId);
      				}
      			}
      		}
      	}
      }

      2、重置锁的有效期

      private void scheduleExpirationRenewal(long threadId) {
      	RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
      	// this.getEntryName():锁的名字,一个锁对应一个entry
      	// putIfAbsent:如果不存在,将锁和entry放到map里
      	RedissonLock.ExpirationEntry oldEntry = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
      	if (oldEntry != null) {
      		// 同一个线程多次获取锁,相当于重入
      		oldEntry.addThreadId(threadId);
      	} else {
      		// 如果是第一次
      		entry.addThreadId(threadId);
      		// 更新有效期
      		this.renewExpiration();
      	}
      }

      更新有效期,递归调用更新有效期,永不过期

      private void renewExpiration() {
      	// 从map中得到当前锁的entry
      	RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
      	if (ee != null) {
      		// 开启延时任务
      		Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
      			public void run(Timeout timeout) throws Exception {
      				RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
      				if (ent != null) {
      					// 取出线程id
      					Long threadId = ent.getFirstThreadId();
      					if (threadId != null) {
      						// 刷新有效期
      						RFuture future = RedissonLock.this.renewExpirationAsync(threadId);
      						future.onComplete((res, e) -> {
      							if (e != null) {
      								RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
      							} else {
      								if (res) {
      									// 递归调用更新有效期,永不过期
      									RedissonLock.this.renewExpiration();
      								}
      							}
      						});
      					}
      				}
      			}
      		}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);// 10S
      		ee.setTimeout(task);
      	}
      }
      更新有效期
      protected RFuture renewExpirationAsync(long threadId) {
      	return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
      	// 判断当前线程的锁是否是当前线程
      	"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
      		// 更新有效期
      		redis.call('pexpire', KEYS[1], ARGV[1]); 
      		return 1; 
      		end; 
      		return 0;", 
      		Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
      }

      3、调用lua脚本

       RFuture tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
      	// 锁释放时间
      	this.internalLockLeaseTime = unit.toMillis(leaseTime);
      	return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, 
      		// 判断锁成功
      		"if (redis.call('exists', KEYS[1]) == 0) then
      			redis.call('hincrby', KEYS[1], ARGV[2], 1); // 如果不存在,记录锁标识,次数+1
      			redis.call('pexpire', KEYS[1], ARGV[1]); // 设置锁有效期
      			return nil; // 相当于Java的null
      		end; 
      		if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
      			redis.call('hincrby', KEYS[1], ARGV[2], 1); // 如果存在,判断锁标识是否是自己的,次数+1
      			redis.call('pexpire', KEYS[1], ARGV[1]); // 设置锁有效期
      			return nil; 
      		end; 
      		// 判断锁失败,pttl:指定锁剩余有效期,单位毫秒,KEYS[1]:锁的名称
      		return redis.call('pttl', KEYS[1]);", 
      			Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
      }

      六、释放锁unlock源码

      1、取消更新任务

      public RFuture unlockAsync(long threadId) {
      	RPromise result = new RedissonPromise();
      	RFuture future = this.unlockInnerAsync(threadId);
      	future.onComplete((opStatus, e) -> {
      		// 取消更新任务
      		this.cancelExpirationRenewal(threadId);
      		if (e != null) {
      			result.tryFailure(e);
      		} else if (opStatus == null) {
      			IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
      			result.tryFailure(cause);
      		} else {
      			result.trySuccess((Object)null);
      		}
      	});
      	return result;
      }

      2、删除定时任务

      void cancelExpirationRenewal(Long threadId) {
      	// 从map中取出当前锁的定时任务entry
      	RedissonLock.ExpirationEntry task = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
      	if (task != null) {
      		if (threadId != null) {
      			task.removeThreadId(threadId);
      		}
      		// 删除定时任务
      		if (threadId == null || task.hasNoThreads()) {
      			Timeout timeout = task.getTimeout();
      			if (timeout != null) {
      				timeout.cancel();
      			}
      
      			EXPIRATION_RENEWAL_MAP.remove(this.getEntryName());
      		}
      	}
      }
      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发
      相关文章 更多
      php72w redis,docker php7安装php-redis
      php72w redis,docker php7安装php-redis

      在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

      php 如何配置 redis,php 如何设置redis
      php 如何配置 redis,php 如何设置redis

      配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

      c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
      c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

      C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

      如何在Golang架构中部署Redict作为高能数据临时存储中转站
      如何在Golang架构中部署Redict作为高能数据临时存储中转站

      Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

      TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
      TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

      在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

      如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
      如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

      在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

      TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
      TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

      先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

      TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
      TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

      高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

      Golang 实现基于 Redis 的延时任务队列
      Golang 实现基于 Redis 的延时任务队列

      基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

      RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
      RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

      操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

      即将离开本站
      您即将前往第三方网站,请确认是否继续?