基于Redis的分布式锁实现方案

后端领域的分布式锁:Redis 实现方案 分布式锁需满足以下四大核心属性互斥性:同一时间只有一个客户端持有锁;防止死锁:客户端崩溃后,锁需自动释放(如过期时间);高可用性:锁服务需容忍节点故障(如Redis集群);性能:获取/释放锁的延迟低(如Redis的亚毫秒级响应)。可选属性公平性:按请求顺序分配锁(避免饥饿);可重入性:同一客户端可重复获取已持有的锁;容错性:部分节点故障不影响锁服务。适用场景:高并发、弱一致的场景(如电商库存、分布式任务);最佳实践。 阅读详情

1.分布式锁简介

简单来说,分布式锁是针对集群环境下多台机器竞争公共资源提出的方案。

单机环境下,线程共享堆内存,jdk提供了同步机制来应对资源竞争,比如synchronized关键字,AQS队列同步器等,只要我们设置内存标记,并且这个标记具有原子性和可见性,这样多线程环境下通过标记实现资源的同步操作,这个标记可以理解为锁的实现。所以我们通常会采取synchronized标记方法或代码块,或者RetreenLock去做互斥。

集群环境下,基于内存的锁机制只对单个节点有效,无法扩展,即节点1只能对自己做多线程同步,无法对节点2做限制,因为这时候已经是多进程了。那就需要一把公共的锁,由第三方实现,对所有节点的所有线程进行并发控制,所以这个锁也必须具有原子性和可见性。

分布式锁的设计应该是:

  1. 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器-上的一个线程执行。
  2. 这把锁要是一把可重入锁(避免死锁)
  3. 这把锁最好是一把阻塞锁(根据业务需求考虑)
  4. 这把锁最好是一把公平锁(根据业务需求考虑)
  5. 有高可用的获取锁和释放锁功能
  6. 获取锁和释放锁的性能要好

2.分布式锁实现

主要有三种,基于数据库、基于Redis、基于Zookeeper,本文介绍前两种;

2.1 基于数据库

  1. 设置唯一主键或字段,通过insert操作实现,这种操作具有幂等性,所以可以保证同一时间只能有一条记录插入,方法执行完后delete这条记录;

  2. 乐观锁和悲观锁:乐观锁就是加版本号,通过修改标记字段,判断是否获取锁成功;悲观锁就是用for update给记录加排它锁,锁住这条记录就可以保证同一时间只能由一条线程操作;

    注:由于性能问题,基于数据库的方案基本不会被采用;

2.2 基于Redis

由于Redis是单进程单线程,IO多路复用,所以线程安全问题和性能问题不用去做过多考虑和设计;
介绍Redis的几个原子操作:

  1. setnx(key, value):如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。
  2. getSet(key, value):设置新值并返回旧值。如果key不存在,把值设置成value并返回null;如果key存在,把值设置成value并返回之前存在的值。
  3. get(key):返回当前key对应的value。

下面是Springboot使用Redis的逻辑代码:

2.2.1.设计一个RedisLock类

RedisLock封装对Redis的操作,重点在tryLock方法,流程如下:

  1. 所有线程进入尝试获取锁,执行setNX方法,value为每个线程获取的当前时间+超时时间,执行成功则拿到锁,执行业务逻辑;执行失败,代表锁已经被占用,进行下一步判断;
  2. 为了防止超时产生死锁,要进行超时判断,如果拿到锁的线程崩溃了,后面的线程通过判断超时后,强制抢占锁;首先调用get()方法获取Redis里被setNX的值currentValue ,和当前时间比较,如果小于当前时间,代表已经超时,开始竞争锁资源;如果大于当前时间,代表未超时,进行下一次尝试;
  3. 在上一步判断超时后,调用getSet()方法进行替换并得到旧值oldValue,由于这个时候多条线程都在调用这个方法,但是只有最快的线程能拿到过期值currentValue ,判断oldValue是否等于currentValue,相等则代表最先拿到并且替换掉过期锁(当然大家都在getSet,当前的值也会很快被其他线程替换掉,即拿到锁,value也已经不是自己的原本的value,但是产生的误差可以忽略),如果不相等,则代表来晚了,锁已经被抢了,进行下一次尝试;
  4. 返回tryLock结果;

public class RedisLock {

    /**
     * 锁默认超时时间60s
     */
    private static final long DEFAULT_EXPIRE_TIME = 60 * 1000;

    private long expireTime;

    private String lockKey;

    private volatile boolean locked = false;

    private RedisTemplate<String, String> redisTemplate;

    public RedisLock(RedisTemplate<String, String> redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
        this.expireTime = DEFAULT_EXPIRE_TIME;
    }

    public RedisLock(RedisTemplate<String, String> redisTemplate, String lockKey, long expireTime) {
        this(redisTemplate, lockKey);
        this.expireTime = expireTime;
    }

    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    private boolean setNX(String key, String value) {
       return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    private String getSet(String key, String value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }

    public boolean tryLock() {
        try {
            //模拟每条线程尝试三次
            int i = 3;
            while (i > 0) {
                String value = String.valueOf(System.currentTimeMillis() + expireTime);
                if (this.setNX(lockKey, value)) {
                    // 获得锁成功并返回
                    locked = true;
                    return true;
                }
                /* 如果获取失败,下面判断是否超时 */
                String currentValue = this.get(lockKey);
                // 如果从redis取出的值小于当前时间,代表已经超时
                if (currentValue != null && Long.valueOf(currentValue) < System.currentTimeMillis()) {
                    String oldValue = this.getSet(lockKey, value);
                    if (oldValue != null && oldValue.equals(currentValue)) {
                        // 这种情况存在于,多个线程同步getSet后,只有最快的线程能拿到过期值currentValue,但是自己set的值可能很快被覆盖,这里忽略相差的时间值
                        locked = true;
                        return true;
                    }
                }

                i --;
                //这里可以设置随机等待时间后再尝试
                try {
                    Thread.sleep(new Random().nextInt(100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return false;
        } catch (Throwable e) {
            e.printStackTrace();
            return false;
        }
    }

    public void unlock() {
        if (locked) {
            redisTemplate.delete(lockKey);
            locked = false;
        }
    }

}

2.2.2.服务层业务调用

拿到锁后执行业务代码,无论执行结果是什么,在finally里面释放锁,防止死锁。

@Service
public class BossService {

    @Autowired
    private RedisTemplate redisTemplate;

    public boolean killBoss(String boss) {
        String name = Thread.currentThread().getName();
        RedisLock redisLock = new RedisLock(redisTemplate, "bossKey");
        if (redisLock.tryLock()) {
            try {
                String bossAmount = redisLock.get(boss);
                System.out.println("当前数量" + bossAmount);
                if (Integer.valueOf(bossAmount) > 0) {
                    redisLock.set(boss, String.valueOf(Integer.valueOf(bossAmount) - 1));
                    System.out.println(name + "成功杀死一个boss");
                } else {
                    System.out.println("boos死完了");
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            } finally {
                redisLock.unlock();
            }
        } else {
            System.out.println(name + "再试一下");
            return false;
        }
    }
}

2.2.3.单元测试

使用CountdownLatch计数器模拟多线程并发:调用await()方法阻塞当前线程,当计数完成后,唤醒所有线程并发执行;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DotACloudApplication.class)
public class BossServiceTest {

    @Autowired
    private BossService bossService;

    ExecutorService exec = Executors.newCachedThreadPool();

    @Test
    public void killBossTest() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);

        for (int i = 0; i < 100; i ++){
            Runnable runnable = () -> {
                try {
                    countDownLatch.await();
                    boolean b = bossService.killBoss("bank");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            exec.submit(runnable);
        }
        countDownLatch.countDown();
        Thread.sleep(120 * 1000);
    }

    @After
    public void after() {
        exec.shutdown();
    }


}

这里有一个本菜鸡踩的坑,那就是线程池提交任务后,子线程任务还未执行完时,主线程就结束了(单元测试和main方法不一样,main是非守护线程,所以main结束了子线程可以继续运行)。导致我以为每次都有线程超时挂掉,因为执行结果不对,去Redis查看lockKey每次都有值(正常情况每次释放锁删除lockKey),解决办法就是让主线程睡一会,确保其他线程执行完,当然为了完整保证时序,可以再加一个CountdownLatch,count为线程数,每个线程执行完后count-1,所有线程执行完后主线程await后面输出一句话就可以了。

3.结论

网上使用Redis的基本上都这种方案,弊端也很明显,需要每个节点的系统时间一致,至少误差时间不能超过设置的超时时间,否则每次判断都超时,由于误差导致锁失效,那么并发操作就会出问题。
所以推荐使用redis官方推荐的Redisson,可以参考我另一篇文章:https://blog.youkuaiyun.com/unclecoco/article/details/99442998

springboot基于redis分布式锁 SpringBoot基于redis的分布式锁,有word使用文档,根据文档配置即可使用 立即下载

相关推荐

基于Redis方式实现分布式锁

基于Redis方式实现分布式锁

集群环境中使用Redis实现分布式锁两种方式

一、介绍 互联网的应用场景中,为了支持高并发的请求,服务都是执行的分布式部署,相同的任务可以在集群中不同的服务器上执行,并且现在的服务容器都是支持多线程,相同的任务也可能会被同一个容器多次执行,都要求执行结果都满足幂等性的设计原则。 分布式锁,就是为了确保在分布式的环境下,相同任务只会执行成功的执行一次,后续的执行不会对这些已经产生了变化的业务再次产生影响。 分布式锁的实现有不少的方式,如:...

冯立彬的博客 9279

基于redis分布式锁实现秒杀功能

主要为大家详细介绍了基于redis分布式锁实现秒杀功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

Lock锁及获取锁的四种方法

Lock锁

是小D吖!的博客 2万+

LIO-SAM编译ubuntu20.04 Noetic

一、下载二、编译&&解决报错报错如下。

Kenneth666888的博客 689

Rsoft仿真实战:长周期光纤光栅参数优化与模式耦合分析

本文详细介绍了使用Rsoft软件进行长周期光纤光栅(LPFG)参数优化与模式耦合分析的实战方法。通过Rsoft仿真,可以高效优化光栅周期、包层厚度等关键参数,深入分析纤芯模与包层模的耦合特性,为光纤通信和传感应用提供可靠的设计依据。

h0i1j2k3l的博客 1115

七种方案!探讨Redis分布式锁的正确使用姿势

前言 日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方,欢迎大家指出哈,一起学习一起进步。 公众号:捡田螺的小男孩 什么是分布式锁 方案一:SETNX + EXPIRE 方案二:SETNX + value值是(系统时间+过期时间) 方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令) 方案四:SET的扩展命令(

SUNXIOAJIE的博客 1075

异常数据处理——3σ原则、箱线图

异常数据处理——3σ原则、箱线图 用不包括异常值的剩余数据的均值替换、删除DataFrame中某列有异常值的整行等方法

杨老师的小跟班(*∩_∩*) 9032

Redis如何实现分布式锁?

redis如何实现分布式锁?本篇文章浅谈一下

那条大鱼的博客 9356

SystemVerilog新手实战指南:iverilog与VScode的完美组合

本文为SystemVerilog新手提供了一套高效入门方案,详细介绍了如何利用开源编译器iverilog与轻量编辑器VScode搭建学习环境。内容涵盖十分钟快速安装配置、首个工程从编码到查看波形的完整流程,并演示了动态数组和队列等SystemVerilog新特性的使用,旨在帮助初学者避开复杂商业工具,快速掌握数字电路设计与验证的核心技能。

f6g7h8i9j的博客 513

一、【vmware虚拟机使用】安装vmware workstations17

本文介绍了VMware Workstation 17的安装与网络配置方法。主要内容包括:通过下载安装包进行标准安装(含许可协议、路径选择等步骤);重点讲解了NAT模式下的网络配置,包括修改子网IP为192.168.2.0网段、设置网关地址和DHCP范围等操作,以避免与主机网络冲突。文章还简要说明了VMware的Hypervisor核心技术原理,包括CPU、内存和存储虚拟化。最后指出完成这些准备工作后,后续将进行CentOS7系统的安装。整个流程配有详细图文说明,适合初学者跟随操作。

信创运维工程师 · 银河麒麟 & 容器实战 | 金融生产环境迁移·等保加固 | 142篇原创·17万+阅读 2734

基于redis实现分布式锁

谈到给代码加锁、代码同步问题,我们都会想到synchronized或者lock,这两种加锁机制虽然可以解决同步问题,但是这种锁是属于JVM级别的,只能适用于单体架构,而分布式应用一般都是部署多台服务器上也就是有多个JVM, 这时synchronized或者lock就没有办法解决,比如一个电商系统里面某个方法要对某件商品进行减库存,这个系统部署在多台服务器上,即使加上了synchronized锁,这时两个人买这个商品同时进来减库存,基于负载均衡可能会出现 两个人的减库存请求访问在两个服务器上...

xx18176612280的博客 1013

GB 47955—2026 L2组合驾驶辅助强制国标全解、合规方案与工程代码落地

GB 47955—2026 L2辅助驾驶国标合规工程代码# 运行环境:Python3.8+,适配车载仿真、台架测试、算法验证场景# 依赖安装:pip install numpy dataclasses tqdm struct time json# ===================== 国标强制时序参数(完全对标GB 47955—2026) =====================HAND_OFF_FIRST_WARN = 5.0 # 手部脱离5s触发一级预警。

lin_xiu的博客 295

Redis实现分布式锁的7种方案

https://www.cnblogs.com/wangyingshuo/p/14510524.html 七种方案前言 日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方,欢迎大家指出哈,一起学习一起进步。 什么是分布式锁 方案一:SETNX + EXPIRE 方案二:SETNX + value值是(系统时间+过期时间) 方案三:使用Lua脚本(包含SET

yygr的博客 2万+

【Python进阶】VSCode Python开发完全指南:从环境配置到高效调试

本文通过15个实战案例,系统讲解VSCode进行Python开发的环境配置、编码技巧、调试方法及性能优化,帮助初学者快速构建专业开发环境。

赶紧的博客 3731

基于STM32的智能宠物喂食系统设计与实现(代码+PCB )

STM32;DHT11;本系统的软件程序基于Keil MDK-ARM V5集成开发环境进行编写和编译。Keil MDK是ARM公司官方推荐的嵌入式开发工具,支持ARM Cortex-M系列微控制器的程序开发,内置ARM C/C++编译器、调试器和仿真器,具有代码编辑、语法高亮、工程管理和在线调试等功能,是STM32开发领域应用最为广泛的IDE之一。

3254968421的博客 72

Redis实现分布式锁有哪些方案?

如果锁过期的时候,并发多个客户端同时请求过来,都执行jedis.get()和set(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。方案四还是可能存在「锁过期释放,业务没执行完」的问题。设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。只要线程一加锁成功,就会启动一个watchdog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。

weixin_66137383的博客 920

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

redis分布式锁的简单实现方案

package com.mqc.lock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class ...

maqinchaoTnT的博客 312

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Redis实现分布式锁的7种方案,及正确使用方式!

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。互斥性: 任意时刻,只有一个客户端能持有锁。锁超时释放:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。可重入性:一个线程如果获取了锁之后,可以再次对其请求加锁。高性能和高可用:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。安全性:锁只能被持有的客户端删除,不能被其他客户端删除。

qq_40823822的博客 1133

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【分布式锁的实现方案之Redis】

【分布式锁的实现方案之Redis】分布式锁就是控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

Coder_ljw的博客 284

Redis分布式锁实现方案

本地锁: 解决同一进程内多个线程操作同一资源的问题。分布式锁: 解决多个进程同时操作同一资源的问题。

bocai_xiaodaidai的博客 622

redis分布式锁的实现问题与解决方案详解

分布式锁是一种在分布式系统中用于控制多个节点对共享资源的并发访问的机制。它可以确保在分布式环境下,多个服务实例或进程在访问关键资源时不会发生竞争。分布式锁的目标是保证每次只有一个节点可以持有锁,并在操作完成后及时释放锁,以便其他节点可以继续操作资源。在分布式环境下,传统的单机锁(如Java中的或Lock)不再适用,因为这些锁无法在多个进程或服务器之间共享。因此,分布式锁的机制通常基于共享的存储系统(如Redis、Zookeeper等)来实现。

qq_38917476的博客 2171

Redis实现分布式锁的几种方案

1.前言 对于Redis实现分布式锁的几种方案这个话题,展开之前我想先简单聊聊什么是分布式锁,分布式锁的使用场景,除了Redis外还有什么技术实现分布式锁等一系列内容。 1.1分布式锁 说大一点,就是在现在发展越来越迅速的大背景下,去中心化分布式系统越来越普及,在我们实际的生产开发当中,有一种不可避免的场景就是多个进程互斥的对其资源的使用,为了保证数据不重复,要求在同一时刻,同一任务只在一个节点上运行,且保证在多进程下的数据安全,分布式锁就十分重要了。 1.2分布式锁的几种方案 方式有很多种,根据技术角度的

GreatSQL2021的博客 257

Redis的分布式锁实现方案

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

JavaTeachers的博客 908
上一篇: Springboot整合Redis及序列化问题
下一篇: [转]SpringBoot集成redisson(单机,集群,哨兵)
可爱徐叔叔
博客等级 码龄7年 5粉丝 12原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值