基于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;
    }
}
海康安防平台接口调试指南:从签名生成到Vue项目集成 本文详细解析了海康安防平台接口认证规则,从签名生成到Vue项目集成的完整流程。通过核心机制解析、签名工具使用技巧及Vue项目中的优雅集成方案,帮助开发者高效完成接口调试,避免常见问题。特别适合需要将海康安防平台接口集成到Vue项目中的开发者参考。 阅读详情

相关推荐

IPMItool 控制风扇转速

然后手动设置风扇,最后一位0x1e为设置30%的转速(16进制)。最后4位 0x0f 表示15%,0x0a,表示10%,不建议小于10%,因为10%已经很静音了 如果再小怕不能控温,烧毁硬件。首先停止服务器的自动风扇控制,最后一位0x00表示停止自动风扇控制,0x01为开启自动风扇控制。首先开启主板上的ipmi 设置好 用户名密码。此过程可以在网上搜索,有很多的资料可以参考。解压只有在目录里面有ipmltool。下面是一些常用的修改温度命令。

m0_37970303的博客 5055

redis几种加锁的实现

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

weixin_30355437的博客 2428

从SAS接口到InfiniBand:一张图看懂数据中心硬件‘黑话’(SFF-8643/8644、QSFP112、NDR/HDR/EDR)

本文深入解析数据中心硬件术语,从SAS接口标准(SFF-8643/8644)到速率标识(NDR/HDR/EDR),帮助工程师快速理解关键性能指标。特别对比了PCIe卡规格(HHHL/FHHL)和光模块选择(QSFP112/OSFP),并探讨InfiniBand在高性能网络中的优势,为硬件选型提供实用指南。

weixin_32103009的博客 425

redis加锁并设置过期时间

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

追梦赤子的博客 4738

AT32F435/437开发实战:把SD卡变成U盘的完整指南(含源码解析)

本文提供了基于AT32F435/437微控制器将SD卡改造为U盘的完整实战指南。通过深入解析USB MSC协议与SDIO驱动的协同工作原理,详细介绍了硬件设计要点、软件架构融合、核心源码解析以及从编译到文件传输的完整调试流程,帮助开发者实现可靠的大容量存储设备扩展。

weixin_29159127的博客 408

Synthesia技术浅析(四):自然语言处理

Synthesia 的自然语言处理(NLP)模块是其核心技术之一,涵盖了文本转语音(TTS)、情感分析以及多语言支持等多个方面。

m0_75253143的博客 1166

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

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

james570的专栏 2271

15、目标检测模型的评估指标与YOLO算法解析

本文详细解析了目标检测模型的评估指标,包括精确率、召回率、AP、mAP及IoU的定义与作用,并深入介绍了YOLO算法的核心原理与推理流程。从网格划分、锚框引入到边界框细化与后处理,全面阐述了YOLO如何实现快速准确的目标检测。同时总结了YOLO在智能安防、自动驾驶和工业检测等领域的应用,并展望了其未来在准确性提升、多类别支持和技术融合方面的发展趋势。

work3的博客 64

Redis进阶: 锁的使用

Redis进阶: 分布式锁

qq_36652517的博客 3390

企业微信开发007_域名主体校验未通过,需配置备案主体与当前企业主体相同或有关联关系的域名_企业内部自建应用开发_配置可信域名_回调URL_可信IP---企业微信开发009

因为我这个企业微信是,已经注册了服务商了,所以我又注册了一个不是服务商的测试企业,没有实名认证的,去试了试。而且,网络上找的教程,都是不够详细的,还是会碰到各种各样的问题,记录下来,为后来者提供便利。首先我用natapp.cn 申请了一个免费的域名,然后去,一个,企业微信上去配置,注意。我这个域名,的主体,可能是域名服务商,比如阿里域名 等等的 不是一般的企业域名。我这个企业微信,是开通了服务商的,企业微信,右上角有服务商管理后台的。到时候,直接用,他们自己企业注册的域名,已经备案的再去尝试,

添柴程序猿的专栏 1691

Vue3 - Element Plus 组件库 Tabs 标签页右侧区域插入按钮,<el-tabs> / <el-tab-pane> 选项卡组件右侧自定义插槽内容教程(右边区域添加自定义按钮功能区)

vue3,elementplus,tabs选项卡如何在插槽按钮,tabs标签页组件怎么自定义右侧内容,vue3elementplustabs组件右侧插槽按钮,element-plus在el-tabs组件的最右侧添加按钮,如何在选项卡右边加入按钮搜索框等内容,哪个属性能在tab选项卡右侧插入自定义按钮,在el-tabs组件最右侧添加按钮,vue3el-tabs组件怎么自定义右侧插槽内容,Tabs标签页右侧自定义插槽,ElementPlusTab右边加按钮,el-tabs组件自定义右侧按钮,在E

🏆 前端领域优质创作者 7014

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

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

weixin_33905756的博客 1470

【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像

【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像

|~~~热爱生活、努力学习的小伙汁~~~| 1088

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

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

2403_83829133的博客 426

精雕软件(JDpaint) 5.5 绿色版

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

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

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

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

奋斗的龙猫的博客 2255

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

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

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

weixin_62513677的博客 1366

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

一文学会基于 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、付费专栏及课程。

余额充值