基于redis的分布式锁应用场景

基于redis的分布式锁应用场景分布式锁 是用来解决分布式应用中 并发冲突 的一种常用手段 实现方式一般有基于 zookeeper 及基于 redis 二种 具体到业务场景中 我们要考虑二种情况 一 抢不到锁的请求 允许丢弃 即 忽略 比如 一些不是很重要的场景 比如 监控数据持续上报 某一篇文章的 已读 未读 标识位更新 对于同一个 id 如果并发的请求同时到达 只要有一个请求处理成功 就算成功 用活动图表示如下 二 并发请求 不论哪一条都必须要处理的场景 即 不允许丢数据 比如 一个订单 客户正在前台修改地址 管理员在后台同时修

“分布式锁”是用来解决分布式应用中“并发冲突”的一种常用手段,实现方式一般有基于zookeeper及基于redis二种。具体到业务场景中,我们要考虑二种情况:

一、抢不到锁的请求,允许丢弃(即:忽略)

二、并发请求,不论哪一条都必须要处理的场景(即:不允许丢数据)

比如:一个订单,客户正在前台修改地址,管理员在后台同时修改备注。地址和备注字段的修改,都必须正确更新,这二个请求同时到达的话,如果不借助db的事务,很容易造成行锁竞争,但用事务的话,db的性能显然比不上redis轻量。

解决思路:A,B二个请求,谁先抢到分布式锁(假设A先抢到锁),谁先处理,抢不到的那个(即:B),在一旁不停等待重试,重试期间一旦发现获取锁成功,即表示A已经处理完,把锁释放了。这时B就可以继续处理了。

但有二点要注意:

a、需要设置等待重试的最长时间,否则如果A处理过程中有bug,一直卡死,或者未能正确释放锁,B就一直会等待重试,但是又永远拿不到锁。

b、等待最长时间,必须小于锁的过期时间。否则,假设锁2秒过期自动释放,但是A还没处理完(即:A的处理时间大于2秒),这时锁会因为redis key过期“提前”误释放,B重试时拿到锁,造成A,B同时处理。(注:可能有同学会说,不设置锁的过期时间,不就完了么?理论上讲,确实可以这么做,但是如果业务代码有bug,导致处理完后没有unlock,或者根本忘记了unlock,分布式锁就会一直无法释放。所以综合考虑,给分布式锁加一个“保底”的过期时间,让其始终有机会自动释放,更为靠谱)

package com.cnblogs.yjmyzz.redisdistributionlock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.util.StringUtils; import java.util.UUID; import java.util.concurrent.TimeUnit; / * 利用redis获取分布式锁 * * @author 菩提树下的杨过 * @blog http://yjmyzz.cnblogs.com/ */ public class RedisLock { 
    private StringRedisTemplate redisTemplate; private Logger logger = LoggerFactory.getLogger(this.getClass()); / * simple lock尝试获取锅的次数 */ private int retryCount = 3; / * 每次尝试获取锁的重试间隔毫秒数 */ private int waitIntervalInMS = 100; public RedisLock(StringRedisTemplate redisTemplate) { 
    this.redisTemplate = redisTemplate; } / * 利用redis获取分布式锁(未获取锁的请求,允许丢弃!) * * @param redisKey 锁的key值 * @param expireInSecond 锁的自动释放时间(秒) * @return * @throws DistributionLockException */ public String simpleLock(final String redisKey, final int expireInSecond) throws DistributionLockException { 
    String lockValue = UUID.randomUUID().toString(); boolean flag = false; if (StringUtils.isEmpty(redisKey)) { 
    throw new DistributionLockException("key is empty!"); } if (expireInSecond <= 0) { 
    throw new DistributionLockException("expireInSecond must be bigger than 0"); } try { 
    for (int i = 0; i < retryCount; i++) { 
    boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, lockValue, expireInSecond, TimeUnit.SECONDS); if (success) { 
    flag = true; break; } try { 
    TimeUnit.MILLISECONDS.sleep(waitIntervalInMS); } catch (Exception ignore) { 
    logger.warn("redis lock fail: " + ignore.getMessage()); } } if (!flag) { 
    throw new DistributionLockException(Thread.currentThread().getName() + " cannot acquire lock now ..."); } return lockValue; } catch (DistributionLockException be) { 
    throw be; } catch (Exception e) { 
    logger.warn("get redis lock error, exception: " + e.getMessage()); throw e; } } / * 利用redis获取分布式锁(未获取锁的请求,将在timeoutSecond时间范围内,一直等待重试) * * @param redisKey 锁的key值 * @param expireInSecond 锁的自动释放时间(秒) * @param timeoutSecond 未获取到锁的请求,尝试重试的最久等待时间(秒) * @return * @throws DistributionLockException */ public String lock(final String redisKey, final int expireInSecond, final int timeoutSecond) throws DistributionLockException { 
    String lockValue = UUID.randomUUID().toString(); boolean flag = false; if (StringUtils.isEmpty(redisKey)) { 
    throw new DistributionLockException("key is empty!"); } if (expireInSecond <= 0) { 
    throw new DistributionLockException("expireInSecond must be greater than 0"); } if (timeoutSecond <= 0) { 
    throw new DistributionLockException("timeoutSecond must be greater than 0"); } if (timeoutSecond >= expireInSecond) { 
    throw new DistributionLockException("timeoutSecond must be less than expireInSecond"); } try { 
    long timeoutAt = System.currentTimeMillis() + timeoutSecond * 1000; while (true) { 
    boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, lockValue, expireInSecond, TimeUnit.SECONDS); if (success) { 
    flag = true; break; } if (System.currentTimeMillis() >= timeoutAt) { 
    break; } try { 
    TimeUnit.MILLISECONDS.sleep(waitIntervalInMS); } catch (Exception ignore) { 
    logger.warn("redis lock fail: " + ignore.getMessage()); } } if (!flag) { 
    throw new DistributionLockException(Thread.currentThread().getName() + " cannot acquire lock now ..."); } return lockValue; } catch (DistributionLockException be) { 
    throw be; } catch (Exception e) { 
    logger.warn("get redis lock error, exception: " + e.getMessage()); throw e; } } / * 锁释放 * * @param redisKey * @param lockValue */ public void unlock(final String redisKey, final String lockValue) { 
    if (StringUtils.isEmpty(redisKey)) { 
    return; } if (StringUtils.isEmpty(lockValue)) { 
    return; } try { 
    String currLockVal = redisTemplate.opsForValue().get(redisKey); if (currLockVal != null && currLockVal.equals(lockValue)) { 
    boolean result = redisTemplate.delete(redisKey); if (!result) { 
    logger.warn(Thread.currentThread().getName() + " unlock redis lock fail"); } else { 
    logger.info(Thread.currentThread().getName() + " unlock redis lock:" + redisKey + " successfully!"); } } } catch (Exception je) { 
    logger.warn(Thread.currentThread().getName() + " unlock redis lock error:" + je.getMessage()); } } } 

然后写个spring-boot来测试一下:

package com.cnblogs.yjmyzz.redisdistributionlock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SpringBootApplication public class RedisDistributionLockApplication { 
    private static Logger logger = LoggerFactory.getLogger(RedisDistributionLockApplication.class); public static void main(String[] args) throws InterruptedException { 
    ConfigurableApplicationContext applicationContext = SpringApplication.run(RedisDistributionLockApplication.class, args); //初始化 StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); RedisLock redisLock = new RedisLock(redisTemplate); String lockKey = "lock:test"; CountDownLatch start = new CountDownLatch(1); CountDownLatch threadsLatch = new CountDownLatch(2); final int lockExpireSecond = 5; final int timeoutSecond = 3; Runnable lockRunnable = () -> { 
    String lockValue = ""; try { 
    //等待发令枪响,防止线程抢跑 start.await(); //允许丢数据的简单锁示例 lockValue = redisLock.simpleLock(lockKey, lockExpireSecond); //不允许丢数据的分布式锁示例 //lockValue = redisLock.lock(lockKey, lockExpireSecond, timeoutSecond); //停一会儿,故意让后面的线程抢不到锁 TimeUnit.SECONDS.sleep(2); logger.info(String.format("%s get lock successfully, value:%s", Thread.currentThread().getName(), lockValue)); } catch (Exception e) { 
    e.printStackTrace(); } finally { 
    redisLock.unlock(lockKey, lockValue); //执行完后,计数减1 threadsLatch.countDown(); } }; Thread t1 = new Thread(lockRunnable, "T1"); Thread t2 = new Thread(lockRunnable, "T2"); t1.start(); t2.start(); //预备:开始! start.countDown(); //等待所有线程跑完 threadsLatch.await(); logger.info("======>done!!!"); } } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/205379.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午6:10
下一篇 2026年3月19日 下午6:10


相关推荐

  • 小议ODT[通俗易懂]

    小议ODT[通俗易懂]不少人看了我写的一篇ODT的文章后仍然觉得不明白:http://blog.csdn.net/Testing_is_believing/archive/2007/09/04/1772334.aspx ODT是TC特有的一个东西,不同于关键字驱动测试。ODT不是在单元测试中使用的,其作用是把数据和脚本中的测试对象构建到一个树上,然后就可以使用一条语句遍历这棵树,读取树中包含的测试数据,

    2025年9月8日
    6
  • IIS 下利用UrlRewriter做图片防盗链

    IIS 下利用UrlRewriter做图片防盗链<?xmlversion=”1.0″encoding=”UTF-8″?><configuration><system.webServer><staticContent><clientCachecacheControlMode=”UseMaxAge”cacheC…

    2022年7月23日
    13
  • Gitflow工作流程

    Gitflow工作流程在工作场合实施 Git 的时候 有很多种工作流程可供选择 此时反而会让你手足无措 本文推荐了一种最常用的 Git 工作流程

    2026年3月18日
    2
  • 常用进程调度算法_进程调度算法例题

    常用进程调度算法_进程调度算法例题写在前面:我是【程序员宝藏】的宝藏派发员,致力于创作原创干货。我热爱技术、热爱开源与分享,创作的【计算机基础面试问题】系列文章和【计算机基础主干知识】系列文章广受好评!后期会创作更多优质原创系列文章!如果您对计算机基础知识、编程等感兴趣,可以关注我,我们一起成长!本人力荐:如果觉得CSDN排版不够美观,欢迎来我的个人原创公zong号【程序员宝藏】(号如其名,诚不欺你!)查看有红色重点标记和排版美观的全系列文章(不细你来找我要红包)参考链接:TCP三次握手四次挥手好多同学问我要pdf版,我干脆.

    2026年4月17日
    4
  • 获取图像的宽度、高度

    获取图像的宽度、高度

    2021年9月4日
    64
  • loadrunner12使用手册_loadrunner脚本编写教程

    loadrunner12使用手册_loadrunner脚本编写教程首先我们安装好loadrunner12之后,出现这三个图标,然后开始操作。操作:1.打开VirtualUserGenerator,新建脚本2.选择脚本协议,对脚本名称位置可以进行编辑。此处以web-http/html为例。3.点击创建后,选择录制脚本,填写好录制的地址,应用程序,然后选择开始录制,此处以测试登录为例4.结束录制后,点击关联回放,确保脚本无误5.对代码进行进一步完善,插入事务或集合点6.对脚本进行执行回放,确保脚本无误,对脚本进行参数化可以直

    2022年10月14日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号