esokullu/predis

适用于 PHP 和 HHVM 的灵活且功能齐全的 Redis 客户端

v1.1.1 2016-06-16 16:22 UTC

README

Software license Latest stable Latest development Monthly installs Build status Gitter room

适用于 PHP >= 5.3 和 HHVM >= 2.3.0 的灵活且功能齐全的 Redis 客户端。

Predis 默认不要求任何额外的 C 扩展,但它可以可选地与 phpiredis 配对,以降低 Redis RESP 协议序列化和解析的开销。对于客户端的 实验性 异步实现,您可以参考 Predis\Async

有关此项目的更多详细信息,请参阅 常见问题解答

主要特性

  • 支持使用配置文件支持 Redis 的不同版本(从 2.03.2)。
  • 支持使用客户端分片和可插拔的键空间分配器进行集群。
  • 支持 redis-cluster(Redis >= 3.0)。
  • 支持主从复制设置和 redis-sentinel
  • 使用可定制的策略对键进行透明前缀。
  • 在单节点和集群(客户端分片仅限)上支持命令管道。
  • Redis 事务(Redis >= 2.0)和 CAS 操作(Redis >= 2.2)的抽象。
  • 对 Lua 脚本(Redis >= 2.6)的抽象,并自动在 EVALSHAEVAL 之间切换。
  • 基于 PHP 迭代器的 SCANSSCANZSCANHSCAN(Redis >= 2.8)的抽象。
  • 客户端会在第一次执行命令时建立连接,并且可以持久化连接。
  • 可以通过 TCP/IP(也支持 TLS/SSL 加密)或 UNIX 域套接字建立连接。
  • 支持 Webdis(需要 ext-curlext-phpiredis)。
  • 支持为提供不同的网络或协议后端定义自定义连接类。
  • 灵活的系统用于定义自定义命令和配置文件,并覆盖默认值。

如何 安装 和使用 Predis

此库可在 Packagist 上找到,以便通过 Composer 更容易地管理项目依赖,或者在 我们自己的 PEAR 频道 上使用 PEAR 进行更传统的安装。最终,每个版本的压缩归档都可在 GitHub 上找到。

加载库

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();

您还可以通过运行 bin/create-phar 脚本直接从存储库创建 phar 归档。生成的 phar 已经包含一个定义其自己的自动加载器的存根,因此您只需要 require() 它即可开始使用库。

连接到 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');

还可以通过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实例,无需配置SSL代理如stunnel。这在连接到运行在各种云托管提供商的节点时非常有用。可以使用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临时注册文件中的规则进行解析。

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

当提供连接参数数组时,Predis会自动以客户端分片模式工作。提供每个节点的配置时,可以混合使用命名数组和URI字符串。

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

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

Redis连接是懒加载的,意味着客户端仅在需要时连接到服务器。虽然建议让客户端在幕后自行处理,但在某些情况下,可能仍然希望控制何时打开或关闭连接:这可以通过调用$client->connect()$client->disconnect()轻松实现。请注意,这些方法对聚合连接的影响可能因每个特定实现而异。

客户端配置

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

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

选项使用类似DI的小型容器进行管理,其值仅在需要时才会懒加载初始化。Predis默认支持的客户端选项有:

  • profile:指定用于匹配特定Redis版本的配置文件。
  • prefix:自动应用于命令中找到的键的前缀字符串。
  • exceptions:客户端是否应在Redis错误时抛出或返回响应。
  • connections:连接后端列表或连接工厂实例。
  • cluster:指定集群后端(predisredis或可调用对象)。
  • replication:指定复制后端(TRUEsentinel或可调用对象)。
  • aggregate:覆盖clusterreplication以提供自定义连接聚合器。
  • parameters:聚合连接的默认连接参数列表。

用户还可以提供具有值或可调用对象的自定义选项(用于懒加载),这些选项存储在选项容器中以供库稍后使用。

聚合连接

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

集群

默认情况下,当没有设置特定的客户端选项且将连接参数数组传递给客户端构造函数时,Predis 会自动配置为在集群模式下工作。它使用传统的客户端分片方法来创建一个由独立节点组成的集群,并在它们之间分配键空间。这种方法需要某种形式的外部节点健康监控,并且在更改配置(通过添加或删除节点)时需要手动操作以重新平衡键空间。

$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];

$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);

复制

客户端可以配置为以单主/多从的设置运行,以提供更好的服务可用性。当使用复制时,Predis 识别只读命令并将它们发送到随机从节点以提供某种形式的负载均衡。一旦它检测到任何修改键空间或键值的操作命令,它就切换到主节点。当从节点失败时,客户端尝试回退到配置中提供的其他从节点。

在复制模式下使用客户端所需的基本配置需要将一个 Redis 服务器识别为主节点(这可以通过使用 alias 参数设置为 master 的连接参数来完成)和作为从节点的一个或多个服务器。

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

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

上述配置具有静态服务器列表,完全依赖于客户端的逻辑,但可以使用 redis-sentinel 来依赖更稳健的 HA 环境,其中 sentinel 服务器作为客户端服务发现的主权来源。客户端与 redis-sentinel 一起工作所需的最小配置是一个指向多个 sentinel 实例的连接参数列表,将 replication 选项设置为 sentinel,并将 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?alias=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\Aggregate\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,这个抽象可以执行检查和设置操作。当被 WATCH 的键被修改时,如果 Redis 中断事务,它会自动重试。关于使用 CAS 的示例,您可以查看以下示例:示例链接

添加新命令

尽管我们努力使 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 profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');

$response = $client->newcmd();

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

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

脚本命令

虽然可以在 Redis 2.6+ 上利用 Lua 脚本 使用 EVALEVALSHA,但 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 profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');

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

可定制连接后端

Predis 可以使用不同的连接后端来连接到 Redis。其中两个利用第三方扩展,如 phpiredis,在处理大型多批量响应时实现重大性能提升。其中一个基于 PHP 流,另一个基于由 ext-socket 提供的套接字资源。两者都支持 TCP/IP 和 UNIX 域套接字。

$client = new Predis\Client('tcp://127.0.0.1', [
    'connections' => [
        'tcp'  => 'Predis\Connection\PhpiredisStreamConnection',  // PHP stream resources
        'unix' => 'Predis\Connection\PhpiredisSocketConnection',  // ext-socket resources
    ],
]);

开发者可以创建自己的连接类来支持全新的网络后端,扩展现有类或提供完全不同的实现。连接类必须实现 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 命名空间中可用的标准连接类的实际实现。

开发

报告错误和贡献代码

Predis 非常欢迎对新功能、错误修复或仅错误报告的贡献。我们只要求您在提交更改或在问题跟踪器上提交错误报告之前遵守 基本规则,以使每个人都能在项目中保持一致性。

测试套件

注意:切勿在生产环境中运行与运行在包含您感兴趣的数据的 Redis 实例的测试套件!

Redis客户端Predis拥有一个全面的测试套件,涵盖了库的各个方面。这个测试套件会对运行中的Redis实例进行集成测试(需要Redis版本≥2.4.0)以验证每个命令的实现是否正确,并自动跳过在指定Redis配置文件中未定义的命令。如果您没有运行Redis,可以禁用集成测试。默认情况下,测试套件配置为使用Redis 3.2(Redis的当前稳定版本)的配置文件来执行集成测试,但可以通过修改phpunit.xml并将REDIS_SERVER_VERSION设置为dev来选择使用unstable分支构建的Redis实例,以便使用开发服务器配置文件。您可以参考测试的README以获取更多关于Predis测试的详细信息。

Predis使用Travis CI进行持续集成,过去和当前的构建历史可以在其项目页面找到。

其他

项目相关链接

作者

许可协议

Predis的代码在MIT许可协议下分发(请参阅LICENSE)。