基于Redis分布式锁(一)

Redis从入门到精通(七)Redis实战(四)库存超卖、一人一单与Redis分布式锁 Redis实战(四)库存超卖和一人一单:库存超卖问题及其解决、一人一单需求、Redis分布式锁。 阅读详情

基于Redis分布式锁(一)

仅适用于单机版Redis(非集群)

1. Redis 指令

1.1 永久锁

SETNX key value

KEY不存在返回1,KEY已经存在返回0

1.2 限时锁

SET key value [EX seconds][PX milliseconds] [NX|XX]
  • EX seconds 设置指定的到期时间(单位:秒)
  • PX milliseconds 设置指定的到期时间(单位:毫秒)
  • NX 仅在键不存在时设置
  • XX 仅在键存在时才设置

1.3 解锁

DEL key

1.4 设置过期时间

# 1.面向剩余时间
# 单位秒
EXPIRE key seconds

# 单位毫秒
PEXPIRE key milliseconds


# 2.面向时间戳
# 时间戳(秒)
EXPIREAT key seconds

# 时间戳(毫秒)
PEXPIREAT key milliseconds

2. 代码实现

使用RedisTemplate<String, Object>

2.1 基础代码

  • 1、使用SETNX key value 上锁
  • 2、使用EXPIRE key seconds 设置锁过期时间
  • 3、使用DEL key 解锁
	public void redisTest() throws Exception {
        String key = "Lock";
        // 1.上锁
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, "Lock");
        // 2.设置过期时间
        redisTemplate.expire(key, 2, TimeUnit.SECONDS);
        while (isLock == null || !isLock) {
            TimeUnit.MILLISECONDS.sleep(100);
            redisTest();
            return;
        }
        try {
            // 业务代码 ...
        } finally {
            // 2.解锁
            redisTemplate.delete(key);
        }
    }
存在问题:
  • 1、非原子性:上锁-设置过期时间期间服务器宕机会导致锁无法释放,发生死锁
  • 2、会存在误删:线程A业务未执行完成锁过期了,线程B获取锁,线程A执行完后把锁释放了

2.2 优化上述问题

  • 1、使用SET key value [EX seconds][PX milliseconds] [NX|XX]指令
  • 2、释放锁前,先判断是否是自己的锁
	public void redisTest() throws Exception {
        String key = "Lock";
        String uuid = UUID.randomUUID().toString();
        // 1.上锁+设置过期时间
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, uuid, 2, TimeUnit.SECONDS);
        while (isLock == null || !isLock) {
            TimeUnit.MILLISECONDS.sleep(100);
            redisTest();
            return;
        }
        try {
                // 业务代码 ...
            } finally {
                // 2.解锁(先判断,再解锁)
                if (StringUtils.equals(uuid, redisTemplate.opsForValue().get(key))){
                    redisTemplate.delete(key);
                }
            }  
    }
存在问题:
  • 1、解锁非原子性:线程A在获取锁内容之后锁过期了,线程B获取锁成功,线程A将B上的锁释放了

2.3 使用Lua脚本解锁

1、EVAL 指令

Redis 提供 EVAL 指令执行 Lua 脚本

  • 一次性可以发送多个指令给redis执行,并且是有序的,遵循one-by-one规则
  • EVAL执行的脚本需要有返回值
EVAL script numkeys key [key...] arg [arg...]

# EVAL "if KEYS[1]>KEYS[2] then return ARGV[1] else return ARGV[2] end" 2 10 20 true false
# EVAL "return {KEYS[1], KEYS[2], ARGV[1],ARGV[2]}" 2 10 20 30 40
  • script:执行的脚本(必须)
  • numkeys:key的个数(必须)
  • key:键列表,下标从1开始:KEYS[1]
  • arg:值列表,下标从1开始:ARGV[1]
2、使用Lua
  • 使用Lua脚本的类库:redis.call() 执行redis指令
# 获取lock值
EVAL "return redis.call('get', 'lock')" 0

# 设置lock值为111
EVAL "return redis.call('set', 'lock', '111')" 0

# 传参设置lock值为111
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 lock 111

# 删除lock值
EVAL "return redis.call('del', 'lock')" 0
3、Java代码使用
  • lua脚本
if redis.call('get', 'KEYS[1]')==ARGV[1]
then
	return redis.call('del', 'KEYS[1]')
else
	return 0
end
  • Java代码
	public void redisTest() throws Exception {
        String key = "Lock";
        String uuid = UUID.randomUUID().toString();
        // 1.上锁+设置过期时间
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, uuid, 2, TimeUnit.SECONDS);
        while (isLock == null || !isLock) {
            TimeUnit.MILLISECONDS.sleep(100);
            redisTest();
            return;
        }
        try {
                // 业务代码 ...
            } finally {
                String script = "if redis.call('get', 'KEYS[1]')==ARGV[1] " +
                        " then return redis.call('del', 'KEYS[1]') else return 0 end";
                // 2.原子性判断解锁
                redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class)
                , Collections.singletonList(key)
                , Collections.singletonList(uuid));
            }
    }

共同存在的问题:

1、A线程未执行完、锁过期失效了,B线程重新上锁成功(锁不会自动续期)

2、不可重入锁(当前线程只可获取锁·一次)

3. 失败重试机制

  • 高并发情况下可能存在获取不到锁情况,可以尝试等待-重试机制

3.1 循环模式

	public Object tautology(int time) throws Exception {
        Boolean isLock;
        for (int i = 0; i <= time; i++) {
            // 获取锁
            isLock = true;
            if (isLock) {
                // 成功获取锁就跳出循环
                break;
            }
            TimeUnit.MILLISECONDS.sleep(300);
        }

        try {
            // 业务代码 ...
            return null;
        } finally {
            // 释放锁
        }
    }

3.2 递归模式

	// 最大重试次数
    private final Integer MAX_TIME = 3;

    public Object tautology(int time) throws Exception {
        // 超过最大重试次数
        if (time > MAX_TIME) {
            // 打印日志
            return null;
        }

        // 获取锁并判断
        Boolean isLock = true;
        if (isLock) {
            try {
                // 业务代码 ...
                return null;
            } finally {
                // 释放锁
            }
        } else {
            TimeUnit.MILLISECONDS.sleep(300);
            // 重试
            return tautology(time + 1);
        }
    }

4.Redis序列化配置

  • 将对象类型存入redis,以便能够正确反序列化
  • 对NULL属性、集合、数组不进行序列化
  • 解决jackson2无法反序列化LocalDateTime
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 解决value的序列化方式
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        // 首先解决key的序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        // Hash序列方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        // 序列化时将类的数据类型存入json,以便反序列化的时候转换成正确的类型
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance
        , ObjectMapper.DefaultTyping.NON_FINAL);
        // 只针对非空的属性、集合、数组进行序列化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        // 解决jackson2无法反序列化LocalDateTime的问题
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }
}
从OFED到rdma-core:手把手带你搞定Linux下RDMA开发环境的选型与搭建 本文详细介绍了在Linux系统下搭建RDMA开发环境的完整流程,涵盖OFED与rdma-core的选型对比及分步安装指南。针对高性能计算和分布式存储需求,解析RDMA技术栈的核心组件,提供环境验证与性能测试方法,帮助开发者快速构建稳定高效的开发环境。特别适合需要优化网络性能的工程师参考。 阅读详情

相关推荐

Claude Skills:不是更聪明,而是更“像专家”的 Agent 养成方法

Claude Skills 不是在告诉 Agent“你能做什么”, 而是在约束它“你应该如何思考、如何判断、如何给结论”。

测试者家园 1293

redis几种加锁的实现

1. redis加锁分类 redis能用的的加锁命令分表是INCR、SETNX、SET 2. 第一种锁命令INCR 这种加锁的思路是, key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作进行加一。然后其它用户在执行 INCR 操作进行加一时,如果返回的数大于 1 ,说明这个锁正在被使用当中。 1、 客户端A请求服务器获取key的值为1表...

weixin_30355437的博客 2428

实战指南:在UE5中高效集成Entt(ECS)框架

本文提供了一份在虚幻引擎5中高效集成Entt ECS框架的实战指南。针对UE5项目在处理大规模动态实体时的性能瓶颈,详细讲解了如何通过数据驱动的ECS架构进行优化。内容包括前期环境准备、核心构建步骤、创建首个ECS子系统以及与UE原生系统的数据同步技巧,旨在帮助开发者快速掌握集成方法,提升游戏逻辑的执行效率。

weixin_29002209的博客 158

redis加锁并设置过期时间

@Service public class RedisService { /** redis锁前缀 */ private static final String REDIS_LOCK_PREFIX = "LOCK_SMART_WARING_"; @Autowired RedisTemplate<String, Object> redisTemplate; /** * 加锁并设置过期时间 * * @param lockName

追梦赤子的博客 4738

Ubuntu升级遇到grub-efi-amd64-signed安装失败?3步搞定这个烦人错误

本文深度解析Ubuntu系统升级时grub-efi-amd64-signed安装失败并提示“cannot find EFI directory”错误的根本原因。该错误通常由EFI系统分区挂载异常、BIOS/UEFI引导模式不匹配或dpkg包状态死锁导致。文章提供了三步精准修复流程:首先检查并修复ESP分区挂载,其次处理dpkg依赖问题,最后针对传统BIOS系统提供适配方案,帮助用户安全彻底地解决这一常见升级难题。

r7s8t的博客 396

实战优化!C#上位机串口通信卡顿问题:超时机制+数据缓存方案

串口名</param>/// <param name="baudRate">波特率

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1063

使用Redis实现分布式锁及其优化

目前实现分布式锁的方式主要有数据库、Redis和Zookeeper三种,本文主要阐述利用Redis的相关命令来实现分布式锁。 相关Redis命令 SETNX   如果当前中没有值,则将其设置为并返回1,否则返回0。 EXPIRE   将设置为秒后自动过期。 GETSET 将的值设置为,并返回其原来的旧值。如果原来没有旧值,则返回nil。 EVAL与EVALSHA Redis2.6

james570的专栏 2271

goahead(嵌入式Web服务器)之文件传输篇

goahead文件上传原理: 使用html form即表单提交文件上传请求,web服务器核心处理接收客户端Post过来的文件数据(注意post的是二进制数据),最后,web服务器把接收到文件数据以二进制格式写到服务器本端存储系统。 goahead文件上传步骤: 1、创建upload.html文件,内容如下: <html> <head> <meta charset="utf-8"/> <title>Upload</title> </head&g

weixin_40732273的博客 3495

Redis进阶: 锁的使用

Redis进阶: 分布式锁

qq_36652517的博客 3390

Temporal Noise Reduction(TNR)时序去噪技术解析:原理、平台实践与工程调优路径

随着高画质夜景拍照和高清视频拍摄需求的持续增长,图像传感器在低照度环境下输出信号的噪声问题日益突出。相比空间域降噪(SNR),时序去噪(Temporal Noise Reduction, TNR)利用帧间信息进行融合处理,具备更高的信噪比提升效率及更强的边缘保护能力,已成为移动影像ISP链路中的核心环节。本文系统剖析TNR的算法原理、平台实现机制与工程调试路径,并基于实际开发案例分析其在不同场景下的效果差异及调优方法,为工程师提供从理论到实操的完整路径参考。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 3197

Lammps纳米压痕接触面积—Matlab接触面积计算(两种方法)

Lammps纳米压痕接触面积计算

Rockyoung 5447

Redis 分布式锁的正确实现方式

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33905756的博客 1470

SAP S/4HANA 数据归档的实施方法

实施 SAP S/4HANA 数据归档是一个系统的过程,涉及到策略的定义、技术的执行以及后续的维护和验证。正确的归档策略不仅能优化系统性能,还能帮助企业降低运营成本,确保数据安全,并符合相关法律法规的要求。通过以上案例,我们可以看到不同行业如何依据自己的业务需求和法规要求,利用 SAP S/4HANA 的强大功能来实现数据归档的目标。这些实践证明了数据归档在现代企业信息管理中的重要地位和作用。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 889

【Redis】超卖问题、一人一单问题、引入分布式锁

我们将服务启动两份,端口分别为8081和8082,用户请求会在这两个节点上。解决了超卖问题后,假如我们要多加一个要求,一人只能下一次单,如何解决?上,尝试获得锁,此时锁对于不在同一个服务器上的请求并未生效,依旧会出现。(加锁的关键),锁监视器只能判断本机的jvm上的锁是否存在。也就是假如用户发了多次请求,会被分配到不同的服务器处理。对于每个服务器,它有自己的jvm,每个jvm就有自己的。在高并发场景下,如果不进行加锁,可能会出现超卖问题。如果用户发了两个请求去买券,第一个请求打到。只需要在原有基础上,

2403_83829133的博客 426

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

day4_redis中分布式锁实现一人一单

单体系统中实现一人一单 集群中实现一人一单

奋斗的龙猫的博客 2255

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【Redis 开发】一人一单,超卖问题(悲观锁,乐观锁,分布式锁)

就是在商品上添加版本号,一个线程查询用户的时候查询到id和版本号信息,当他需要进行修改数据的时候进行对比现在的版本号看有没有发生变化马,如果有就停止操作,并重新开始,如果没有则修改数据并将版本号加一,每个线程都执行此操作。就是在下单的时候,在订单数据库中进行查询如果该用户已经存在单子,那么会返回异常,如果在库中没有该用户,在创建新的订单,注意在创建新的订单之前需要使用悲观锁,防止多线程问题。原因:在多个集群中就会有多个jvm下的锁监视器,这些锁监视器只会监控本服务器下的悲观锁。判断库存有没有发生变化。

weixin_62513677的博客 1366

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

一文学会基于 Redis 的分布式锁实现

分布式锁是一种机制,用于在分布式系统中控制对共享资源的访问。它确保在同一时间只有一个进程可以访问特定资源,从而避免数据不一致和竞争条件。通过本文,可以实现基于 Jedis 的分布式锁,该锁适用于大多数场景。

谷美的互联网练摊记 2439

redis分布式锁实现原理-以秒杀活动一人一单问题为例

redis分布式锁的实现原理,从使用原因到实现细节,细致解读。

qq_43856972的博客 1675

05、Redis实战:优惠券秒杀、全局唯一ID、超买乐观悲观锁、一人一单逻辑、分布式锁、分布式锁的原子性

乐观锁:乐观锁会有一个版本号,每次操作数据会对版本号+1,再提交回数据时,会去校验是否比之前的版本大1 ,如果大1 ,则进行操作成功,这套机制的核心逻辑在于,如果在操作过程中,版本号只比原来大1 ,那么就意味着操作过程中没有人对他进行过修改,他的操作就是安全的,如果不大1,则数据被修改过,当然乐观锁还有一些变种的处理方式比如CAS。但是以上做法依然有问题,因为你调用的方法,其实是this.的方式调用的,事务想要生效,还得利用代理来生效,所以这个地方,我们需要获得原始的事务对象, 来操作事务,这里可以使用。

u012011912的博客 1045

Redis分布式锁原理(一)——redis分布式锁需要注意的问题

1、什么场景会用到分布式锁(用本地锁举例,分布式场景在并发情况下存在的问题) 2、redis为什么可以实现分布式锁 3、假设一分布式场景,运用redis分布式锁并剖析各种坑 结合视频157、158

齐天小圣^O^ 的博客 2902

Redis 实战篇1.3(一人一单分布式锁)

本文介绍了基于Redis的分布式锁实现方案及其优化过程。针对集群环境下JVM锁失效问题,采用Redis的SETNX命令实现跨JVM互斥锁。初期方案通过线程ID识别锁持有者,但存在误删锁的风险。优化后引入UUID+线程ID作为唯一标识,并利用Lua脚本保证判断与释放锁的原子性操作。最终实现通过RedisTemplate执行Lua脚本,确保在高并发场景下锁操作的安全性和可靠性。该方案有效解决了分布式环境下的并发控制问题,避免了超时误删和锁竞争导致的业务异常。

2401_84162100的博客 185

Redis 秒杀功能 超卖问题 一人一单问题 分布式锁 精彩!精彩!

Redis 秒杀功能 超卖问题 一人一单问题 分布式锁 精彩!精彩!

道友 698

《Redis实现分布式锁》一、JAVA实现Redis分布式锁

在Java中,关于锁我想大家都很熟悉。在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常,我们以synchronized 、Lock来使用它。 但是Java中的锁,只能保证在同一个JVM进程内中执行。如果在分布式集群环境下呢? 一、分布式锁 分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的: 加锁 在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。 解锁 把脚

后端研发工程师Marion的博客 1327

【一】面试官问我Redis分布式锁的可靠性,嘿嘿嘿

Redis工作在主从集群模式(sentinel保证高可用),分片集群模式下,加锁成功但key没有同步到slave时发生主从切换;虽然主从 写是会从master写,读会从slave节点去,但依然存在这个问题;如果抢到锁的任务执行时间很短,绝对的小于锁丢失时间,那么理论上是可以忽略主上锁所述的锁丢失问题,在设计系统时,则关键的考虑因素是主从同步时效;问题:可能存在重复性加锁问题。

econg的博客 437

黑马点评项目Redis实现分布式锁

黑马点评项目,Redis实现分布锁

abc123mma的博客 708

从零到一编码实现Redis分布式锁

有的同学,就是这么尿性。也能理解,不自己弄一下,怎么能理解透彻,那就一起来搞一下呗! 使用场景和选型 分布式多节点的部署方式,使得共享变量有可能被同时操作,遇到有数据一致性要求的情况,就需要采取全局锁定的措施来保障并发操作下的一致性要求,如,库存扣减操作、同一个商品的上下架和更新操作等等。 常见的,分布式锁采用Zookeeper和Redis来实现。怎么取舍呢? 生产环境下,性能往往被优先考虑,相比较各自的优缺点,综合考虑,我们一般更倾向于redis。 从0到1 实现分布式锁 step1: 加

老男孩的架构路 483
上一篇: Linux日志排查方法
hanlin-hl
博客等级 码龄8年 3粉丝 35原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hanlin-hl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值