本文共 5302 字,大约阅读时间需要 17 分钟。
Zookeeper作为一款开源的分布式协调服务,能够为我们提供强一致性的数据管理能力。分布式锁可以分为两类:一种是保持独占性锁,另一种是控制时序锁。
这种类型的锁确保在所有客户端中,最终只有一个客户端能够成功获取锁。实现方法是将Zookeeper的一个节点视为锁。所有客户端尝试在/distribute_lock节点下创建临时子节点,最终成功创建的客户端即为锁的持有者。
这种类型的锁确保所有客户端都能按照一定的顺序获取锁。实现方法是将/distribute_lock节点作为父节点,客户端在其下创建临时顺序节点。Zookeeper会根据顺序保证子节点的唯一性,从而形成全局时序。
所有客户端尝试在/distribute_lock/lock节点下创建临时子节点。Zookeeper保证在所有客户端中,只有一个客户端能够成功创建节点。成功创建节点的客户端即为锁的持有者。未成功创建的客户端需要注册监听器,实时监控/distribute_lock/lock节点的状态变化。这种方法虽然简单,但容易出现“羊群效应”,影响性能。
这种方法更为合理。每个试图加锁的客户端都创建一个临时顺序节点。Zookeeper保证节点的唯一性和顺序性。客户端需要获取所有子节点,按顺序排列,判断最小节点是否为自己。如果不是,则获取上一个节点并注册监听器,等待通知。
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; } } private 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; } private static function createSubPath($path, $value) { $aclArray = [ ['perms' => Zookeeper::PERM_ALL, 'scheme' => 'world', 'id' => 'anyone'], ]; self::$myNode = self::$zk->create($path, $value, $aclArray, Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE); if (false == self::$myNode) { throw new \Exception('create -s -e ' . $path . ' fail'); } return true; } private static function getLock() { if (self::checkMyNodeOrBefore()) { return true; } else { self::$isNotifyed = false; $result = self::$zk->get(self::$myNode, [zkCli::class, 'watcher']); while (!$result) { $res1 = self::checkMyNodeOrBefore(); if ($res1 === true) { return true; } else { $result = self::$zk->get($res1, [zkCli::class, 'watcher']); } } while (!self::$isNotifyed) { echo '.'; usleep(500000); } return true; } } private static function checkMyNodeOrBefore() { $list = self::$zk->getChildren(self::$root); sort($list); array_walk($list, function($val) use ($root) { $val = $root . '/' . $val; }); if ($list[0] == self::$myNode) { echo 'get lock node ' . self::$myNode . '....'; return true; } else { $index = array_search(self::$myNode, $list); $before = $list[$index - 1]; echo 'before node ' . $before . '.........'; return $before; } } public static function watcher($type, $state, $key) { echo $key . ' notifyed ....'; self::$isNotifyed = true; self::getLock(); }} 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'; } else { echo 'get lock fail'; return; } try { doSomething(); } catch (\Exception $e) { echo $e->getMessage() . PHP_EOL; } finally { $re = zkCli::releaseDistributedLock(); if ($re) { echo 'release lock success'; } else { echo 'release lock fail'; } 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); break; }}// 执行 zkLock(0);zkLock(0); 通过以上实现,我们可以利用Zookeeper的强一致性特性,实现分布式锁。两种方法都有其优缺点,方法二虽然麻烦一些,但更为合理。代码实现中,Zookeeper的临时顺序节点机制被充分利用,确保了锁的释放自动化,减少了“羊群效应”。
转载地址:http://dgvfk.baihongyu.com/