返回顶部
首页 > 资讯 > 后端开发 > Python >SpringBootRedisTemplate分布式锁的项目实战
  • 709
分享到

SpringBootRedisTemplate分布式锁的项目实战

2024-04-02 19:04:59 709人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录1.使用场景2.加锁解决3.分布式锁4.增加失效时间5.增加线程唯一值6.lua脚本7.Lua是如何实现原子性的8.代码演示9. 总结1.使用场景 想直接获取加锁和解锁代码,请直

1.使用场景

想直接获取加锁解锁代码,请直接到代码处

在下单场景减库存时我们一般会将库存查询出来,进行库存的扣除

@GetMapping(value = "order")
public R order() {
    int stock = RedisUtil.getObject("stock", Integer.class);
    if (stock > 0) {
        RedisUtil.set("stock", --stock);
    }
    return R.ok(stock);
}

上述的操作看起来很正常,但是其实是有问题的,试想一下当我们有两个线程同时访问这个接口会发生什么

Thread-1 查询库存结果为100

Thread-2 也来查询库存,此时Thread-1还没有执行减少库存操作,Thread-2 查询库存的结果也是100

Thread-1 Set库存为99

Thread-2 Set库存为99

这样就出问题了,明天扣了两次库存,但是库存仅仅减了1次

使用idea时,我们可以使在断点处右键将Suspend调整为Thread,仅阻断线程,并使用多个客户端同时请求接口,即可复现上述过程

多线程调试

2.加锁解决

synchronized 我们可以用Java提供的synchronized关键字将方法分布式锁,分布式锁的实现方案有很多种, ZooKeeper,redis,db,这边我们使用redis来实现以下分布式锁

3.分布式锁

上述两个线程同时进行的时候没有正确扣除库存正是因为【查询库存】和【扣除库存】不是一个原子操作,我们增加一个锁的机制,当线程持有锁的时候才允许进行【查询库存】和【扣除库存】,redis有一个sexNx命令允许当指定的key不存在时才进行set操作,在java中为RedisTemplate的setIfAbsent方法,这个方法保证了同时只能有一个线程set成功,set成功时就表明我们拿到了锁,可以进行原子操作了,当我们执行完原子操作时我们也需要将锁释放掉,在redis实现中也就是将key删除,允许下一个线程set值,加锁和释放锁的代码如下


public static boolean lock(String key, String value) {
    final boolean result = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY + key, value));
    if (result) {
        log.info("[redisTemplate redis]设置锁缓存 缓存  url:{} ", key);
    }
    return result;
}


public static boolean unlock(String key) {
    final boolean result = Boolean.TRUE.equals(redisTemplate.delete(CacheConstant.LOCK_KEY + key));
    if (result) {
        log.info("[redisTemplate redis]释放锁 缓存  url:{}", key);
    }
    return result;
}

那么我们将代码稍微修改一下,来利用锁来完成接口的改进

@GetMapping(value = "order")
public R order() {
    boolean lock;
    int stock;
    try {
        lock = RedisUtil.lock("stock", "");
        if (!lock) {
            return R.failed("服务繁忙,稍后再试");
        }
        stock = RedisUtil.getObject("stock", Integer.class);
        if (stock > 0) {
            RedisUtil.set("stock", --stock);
        }
    } finally {
        RedisUtil.unlock("stock");
    }
    return R.ok(stock);
}

此时,我们再将断点放在获取库存之后,并先用一个终端请求接口

终端1

然后,我们再从终端2发起请求,可以看到我们终端1没有结束自己的原子操作时,终端2是无法进行库存的扣除的

终端2

4.增加失效时间

在上一步中,我们仿佛已经完成了需求,同时进行扣除库存的只有一个线程,但是试想一下,当线程获取到锁之后,服务突然宕机了,这时候就算及时重启机器,那么锁也一直得不到释放,那么扣除库存接口始终无法获取到锁,这肯定不是我们想要的效果,那么我们改进一下我们加锁的方法,增加一下失效时间,即使服务宕机了,我们重启机器之后,锁也能正常释放掉不会影响一下个线程获取到锁


public static boolean lock(String key, String value, long time) {
    final boolean result = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY + key, value, time, TimeUnit.SECONDS));
    if (result) {
        log.info("[redisTemplate redis]设置锁缓存 缓存  url:{} ========缓存时间为{}秒", key, time);
    }
    return result;
}

5.增加线程唯一值

还有一种情况会导致我们可能误删除别人的锁,比如当线程1执行完流程之后准备释放锁之时,这时候锁正好失效了,线程2此时获取到锁,线程1释放锁时并不知道锁失效了,那么线程1执行释放操作就会将线程2拥有的锁释放掉,这肯定是不对的,那么我们再对unlock方法改进一下


public static boolean unlock(String key, String value) {
    if (Objects.equals(value, redisTemplate.opsForValue().get(CacheConstant.LOCK_KEY))) {
        final boolean result = Boolean.TRUE.equals(redisTemplate.delete(CacheConstant.LOCK_KEY + key));
        if (result) {
            log.info("[redisTemplate redis]释放锁 缓存  url:{}", key);
        }
        return result;
    }
    return false;
}

@GetMapping(value = "order")
public R order() {
    boolean lock;
    int stock;
    String uuid = IdUtil.fastUUID();
    try {
        lock = RedisUtil.lock("stock", uuid, 60L);
        if (!lock) {
            return R.failed("服务繁忙,稍后再试");
        }
        stock = RedisUtil.getObject("stock", Integer.class);
        if (stock > 0) {
            RedisUtil.set("stock", --stock);
        }
    } finally {
        // 在此释放锁时,判断锁是为自己持有才进行释放
        RedisUtil.unlock("stock", uuid);
    }
    return R.ok(stock);
}

6.Lua脚本

上面我们说了为了防止误删别人的锁,我们需要在删除锁时判断一下锁是否为自己持有,那么问题来了,我们这个查询锁值和删除锁的操作也并不是一个原子操作,也就是说可能你在获取锁值时锁还为自己持有,但是执行删除时锁已经不为自己持有了,还是会可能误删别人的锁,想要保证释放锁的原子性,我们可以通过redis原生支持的lua脚本来实现


public static boolean unlock(String key, String value) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
    Long result = redisTemplate.execute(redisScript, Collections.singletonList(CacheConstant.LOCK_KEY + key), value);
    if (Objects.equals(1L, result)) {
        log.info("[redisTemplate redis]释放锁 缓存  url:{}", key);
        return true;
    }
    return false;
}

7.Lua是如何实现原子性的

可以看到Lua脚本的大致意思也是跟我们自己写的代码差不多,判断是否为自己持有如果是才进行删除,那为什么Lua脚本可以保证原子性呢

Redis使用同一个Lua解释器来执行所有命令,同时,Redis保证以一种原子性的方式来执行脚本:当lua脚本在执行的时候,不会有其他脚本和命令同时执行,这种语义类似于 MULTI/EXEC。从别的客户端的视角来看,一个lua脚本要么不可见,要么已经执行完。

然而这也意味着,执行一个较慢的lua脚本是不建议的,由于脚本的开销非常低,构造一个快速执行的脚本并非难事。但是你要注意到,当你正在执行一个比较慢的脚本时,所以其他的客户端都无法执行命令。

8.代码演示

代码演示


public static boolean lock(String key, String value, long time) {
    final boolean result = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY + key, value, time, TimeUnit.SECONDS));
    if (result) {
        log.info("[redisTemplate redis]设置锁缓存 缓存  url:{} ========缓存时间为{}秒", key, time);
    }
    return result;
}


public static boolean unlock(String key, String value) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
    Long result = redisTemplate.execute(redisScript, Collections.singletonList(CacheConstant.LOCK_KEY + key), value);
    if (Objects.equals(1L, result)) {
        log.info("[redisTemplate redis]释放锁 缓存  url:{}", key);
        return true;
    }
    return false;
}
@GetMapping(value = "order")
public R order() {
    boolean lock;
    int stock;
    String uuid = IdUtil.fastUUID();
    try {
        lock = RedisUtil.lock("stock", uuid,6000L);
        if (!lock) {
            return R.failed("服务繁忙,稍后再试");
        }
        stock = RedisUtil.getObject("stock", Integer.class);
        if (stock > 0) {
            RedisUtil.set("stock", --stock);
        }
    } finally {
        RedisUtil.unlock("stock", uuid);
    }
    return R.ok(stock);
}

9. 总结

分布式锁在使用的过程中还是有挺多的讲究的,主要看应用场景例如还需要保证上述流程中可能碰到的锁失效时间小于代码执行时间,锁提前失效的问题,锁如何保证重入性的问题,欢迎大家讨论

 到此这篇关于SpringBoot RedisTemplate分布式锁的项目实战的文章就介绍到这了,更多相关springBoot RedisTemplate分布式锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: SpringBootRedisTemplate分布式锁的项目实战

本文链接: https://lsjlt.com/news/148834.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • SpringBootRedisTemplate分布式锁的项目实战
    目录1.使用场景2.加锁解决3.分布式锁4.增加失效时间5.增加线程唯一值6.Lua脚本7.Lua是如何实现原子性的8.代码演示9. 总结1.使用场景 想直接获取加锁和解锁代码,请直...
    99+
    2024-04-02
  • 基于Redission的分布式锁实战
    目录一、为什么需要分布式锁二、Redission的实战使用2.1 Redission执行流程2.2 Watch Dog 机制2.3 对比setnx三、代码案例一、为什么需要分布式锁 在系统中,当存在多个进程和线程可以改变...
    99+
    2022-08-14
    Redission分布式锁
  • 《Redis实战篇》四、分布式锁
    文章目录 4.1 基本原理和实现方式对比4.2 Redis分布式锁的实现核心思路4.3 实现分布式锁版本一4.4 Redis分布式锁误删情况说明4.5 解决Redis分布式锁误删问题4.6 分布式锁的原子性问题4.7 Lua脚本解决...
    99+
    2023-08-17
    redis 分布式 java
  • zookeeper实战之实现分布式锁的方法
    目录一、分布式锁的通用实现思路二、ZK实现分布式锁的思路三、ZK实现分布式锁的编码实现1、核心工具类实现2、测试代码编写线程安全问题复现使用上面封装的ZkLockHelper实现的分...
    99+
    2022-11-13
    zookeeper分布式锁 zookeeper实现分布式锁 zookeeper分布式锁原理
  • SpringBoot 整合 Spring-Session 实现分布式会话项目实战
    目录一、配置及开发二、测试三、Spring-Session 的缺点文章参考:Spring 提供了处理分布式会话的解决方案:Spring-Session。Spring-Session ...
    99+
    2024-04-02
  • 分布式锁的原理及Redis怎么实现分布式锁
    这篇文章主要介绍“分布式锁的原理及Redis怎么实现分布式锁”,在日常操作中,相信很多人在分布式锁的原理及Redis怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解...
    99+
    2023-02-02
    redis
  • redis分布式锁的实现
    一、使用分布式锁要满足的几个条件:1、系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现)2、共享资源(各个系统访问同一个资源,资源的载体可...
    99+
    2024-04-02
  • Python+Redis从零打造分布式锁实战示例
    目录引言1. 简单实现(基于SETNX命令)2. 改进:设置超时时间(expire命令)3. 进一步改进:使用Lua脚本保证原子性4. 更完善的实现,Redlock算法总结引言 在分布式系统中,多个节点可能需要访问同一共...
    99+
    2024-01-29
    Python Redis分布式锁 Python Redis
  • 在Spring项目中实现分布式session
    在Spring项目中实现分布式session?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. 引入Spring Session maven依赖<!-- spring s...
    99+
    2023-05-31
    分布式 session spring
  • 分布式事务实战
    转载本文需注明出处:微信公众号EAWorld,违者必究。引言:微服务倡导将复杂的单体应用拆分为若干个功能简单、松耦合的服务,这样可以降低开发难度、增强扩展性、便于敏捷开发,从而被越来越多的开发者和公司推崇运用。但系统微服务化后,一个看似简单...
    99+
    2023-06-05
  • Redis分布式锁之红锁的实现
    目录一、问题二、办法三、原理四、实战一、问题 分布式锁,当我们请求一个分布式锁的时候,成功了,但是这时候slave还没有复制我们的锁,masterDown了,我们的应用继续请求锁的时候,会从继任了master的原slav...
    99+
    2022-08-09
    Redis红锁
  • ZooKeeper分布式锁的实现方式
    本篇内容介绍了“ZooKeeper分布式锁的实现方式”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!目录一、分布式锁方案比较二、ZooKeep...
    99+
    2023-06-20
  • Redis分布式锁的实现方式
    目录一、分布式锁是什么1、获取锁2、释放锁二、代码实例上面代码存在锁误删问题:三、基于SETNX实现的分布式锁存在下面几个问题1、不可重入2、不可重试3、超时释放4、主从一致性四、Redisson实现分布式锁1、pom2...
    99+
    2023-04-03
    Java Redis分布式锁实现方式 实现Redis分布式锁 Redis分布式锁实现
  • Redis——》实现分布式锁
    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结—...
    99+
    2023-09-03
    redis 分布式 过期 lua
  • MySQL实现分布式锁
    目录基于MySQL分布式锁实现原理及代码MySQL锁InnoDB共享锁排它锁MyISAM表共享读锁表独占写锁分布式锁实现难点:为什么需要for(;总结基于MySQL分布式锁实现原理及...
    99+
    2022-11-13
    MySQL实现分布式锁 MySQL分布式锁
  • Redis实现分布式锁
    单体锁存在的问题 在单体应用中,如果我们对共享数据不进行加锁操作,多线程操作共享数据时会出现数据一致性问题。 (下述实例是一个简单的下单问题:从redis中获取库存,检查库存是否够,>0才允许下单) 我们的解决办法通常是加锁。如下加单体锁...
    99+
    2023-08-16
    分布式 java jvm
  • 分析ZooKeeper分布式锁的实现
    目录一、分布式锁方案比较二、ZooKeeper实现分布式锁2.1、方案一2.2、方案二一、分布式锁方案比较 方案 ...
    99+
    2024-04-02
  • redis分布式锁与zk分布式锁的对比分析
    目录Redis实现分布式锁原理能实现的锁类型注意事项 zk实现分布式锁原理能实现的锁类型两种锁的对比在分布式环境下,传统的jvm级别的锁会失效,那么分布式锁就是非常有必要的一个技术,一般我们可以通过redis,...
    99+
    2022-11-18
    redis分布式锁 分布式锁 zk分布式锁
  • Zookeeper的分布式锁的实现方式
    这篇文章主要讲解了“Zookeeper的分布式锁的实现方式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Zookeeper的分布式锁的实现方式”吧!1. 背景最近在学习 Zookeeper,...
    99+
    2023-06-05
  • C#实现Redis的分布式锁
    目录Redis实现分布式锁(悲观锁/乐观锁)Redis连接池使用Redis的SetNX命令实现加锁,调用方式Redis实现分布式锁(悲观锁/乐观锁) 对锁的概念和应用场景在此就不阐...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作