聊聊分布式锁原理及Redis如何实现分布式锁


本篇文章给大家带来了关于redis的相关知识,其中主要介绍了关于分布式锁是什么?redis又是怎么实现分布式锁的?需要满足什么条件?下面一起来看一下吧,希望对需要的朋友有帮助。

一、分布式锁基本原理

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

分布式锁应该满足的条件:

  • 可见性:多个线程都能看到相同的结果,注意:这个地方说的可见性并不是并发编程中指的内存可见性,只是说多个进程之间都能感知到变化的意思
  • 互斥:互斥是分布式锁的最基本的条件,使得程序串行执行
  • 高可用:程序不易崩溃,时时刻刻都保证较高的可用性
  • 高性能:由于加锁本身就让性能降低,所有对于分布式锁本身需要他就较高的加锁性能和释放锁性能
  • 安全性:安全也是程序中必不可少的一环

常见的分布式锁有三种:

  • Mysql:mysql本身就带有锁机制,但是由于mysql性能本身一般,所以采用分布式锁的情况下,其实使用mysql作为分布式锁比较少见

  • Redis:redis作为分布式锁是非常常见的一种使用方式,现在企业级开发中基本都使用redis或者zookeeper作为分布式锁,利用setnx这个方法,如果插入key成功,则表示获得到了锁,如果有人插入成功,其他人插入失败则表示无法获得到锁,利用这套逻辑来实现分布式锁

  • Zookeeper:zookeeper也是企业级开发中较好的一个实现分布式锁的方案

1653382219377.png

二、基于Redis实现分布式锁

实现分布式锁时需要实现的两个基本方法:

  • 获取锁:

    • 互斥:确保只能有一个线程获取锁
    • 非阻塞:尝试一次,成功返回true,失败返回false
  • 释放锁:

    • 手动释放
    • 超时释放:获取锁时添加一个超时时间

基于Redis实现分布式锁原理:

SET resource_name my_random_value NX PX 30000
  • resource_name:资源名称,可根据不同的业务区分不同的锁
  • my_random_value:随机值,每个线程的随机值都不同,用于释放锁时的校验
  • NX:key不存在时设置成功,key存在则设置不成功
  • PX:自动失效时间,出现异常情况,锁可以过期失效

利用NX的原子性,多个线程并发时,只有一个线程可以设置成功,设置成功表示获得锁,可以执行后续的业务处理;如果出现异常,过了锁的有效期,锁自动释放;

版本一

1、定义ILock接口

一览妙笔 一览妙笔

自媒体、编剧、营销人员写作工具

一览妙笔 50 查看详情 一览妙笔
public interface ILock extends AutoCloseable {
    /**
     * 尝试获取锁
     *
     * @param timeoutSec 锁持有的超时时间,过期后自动释放
     * @return true代表获取锁成功;false代表获取锁失败
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     * @return
     */
    void unLock();
}

2、基于Redis实现分布式锁—RedisLock

public class SimpleRedisLock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        //通过del删除锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }

    @Override
    public void close() {
        unLock();
    }
}

锁误删问题

问题说明:

持有锁的线程1在锁的内部出现了阻塞,这时锁超时自动释放,这时线程2尝试获得锁,然后线程2在持有锁执行过程中,线程1反应过来,继续执行,走到了删除锁逻辑,此时就会把本应该属于线程2的锁进行删除,这就是锁误删的情况。

解决方案:

在存入锁时,放入自己线程的标识,在删除锁时,判断当前这把锁的标识是不是自己存入的,如果是,则进行删除,如果不是,则不进行删除。

版本二:解决锁误删问题

public class SimpleRedisLock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        // 获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取锁中的标示
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        // 判断标示是否一致
        if(threadId.equals(id)) {
            // 释放锁
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }
    }

    @Override
    public void close() {
        unLock();
    }
}

锁释放的原子性问题

问题分析:

上述释放锁的代码依然存在锁误删问题,当线程1获取锁中的线程标识,并根据标识判断是自己的锁,这时锁到期自动释放,恰好线程2尝试获取锁,并拿到了锁,此时线程1依然执行释放锁的操作,就导致误删了线程2持有的锁。

原因在于,由j*a代码实现的释放锁流程不是原子操作,存在线程安全问题。

解决方案:

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,可以确保多条命令执行时的原子性。

版本三:调用Lua脚本改造分布式锁

public class SimpleRedisLock implements ILock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        String script = "if redis.call("get",KEYS[1]) == ARGV[1] then\n" +
                " return redis.call("del",KEYS[1])\n" +
                "else\n" +
                " return 0\n" +
                "end";
        //通过执行lua脚本实现锁删除,可以校验随机值
        RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
        stringRedisTemplate.execute(redisScript,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId());
    }

    @Override
    public void close() {
        unLock();
    }
}

推荐学习:《Redis视频教程》

以上就是聊聊分布式锁原理及Redis如何实现分布式锁的详细内容,更多请关注其它相关文章!


# 分布式锁  # 自己的  # 中阳网站推广售后服务  # 网站建设人员安排方案  # 河南推广行业网站建站  # 海鲜店营销推广策略  # 完美的网站推广方案  # 设计网站与推广  # 武汉问答营销推广哪里有  # seo交易网  # 道县水文站网站建设  # seo综合查询工具广告  # 加锁  # 如何实现  # 多条  # 见性  # 较高  # 都能  # 互斥  # 客户端  # 多个  # 后端  # Redis 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: 芒果TV官网登录入口 芒果TV官方网站登录入口  Fedora怎么安装 Fedora Workstation安装步骤  虫虫漫画排行榜单入口_虫虫漫画编辑推荐入口  Lar*el 关联查询:同时筛选父表与子表数据的高效策略  如何查找哪个composer包引入了特定的依赖?  优化 React onClick 事件处理:函数引用与箭头函数的对比  Sublime Text怎么关闭自动完成_Sublime禁用Auto Complete设置  使用VS Code调试Python代码:从入门到精通  汽水音乐网页版登录 汽水音乐网页端官方入口  使用VS Code作为你的个人知识管理系统  CSS过渡与滚动滚动事件结合应用_scroll与transition动画  如何用Golang优化微服务间请求性能_Golang 微服务请求性能优化方法  Sublime怎么配置YAML文件格式化_Sublime YAML Formatter插件教程  怎样设置开机后自动运行某个程序_Windows启动文件夹与任务计划【自动化】  GBA模拟器手柄按键设置  QQ网站入口直接登录 QQ官方正版登录页面  邦丰播放器频道搜索设置  《新三国志曹操传》游历事件袁尚突围攻略  教资成绩怎么查询  J*aScript模拟悬停与点击:自动化网页动态元素交互指南  抖音网页版官方链接 抖音网页版官网链接入口  FotoBalloon图片左右镜像教程  如何在Python中安全地将环境变量转换为整数并满足Mypy类型检查  抖音评论无法发送如何修复 抖音评论功能操作指南  深入理解随机递归函数的确定性:内部节点、叶节点与时间复杂度分析  《饿了么》拼好饭点外卖教程2025  Go App Engine 项目结构与包管理深度指南  铁路12306官网登录入口 铁路12306在线购票官方平台  发布小红书怎么屏蔽粉丝?屏蔽粉丝能看到吗?  《kimi智能助手》制作ppt教程  MacBook Pro词典使用指南  J*a实现任务清单管理_集合框架综合入门练手  《金山词霸》语音翻译方法  《虎扑》取消评分记录方法  铁路12306入口 铁路12306官网版入口登录网址  在PySimpleGUI中实现键盘按键绑定按钮事件  Mac hosts文件在哪里_Mac修改hosts文件详细教程  VB表达式书写规则解析  感染了幽门螺杆菌一定会导致胃癌吗?蚂蚁庄园今日答案最新11.30  我居然低估了 DeepSeek,这次更新它做到了这些!  汽水音乐在线入口 汽水音乐网页端官方页面快速打开  msn官方入口2025登录 msn官网2025直达首页入口  手机雨课堂网页版入口免登录 雨课堂网页版可点击直接进入  QQ邮箱注册地址 免费获取QQ邮箱账号  Firefox OS应用开发:解决XMLHttpRequest跨域请求阻塞问题  Win10共享文件夹设置方法 Win10局域网文件共享全攻略【教程】  什么是Satis,如何用它搭建一个私有的composer仓库?  猫眼电影app如何筛选支持退改签的影院_猫眼电影退改签影院筛选方法  sublime如何处理超大文件不卡顿 _sublime打开大日志文件技巧  动漫岛在线动漫网 动漫岛动漫在线观看官方入口 

 2023-01-27

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.