diff --git a/README.md b/README.md index 3cc1086..5ecc7a2 100644 --- a/README.md +++ b/README.md @@ -42,4 +42,19 @@ $client->get("key"); $client->delete("key"); $client->putIf("key", "newValue", "expectedPreviousValue"); $client->deleteIf("key", "expectedPreviousValue"); -``` \ No newline at end of file +``` + +#### Sharded client +```php +put("key", "value"); +$shardedClient->get("key"); +``` diff --git a/composer.json b/composer.json index f472463..d09e921 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "require": { "google/protobuf": "v3.6.1.3", "grpc/grpc": "^1.15", - "ext-grpc": "*" + "ext-grpc": "*", + "flexihash/flexihash": "^2.0" }, "license": "MIT", "authors": [ diff --git a/composer.lock b/composer.lock index 7593a08..092b032 100644 --- a/composer.lock +++ b/composer.lock @@ -1,11 +1,64 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "586422d6b254a6c3792893fcca276003", + "content-hash": "59354f39eb34f0a17d47d0e634e6c3ee", "packages": [ + { + "name": "flexihash/flexihash", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/pda/flexihash.git", + "reference": "497ba5782606d998f8ab0b4e5942e3a799bec018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pda/flexihash/zipball/497ba5782606d998f8ab0b4e5942e3a799bec018", + "reference": "497ba5782606d998f8ab0b4e5942e3a799bec018", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "satooshi/php-coveralls": "~1.0", + "squizlabs/php_codesniffer": "^2.3", + "symfony/config": "^2.0.0", + "symfony/console": "^2.0.0", + "symfony/filesystem": "^2.0.0", + "symfony/stopwatch": "^2.0.0", + "symfony/yaml": "^2.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Flexihash\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Annesley", + "email": "paul@annesley.cc", + "homepage": "http://paul.annesley.cc" + }, + { + "name": "Dom Morgan", + "email": "dom@d3r.com", + "homepage": "https://d3r.com" + } + ], + "description": "Flexihash is a small PHP library which implements consistent hashing", + "homepage": "https://github.com/pda/flexihash", + "time": "2016-04-22T21:03:23+00:00" + }, { "name": "google/protobuf", "version": "v3.6.1.3", diff --git a/src/Client.php b/src/Client.php index 6870176..43ca553 100644 --- a/src/Client.php +++ b/src/Client.php @@ -28,7 +28,7 @@ * * @author Matthias Neid */ -class Client +class Client implements ClientInterface { /** * @var string @@ -72,6 +72,11 @@ public function __construct($hostname = "localhost:2379", $username = false, $pa $this->password = $password; } + public function getHostname(): string + { + return $this->hostname; + } + /** * Put a value into the key store * diff --git a/src/ClientInterface.php b/src/ClientInterface.php new file mode 100644 index 0000000..db74f85 --- /dev/null +++ b/src/ClientInterface.php @@ -0,0 +1,70 @@ +clients[$client->getHostname()] = $client; + } + } + + /** + * Get the correct client object for that key through consistent hashing + * + * @param string $key + * @return ClientInterface + * @throws \Flexihash\Exception + */ + protected function getClientFromKey(string $key): ClientInterface + { + if (isset($this->keyCache[$key])) { + return $this->keyCache[$key]; + } + + if ($this->hash === null) { + $this->hash = new Flexihash(); + foreach ($this->clients as $client) { + $this->hash->addTarget($client->getHostname()); + } + } + + $clientHostname = $this->hash->lookup($key); + $this->keyCache[$key] = $this->clients[$clientHostname]; + return $this->keyCache[$key]; + } + + + public function getHostname(): string + { + return implode("-", array_keys($this->clients)); + } + + /** + * @inheritDoc + * @throws \Flexihash\Exception + */ + public function put(string $key, $value, bool $prevKv = false, int $lease = 0, bool $ignoreLease = false, bool $ignoreValue = false) + { + return $this->getClientFromKey($key)->put($key, $value, $prevKv, $lease, $ignoreLease, $ignoreValue); + } + + /** + * @inheritDoc + * @throws \Flexihash\Exception + */ + public function get(string $key) + { + return $this->getClientFromKey($key)->get($key); + } + + /** + * @inheritDoc + * @throws \Flexihash\Exception + */ + public function delete(string $key) + { + return $this->getClientFromKey($key)->delete($key); + } + + /** + * @inheritDoc + * @throws \Flexihash\Exception + */ + public function putIf(string $key, $value, $previousValue, bool $returnNewValueOnFail = false) + { + return $this->getClientFromKey($key)->putIf($key, $value, $previousValue, $returnNewValueOnFail); + } + + /** + * @inheritDoc + * @throws \Flexihash\Exception + */ + public function deleteIf(string $key, $previousValue, bool $returnNewValueOnFail = false) + { + return $this->getClientFromKey($key)->deleteIf($key, $previousValue, $returnNewValueOnFail); + } +} \ No newline at end of file