wpjscc / redis-react
基于 ReactPHP 的异步 Redis 客户端实现。
Requires
- php: >=7.1
- clue/redis-protocol: 0.3.*
- evenement/evenement: ^3.0 || ^2.0 || ^1.0
- react/event-loop: ^1.2
- react/promise: ^3
- react/promise-timer: ^1.10
- react/socket: ^1.15
Requires (Dev)
- phpstan/phpstan: 1.10.15 || 1.4.10
- phpunit/phpunit: ^9.6 || ^7.5
- react/async: ^4.2 || ^3.2
README
基于 ReactPHP 的异步 Redis 客户端实现。
开发版本:此分支包含即将发布的 3.0 版本的代码。对于当前稳定版 2.x 的代码,请查看
2.x
分支。即将发布的 3.0 版本将是此包的未来发展方向。但是,我们仍将积极支持 2.x 版本,以便那些尚未使用最新版本的用户。有关更多详细信息,请参阅 安装说明。
Redis 是一个开源、高级的内存键值数据库。它提供了一套简单的原子操作,用于处理其基本数据类型。其轻量级设计和快速操作使其成为现代应用程序堆栈的理想选择。此库为您提供了从 PHP 内部与 Redis 数据库交互的简单 API。它使您能够设置和查询其数据或使用其 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; });
请参阅 示例。
用法
命令
最重要的是,此项目提供了一个 RedisClient
实例,可以用来调用所有 Redis 命令(例如 GET
、SET
等)。
$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内部要求所有参数始终转换为字符串值。
这些命令都支持异步操作,并返回一个最终以它的结果成功履行或以Exception
错误拒绝的Promise。有关更多详细信息,请参阅有关promises的下一节。
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 });
当底层连接丢失时,将自动调用unsubscribe
和punsubscribe
事件。这使您可以控制在适当的时候重新订阅频道和模式。
API
RedisClient
RedisClient
负责与Redis服务器交换消息并跟踪挂起的命令。
$redis = new Clue\React\Redis\RedisClient('localhost:6379'); $redis->incr('hello');
除了定义几个方法外,此接口还实现了EventEmitterInterface
,允许您按以下文档所述响应某些事件。
内部,此类仅在首次在实例上调用第一个请求时创建到Redis的底层连接,并将所有挂起的请求排队,直到底层连接就绪。此底层连接将用于所有请求,直到它关闭。默认情况下,空闲连接在未使用时将保持开启1毫秒(0.001秒)。下一个请求将重用现有的连接,如果此空闲时间已过期,将自动创建新的底层连接。
对于消费者来说,这意味着您可以直接开始向数据库发送命令,而底层连接可能仍在挂起。因为创建此底层连接可能需要一些时间,所以它将挂起所有挂起的命令,并确保一旦连接就绪,所有命令都将按正确顺序执行。
如果底层数据库连接失败,将拒绝所有挂起的命令,并返回到初始的“空闲”状态。这意味着您可以在稍后时间继续发送额外的命令,这将再次尝试打开新的底层连接。请注意,如果您使用的是持续时间超过空闲期的长事务(MULTI
/EXEC
),则可能需要特别小心。
在使用PubSub频道(请参阅SUBSCRIBE
和PSUBSCRIBE
命令)时,此客户端永远不会达到“空闲”状态,并将永远挂起(或直到底层数据库连接丢失)。此外,如果底层数据库连接断开,它将自动为所有当前活动的频道和模式订阅发送适当的unsubscribe
和punsubscribe
事件。这允许您响应这些事件并通过创建新的底层连接并重复上述命令来恢复您的订阅。
请注意,底层连接将在首次调用请求时被延迟创建。相应地,任何潜在连接问题都将在第一次使用此实例时被发现。您可以使用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编码(百分编码)的一部分作为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://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);
该类接受一个可选的LoopInterface|null $loop
参数,可以用来传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确定想要显式使用给定的事件循环实例,否则不应提供此值。
__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(在成功时)或拒绝带有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()
方法。
安装
推荐安装此库的方式是通过Composer。您是Composer的新手吗?了解Composer?
一旦发布,该项目将遵循SemVer。目前,这将安装最新开发版本。
composer require clue/redis-react:^3@dev
有关版本升级的详细信息,请参阅变更日志。
该项目旨在在任何平台上运行,因此不需要任何PHP扩展,并支持在PHP 7.1到当前PHP 8+上运行。强烈建议使用此项目支持的最新PHP版本。
我们致力于提供长期支持(LTS)选项,并提供平滑的升级路径。您可以同时针对多个版本进行目标定位,以支持更广泛的PHP版本,如下所示。
composer require "clue/redis-react:^3@dev || ^2"
测试
要运行测试套件,您首先需要克隆此仓库,然后通过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)。