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 分布式场景应用比较广泛,redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能;zk分布式锁,获取不到锁,注册个监听器即可,不需要不…...
力扣-可回收且低脂的产品
大家好,我是空空star,本篇带大家了解一道超级超级超级简单的力扣sql练习题。 文章目录前言一、题目:1757. 可回收且低脂的产品二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交S…...
代码随想录刷题-数组-二分查找
文章目录写在前面原理习题题目1思路和代码题目-2写在前面 这个专栏是记录我刷代码随想录过程中的随想和总结。每一小节都是根据自己的理解撰写的,文章比较短,主要是为了记录和督促自己。刷完一章后,我会再单独整理一篇文章来总结和分享。 本…...
HCIA复习1
HCIA复习 抽象语言---->编码 编码---->二进制 二进制--->电信号 处理电信号 OSI参考模型----OSI/RM 应用层 表示层 会话层 传输层 端口号:0-65535;1-1023是注明端口 网络层 IP地址 数据链路层 物理层 ARP协议 正向ARP---通过IP地址获取目的MAC地…...
Kotlin中的destructuring解构声明
开发中有时只是想分解一个包含多个字段的对象来初始化几个单独的变量。要实现这一点,可以使用Kotlin的解构声明。本文主要了解:“1、如何使用解构声明这种特性 2、底层是如何实现的 3、如何在你自己的类中实现它1、解构声明的使用解构声明&a…...
Kubernetes Pod 水平自动伸缩(HPA)
Pod 自动扩缩容 之前提到过通过手工执行kubectl scale命令和在Dashboard上操作可以实现Pod的扩缩容,但是这样毕竟需要每次去手工操作一次,而且指不定什么时候业务请求量就很大了,所以如果不能做到自动化的去扩缩容的话,这也是一个…...
钉钉、企业微信和飞书向“钱”看
在急剧变革的时候,不管黑猫白猫,要抓到老鼠才算好猫。如今,各互联网企业早已进入降本增效的新阶段。勒紧裤腰带过日子之下,能不能盈利、商业化空间有多大,就成为各个业务极为重要的考核指标。在各业务板块中࿰…...
网上购物网站的设计
技术:Java、JSP等摘要:本文介绍了JSP和JAVA等相关技术,针对网上购物系统的实际需求,设计开发了一个基于JSP的小型电子商务网站也就是网上购物系统,。在设计开发中,采用的是SSH框架(strutsspring…...
【Java学习笔记】8.Java 运算符
Java 运算符 计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量。我们可以把运算符分成以下几组: 算术运算符关系运算符位运算符逻辑运算符赋值运算符其他运算符 算术运算符 算术运算符…...
RHCSA-使用命令管理文件(3.6)
硬链接与软链接基本操作: 创建软硬连接的命令:ln 硬链接:ln 源文件(已经存在的文件) 链接文件名(新建) 软连接:ln -s 源文件(已存在的文件) 快捷方式文件名…...
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是来解决公式问题的协议,那么什么是共识呢? 在分布式系统里面,consensus指的是多个节点对…...
春季出游,学会这些功能,让你旅途更舒心
春意盎然,万物复苏,春天正是旅游观光的好时节,相信不少小伙伴已经做好了出游的准备。想拥有好的心情,除了美食美景,好的出游神器也必不可少,好的出游神器能让我们的旅途更舒心,一起来看看是哪些…...
【华为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 修改国内源:npm config set registry https://registry.npm.taobao.org 使用create-react-app脚手架创建项目 安装脚手架 npm install -g create-react-app 全局安装,可以在任意的…...
vulnhub five86-2
总结:sudo -l,抓流量包,搜索引擎。。 目录 下载地址 漏洞分析 信息收集 网站渗透 编辑 反弹shell提权 下载地址 Five86-2.zip (Size: 1.7 GB)Download (Mirror): https://download.vulnhub.com/five86/Five86-2.zip使用:下…...
OpenCV入门(四)快速学会OpenCV3画基本图形
OpenCV入门(四)快速学会OpenCV3画基本图形 1.画点 在OpenCV中,点分为2D平面中的点和3D平面中的点,区别就是3D中点多了一个z坐标。我们首先介绍2D中的点,坐标为整数的点可以直接用(x, y)代替,其中x是横坐标…...
【MAC OS 命令行】Redis的安装、启动和停止。就是如此简单
目录Mac 安装 Redis使用 Homebrew 安装 Redis总结Mac 安装 Redis 使用 Homebrew 安装 Redis 如果没有安装 Homebrew,先安装 Homebrew 执行命令: 方法一、brew 官网的安装脚本 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homeb…...
Leetecode 661. 图片平滑器
图像平滑器 是大小为 3 x 3 的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度。 每个单元格的 平均灰度 定义为:该单元格自身及其周围的 8 个单元格的平均值,结果需向下取整。(即&…...
剑指 Offer II 020. 回文子字符串的个数
题目链接 剑指 Offer II 020. 回文子字符串的个数 mid 题目描述 给定一个字符串 s,请计算这个字符串中有多少个回文子字符串。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 示例 1: 输入…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...
