clue/redis-react

基于 ReactPHP 的异步 Redis 客户端实现。

v2.7.0 2024-01-05 15:54 UTC

This package is auto-updated.

Last update: 2024-09-08 13:08:29 UTC


README

CI status code coverage PHPStan level installs on Packagist

基于 ReactPHP 的异步 Redis 客户端实现。

开发版本:此分支包含即将发布的 3.0 版本的代码。要查看当前稳定版 2.x 的代码,请查看 2.x 分支

即将发布的 3.0 版本将是此包的发展方向。但是,我们仍将积极支持 2.x,以便尚未升级到最新版本的用户。有关更多信息,请参阅 安装说明

Redis 是一个开源的、高级的内存键值数据库。它提供了一组简单、原子的操作来处理其原始数据类型。它轻量级的设计和快速操作使其成为现代应用程序堆栈的理想选择。此库为您提供了一个简单的 API,可以在 PHP 中操作 Redis 数据库。它使您能够设置和查询其数据或使用其 Pub/Sub 主题来响应传入的事件。

  • 命令的异步执行 - 并行(自动管道)向 Redis 发送任意数量的命令,并在结果到达时立即处理它们的响应。基于 Promise 的设计提供了一个合理的异步响应接口。
  • 基于事件的核心 - 注册您的事件处理程序回调以响应传入的事件,例如传入的 PubSub 消息事件。
  • 轻量级、SOLID 设计 - 提供了一个足够好的抽象层,不会妨碍您。未来的或自定义命令和事件不需要进行更改即可得到支持。
  • 良好的测试覆盖率 - 配备了自动测试套件,并定期针对 Redis v2.6 及更高版本进行测试。

目录

支持我们

我们在开发、维护和更新我们的开源项目上投入了大量的时间。您可以通过 成为 GitHub 的赞助商 来帮助我们维持我们工作的这种高质量。赞助商将获得许多回报,有关详细信息,请参阅我们的 赞助页面

让我们共同将这些项目提升到下一个水平!🚀

快速入门示例

安装后,您可以使用以下代码连接到本地 Redis 服务器并发送一些请求

<?php

require __DIR__ . '/vendor/autoload.php';

$redis = new Clue\React\Redis\RedisClient('localhost:6379');

$redis->set('greeting', 'Hello world');
$redis->append('greeting', '!');

$redis->get('greeting')->then(function (string $greeting) {
    // Hello world!
    echo $greeting . PHP_EOL;
});

$redis->incr('invocation')->then(function (int $n) {
    echo 'This is invocation #' . $n . PHP_EOL;
});

请参阅 示例

用法

命令

最重要的是,该项目提供了一个可以用来调用所有Redis命令(如GETSET等)的RedisClient实例。

$redis = new Clue\React\Redis\RedisClient('localhost:6379');

$redis->get($key);
$redis->set($key, $value);
$redis->exists($key);
$redis->expire($key, $seconds);
$redis->mget($key1, $key2, $key3);

$redis->multi();
$redis->exec();

$redis->publish($channel, $payload);
$redis->subscribe($channel);

$redis->ping();
$redis->select($database);

// many more…

每个方法调用都对应相应的Redis命令。例如,$redis->get()方法将调用GET命令

所有Redis命令都通过魔法__call()方法作为公共方法自动可用。此处不列出所有可用命令,请参阅Redis命令参考

传递给方法调用的任何参数都将作为命令参数转发。例如,$redis->set('name', 'Alice')调用将执行相当于SET name Alice的命令。在适用的情况下(例如$redis->expire($key, 60))传递整数参数是安全的,但Redis内部要求所有参数始终转换为字符串值。

这些命令都支持异步操作,并返回一个最终在成功时履行结果或在错误时拒绝ExceptionPromise。有关更多详细信息,请参阅有关promises的下一节。

承诺

发送命令是异步的(非阻塞的),因此您实际上可以并行发送多个命令。Redis将针对每个命令请求返回一个响应消息,待处理的命令将自动排队。

发送命令使用基于Promise的接口,这使得在命令完成时(即成功履行或因错误而拒绝)做出反应变得容易。

$redis->get($key)->then(function (?string $value) {
    var_dump($value);
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

PubSub

此库通常用于通过Redis的Pub/Sub(发布/订阅)通道高效地传输消息。例如,这可以用于向更多订阅者分发单个消息(例如,为类似聊天的应用程序进行水平扩展)或作为分布式系统中的高效消息传输(微服务架构)。

可以使用PUBLISH命令将消息发送到当前订阅给定通道的所有客户端。

$channel = 'user';
$message = json_encode(['id' => 10]);
$redis->publish($channel, $message);

可以使用SUBSCRIBE命令订阅一个通道,然后接收传入的Pub/Sub message事件。

$channel = 'user';
$redis->subscribe($channel);

$redis->on('message', function (string $channel, string $payload) {
    // pubsub message received on given $channel
    var_dump($channel, json_decode($payload));
});

同样,您可以通过多次执行此命令简单地使用相同的客户端连接订阅多个通道。

$redis->subscribe('user.register');
$redis->subscribe('user.join');
$redis->subscribe('user.leave');

同样,可以使用PSUBSCRIBE命令订阅匹配给定模式的通道,然后接收所有传入的带有pmessage事件的Pub/Sub消息。

$pattern = 'user.*';
$redis->psubscribe($pattern);

$redis->on('pmessage', function (string $pattern, string $channel, string $payload) {
    // pubsub message received matching given $pattern
    var_dump($channel, json_decode($payload));
});

一旦您处于订阅状态,Redis就不再允许在同一客户端连接上执行任何其他命令。通常通过简单地创建第二个客户端连接并仅将一个客户端连接专门用于Pub/Sub订阅,另一个用于所有其他命令来解决这个问题。

可以使用UNSUBSCRIBE命令PUNSUBSCRIBE命令来取消订阅活动订阅,如果您不再对接收给定通道和模式订阅的任何进一步事件感兴趣。

$redis->subscribe('user');

Loop::addTimer(60.0, function () use ($redis) {
    $redis->unsubscribe('user');
});

同样,一旦您退订了最后一个频道和模式,客户端连接就不再处于已订阅状态,您就可以通过此客户端连接再次发出任何其他命令。

上述每种方法都遵循正常的请求-响应语义,并返回一个Promise以等待成功的订阅。请注意,虽然Redis允许每个这些命令有可变数量的参数,但这个库目前限制为每个这些方法只能有一个参数,以便与每个命令请求精确匹配一个响应。作为替代,这些方法可以简单地多次调用,每次提供一个参数。

此外,可以监听以下PubSub事件,以获取有关已订阅/退订的频道和模式的通知

$redis->on('subscribe', function (string $channel, int $total) {
    // subscribed to given $channel
});
$redis->on('psubscribe', function (string $pattern, int $total) {
    // subscribed to matching given $pattern
});
$redis->on('unsubscribe', function (string $channel, int $total) {
    // unsubscribed from given $channel
});
$redis->on('punsubscribe', function (string $pattern, int $total) {
    // unsubscribed from matching given $pattern
});

当底层连接丢失时,将自动调用unsubscribepunsubscribe事件。这使您能够根据需要重新订阅频道和模式。

API

RedisClient

RedisClient负责与Redis服务器交换消息,并跟踪挂起的命令。

$redis = new Clue\React\Redis\RedisClient('localhost:6379');

$redis->incr('hello');

除了定义几个方法外,此接口还实现了EventEmitterInterface,允许您像以下文档所述那样对某些事件做出反应。

内部,此类仅在首次在此实例上调用第一个请求时才创建到Redis的底层连接,并将所有挂起的请求排队,直到底层连接就绪。此底层连接将用于所有请求,直到关闭。默认情况下,空闲连接在不使用时将保持打开1毫秒(0.001秒)。下一个请求将重用现有连接,或者如果此空闲时间已过,将自动创建新的底层连接。

从消费者方面来说,这意味着您可以直接向数据库发送命令,同时底层连接可能仍然挂起。因为创建此底层连接可能需要一些时间,所以它会将所有挂起的命令排队,并确保一旦连接就绪,所有命令都将按正确顺序执行。

如果底层数据库连接失败,它将拒绝所有挂起的命令,并返回到初始的“空闲”状态。这意味着您可以在以后的时间发送额外的命令,这些命令将再次尝试打开新的底层连接。请注意,如果您使用的是持续时间超过空闲期的长时间打开的事务(MULTI/EXEC),这可能需要特别注意。

当使用PubSub频道(请参阅SUBSCRIBEPSUBSCRIBE命令)时,此客户端永远不会达到“空闲”状态,并将永远挂起(或者直到底层数据库连接丢失)。此外,如果底层数据库连接断开,它将自动为所有当前活跃的频道和模式订阅发送适当的unsubscribepunsubscribe事件。这允许您对这些事件做出反应,并通过创建新的底层连接并再次执行上述命令来恢复您的订阅。

请注意,将仅在首次调用第一个请求时创建底层连接。因此,任何潜在的连接问题都将在首次使用此实例时检测到。您可以使用end()方法确保连接将被软关闭,并且无法排队进一步命令。同样,在未连接时调用此实例上的end()将立即成功,无需等待实际的底层连接。

__construct()

可以使用new RedisClient(string $url, ConnectorInterface $connector = null, LoopInterface $loop = null)构造函数创建新的RedisClient实例。

$url可以采用标准形式[redis[s]://][:auth@]host[:port][/db]给出。如果您连接到默认端口6379,则可以省略URI方案和端口。

// both are equivalent due to defaults being applied
$redis = new Clue\React\Redis\RedisClient('localhost');
$redis = new Clue\React\Redis\RedisClient('redis://localhost:6379');

Redis支持基于密码的认证(AUTH命令)。请注意,Redis的认证机制不使用用户名,因此您可以将密码h@llo作为URL编码(百分编码)的一部分传递,如下所示

// all forms are equivalent
$redis = new Clue\React\Redis\RedisClient('redis://:h%40llo@localhost');
$redis = new Clue\React\Redis\RedisClient('redis://ignored:h%40llo@localhost');
$redis = new Clue\React\Redis\RedisClient('redis://localhost?password=h%40llo');

您可以可选地包含一个路径,该路径将被用于选择(SELECT命令)正确的数据库

// both forms are equivalent
$redis = new Clue\React\Redis\RedisClient('redis://localhost/2');
$redis = new Clue\React\Redis\RedisClient('redis://localhost?db=2');

如果您在Redis前面使用安全的TLS代理,可以使用标准rediss:// URI方案

$redis = new Clue\React\Redis\RedisClient('rediss://redis.example.com:6340');

如果您的Redis实例正在监听Unix域套接字(UDS)路径,可以使用redis+unix:// URI方案

$redis = new Clue\React\Redis\RedisClient('redis+unix:///tmp/redis.sock');

// the URI MAY contain `password` and `db` query parameters as seen above
$redis = new Clue\React\Redis\RedisClient('redis+unix:///tmp/redis.sock?password=secret&db=2');

// the URI MAY contain authentication details as userinfo as seen above
// should be used with care, also note that database can not be passed as path
$redis = new Clue\React\Redis\RedisClient('redis+unix://:secret@/tmp/redis.sock');

此方法将尊重PHP的default_socket_timeout设置(默认60秒)作为建立底层连接和等待成功认证的超时时间。您可以显式传递自定义的超时时间(秒),或者使用负数来不应用超时,如下所示

$redis = new Clue\React\Redis\RedisClient('localhost?timeout=0.5');

默认情况下,空闲连接在不使用时会保持开启1毫秒(0.001秒)。下一个请求将重用现有的连接,或者如果此空闲时间已过期,将自动创建新的底层连接。这确保您始终获得一个“新鲜”的连接,因此不应将其与“保持连接”或“心跳”机制混淆,因为此机制不会主动尝试探测连接。您可以显式传递自定义的空闲超时时间(秒),或者使用负数来不应用超时,如下所示

$redis = new Clue\React\Redis\RedisClient('localhost?idle=10.0');

如果您需要自定义DNS、代理或TLS设置,可以显式传递自定义的ConnectorInterface实例

$connector = new React\Socket\Connector([
    'dns' => '127.0.0.1',
    'tcp' => [
        'bindto' => '192.168.10.1:0'
    ],
    'tls' => [
        'verify_peer' => false,
        'verify_peer_name' => false
    ]
]);

$redis = new Clue\React\Redis\RedisClient('localhost', $connector);

__call()

可以使用__call(string $name, string[] $args): PromiseInterface<mixed>方法来调用给定的命令。

这是一个魔术方法,将在调用此实例上的任何Redis命令时被调用。每个方法调用都与相应的Redis命令相匹配。例如,$redis->get()方法将调用GET命令

$redis->get($key)->then(function (?string $value) {
    var_dump($value);
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

所有Redis命令都自动作为公共方法通过此魔术__call()方法提供。在此不列出所有可用命令,请参阅Redis命令参考

传递给方法调用的任何参数都将作为命令参数转发。例如,$redis->set('name', 'Alice')调用将执行相当于SET name Alice的命令。在适用的情况下(例如$redis->expire($key, 60))传递整数参数是安全的,但Redis内部要求所有参数始终转换为字符串值。

这些命令都支持异步操作,并返回一个Promise,该Promise最终在成功时解析结果,或在错误时拒绝并抛出Exception。有关更多信息,请参阅promises

end()

可以使用end():void方法在所有挂起的命令完成后软关闭Redis连接。

close()

可以使用close():void方法强制关闭Redis连接并拒绝所有挂起的命令。

错误事件

当发生致命错误时,例如客户端连接丢失或无效时,将触发error事件。事件接收一个用于错误实例的单一Exception参数。

$redis->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

此事件仅针对致命错误触发,并随后关闭客户端连接。它不应与由无效命令引起的“软”错误混淆。

关闭事件

一旦客户端连接关闭(终止),将触发close事件。

$redis->on('close', function () {
    echo 'Connection closed' . PHP_EOL;
});

另请参阅close()方法。

安装

安装此库的推荐方法是通过ComposerComposer新手?

一旦发布,此项目将遵循SemVer。目前,这将安装最新开发版本

composer require clue/redis-react:^3@dev

请参阅CHANGELOG了解版本升级的详细信息。

本项目的目标是能够在任何平台上运行,因此不需要任何PHP扩展,并支持从PHP 7.1到当前PHP 8+的运行。强烈建议使用本项目所支持的最新PHP版本。

我们致力于提供长期支持(LTS)选项,并提供平滑的升级路径。您可以同时针对多个版本进行目标设定,以支持更广泛的PHP版本,如下所示

composer require "clue/redis-react:^3@dev || ^2"

测试

要运行测试套件,首先需要克隆此仓库,然后通过Composer安装所有依赖项(Composer)

composer install

要运行测试套件,请转到项目根目录并运行

vendor/bin/phpunit

测试套件已设置为始终确保所有支持环境下的100%代码覆盖率。如果您已安装Xdebug扩展,您还可以本地生成代码覆盖率报告,如下所示

XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text

测试套件包含单元测试和功能集成测试。功能测试需要访问正在运行的Redis服务器实例,默认情况下将被跳过。

如果您无法访问正在运行的Redis服务器,您还可以使用临时的Redis Docker镜像

docker run --net=host redis

要运行功能测试,您需要提供您的登录详细信息,如下所示的环境变量中

REDIS_URI=localhost:6379 vendor/bin/phpunit

此外,我们使用PHPStan在最大级别上确保项目中的类型安全

vendor/bin/phpstan

许可

本项目采用宽松的MIT许可证发布。

您知道吗?我可以提供定制开发服务,并开具为版本发布和贡献提供的赞助发票。请与我联系(@clue)获取详细信息。