app开发者平台在数字化时代的重要性与发展趋势解析
628
2022-11-27
php zookeeper实现分布式锁
目录
介绍
一、zookeeper和redis实现分布式锁的对比
二、zookeeper分布式锁原理
三、zookeeper实现分布式锁的方式
四、安装PHP的zookeeper扩展
1、安装Zookeeper C扩展支持
2、安装php的zookeeper扩展
五、PHP+zookeeper实现分布式锁示例
介绍
基础知识介绍:分布式锁的三种实现方式是什么?-常见问题-PHP中文网
一、zookeeper和redis实现分布式锁的对比
1、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版本,如果要使用的3.5+编译C扩展:PHP-Zookeeper扩展安装 - 简书
cd /usr/local/zk3.4/src/c#编译C扩展,由于zookeeper解压即可用,这里-源码主要是了编译C扩展,为后面安装PHP的扩展用./configure --prefix=/usr/local/zk3.4/makemake install
2、安装php的zookeeper扩展
zookeeper的PHP扩展可以在pecl上-,也可以在github上-,但有人说pecl上有bug。
git clone 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-configmake && make installvim php.iniextension=zookeeper/etc/php-fpm restart
五、PHP+zookeeper实现分布式锁示例
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);
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~