当前位置: 首页 > news >正文

php使用zookeeper实现分布式锁

介绍

一、zookeeper和redis实现分布式锁的对比
1、redis 分布式场景应用比较广泛,redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能;zk分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小

2、如果是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁。

二、zookeeper分布式锁原理
这个主要得益于ZooKeeper为我们保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

1、保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /mylock节点,最终成功创建的那个客户端也即拥有了这把锁。

2、控制时序,就是所有试图来获取这个锁的客户端,最终都会被安排执行,只是有个全局时序。做法和上面基本类似,只是这里 /mylock已经预先存在,客户端在它下面创建临时顺序节点。Zk的父节点(/mylock)维持一份sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

三、zookeeper实现分布式锁的方式
(1)方式一:创建一个临时节点

        在需要获取排他锁时,所有的客户端都会试图通过调用 create -e 接口,在/mylock节点下创建临时子节点/mylock/lock。 ZooKeeper会保证在所有的客户端中,最络只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。

        同时,所有没有获取到锁的客户端就需要对 /mylock/lock 节点上注册一个Watcher监听,以便实时监听到lock节点的变更情况。如果节点被使用完删除了,zookeeper要向所有监听者发送通知,这会阻塞其他操作,并且会导致所有客户端来争抢锁,这种情况称为“羊群效应”,试想一下,如果监听者众多的话,会拖累性能。

(2)方式二:创建临时顺序节点

create -s -e /mylock/lock-data
1、每个试图加锁的客户端都会创建一个临时顺序节点 /mylock/lock-xxxxx,并且zk可以保证序号连续且唯一;

2、然后获取 /mylock/ 下的所有子节点,并按从小到大排序list;

3、判断最小节点是不是自己,如果是证明你就获取锁了,可以去处理业务逻辑了;

4、如果不是,获取到list中你的上一个节点名称(不一定是 -1 的那一个,因为此时它对应的客户端有可能主动放弃了),对其实施监听操作 get /mylock/lock-xxxxx watch 如果get监听失败了,说明节点已经别清除了,重复 2,3 直到监听成功或者获取锁,如果监听成功,就在这里阻塞,等待通知;

5、如果通知过来了,重复 2,3,4 的步骤,直到获取锁,因为上一个节点被释放的原因并不一定是它得到锁-使用完-释放,有可能是客户端断开连接了;

6、锁用完后记得主动清除,不然要等到心跳检测的时候才会清除。

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

四、安装PHP的zookeeper扩展
虽然ZooKeeper是一个Java应用程序,但C也可以使用。所以安装PHP的扩展,则首先安装zookeeper的C扩展支持

1、安装Zookeeper C扩展支持
示例中使用的zookeeper3.4.12版本

cd /usr/local/zk3.4/src/c
 
#编译C扩展,由于zookeeper解压即可用,这里下载源码主要是了编译C扩展,为后面安装PHP的扩展用
./configure --prefix=/usr/local/zk3.4/
make
make install
2、安装php的zookeeper扩展
zookeeper的PHP扩展可以在pecl上下载,也可以在github上下载,但有人说pecl上有bug。

git clone https://github.com/php-zookeeper/php-zookeeper.git
cd php-zookeeper
/usr/local/php7.4/bin/phpize
./configure --with-libzookeeper-dir=/usr/local/zk3.4/ --with-php-config=/usr/local/php7.4/bin/php-config
make && make install
 
vim php.ini
extension=zookeeper
/etc/php-fpm restart
五、PHP+zookeeper实现分布式锁示例
<?php
 
/*
 * zookeeper 类属性常量参考
 * https://www.php.net/manual/zh/class.zookeeper.php#zookeeper.class.constants.perms
 */
class zkCli {
    protected static $zk;
    protected static $myNode;
    protected static $isNotifyed;
    protected static $root;
 
    public static function getZkInstance($conf, $root){
        try{
 
            if(isset(self::$zk)){
                return self::$zk;
            }
 
            $zk = new \Zookeeper($conf['host'] . ':' . $conf['port']);
            if(!$zk){
                throw new \Exception('connect zookeeper error');
            }
 
            self::$zk = $zk;
            self::$root = $root;
 
            return $zk;
        } catch (\ZookeeperException $e){
            die($e->getMessage());
        } catch (\Exception $e){
            die($e->getMessage());
        }
    }
 
    // 获取锁
    public static function tryGetDistributedLock($lockKey, $value){
        try{
            // 创建根节点
            self::createRootPath($value);
            // 创建临时顺序节点
            self::createSubPath(self::$root . $lockKey, $value);
            // 获取锁
            return self::getLock();
 
        } catch (\ZookeeperException $e){
            return false;
        } catch (\Exception $e){
            return false;
        }
    }
 
    // 释放锁
    public static function releaseDistributedLock(){
        if(self::$zk->delete(self::$myNode)){
            return true;
        }else{
            return false;
        }
    }
 
    public static function createRootPath($value){
        $aclArray = [
            [
                   'perms'  => Zookeeper::PERM_ALL,
                    'scheme' => 'world',
                    'id'     => 'anyone',
            ]
        ];
        
          // 判断根节点是否存在
        if(false == self::$zk->exists(self::$root)){
            // 创建根节点
            $result = self::$zk->create(self::$root, $value, $aclArray);
            if(false == $result){
                throw new \Exception('create '.self::$root.' fail');
            }
        }
            
        return true;
    }
 
    public static function createSubPath($path, $value){
        // 全部权限
        $aclArray = [
            [
                'perms'  => Zookeeper::PERM_ALL,
                'scheme' => 'world',
                'id'     => 'anyone',
            ]
        ];
        /**
         * flags :
         * 0 和 null 永久节点,
         * Zookeeper::EPHEMERAL临时,
         * Zookeeper::SEQUENCE顺序,
         * Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE 临时顺序
         */
        self::$myNode = self::$zk->create($path, $value, $aclArray, Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE);
        if(false == self::$myNode){
            throw new \Exception('create -s -e '.$path.' fail');
        }
        echo 'my node is ' . self::$myNode.'-----------'.PHP_EOL;
 
        return true;
    }
 
    public function getLock(){
        // 获取子节点列表从小到大,显然不可能为空,至少有一个节点
        $res = self::checkMyNodeOrBefore();
        if($res === true){
            return true;
        }else{
            self::$isNotifyed = false;// 初始化状态值
            // 考虑监听失败的情况:当我正要监听before之前,它被清除了,监听失败返回 false
            $result = self::$zk->get($res, [zkCli::class, 'watcher']);
            while(!$result){
                $res1 = self::checkMyNodeOrBefore();
                if($res1 === true){
                    return true;
                }else{
                    $result = self::$zk->get($res1, [zkCli::class, 'watcher']);
                }
            }
 
            // 阻塞,等待watcher被执行,watcher执行完回到这里
            while(!self::$isNotifyed){
                echo '.';
                usleep(500000); // 500ms
            }
            
            return true;
        }
    }
 
    /**
     * 通知回调处理
     * @param $type 变化类型 Zookeeper::CREATED_EVENT, Zookeeper::DELETED_EVENT, Zookeeper::CHANGED_EVENT
     * @param $state
     * @param $key 监听的path
     */
    public static function watcher($type, $state, $key){
        echo PHP_EOL.$key.' notifyed ....'.PHP_EOL;
        self::$isNotifyed = true;
        self::getLock();
    }
 
    public static function checkMyNodeOrBefore(){
        $list = self::$zk->getChildren(self::$root);
        sort($list);
        $root = self::$root;
        array_walk($list, function(&$val) use ($root){
            $val = $root . '/' . $val;
        });
 
        if($list[0] == self::$myNode){
            echo 'get locak node '.self::$myNode.'....'.PHP_EOL;
            return true;
        }else{
            // 找到上一个节点
            $index = array_search(self::$myNode, $list);
            $before = $list[$index - 1];
            echo 'before node '.$before.'.........'.PHP_EOL;
            return $before;
        }
    }
}
 
 
function zkLock($resourceId){
    $conf = ['host'=>'127.0.0.1', 'port'=>2181];
    $root = '/lockKey_' . $resourceId;
    $lockKey = '/lock_';
    $value = 'a';
 
    $client = zkCli::getZkInstance($conf, $root);
    $re = zkCli::tryGetDistributedLock($lockKey, $value);
 
    if($re){
        echo 'get lock success'.PHP_EOL;
    }else{
        echo 'get lock fail'.PHP_EOL;
        return ;
    }
 
    try {
 
        doSomething();
 
    } catch(\Exception $e) {
 
        echo $e->getMessage() . PHP_EOL;
 
    } finally {
 
        $re = zkCli::releaseDistributedLock();
        if($re){
            echo 'release lock success'.PHP_EOL;
        }else{
            echo 'release lock fail'.PHP_EOL;
        }
 
        return ;
    }
}
 
function doSomething(){
    $n = rand(1, 20);
    switch($n){
        case 1: 
            sleep(15);// 模拟超时
            break;
        case 2:
            throw new \Exception('system throw message...');// 模拟程序中止
            break;
        case 3:
            die('system crashed...');// 模拟程序崩溃
            break;
        default:
            sleep(13);// 正常处理过程
    }
}
 
// 执行
zkLock(0);

相关文章:

php使用zookeeper实现分布式锁

介绍 一、zookeeper和redis实现分布式锁的对比 1、redis 分布式场景应用比较广泛&#xff0c;redis分布式锁&#xff0c;其实需要自己不断去尝试获取锁&#xff0c;比较消耗性能&#xff1b;zk分布式锁&#xff0c;获取不到锁&#xff0c;注册个监听器即可&#xff0c;不需要不…...

力扣-可回收且低脂的产品

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道超级超级超级简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1757. 可回收且低脂的产品二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交S…...

代码随想录刷题-数组-二分查找

文章目录写在前面原理习题题目1思路和代码题目-2写在前面 这个专栏是记录我刷代码随想录过程中的随想和总结。每一小节都是根据自己的理解撰写的&#xff0c;文章比较短&#xff0c;主要是为了记录和督促自己。刷完一章后&#xff0c;我会再单独整理一篇文章来总结和分享。 本…...

HCIA复习1

HCIA复习 抽象语言---->编码 编码---->二进制 二进制--->电信号 处理电信号 OSI参考模型----OSI/RM 应用层 表示层 会话层 传输层 端口号&#xff1a;0-65535&#xff1b;1-1023是注明端口 网络层 IP地址 数据链路层 物理层 ARP协议 正向ARP---通过IP地址获取目的MAC地…...

Kotlin中的destructuring解构声明

开发中有时只是想分解一个包含多个字段的对象来初始化几个单独的变量。要实现这一点&#xff0c;可以使用Kotlin的解构声明。本文主要了解&#xff1a;“1、如何使用解构声明这种特性 2、底层是如何实现的 3、如何在你自己的类中实现它1、解构声明的使用解构声明&a…...

Kubernetes Pod 水平自动伸缩(HPA)

Pod 自动扩缩容 之前提到过通过手工执行kubectl scale命令和在Dashboard上操作可以实现Pod的扩缩容&#xff0c;但是这样毕竟需要每次去手工操作一次&#xff0c;而且指不定什么时候业务请求量就很大了&#xff0c;所以如果不能做到自动化的去扩缩容的话&#xff0c;这也是一个…...

钉钉、企业微信和飞书向“钱”看

在急剧变革的时候&#xff0c;不管黑猫白猫&#xff0c;要抓到老鼠才算好猫。如今&#xff0c;各互联网企业早已进入降本增效的新阶段。勒紧裤腰带过日子之下&#xff0c;能不能盈利、商业化空间有多大&#xff0c;就成为各个业务极为重要的考核指标。在各业务板块中&#xff0…...

网上购物网站的设计

技术&#xff1a;Java、JSP等摘要&#xff1a;本文介绍了JSP和JAVA等相关技术&#xff0c;针对网上购物系统的实际需求&#xff0c;设计开发了一个基于JSP的小型电子商务网站也就是网上购物系统&#xff0c;。在设计开发中&#xff0c;采用的是SSH框架&#xff08;strutsspring…...

【Java学习笔记】8.Java 运算符

Java 运算符 计算机的最基本用途之一就是执行数学运算&#xff0c;作为一门计算机语言&#xff0c;Java也提供了一套丰富的运算符来操纵变量。我们可以把运算符分成以下几组&#xff1a; 算术运算符关系运算符位运算符逻辑运算符赋值运算符其他运算符 算术运算符 算术运算符…...

RHCSA-使用命令管理文件(3.6)

硬链接与软链接基本操作&#xff1a; 创建软硬连接的命令&#xff1a;ln 硬链接&#xff1a;ln 源文件&#xff08;已经存在的文件&#xff09; 链接文件名&#xff08;新建&#xff09; 软连接&#xff1a;ln -s 源文件&#xff08;已存在的文件&#xff09; 快捷方式文件名…...

socket聊天室--socket的建立

socket聊天室–socket实现 文章目录 socket聊天室--socket实现socket()bind()listen()accept()connect()发送接收read()函数recv()函数write()函数send()函数close()关闭套接字IP 地址格式转换函数socket() #include <sys/types...

Raft图文详解

Raft图文详解 refer to: Raft lecture (Raft user study) - YouTube Raft PDF Raft算法详解 - 知乎 (zhihu.com) 今天来详细介绍一下Raft协议 Raft是来解决公式问题的协议&#xff0c;那么什么是共识呢&#xff1f; 在分布式系统里面&#xff0c;consensus指的是多个节点对…...

春季出游,学会这些功能,让你旅途更舒心

春意盎然&#xff0c;万物复苏&#xff0c;春天正是旅游观光的好时节&#xff0c;相信不少小伙伴已经做好了出游的准备。想拥有好的心情&#xff0c;除了美食美景&#xff0c;好的出游神器也必不可少&#xff0c;好的出游神器能让我们的旅途更舒心&#xff0c;一起来看看是哪些…...

【华为OD机试真题java、python、c++、jsNode】简单的自动曝光【2022 Q4 100分】(100%通过)

代码请进行一定修改后使用,本代码保证100%通过率。本文章提供java、python、c++、jsNode四种代码 题目描述 一个图像有n个像素点,存储在一个长度为n的数组img里,每个像素点的取值范围[0,255]的正整数。 请你给图像每个像素点值加上一个整数k(可以是负数),得到新图newImg…...

react学习笔记-1:创建项目

安装nodejs https://nodejs.org/dist/v18.14.2/node-v18.14.2-x64.msi 修改国内源&#xff1a;npm config set registry https://registry.npm.taobao.org 使用create-react-app脚手架创建项目 安装脚手架 npm install -g create-react-app 全局安装&#xff0c;可以在任意的…...

vulnhub five86-2

总结&#xff1a;sudo -l&#xff0c;抓流量包&#xff0c;搜索引擎。。 目录 下载地址 漏洞分析 信息收集 网站渗透 ​编辑 反弹shell提权 下载地址 Five86-2.zip (Size: 1.7 GB)Download (Mirror): https://download.vulnhub.com/five86/Five86-2.zip使用&#xff1a;下…...

OpenCV入门(四)快速学会OpenCV3画基本图形

OpenCV入门&#xff08;四&#xff09;快速学会OpenCV3画基本图形 1.画点 在OpenCV中&#xff0c;点分为2D平面中的点和3D平面中的点&#xff0c;区别就是3D中点多了一个z坐标。我们首先介绍2D中的点&#xff0c;坐标为整数的点可以直接用(x, y)代替&#xff0c;其中x是横坐标…...

【MAC OS 命令行】Redis的安装、启动和停止。就是如此简单

目录Mac 安装 Redis使用 Homebrew 安装 Redis总结Mac 安装 Redis 使用 Homebrew 安装 Redis 如果没有安装 Homebrew&#xff0c;先安装 Homebrew 执行命令&#xff1a; 方法一、brew 官网的安装脚本 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homeb…...

Leetecode 661. 图片平滑器

图像平滑器 是大小为 3 x 3 的过滤器&#xff0c;用于对图像的每个单元格平滑处理&#xff0c;平滑处理后单元格的值为该单元格的平均灰度。 每个单元格的 平均灰度 定义为&#xff1a;该单元格自身及其周围的 8 个单元格的平均值&#xff0c;结果需向下取整。&#xff08;即&…...

剑指 Offer II 020. 回文子字符串的个数

题目链接 剑指 Offer II 020. 回文子字符串的个数 mid 题目描述 给定一个字符串 s&#xff0c;请计算这个字符串中有多少个回文子字符串。 具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字符组成&#xff0c;也会被视作不同的子串。 示例 1&#xff1a; 输入…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...