一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

news2024/12/23 17:34:40

目录

1.Redis 分布式锁

(1)Redis 最普通的分布式锁

 (2)RedLock 算法

2.zk 分布式锁

3.redis 分布式锁和zk分布式锁的对比


1.Redis 分布式锁

        官方叫做 RedLock 算法,是 Redis 官方支持的分布式锁算法。
这个分布式锁有3个重要的考量点:
        · 互斥(只能有一个客户端获取锁)
        · 不能死锁
        · 容错(只要大部分 Redis 节点创建了这把锁就可以)

(1)Redis 最普通的分布式锁

        第一个最普通的实现方式,就是在 Redis 里使用 SET key value [EX seconds][PX milliseconds] NX 创建一个 key,这样就算加锁。其中:
        · NX :表示只有 key 不存在的时候才会设置成功,如果此时 redis 中存在这个 key ,那么设置失败,返回 nil 。
        · EX seconds :设置 key 的过期时间,精确到秒级。意思是 seconds 秒后锁自动释放,别人创建的时候如果发现已经有了就不能加锁了。
        · PX milliseconds :同样是设置 key 的过期时间,精确到毫秒级。
        比如执行以下命令:

SET resource_name my_random_value PX 30000 NX

        释放锁就是删除 key ,但是一般可以用 lua 脚本删除,判断 value 一样才删除: 

-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

        为啥要用 random_value 随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 lua 脚本来释放锁。
        但是这样是肯定不行的。因为如果是普通的 Redis 单实例,那就是单点故障。或者是 Redis 普通主从,那 Redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。 

 (2)RedLock 算法

        这个场景是假设有一个 Redis cluster,有5个 Redis master 实例。然后执行如下步骤获取一把锁:
        1.获取当前时间戳,单位是毫秒;
        2.跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒;

        3.尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点 n/2+1;

        4.客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了;

        5.要是锁建立失败了,那么就依次之前建立过的锁删除;
        6.只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。

Redis 官方 给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看:
https://redis.io/topics/distlock

2.zk 分布式锁

        zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁:这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。

 

/**
* ZooKeeperSession
*/
public class ZooKeeperSession {
    private static CountDownLatch connectedSemaphore = new CountDownLatch();
    private ZooKeeper zookeeper;
    private CountDownLatch latch;
    public ZooKeeperSession() {
        try {
            this.zookeeper = new ZooKeeper("192.168.31.187:2181,192.168.31
            try {
                connectedSemaphore.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ZooKeeper session established......");
        } catch (Exception e) {
            e.printStackTrace();
        }
}


/**
* 获取分布式锁
*
* @param productId
*/
public Boolean acquireDistributedLock(Long productId) {
    String path = "/product-lock-" + productId;
    try {
        zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, Cre
        return true;
    } catch (Exception e) {
    while (true) {
        try {
        // 相当于是给node注册一个监听器,去看看这个监听器是否存在
            Stat stat = zk.exists(path, true);
            if (stat != null) {
                this.latch = new CountDownLatch(1);
                this.latch.await(waitTime, TimeUnit.MILLISECONDS);
                this.latch = null;
            }
            zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSA
            return true;
        } catch (Exception ee) {
            continue;
        }
    }
}
        return true;
    }
}


/**
* 释放掉一个分布式锁
*
* @param productId
*/
public void releaseDistributedLock(Long productId) {
    String path = "/product-lock-" + productId;
    try {
        zookeeper.delete(path, -1);
        System.out.println("release the lock for product[id=" + produc
    } catch (Exception e) {
        e.printStackTrace();
    }
}


/**
* 建立 zk session 的 watcher
*/
private class ZooKeeperWatcher implements Watcher {
    public void process(WatchedEvent event) {
        System.out.println("Receive watched event: " + event.getState(
        if (KeeperState.SyncConnected == event.getState()) {
        connectedSemaphore.countDown();
        }
        if (this.latch != null) {
            this.latch.countDown();
        }
    }
}


/**
* 封装单例的静态内部类
*/
private static class Singleton {
    private static ZooKeeperSession instance;
        static {
            instance = new ZooKeeperSession();
        }
    public static ZooKeeperSession getInstance() {
        return instance;
    }
}


/**
* 获取单例
*
* @return
*/
public static ZooKeeperSession getInstance() {
    return Singleton.getInstance();
}


/**
* 初始化单例的便捷方法
*/
public static void init() {
    getInstance();
}
}

        也可以采用另一种方式,创建临时顺序节点:如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听排在自己前面的那个人创建的 node ,一旦某个人释放了锁,排在自己后面的人就会被 ZooKeeper 给通知,一旦被通知了之后,就 ok了,自己就获取到了锁,就可以执行代码了。

public class ZooKeeperDistributedLock implements Watcher {
    private ZooKeeper zk;
    private String locksRoot = "/locks";
    private String productId;
    private String waitNode;
    private String lockNode;
    private CountDownLatch latch;
    private CountDownLatch connectedLatch = new CountDownLatch(1);
    private int sessionTimeout = 30000;
    public ZooKeeperDistributedLock(String productId) {
        this.productId = productId;
        try {
            String address = "192.168.31.187:2181,192.168.31.19:2181,192.1
            zk = new ZooKeeper(address, sessionTimeout, this);
            connectedLatch.await();
        } catch (IOException e) {
            throw new LockException(e);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }

    public void process(WatchedEvent event) {
        if (event.getState() == KeeperState.SyncConnected) {
            connectedLatch.countDown();
            return;
        }
        if (this.latch != null) {
            this.latch.countDown();
        }
    }

    public void acquireDistributedLock() {
        try {
            if (this.tryLock()) {
                return;
            } else {
            waitForLock(waitNode, sessionTimeout);
            }
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }

    public boolean tryLock() {
        try {
            // 传入进去的locksRoot + “/” + productId
            // 假设productId代表了一个商品id,比如说1
            // locksRoot = locks
            // /locks/10000000000,/locks/10000000001,/locks/10000000002
            lockNode = zk.create(locksRoot + "/" + productId, new byte[0],
            // 看看刚创建的节点是不是最小的节点
            // locks:10000000000,10000000001,10000000002
            List<String> locks = zk.getChildren(locksRoot, false);
            Collections.sort(locks);
            if(lockNode.equals(locksRoot+"/"+ locks.get(0))){
            //如果是最小的节点,则表示取得锁
                return true;
            }
            //如果不是最小的节点,找到比自己小1的节点
            int previousLockIndex = -1;
            for(int i = 0; i < locks.size(); i++) {
                if(lockNode.equals(locksRoot + “/” + locks.get(i))) {
                    previousLockIndex = i - 1;
                    break;
                }
            }
            this.waitNode = locks.get(previousLockIndex);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
            return false;
    }

    private boolean waitForLock(String waitNode, long waitTime) throws Int
        Stat stat = zk.exists(locksRoot + "/" + waitNode, true);
        if (stat != null) {
            this.latch = new CountDownLatch(1);
            this.latch.await(waitTime, TimeUnit.MILLISECONDS);
            this.latch = null;
        }
        return true;
    }

    public void unlock() {
        try {
        // 删除/locks/10000000000节点
        // 删除/locks/10000000001节点
            System.out.println("unlock " + lockNode);
            zk.delete(lockNode, -1);
            lockNode = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e) {
            super(e);
        }
        public LockException(Exception e) {
            super(e);
        }
    }
}

3.redis 分布式锁和zk分布式锁的对比

        redis 分布式锁,需要自己不断去尝试获取锁,比较消耗性能。
        zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销小。
        另外,如果是 Redis 获取锁的那个客户端出现 bug 挂了,那么只能等待超时时间才能释放锁;而zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时自动释放锁。
        Redis 分布式锁大家没发现好麻烦吗?遍历上锁,计算时间等等;zk的分布式锁语义清晰实现简单。
        所以先不分析太多的东西,就说这两点,我个人实践认为 zk 的分布式锁比 Redis 的分布式锁牢靠、而且模型简单易用。

( ps:一个点赞一份爱,点个关注不迷路!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1646930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[qnx] 通过zcu104 SD卡更新qnx镜像的步骤

0. 概述 本文演示如果给Xlinx zcu104开发板刷入自定义的qnx镜像 1.将拨码开关设置为SD卡启动 如下图所示&#xff0c;将1拨到On,2,3,4拨到Off&#xff0c;即为通过SD启动。 2.准备SD卡中的内容 首先需要将SD格式化为FAT32的&#xff08;如果已经是FAT32格式&#xff0c;则…

力扣每日一题114:二叉树展开为链表

题目 中等 提示 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…

sass-loader和node-sass与node版本的依赖问题

sass-loader和node-sass与node版本的依赖问题 没有人会陪你走到最后&#xff0c;碰到了便是有缘&#xff0c;即使到了要下车的时候&#xff0c;也要心存感激地告别&#xff0c;在心里留下空白的一隅之地&#xff0c;多年后想起时依然心存甘味。——林清玄 报错截图 报错信息 np…

linux/windows安装Tomcat

安装tomcat 注意版本一致问题 Tomcat 10.1.0-M15(alpha)Tomcat 10.0.x-10.0.21Tomcat 9.0.x-9.0.63Tomcat 8.5.x-8.0.53规范版本 Servlet 6.0,JSP 3.1, EL 5.0 WebSocket 2.1&#xff0c;JASPIC 3.0 Servlet 5.0,JSP 3.0, EL 4.0 WebSocket 2.0&#xff0c;JASPIC 2.0 Serv…

20240507最新 ubuntu20.04安装ros noetic

ubuntu20.04安装ros 主要参考博客 【ROS】在 Ubuntu 20.04 安装 ROS 的详细教程_ubuntu20.04安装ros-CSDN博客 出现问题 1.ubuntu20.04 更换清华源报错 ubuntu20.04 更换清华源报错_gvfs metadata is not supported. fallback to teplme-CSDN博客 &#xff1f;&#xff1f…

CNN-BiLSTM-Attention(12种算法优化CNN-BiLSTM-Attention多输入单输出)

12种算法优化CNN-BiLSTM-Attention模型预测的代码。其中Attention模型可以改为单头或者多头&#xff0c;在代码中就是改个数字而已。代码注释已写好如何更改。 12种算法优化CNN-BiLSTM-Attention多特征输入单步预测代码获取戳此处代码获取戳此处代码获取戳此处 主要功能为:采用…

数据库SQL语言实战(七)

前言 这次的有一点点难~~~~~我也写了好久 练习题 题目一 在学生表pub.student中统计名字&#xff08;姓名的第一位是姓氏&#xff0c;其余为名字&#xff0c;不考虑复姓&#xff09;的使用的频率&#xff0c;将统计结果放入表test5_01中 create table test5_01(First_name…

【一步一步了解Java系列】:探索Java基本类型转换的秘密

看到这句话的时候证明&#xff1a;此刻你我都在努力~ 加油陌生人~ 个人主页&#xff1a; Gu Gu Study ​​ 专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹。 如果喜欢能否点个赞支持一下&#…

ComfyUI 基础教程(十四):ComfyUI中4种实现局部重绘方法

在ComfyUI中有多种方式可以实现局部重绘,简单的方式是使用VAE内补编码器进行局部重绘,也可以用Fooocus inpaint进行局部重绘,还可以用controlNet的inpaint模型进行局部重绘,以及使用Clip seg蒙版插件! 本篇介绍使用VAE內补编码器进行局部重绘的方法。 1、VAE内补编码器 局…

【docker】docker compose 搭建私服

安装 Docker Registry 创建目录 mkdir -pv /usr/local/docker/registrymkdir -pv /usr/local/docker/data 创建 docker-compose.yml文件 进入目录创建docker-compose.yml cd /usr/local/docker/registrytouch docker-compose.yml 编辑docker-compose.yml vim docker-compo…

ASRPRO

https://gitee.com/gitee-128/ASR-PRO-U8G2/tree/main 不下载模型 语音就是天问51唤醒我 u8g2的移植过程 第一步 下载u8g2的源代码 第二步 修改 delay and 函数 第三步 添加头文件 显示 显示 动画 SPI I2C(SOFT SPI ;SOFT I2C U8G2 移植过程&#xff08;移植过程参考…

JUC基础概念

文章目录 JUC的基础概念什么是JUC进程与线程并行与并发线程的五种状态JUC的架构 JUC的基础概念 什么是JUC JUC 是 Java.utils.concurrent 包内的类&#xff0c;是为了开发者可以在多线程的情况下减少竞争条件和防止死锁而出现的。 进程与线程 进程&#xff1a;一个进程包含…

【LeetCode算法】389. 找不同

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、题目二、思路三、解决方案 一、题目 给定两个字符串 s 和 t &#xff0c;它们只包含小写字母。字符串 t 由字符串 s 随机重排&#xff0c;然后在随机位置添…

大模型_基于医疗领域用lora微调ChatDoctor模型

文章目录 ChatDoctor目标方法结果结论收集和准备医患对话数据集创建外部知识数据库具有知识大脑的自主聊天医生的开发模型培训结果数据和模型&#xff1a; 微调推理 ChatDoctor 目标 这项研究的主要目的是通过创建一个在医学建议中具有更高准确性的专业语言模型&#xff0c;来…

[Flutter]创建一个私有包并使用

在Flutter中创建一个自己的私有组件&#xff08;通常称为包或库&#xff09;&#xff0c;并通过Dart的包管理工具pub进行使用。 一、创建一个新的Flutter包 1.使用命令行创建 使用Flutter命令行工具来创建一个新的包&#xff1a; $ flutter create --templatepackage my_pri…

ECS弹性云服务器居然这么好用。

引言 在过去的十年里&#xff0c;云计算从一个前沿概念发展为企业和开发者的必备工具。传统的计算模型通常局限于单一的、物理的位置和有限的资源&#xff0c;而云计算则通过分布式的资源和服务&#xff0c;为计算能力带来了前所未有的"弹性"。 云弹性服务器——为什…

N9048B PXE EMI 测试接收机,1 Hz 至 44 GHz

​ _EMI_ N9048B EMI 测试接收机 1 Hz 至 44 GHz Keysight N9048B PXE 是一款符合标准的 EMI 测试接收机&#xff0c;配有射频预选器和 LNA 设计。其实时扫描&#xff08;RTS&#xff09;功能有助于您缩短总体测试时间&#xff0c;轻松执行无间隙的信号捕获和分析。 特点 …

【人工智能基础】GAN与WGAN实验

一、GAN网络概述 GAN&#xff1a;生成对抗网络。GAN网络中存在两个网络&#xff1a;G&#xff08;Generator&#xff0c;生成网络&#xff09;和D&#xff08;Discriminator&#xff0c;判别网络&#xff09;。 Generator接收一个随机的噪声z&#xff0c;通过这个噪声生成图片…

视频改字祝福 豪车装X系统源码uniapp前端小程序源码

视频改字祝福 豪车装X系统源码uniapp前端小程序源码&#xff0c;创意无限&#xff01;AI视频改字祝福&#xff0c;豪车装X系统源码开源&#xff0c;打造个性化祝 福视频不再难&#xff01; 想要为你的朋友或家人送上一份特别的祝福&#xff0c;让他们感受到你的真诚与关怀吗&am…

若依前后端分离部署nginx

1、v.sj 2、生产环境修改 3、退出登录修改 4、路由改为hash模式 5、nginx配置 location /gldhtml/ {alias D:/java/tool/nginx-1.19.6/project/jxal/html/; } location /jxal/ {proxy_pass http://localhost:8081/; }