This commit is contained in:
2021-09-04 01:55:34 +03:00
committed by GitHub
parent e9dce19e25
commit ca2c2ee633
9 changed files with 344 additions and 15 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
NSQ_VERSION=1.2.1

View File

@ -31,7 +31,7 @@ Features
- [x] PUB
- [x] SUB
- [X] Feature Negotiation
- [ ] Discovery
- [X] Discovery
- [ ] Backoff
- [X] TLS
- [ ] Deflate
@ -80,6 +80,27 @@ $consumer = Consumer::create(
);
```
### Lookup
```php
use Nsq\Lookup;
use Nsq\Message;
$lookup = new Lookup('http://nsqlookupd0:4161');
$lookup = new Lookup(['http://nsqlookupd0:4161', 'http://nsqlookupd1:4161', 'http://nsqlookupd2:4161']);
$callable = static function (Message $message): Generator {
yield $message->touch(); // Reset the timeout for an in-flight message
yield $message->requeue(timeout: 5000); // Re-queue a message (indicate failure to process)
yield $message->finish(); // Finish a message (indicate successful processing)
};
$lookup->subscribe(topic: 'topic', channel: 'channel', onMessage: $callable);
$lookup->subscribe(topic: 'anotherTopic', channel: 'channel', onMessage: $callable);
$lookup->run();
```
### Integrations
- [Symfony](https://github.com/nsqphp/NsqBundle)

View File

@ -13,6 +13,7 @@
"require": {
"php": "^8.0.1",
"ext-json": "*",
"amphp/http-client": "^4.6",
"amphp/socket": "^1.1",
"composer/semver": "^3.2",
"phpinnacle/buffer": "^1.2",

View File

@ -1,24 +1,82 @@
version: '3.7'
services:
nsqd:
image: nsqio/nsq:v1.2.0
nsqd0:
image: nsqio/nsq:v${NSQ_VERSION}
labels:
ru.grachevko.dhu: 'nsqd'
command: /nsqd -log-level debug
# command: /nsqd
ports:
- 4150:4150
- 4151:4151
ru.grachevko.dhu: 'nsqd0'
command: >-
nsqd
--log-level debug
--lookupd-tcp-address nsqlookupd0:4160
--lookupd-tcp-address nsqlookupd1:4160
--lookupd-tcp-address nsqlookupd2:4160
--broadcast-address nsqd0
nsqd1:
image: nsqio/nsq:v${NSQ_VERSION}
labels:
ru.grachevko.dhu: 'nsqd1'
command: >-
nsqd
--log-level debug
--lookupd-tcp-address nsqlookupd0:4160
--lookupd-tcp-address nsqlookupd1:4160
--lookupd-tcp-address nsqlookupd2:4160
--broadcast-address nsqd1
nsqd2:
image: nsqio/nsq:v${NSQ_VERSION}
labels:
ru.grachevko.dhu: 'nsqd2'
command: >-
nsqd
--log-level debug
--lookupd-tcp-address nsqlookupd0:4160
--lookupd-tcp-address nsqlookupd1:4160
--lookupd-tcp-address nsqlookupd2:4160
--broadcast-address nsqd2
nsqlookupd0:
image: nsqio/nsq:v${NSQ_VERSION}
labels:
ru.grachevko.dhu: 'nsqlookupd0'
command: /nsqlookupd -log-level debug
nsqlookupd1:
image: nsqio/nsq:v${NSQ_VERSION}
labels:
ru.grachevko.dhu: 'nsqlookupd1'
command: /nsqlookupd -log-level debug
nsqlookupd2:
image: nsqio/nsq:v${NSQ_VERSION}
labels:
ru.grachevko.dhu: 'nsqlookupd2'
command: /nsqlookupd -log-level debug
nsqadmin:
image: nsqio/nsq:v1.2.0
image: nsqio/nsq:v${NSQ_VERSION}
labels:
ru.grachevko.dhu: 'nsqadmin'
command: /nsqadmin --nsqd-http-address=nsqd:4151 --http-address=0.0.0.0:4171
ports:
- 4171:4171
command:
- nsqadmin
- --http-address=0.0.0.0:4171
- --lookupd-http-address=nsqlookupd0:4161
- --lookupd-http-address=nsqlookupd1:4161
- --lookupd-http-address=nsqlookupd2:4161
depends_on:
- nsqlookupd0
- nsqlookupd1
- nsqlookupd2
tail:
image: nsqio/nsq:v1.2.0
command: nsq_tail -channel nsq_tail -topic local -nsqd-tcp-address nsqd:4150
image: nsqio/nsq:v${NSQ_VERSION}
command: >-
nsq_tail
--channel nsq_tail
--topic local
--lookupd-http-address nsqlookupd1:4161
depends_on:
- nsqd1
- nsqlookupd1

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Nsq\Config;
final class LookupConfig
{
public function __construct(
public int $pollingInterval = 10000,
) {
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Nsq\Exception;
final class LookupException extends NsqException
{
}

162
src/Lookup.php Normal file
View File

@ -0,0 +1,162 @@
<?php
declare(strict_types=1);
namespace Nsq;
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
use Amp\Loop;
use Nsq\Config\ClientConfig;
use Nsq\Config\LookupConfig;
use Nsq\Exception\LookupException;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use function Amp\call;
final class Lookup
{
private array $addresses;
private array $subscriptions = [];
private array $consumers = [];
private LookupConfig $config;
private LoggerInterface $logger;
private ?string $watcherId = null;
public function __construct(
string | array $address,
LookupConfig $config = null,
LoggerInterface $logger = null,
) {
$this->addresses = (array) $address;
$this->config = $config ?? new LookupConfig();
$this->logger = $logger ?? new NullLogger();
}
public function run(): void
{
if (null !== $this->watcherId) {
return;
}
$client = HttpClientBuilder::buildDefault();
$logger = $this->logger;
$requestHandler = static function (string $uri) use ($client, $logger): \Generator {
/** @var Response $response */
$response = yield $client->request(new Request($uri));
$buffer = yield $response->getBody()->buffer();
try {
return Lookup\Response::fromJson($buffer);
} catch (LookupException $e) {
$logger->warning($e->getMessage());
return null;
}
};
$callback = function () use ($requestHandler): \Generator {
foreach ($this->addresses as $address) {
foreach ($this->subscriptions as $key => $subscription) {
[$topic, $channel] = \explode(':', $key);
$promise = call($requestHandler, $address.'/lookup?topic='.$topic);
$promise->onResolve(
function (?\Throwable $e, ?Lookup\Response $response) use (
$key,
$subscription,
$topic,
$channel
) {
if (null !== $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return;
}
if (null === $response) {
return;
}
foreach ($response->producers as $producer) {
$address = sprintf('%s:%s', $producer->broadcastAddress, $producer->tcpPort);
$consumerKey = $key.$address;
if (\array_key_exists($consumerKey, $this->consumers)) {
continue;
}
$this->logger->info('Consumer created.', \compact('address', 'topic', 'channel'));
yield ($this->consumers[$consumerKey] = new Consumer(
$address,
$topic,
$channel,
$subscription['callable'],
$subscription['config'],
$this->logger,
))->connect();
}
},
);
yield $promise;
}
}
};
Loop::defer($callback);
$this->watcherId = Loop::repeat($this->config->pollingInterval, $callback);
}
public function stop(): void
{
if (null === $this->watcherId) {
return;
}
$this->logger->info('Lookup stopped, cancel watcher.');
Loop::cancel($this->watcherId);
$this->watcherId = null;
foreach ($this->consumers as $key => $consumer) {
$consumer->close();
unset($this->consumers[$key]);
}
}
public function subscribe(string $topic, string $channel, callable $onMessage, ClientConfig $config = null): void
{
$key = $topic.':'.$channel;
if (\array_key_exists($key, $this->subscriptions)) {
throw new \InvalidArgumentException('Subscription already exists.');
}
$this->subscriptions[$key] = [
'callable' => $onMessage,
'config' => $config,
];
$this->logger->info('Subscribed', \compact('topic', 'channel'));
}
public function unsubscribe(string $topic, string $channel): void
{
$key = $topic.':'.$channel;
unset($this->subscriptions[$key]);
$this->logger->info('Unsubscribed', \compact('topic', 'channel'));
}
}

30
src/Lookup/Producer.php Normal file
View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Nsq\Lookup;
final class Producer
{
public function __construct(
public string $broadcastAddress,
public string $hostname,
public string $remoteAddress,
public int $tcpPort,
public int $httpPort,
public string $version,
) {
}
public static function fromArray(array $array): self
{
return new self(
$array['broadcast_address'],
$array['hostname'],
$array['remote_address'],
$array['tcp_port'],
$array['http_port'],
$array['version'],
);
}
}

34
src/Lookup/Response.php Normal file
View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Nsq\Lookup;
use Nsq\Exception\LookupException;
final class Response
{
/**
* @param string[] $channels
* @param Producer[] $producers
*/
public function __construct(
public array $channels,
public array $producers,
) {
}
public static function fromJson(string $json): self
{
$array = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
if (\array_key_exists('message', $array)) {
throw new LookupException($array['message']);
}
return new self(
$array['channels'],
array_map([Producer::class, 'fromArray'], $array['producers']),
);
}
}