sadistdimaz/redis-react

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

v2.6.1 2022-08-12 08:34 UTC

This package is auto-updated.

Last update: 2024-09-12 13:12:03 UTC


README

CI status installs on Packagist

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

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

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

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

  • 命令的异步执行 - 并行(自动管道)向 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内部要求所有参数始终转换为字符串值。

这些命令都支持异步操作,并返回一个最终在成功时实现结果或错误时拒绝Promise。有关更多信息,请参阅以下关于Promise的部分。

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命令订阅一个通道,然后接收传入的PubSub 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事件的PubSub消息。

$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就不再允许在同一客户端连接上执行任何其他命令。这通常通过简单地创建第二个客户端连接,并将一个客户端连接专门用于PubSub订阅,另一个用于所有其他命令来解决。

可以使用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://:6379');

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

// 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://?password=h%40llo');

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

// both forms are equivalent
$redis = new Clue\React\Redis\RedisClient('redis:///2');
$redis = new Clue\React\Redis\RedisClient('redis://?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);

此类接受一个可选的LoopInterface|null $loop参数,可以用于传递要为此对象使用的循环实例。您可以使用null值来使用默认循环。除非您确定要显式使用给定的循环实例,否则不应提供此值。

__call()

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

这是一个在调用此实例上的任何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内部要求所有参数始终转换为字符串值。

每个命令都支持异步操作,并返回一个最终以其结果成功解决或以Exception在错误时拒绝的Promise。有关更多详细信息,请参阅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()方法。

安装

推荐通过Composer安装此库。初识Composer?

发布后,此项目将遵循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

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

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

$ docker run --net=host redis

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

$ REDIS_URI=localhost:6379 vendor/bin/phpunit

许可

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

您知道吗?我提供定制开发服务,并为发行版赞助和贡献开具发票。如需详情,请联系我(@clue)。