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

目录

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:一个点赞一份爱,点个关注不迷路!)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ | Leetcode C++题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; class Solution { public:int climbStairs(int n) {double sqrt5 sqrt(5);double fibn pow((1 sqrt5) / 2, n 1) - pow((1 - sqrt5) / 2, n 1);return (int)round(fibn / sqrt5);} };

从0到1:商场导览小程序开发笔记一

背景 购物中心与商场小程序&#xff1a;旨在提供便捷的购物、导航、活动报名、服务查询等功能&#xff0c;让用户更好地体验购物和享受服务。通过提供便捷的购物、信息查询和互动预约等功能&#xff0c;提升了商场的服务水平和用户体验&#xff0c;帮助商场与消费者建立更紧密…

mq发送消息之后,业务代码回滚,导致发了一条中奖消息给用户!!

背景是这样的&#xff1a;在一个名为"幸运大转盘"的线上活动中&#xff0c;用户可以通过消耗一定的积分来参与抽奖&#xff0c;有机会赢取各种奖品。这个活动的后台系统使用了消息队列&#xff08;MQ&#xff09;来处理用户的抽奖请求和发送中奖消息。 一天&#xf…

linux(ubuntu18.04.2) Qt编译 MySQL(8.0以上版本)链接库 Qt版本 5.12.12及以上 包含Mysql动态库缺失问题

整理这篇文档的意义在于&#xff1a;自己走了很多弯路&#xff0c;淋过雨所以想为别人撑伞&#xff0c;也方便回顾&#xff0c;仅供参考 一、搭建开发环境&#xff1a; 虚拟机&#xff08;ubuntu-20.04.6-desktop-amd64&#xff09;&#xff1a;Mysql数据库 8.0.36Workbench …

QtWindows任务栏

目录 引言任务栏进度右键菜单缩略图工具栏完整代码 引言 针对Windows系统的任务栏&#xff0c;Qt基于系统的原生接口封装有一些非常见类&#xff0c;如QWinTaskbarButton、QWinTaskbarButton、QWinThumbnailToolBar等&#xff0c;用于利用工具栏提供更多的信息&#xff0c;诸如…

开源电子邮件营销平台 listmonk 使用教程

做产品肯定要做电子邮件营销&#xff0c;特别是面向海外的产品&#xff0c;电子邮件营销已成为企业与客户沟通、建立品牌忠诚度和推动销售的重要工具&#xff0c;可以直接接触到目标受众&#xff0c;提供个性化内容&#xff0c;并以相对较低的成本获得可观的投资回报。你看&…

用HAL库改写江科大的stm32入门例子_1、按键控制led灯

1 如下图设置PB11 管脚 2 设置PB11为下降沿中断&#xff1a; 3 PA1 设置为推挽输出 4、NVIC 开启中断使能&#xff1a; 5、写中断事件&#xff1a; 完整代码如下&#xff1a; void EXTI15_10_IRQHandler(void) {/* USER CODE BEGIN EXTI15_10_IRQn 0 *///torning on the led…

母婴店运用商城小程序店铺的效果是什么

母婴市场规模高&#xff0c;还可与不少行业无缝衔接&#xff0c;尤其是以90后、00后为主的年轻人&#xff0c;在备孕生育和婴儿护理前后等整体流程往往不惜重金且时间长&#xff0c;母婴用品无疑是必需品&#xff0c;商家需要多方面拓展全面的客户及打通场景随时消费路径。 运…

24.5.5(离散化+树状数组,线段树)

星期一&#xff1a; dp题单 背包 第四题 混可乐 cf传送门 思路&#xff1a;条件可演化为每种可乐值为 ai-n&#xff0c;选最少的可乐使总和为0&#xff08;具体可看官方题解 到这会发现背包并不适合了&#xff0c;其实这是道bfs伪装的背包…

【Linux网络】网络文件共享

目录 一、存储类型 二、FTP文件传输协议 2.1 FTP工作原理 2.2 FTP用户类型 2.3 FTP软件使用 2.3.1 服务端软件vsftpd 2.3.2 客户端软件ftp 2.4 FTP的应用 2.4.1 修改端口号 2.4.2 匿名用户的权限 2.4.3 传输速率 三、NFS 3.1 工作原理 3.2 NFS软件介绍 3.3 NFS配…

数据结构===二叉树

文章目录 概要二叉树的概念分类存储遍历前序中序后序 小结 概要 简单写下二叉树都有哪些内容&#xff0c;这篇文章要写什么 二叉树的概念分类&#xff0c;都有哪些二叉树遍历 对一个数据结构&#xff0c;最先入手的都是定义&#xff0c;然后才会有哪些分类&#xff0c;对二叉…

C语言 | Leetcode C语言题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; int climbStairs(int n) {double sqrt5 sqrt(5);double fibn pow((1 sqrt5) / 2, n 1) - pow((1 - sqrt5) / 2, n 1);return (int) round(fibn / sqrt5); }

机器人系统ros2-开发实践05-将静态坐标系广播到 tf2(Python)-定义机器人底座与其传感器或非移动部件之间的关系

发布静态变换对于定义机器人底座与其传感器或非移动部件之间的关系非常有用。例如&#xff0c;最容易推断激光扫描仪中心框架中的激光扫描测量结果。 1. 创建包 首先&#xff0c;我们将创建一个用于本教程和后续教程的包。调用的包learning_tf2_py将依赖于geometry_msgs、pyth…

【负载均衡式在线OJ项目day1】项目结构

一.功能 查看题目列表&#xff0c;在线编程&#xff0c;判题功能&#xff0c;即leetcode的部分功能 二.宏观结构 整个项目是BS模式&#xff0c;客户端是浏览器&#xff0c;和用户交互并向服务器发起请求。 服务端从功能上来说分为两个模块&#xff0c;第一个是OJServer&…

FFmpeg———encode_video(学习)

目录 前言源码函数最终效果 前言 encode_video:实现了对图片使用指定编码进行编码&#xff0c;生成可播放的视频流&#xff0c;编译时出现了一些错误&#xff0c;做了一些调整。 基本流程&#xff1a; 1、获取指定的编码器 2、编码器内存申请 3、编码器上下文内容参数设置 4、…

平平科技工作室-Python-超级玛丽

一.准备图片 放在文件夹取名为images 二.准备一些音频和文字格式 放在文件夹media 三.编写代码 import sys, os sys.path.append(os.getcwd()) # coding:UTF-8 import pygame,sys import os from pygame.locals import* import time pygame.init() # 设置一个长为1250,宽为…

03.配置监控一台服务器主机

配置监控一台服务器主机 安装zabbix-agent rpm -ivh https://mirror.tuna.tsinghua.edu.cn/zabbix/zabbix/4.0/rhel/7/x86_64/zabbix-agent-4.0.11-1.el7.x86_64.rpm配置zabbix-agent,配置的IP地址是zabbix-server的地址&#xff0c;因为要监控这台主机 vim /etc/zabbix/zab…

淘宝线上扭蛋机小程序:推动扭蛋机销量

扭蛋机作为一个新兴的娱乐消费模式&#xff0c;能够带给消费者“盲盒式”的消费乐趣&#xff0c;正在快速发展中。消费者通过投币、扫码支付等&#xff0c;在机器上扭下按钮就可以随机获得一个扭蛋商品&#xff0c;这些商品也包括动漫衍生周边、IP主题商品等&#xff0c;种类多…

先电2.4的openstack搭建

先电2.4版本的openstack&#xff0c;前期虚拟机部署参考上一篇2.2版本&#xff0c;基本步骤是一样的&#xff0c;准备两个镜像文件CentOS-7.5-x86_64-DVD-1804.iso&#xff0c;XianDian-IaaS-V2.4.iso [rootcontroller ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno16777…

新手开抖店多久可以出单?做好这两点!七天必出单!

哈喽~我是电商月月 很多新手开抖店长时间不出单&#xff0c;觉得不正常&#xff0c;害怕新手根本做不起来店&#xff0c;就会搜索&#xff1a;新手开抖店多久可以出单&#xff1f; 新手开店&#xff0c;合理运营的话&#xff0c;七天里肯定是能出几单的&#xff0c;但没做好的…