predis:灵活且功能完善的 Redis/Valkey PHP 客户端

A flexible and feature-complete Redis/Valkey client for PHP.

分支12Tags75
文件最后提交记录最后更新时间
25 天前
1 年前
5 个月前
21 天前
26 天前
1 年前
1 年前
1 年前
1 年前
3 年前
6 个月前
21 天前
1 年前
1 年前
26 天前
21 天前
3 年前
6 个月前
3 年前
8 个月前
1 年前
6 个月前

Predis

软件许可证 最新稳定版 最新开发版 月安装量 构建状态 覆盖率状态

一个适用于 PHP 7.2 及更高版本的灵活且功能完备的 Redis / Valkey 客户端。

关于此项目的更多详细信息,请参见 常见问题

主要特性

  • 支持 Redis 3.08.0 版本。
  • 支持使用客户端分片和可插拔键空间分发器进行集群。
  • 支持 redis-cluster(Redis >= 3.0)。
  • 支持主从复制设置和 redis-sentinel
  • 使用可自定义的前缀策略对键进行透明的键前缀处理。
  • 支持在单个节点和集群(仅客户端分片)上进行命令流水线操作。
  • 对 Redis 事务(Redis >= 2.0)和 CAS 操作(Redis >= 2.2)的抽象。
  • 对 Lua 脚本(Redis >= 2.6)的抽象,并自动在 EVALSHAEVAL 之间切换。
  • 对 Hinted Hash Templates(HIMPORT,Redis >= 8.10)的抽象,支持自动的每连接字段集重放。
  • 基于 PHP 迭代器对 SCANSSCANZSCANHSCAN(Redis >= 2.8)的抽象。
  • 客户端在首次执行命令时延迟建立连接,并且连接可以保持持久。
  • 可通过 TCP/IP(也支持 TLS/SSL 加密)或 UNIX 域套接字建立连接。
  • 支持自定义连接类,以提供不同的网络或协议后端。
  • 灵活的系统,用于定义自定义命令和覆盖默认命令。

如何 安装 和使用 Predis

此库可在 Packagist 上找到,以便使用 Composer 更轻松地管理项目依赖。每个版本的压缩归档文件可在 GitHub 上获取

composer require predis/predis

加载库

Predis 依赖 PHP 的自动加载功能在需要时加载其文件,并符合 PSR-4 标准。当通过 Composer 管理依赖时,自动加载会自动处理,但在缺乏任何自动加载机制的项目或脚本中,也可以利用其自带的自动加载器:

// Prepend a base path if Predis is not available in your "include_path".
require 'Predis/Autoloader.php';

Predis\Autoloader::register();

连接到 Redis

在创建客户端实例时,如果不传递任何连接参数,Predis 会将 127.0.0.16379 分别作为默认的主机和端口。connect() 操作的默认超时时间为 5 秒:

$client = new Predis\Client();
$client->set('foo', 'bar');
$value = $client->get('foo');

连接参数可以以 URI 字符串或命名数组的形式提供。后者是提供参数的首选方式,但当需要从非结构化或部分结构化的来源读取参数时,URI 字符串会非常有用:

// Parameters passed using a named array:
$client = new Predis\Client([
    'scheme' => 'tcp',
    'host'   => '10.0.0.1',
    'port'   => 6379,
]);

// Same set of parameters, passed using an URI string:
$client = new Predis\Client('tcp://10.0.0.1:6379');

可以通过在参数集中添加 password 来访问受密码保护的服务器。当 Redis >= 6.0 启用 ACL 时,用户认证需要同时提供 usernamepassword

也可以使用 UNIX 域套接字连接到本地 Redis 实例,此时参数必须使用 unix 方案并指定套接字文件的路径:

$client = new Predis\Client(['scheme' => 'unix', 'path' => '/path/to/redis.sock']);
$client = new Predis\Client('unix:/path/to/redis.sock');

该客户端可利用 TLS/SSL 加密连接到受保护的远程 Redis 实例,无需配置 stunnel 等 SSL 代理。这在连接运行于各种云托管服务提供商的节点时非常有用。可通过使用 tls 协议并经由 ssl 参数传递包含适当选项的数组来启用加密:

// Named array of connection parameters:
$client = new Predis\Client([
  'scheme' => 'tls',
  'ssl'    => ['cafile' => 'private.pem', 'verify_peer' => true],
]);

// Same set of parameters, but using an URI string:
$client = new Predis\Client('tls://127.0.0.1?ssl[cafile]=private.pem&ssl[verify_peer]=1');

连接方案 redistcp 的别名)和 redisstls 的别名)也受支持,不同之处在于包含这些方案的 URI 字符串会按照其各自 IANA 临时注册文档中描述的规则进行解析。

从 Redis 8.6 开始,您可以使用 TLS 客户端证书(mTLS)中的 Subject CN 对客户端进行身份验证。当服务器上启用此功能时,客户端会在 TLS 握手期间进行身份验证,因此无需发送 AUTH 命令。

要使用此功能,请配置:

  • 用于验证服务器证书的 CA 证书(cafile),
  • 由 Redis 服务器信任的 CA 签名的客户端证书(local_cert),用于客户端身份验证,
  • 相应的私钥(local_pk)。

确保:

  • Redis 服务器证书由客户端信任的 CA 签名,并且
  • 客户端证书由 Redis 服务器信任的 CA 签名(mTLS)。
// Named array of connection parameters:
$client = new Predis\Client([
    'scheme' => 'tls',
    'ssl' => [
        'cafile'      => 'ca.pem',          // CA used to verify the server certificate
        'local_cert'  => 'client.crt',      // client certificate (Subject CN maps to ACL user)
        'local_pk'    => 'client.key',      // client private key
        'verify_peer' => true,
    ],
]);

// ACL user must exist and match the certificate Subject CN (example: CN=CN_NAME).
// Enable the user and grant permissions as needed:
$client->acl->setUser('CN_NAME', 'on', '>clientpass', 'allcommands', 'allkeys')

echo $client->acl->whoami() // CN_NAME

支持的连接参数实际列表可能因每个连接后端而异,因此建议参考它们的特定文档或实现以获取详细信息。

当提供连接参数数组以及适当的选项来指示客户端如何聚合这些参数(集群、复制或自定义聚合逻辑)时,Predis 可以聚合多个连接。提供每个节点的配置时,可以混合使用命名数组和 URI 字符串:

$client = new Predis\Client([
    'tcp://10.0.0.1?alias=first-node', ['host' => '10.0.0.2', 'alias' => 'second-node'],
], [
    'cluster' => 'predis',
]);

有关更多详细信息,请参阅本文档的聚合连接部分。

与 Redis 的连接是惰性的,这意味着客户端仅在需要时才会连接到服务器。虽然建议让客户端在底层自行处理连接,但有时可能仍希望控制连接的打开或关闭时机:这可以通过调用 $client->connect()$client->disconnect() 轻松实现。请注意,这些方法对聚合连接的影响可能因具体实现而异。

持久连接

为了提高应用程序的性能,您可以将客户端设置为使用持久 TCP 连接,这样客户端可以节省创建套接字和连接握手的时间。默认情况下,连接在首次执行命令时创建,并会在进程终止前由 GC 自动关闭。但是,如果您的应用程序由 PHP-FPM 支持,进程处于空闲状态,您可以将其设置为持久连接,以便在同一进程内的多个脚本执行之间重用。

要启用持久连接模式,您应提供以下配置:

// Standalone
$client = new Predis\Client(['persistent' => true]);

// Cluster
$client = new Predis\Client(
    ['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
    ['cluster' => 'redis', 'parameters' => ['persistent' => true]]
);

重要提示

如果您在同一应用程序中操作多个客户端,且这些客户端与同一资源通信,默认情况下它们将共享同一个套接字(这是持久套接字的默认行为)。因此,在这种情况下,您需要为每个客户端额外提供一个 conn_uid 标识符,这样每个客户端将创建自己的套接字,确保连接上下文不会在客户端之间共享。此套接字行为在此处有详细解释。

// Standalone
$client1 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_1']);
$client2 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_2']);

// Cluster
$client1 = new Predis\Client(
    ['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
    ['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_1']]
);
$client2 = new Predis\Client(
    ['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
    ['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_2']]
);

客户端配置

客户端的诸多方面和行为可通过向 Predis\Client::__construct() 的第二个参数传递特定的客户端选项来进行配置:

$client = new Predis\Client($parameters, ['prefix' => 'sample:']);

选项通过一个类似迷你依赖注入(DI)的容器进行管理,其值仅在需要时进行延迟初始化。Predis 默认支持的客户端选项如下:

  • prefix:应用于命令中所有键的前缀字符串。
    • exceptions:客户端在遇到 Redis 错误时应抛出异常还是返回响应。
    • connections:连接后端列表或连接工厂实例。
    • cluster:指定集群后端(predisredis 或可调用对象)。
    • replication:指定复制后端(predissentinel 或可调用对象)。
    • aggregate:使用自定义聚合连接配置客户端(可调用对象)。
    • parameters:聚合连接的默认连接参数列表。
    • commands:指定库中使用的命令工厂实例。
    • readTimeout:(仅集群模式)循环遍历连接时读取操作之间的超时时间。

用户还可以提供带有值的自定义选项或可调用对象(用于延迟初始化),这些选项会存储在选项容器中,供库后续使用。

聚合连接

聚合连接是 Predis 实现集群和复制功能的基础,它们用于将多个到单个 Redis 节点的连接分组,并根据上下文隐藏正确处理这些连接所需的特定逻辑。创建新客户端实例时,聚合连接通常需要一组连接参数以及相应的客户端选项。

集群

Predis 可以配置为集群模式,采用传统的客户端分片方法来创建独立节点的集群,并在这些节点之间分配键空间。这种方法需要某种形式的节点外部健康监控,并且在添加或移除节点时需要手动重新平衡键空间:

$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options    = ['cluster' => 'predis'];

$client = new Predis\Client($parameters);

随着 Redis 3.0 的发布,一种新型的受监督且协同的集群模式以 redis-cluster 的形式被引入。这种方案采用不同的算法来分布键空间,Redis 节点通过 gossip 协议相互通信,以协同处理健康状态、负载均衡、节点发现和请求重定向。为了连接到由 redis-cluster 管理的集群,客户端需要提供其节点列表(不必完整,因为必要时客户端会自动发现新节点),并将 cluster 客户端选项设置为 redis

$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options    = ['cluster' => 'redis'];

$client = new Predis\Client($parameters, $options);

Redis Gears 与集群

从 Redis v7.2 开始,Redis Gears 模块已成为 Redis Stack 捆绑包的一部分。客户端支持多种可与 OSS 集群 API 配合使用的 Redis Gears 命令。目前,在对 OSS 集群使用任何 Redis Gears 命令之前,Redis 服务器需要了解集群拓扑结构。

REDISGEARS_2.REFRESHCLUSTER 命令应在集群创建时以及每次集群拓扑结构发生变化时,针对每个主节点(应忽略从副本)调用。

在大多数情况下,这些操作应由管理员、DevOPS 人员甚至 Kubernetes 从 CLI 界面执行,具体取决于您的基础设施管理流程。不过,客户端也提供了通过编程方式执行此操作的 API。

/** @var \Predis\Connection\Cluster\ClusterInterface $connection */
$connection->executeCommandOnEachNode(
    new \Predis\Command\RawCommand('REDISGEARS_2.REFRESHCLUSTER')
);

复制机制

客户端可配置为单主多从架构,以提升服务可用性。在使用复制功能时,Predis 能够识别只读命令,并将其发送至随机从节点,实现一定程度的负载均衡;一旦检测到会修改键空间或键值的命令,便会切换至主节点执行。当从节点发生故障时,客户端不会抛出连接错误,而是尝试从配置中提供的其他从节点中进行故障转移。

要使客户端工作在复制模式,基本配置需指定一个 Redis 服务器作为主节点(可通过连接参数将 role 设为 master 实现),以及一个或多个从节点(对于从节点,将 role 设为 slave 是可选操作):

$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options    = ['replication' => 'predis'];

$client = new Predis\Client($parameters, $options);

上述配置包含一个静态服务器列表,完全依赖客户端逻辑,但也可以借助 redis-sentinel 构建更稳健的高可用(HA)环境,其中哨兵服务器充当客户端进行服务发现的权威来源。客户端与 redis-sentinel 配合工作所需的最低配置包括:指向多个哨兵实例的连接参数列表、设置为 sentinelreplication 选项,以及设置为服务名称的 service 选项。

$sentinels = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options   = ['replication' => 'sentinel', 'service' => 'mymaster'];

$client = new Predis\Client($sentinels, $options);

如果主节点和从节点配置为要求客户端进行身份验证,则必须通过全局 parameters 客户端选项提供密码。此选项还可用于指定不同的数据库索引。此时,客户端选项数组如下所示:

$options = [
    'replication' => 'sentinel',
    'service' => 'mymaster',
    'parameters' => [
        'password' => $secretpassword,
        'database' => 10,
    ],
];

虽然 Predis 能够区分执行写入操作和只读操作的命令,但 EVALEVALSHA 属于一种特殊情况——客户端会切换到主节点,因为它无法判断 Lua 脚本是否可以安全地在从节点上执行。虽然这确实是默认行为,但当某些 Lua 脚本不执行写入操作时,可以提供一个提示,告知客户端在执行这些脚本时继续使用从节点:

$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options    = ['replication' => function () {
    // Set scripts that won't trigger a switch from a slave to the master node.
    $strategy = new Predis\Replication\ReplicationStrategy();
    $strategy->setScriptReadOnly($LUA_SCRIPT);

    return new Predis\Connection\Replication\MasterSlaveReplication($strategy);
}];

$client = new Predis\Client($parameters, $options);
$client->eval($LUA_SCRIPT, 0);             // Sticks to slave using `eval`...
$client->evalsha(sha1($LUA_SCRIPT), 0);    // ... and `evalsha`, too.

examples目录包含一些脚本,演示了如何配置客户端以及在基础和复杂场景中利用复制功能。

命令流水线

当需要向服务器发送多个命令时,流水线技术可通过减少网络往返时间带来的延迟来提升性能。流水线技术也适用于聚合连接。客户端可以在可调用块中执行流水线,或者返回一个流水线实例,该实例借助流畅接口支持命令链式调用:

// Executes a pipeline inside the given callable block:
$responses = $client->pipeline(function ($pipe) {
    for ($i = 0; $i < 1000; $i++) {
        $pipe->set("key:$i", str_pad($i, 4, '0', 0));
        $pipe->get("key:$i");
    }
});

// Returns a pipeline that can be chained thanks to its fluent interface:
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();

事务

客户端基于 MULTIEXEC 为 Redis 事务提供了抽象,其接口与命令流水线类似:

// Executes a transaction inside the given callable block:
$responses = $client->transaction(function ($tx) {
    $tx->set('foo', 'bar');
    $tx->get('foo');
});

// Returns a transaction that can be chained thanks to its fluent interface:
$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute();

借助 WATCHUNWATCH 命令,此抽象层能够执行检查并设置(CAS)操作,并且当被 WATCH 的键被修改导致事务中止时,它会自动重试事务。有关使用 CAS 的事务示例,可参见 以下示例

对集群连接的支持

从 Predis v3.0 开始,事务可用于集群连接。但由于 Redis 不支持分布式事务,因此存在一些限制。事务上下文中的所有键都应在同一个哈希槽上操作,基于此限制,建议使用 {} 语法来确保所有键都将映射到同一个哈希槽。除此之外,客户端无需额外配置。

$redis = $this->getClient();

$response = $redis->transaction(function (MultiExec $tx) {
    $tx->set('{foo}foo', 'value');
    $tx->set('{foo}bar', 'value');
    $tx->set('{foo}baz', 'value');
});

// ['OK', 'OK', 'OK']

带提示的哈希模板(HIMPORT)###

实验性: 此功能尚处于实验阶段,其 API(himport 容器及选项)可能在未来版本中发生变化。

HIMPORT(Redis >= 8.10)可加速加载多个共享相同字段名的哈希。通过 HIMPORT PREPARE 命令一次性发送字段名,并将其注册到一个字段集名称下,然后使用 HIMPORT SET 命令仅发送值即可创建哈希。服务器会以内存高效的编码方式存储此类哈希,其中字段名仅保留一次;生成的键是普通哈希,可与所有常规哈希命令配合使用。

Predis 通过 himport 容器公开该命令族:

$client->himport->prepare('users', ['name', 'email', 'age']);

$client->himport->set('user:1', 'users', ['alice', 'alice@example.com', '25']);
$client->himport->set('user:2', 'users', ['bob', 'bob@example.com', '30']);

$client->himport->discard('users');   // 1
$client->himport->discardAll();        // number of fieldsets removed

值会按照调用者提供给 prepare() 的字段顺序按位置配对 —— Predis 绝不会对它们重新排序。哈希枚举顺序(例如 HGETALL)不保证与 PREPARE 顺序匹配,仅保证值与字段的配对关系。

字段集是存在于单个物理连接上的服务器端会话状态,当该连接断开时(重新连接、RESET、集群故障转移),字段集将丢失。为确保这一过程的透明性,Predis 会跟踪通过容器准备的字段集,并执行以下操作:

  • 当连接重新建立时,自动重放每个 PREPARE(每个物理连接最多一次);此操作始终执行,与重试无关;
  • 如果 HIMPORT SET 仍然报告 no such fieldset(例如在集群重定向后创建的连接上),则在执行连接上重新准备字段集并重试写入。

重新准备并重试的步骤复用客户端配置的重试策略 —— 它不是一个单独的机制。因此,只有在启用重试时(通过 retry 连接参数)才会发生,且重试次数不会超过配置的尝试次数;如果禁用重试,no such fieldset 错误将原样传播。此外,即使启用了重试,也可以通过 himport 选项关闭此步骤:

$client = new Predis\Client(
    $parameters + ['retry' => new Predis\Retry\Retry(new Predis\Retry\Strategy\ExponentialBackoff(), 3)],
    ['himport' => ['auto_prepare' => false]] // opt out of HIMPORT re-prepare specifically
);

也可以通过 himport 选项预先声明字段集。以这种方式声明的字段集,会在连接上首次通过 HIMPORT SET 引用它们时按需准备,因此应用程序无需为它们调用 prepare()(这使用上述的重新准备和重试路径,因此需要启用重试):

$client = new Predis\Client($parameters + ['retry' => new Predis\Retry\Retry(new Predis\Retry\Strategy\ExponentialBackoff(), 3)], [
    'himport' => [
        'fieldsets' => [
            'users' => ['name', 'email', 'age'],
        ],
    ],
]);

// No prepare() call needed — "users" is known from configuration:
$client->himport->set('user:1', 'users', ['alice', 'alice@example.com', '25']);

在集群中,prepare()discard()discardAll() 会分发到每个主分片,而 set() 则像其他写入操作一样,根据其键的哈希槽进行路由。这确保了 HIMPORT SET 在拥有其键的任何分片上都能成功执行。

原始命令形式($client->himport('PREPARE', 'users', 'name', 'email'))也可用,且应在流水线和事务中使用;它不执行客户端跟踪或恢复,因此 PREPARE 和相关的 SET 命令必须在同一连接上运行(流水线和事务可保证这一点)。

添加新命令

尽管我们会努力更新 Predis,以支持 Redis 中的所有可用命令,但你可能希望坚持使用旧版本的库,或者为特定命令提供不同的参数过滤或响应解析方式。为此,Predis 允许实现新的命令类,以在客户端使用的默认命令工厂中定义或覆盖命令:

// Define a new command by extending Predis\Command\Command:
class BrandNewRedisCommand extends Predis\Command\Command
{
    public function getId()
    {
        return 'NEWCMD';
    }
}

// Inject your command in the current command factory:
$client = new Predis\Client($parameters, [
    'commands' => [
        'newcmd' => 'BrandNewRedisCommand',
    ],
]);

$response = $client->newcmd();

此外,还有一种方法可以发送原始命令,而无需过滤其参数或解析响应。 用户必须以数组形式提供命令的参数列表,并遵循 Redis 命令文档 中定义的签名:

$response = $client->executeRaw(['SET', 'foo', 'bar']);

脚本命令

虽然在 Redis 2.6+ 版本中可以直接使用 EVALEVALSHA 来利用 Lua 脚本,但 Predis 提供了基于这些命令构建的更高级抽象脚本命令,以简化操作。脚本命令可以注册到客户端使用的命令工厂中,并像普通 Redis 命令一样被访问,不过它们定义的是将传输到服务器进行远程执行的 Lua 脚本。在内部,它们默认使用 EVALSHA,通过其 SHA1 哈希来标识脚本以节省带宽,但在需要时会回退使用 EVAL

// Define a new script command by extending Predis\Command\ScriptCommand:
class ListPushRandomValue extends Predis\Command\ScriptCommand
{
    public function getKeysCount()
    {
        return 1;
    }

    public function getScript()
    {
        return <<<LUA
math.randomseed(ARGV[1])
local rnd = tostring(math.random())
redis.call('lpush', KEYS[1], rnd)
return rnd
LUA;
    }
}

// Inject the script command in the current command factory:
$client = new Predis\Client($parameters, [
    'commands' => [
        'lpushrand' => 'ListPushRandomValue',
    ],
]);

$response = $client->lpushrand('random_values', $seed = mt_rand());

可自定义的连接后端

Predis 可以使用不同的连接后端来连接 Redis。内置的 Relay 集成利用 PHP 的 Relay 扩展实现了显著的性能提升,该扩展通过在 PHP 共享运行时内存中缓存 Redis 数据集的部分副本实现这一效果。

$client = new Predis\Client('tcp://127.0.0.1', [
    'connections' => 'relay',
]);

开发人员可以创建自己的连接类来支持全新的网络后端,扩展现有类或提供完全不同的实现。连接类必须实现 Predis\Connection\NodeConnectionInterface 或扩展 Predis\Connection\AbstractConnection

class MyConnectionClass implements Predis\Connection\NodeConnectionInterface
{
    // Implementation goes here...
}

// Use MyConnectionClass to handle connections for the `tcp` scheme:
$client = new Predis\Client('tcp://127.0.0.1', [
    'connections' => ['tcp' => 'MyConnectionClass'],
]);

若要深入了解如何创建新的连接后端,你可以参考 Predis\Connection 命名空间中提供的标准连接类的实际实现。

重试异常

默认情况下自动重试功能是关闭的,你可以启用它,以减少网络问题导致的误判。默认情况下,我们会对任何连接异常、超时异常或套接字初始化异常进行重试,但你可以更新重试异常列表。目前有 EqualBackoffExponentialBackoff 两种重试策略可供选择,你也可以提供自定义策略。重试功能可配置用于任何类型的通信(独立节点、集群、管道、事务、复制)。以下是一个配置示例:

// Standalone client
$client = new Predis\Client([
    'retry' => new \Predis\Retry\Retry(
        new \Predis\Retry\Strategy\ExponentialBackoff(1000, 10000), // Base and cap configuration in microseconds
        3                                                           // Number of retries
    ),
]);

// Cluster configuration
$options = [
    'parameters' => [
        'retry' => new \Predis\Retry\Retry(new \Predis\Retry\Strategy\ExponentialBackoff(1000, 10000), 3),
    ],
];

$client = new Predis\Client(['tcp://host:port', 'tcp://host:port', 'tcp://host:port'], $options);

$retry = new \Predis\Retry\Retry(
    new \Predis\Retry\Strategy\ExponentialBackoff(1000, 10000),
    3
);

// Update a list of exceptions to catch
$retry->updateCatchableExceptions([Exception::class]);

RESP3

连接

要使用 RESP3 协议建立连接,需设置参数 protocol => 3。默认协议为 RESP2。

您可以将参数作为数组中的配置选项传递,或作为 redis_url 中的查询参数传递。

  // Configuration option
  $client = new \Predis\Client(['protocol' => 3]);

  // Redis URL
  $client = new \Predis\Client('redis://localhost:6379?protocol=3');

  // ["proto" => "3"]
  $client->executeRaw(['HELLO']);

命令响应

RESP3 协议引入了多种新的 响应类型,因此在客户端,我们能够更清晰地了解从服务器获取的数据类型。以下是一些示例,展示 RESP2 和 RESP3 响应之间的区别。

浮点型响应

// RESP2 connection
$client = new \Predis\Client();

$client->geoadd('my_geo', 11.111, 22.222, 'member1');

// [[0 => string(20) "11.11099988222122192", 1 => string(20) "22.22200052541037252"]]
// RESP2 returns float values as simple strings.
var_dump($client->geopos('my_geo', ['member1']));

// RESP3 connection
$client = new \Predis\Client(['protocol' => 3]);

// [[0 => float(11.110999882221222), 1 => float(22.222000525410373)]]
// RESP3 introduces new double type, that corresponds to PHP float.
var_dump($client->geopos('my_geo', ['member1']));

聚合类型

在 RESP3 中引入了新的聚合类型 Map,它表示字段-值对的序列。因此,它简化了解析过程,因为我们无需为每个命令指定解析策略(RESP2),而是依赖于协议定义的类型(RESP3)。

在大多数情况下,RESP2 响应与 RESP3 不应有差异,因为我们为那些返回字段-值对的命令添加了额外的解析。然而,由于 RESP2 需要额外的解析,可能有些命令缺乏这种解析,从而返回未处理的响应。在这种情况下,可能会出现如下差异:

$client = new \Predis\Client();

// RESP2: ['field', 'value]
$client->commandThatReturnsFieldValuePair('key');

$client = new \Predis\Client(['protocol' => 3]);

// RESP3: ['field' => 'value]
$client->commandThatReturnsFieldValuePair('key');

如果遇到协议不匹配的问题,请随时提交 PR 或 GitHub issue。

推送通知

RESP3 引入了推送连接的概念,即服务器可以向客户端发送未明确请求的异步数据。Predis 3.0 提供了一个 API 来建立这种连接作为单独的阻塞进程(工作器),并根据推送通知消息类型调用回调函数。

消费者

首先,您需要设置一个消费者连接,并提供一个可选的回调函数,该函数将在事件循环启动前执行。这使您能够订阅频道、启用键失效跟踪或启用监控连接,以及任何让服务器知道您希望在此连接中接收推送通知的 Redis 命令。

// Make sure that RESP3 protocol enabled and read_write_timeout set 0,
// so connection won't be killed by timeout.
$client = new Predis\Client(['read_write_timeout' => 0, 'protocol' => 3]);

// Create push notifications consumer.
// Provides callback where current consumer subscribes to few channels before
// enter the loop.
$push = $client->push(function (ClientInterface $client) {
    $response = $client->subscribe('channel', 'control');
    $status = ($response[2] === 1) ? 'OK' : 'FAILED';
    echo "Channel subscription status: {$status}\n";
});

调度循环

调度器对象允许您将回调函数附加到指定的推送通知类型,并运行实际的工作进程来监听传入的推送通知。为了能够在运行时停止阻塞进程,您可以指定一个条件,并从给定的回调函数中调用 $dispatcher->stop() 方法。在此示例中,我们在进入循环之前订阅的 control 频道中等待特定消息 terminate

// Storage for incoming notifications.
$messages = [];

// Create dispatcher for push notifications.
$dispatcher = new Predis\Consumer\Push\DispatcherLoop($push);

$dispatcher->attachCallback(
    PushResponseInterface::MESSAGE_DATA_TYPE,
    static function (array $payload, DispatcherLoopInterface $dispatcher) {
        global $messages;
        [$channel, $message] = $payload;

        if ($channel === 'control' && $message === 'terminate') {
            echo "Terminating notification consumer.\n";
            $dispatcher->stop();

            return;
        }

        $messages[] = $message;
        echo "Received message: {$message}\n";
    }
);

// Run consumer loop with attached callbacks.
$dispatcher->run();

// Count all messages that were received during consumer loop.
$messagesCount = count($messages);
echo "We received: {$messagesCount} messages\n";

此示例展示了一个简单脚本,用于统计从已订阅频道接收的推送通知中的所有传入消息,直到满足停止条件为止。示例可在 examples/ 文件夹中找到。

分片发布/订阅

从 Redis 7.0 开始,引入了分片发布/订阅(Sharded Pub/Sub),其中分片频道通过与键分配槽位相同的算法分配到槽位。

Predis 3.0 提供了一个 API,允许通过 Redis 的分片发布/订阅功能在集群连接上使用发布/订阅。无需指定任何额外配置来启用分片发布/订阅,若使用集群连接,它将自动启用。

其实现与推送通知非常相似,因此你需要设置消费者并通过 Dispatcher 循环对象运行它。所有示例可在 examples/ 文件夹中找到。

开发

报告错误和贡献代码

我们非常欢迎对 Predis 的贡献,无论是新功能、错误修复的拉取请求,还是仅错误报告。我们只要求你遵循问题和拉取请求模板。

测试套件

注意:切勿在生产环境中运行的 Redis 实例上,或包含你关心数据的实例上运行 Predis 附带的测试套件!

Predis 拥有全面的测试套件,涵盖了库的各个方面,并且可以选择针对运行中的 Redis 实例执行集成测试(要求 Redis >= 2.4.0,以验证每个命令实现的正确行为)。不支持的 Redis 命令的集成测试会自动跳过。如果你没有运行 Redis,可以禁用集成测试。有关测试此库的更多详细信息,请参见 测试 README

Predis 使用 GitHub Actions 进行持续集成,过去和当前构建的历史记录可在 其操作页面 上找到。

许可证

Predis 的代码根据 MIT 许可证的条款分发(参见 LICENSE)。

项目介绍

一个为 PHP 提供的灵活且功能完整的 Redis 客户端。【此简介由AI生成】

定制我的领域
2017.78 K995访问 GitHub