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: 输入…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
这个警告表明您在使用Vue的esm-bundler构建版本时,未明确定义编译时特性标志。以下是详细解释和解决方案: 问题原因: 该标志是Vue 3.4引入的编译时特性标志,用于控制生产环境下SSR水合不匹配错误的详细报告1使用esm-bundler…...

MCP和Function Calling
MCP MCP(Model Context Protocol,模型上下文协议) ,2024年11月底,由 Anthropic 推出的一种开放标准,旨在统一大模型与外部数据源和工具之间的通信协议。MCP 的主要目的在于解决当前 AI 模型因数据孤岛限制而…...

Unity-ECS详解
今天我们来了解Unity最先进的技术——ECS架构(EntityComponentSystem)。 Unity官方下有源码,我们下载源码后来学习。 ECS 与OOP(Object-Oriented Programming)对应,ECS是一种完全不同的编程范式与数据架构…...
关于疲劳分析的各种方法
疲劳寿命预测方法很多。按疲劳裂纹形成寿命预测的基本假定和控制参数,可分为名义应力法、局部应力一应变法、能量法、场强法等。 1名义应力法 名义应力法是以结构的名义应力为试验和寿命估算的基础,采用雨流法取出一个个相互独立、互不相关的应力循环&…...