zenstruck / redis
php-redis 的懒代理,包含 DX 助手、实用工具和统一的 API。
Requires
- php: >=8.0
- ext-redis: *
Requires (Dev)
- phpstan/phpstan: ^1.4
- phpunit/phpunit: ^9.5.0
- symfony/phpunit-bridge: ^6.0
- symfony/var-dumper: ^4.4|^5.0|^6.0
README
php-redis 的懒代理,包含 DX 助手、实用工具和统一的 API。
Zenstruck\Redis
是 \Redis|\RedisArray|\RedisCluster
的统一代理。在少数例外和考虑的情况下,API 在底层客户端之间是相同的。这允许你在开发时使用相同的 API,你很可能只使用 \Redis
,在生产中,你可能使用 \RedisArray
或 \RedisCluster
。
代理是懒加载的,即通过 DSN 创建时,直到执行命令才会实例化底层客户端。
此库与 Symfony 集成良好,并提供了一个配方。
安装
composer require zenstruck/redis
Redis 工厂
通过 DSN 字符串创建 Redis 客户端实例。DSN 必须使用以下格式
redis[s]://[pass@][ip|host|socket[:port]][/db-index][?{option}={value}...]
Redis 代理工厂
建议尽可能使用代理。与使用 真实 客户端相比,它有以下优点
- 懒加载:在真正调用 Redis 命令之前不会建立连接。
- 封装:大部分情况下,不需要了解 真实 客户端。您不需要根据使用的客户端更改您的使用方式。这里有一些 例外。
- 开发者体验 (DX):使用 流畅的序列和事务 API。
以下是从 DSN 创建代理的一些示例。
use Zenstruck\Redis; $proxy = Redis::create('redis://localhost'); // Zenstruck\Redis<\Redis> $proxy = Redis::create('redis://localhost?redis_sentinel=sentinel_service'); // Zenstruck\Redis<\Redis> (using Redis Sentinel) $proxy = Redis::create('redis:?host[host1]&host[host2]'); // Zenstruck\Redis<\RedisArray> $proxy = Redis::create('redis:?host[host1]&host[host2]&redis_cluster=1'); // Zenstruck\Redis<\RedisCluster>
您还可以从现有的 \Redis|\RedisArray|\RedisCluster
实例创建代理
use Zenstruck\Redis; /** @var \Redis|\RedisArray|\RedisCluster $client */ $proxy = Redis::wrap($client)
Redis 真实 客户端工厂
可以直接创建 \Redis|\RedisArray|\RedisCluster
实例
use Zenstruck\Redis; $client = Redis::createClient('redis://localhost'); // \Redis $client = Redis::createClient('redis://localhost?redis_sentinel=sentinel_service'); // \Redis (using Redis Sentinel) $client = Redis::createClient('redis:?host[host1]&host[host2]'); // \RedisArray $client = Redis::createClient('redis:?host[host1]&host[host2]&redis_cluster=1'); // \RedisCluster
工厂选项
某些 Redis 选项可以通过您的 DSN 的查询参数设置,或者作为数组传递给 Zenstruck\Redis::create/createClient()
的第二个参数。
前缀
您可以设置所有键的前缀
use Zenstruck\Redis; $proxy = Redis::create('redis://localhost?prefix=app:'); $proxy = Redis::create('redis://localhost', ['prefix' => 'app:']); // equivalent to above
序列化器选项
默认情况下,Redis 将所有标量/空值存储为字符串,将对象/数组存储为 "Array"/"Object"。为了正确存储具有类型的值和对象/数组,您必须配置 Redis 序列化器
use Zenstruck\Redis; // PHP: serialize/unserialize values $proxy = Redis::create('redis://localhost?serializer=php'); $proxy = Redis::create('redis://localhost', ['serializer' => \Redis::SERIALIZER_PHP]); // equivalent to above // JSON: json_encode/json_decode values (doesn't work for objects) $proxy = Redis::create('redis://localhost?serializer=json'); $proxy = Redis::create('redis://localhost', ['serializer' => \Redis::SERIALIZER_JSON]); // equivalent to above
注意:使用 Redis 序列化时存在性能权衡。考虑为需要序列化的操作/逻辑创建单独的客户端。
Redis 代理 API
/** @var Zenstruck\Redis $proxy */ // call any \Redis|\RedisArray|\RedisCluster method $proxy->set('mykey', 'value'); $proxy->get('mykey'); // "value" // get the "real" client $proxy->realClient(); // \Redis|\RedisArray|\RedisCluster
序列/管道和事务
代理具有用于 Redis 管道和事务的流畅、自动完成的 API
/** @var Zenstruck\Redis $proxy */ // use \Redis::multi() $results = $proxy->transaction() ->set('x', '42') ->incr('x') ->get('x')->as('value') // alias the result of this command ->del('x') ->execute() // the results of the above transaction as an array (keyed by index of command or alias if set) ; $results['value']; // "43" (result of ->get()) $results[3]; // true (result of ->del()) // use \Redis::pipeline() - see note below about \RedisCluster $proxy->sequence() ->set('x', '42') ->incr('x') ->get('x')->as('value') // alias the result of this command ->del('x') ->execute() // the results of the above sequence as an array (keyed by index of command of alias if set) ; $results['value']; // "43" (result of ->get()) $results[3]; // true (result of ->del())
注意:当使用 sequence()
与 \RedisCluster
时,命令作为原子操作执行,因为不支持管道。
注意:当使用 sequence()
/transaction()
与 \RedisArray
实例时,序列/事务中的第一个命令必须是 "基于键的命令"(例如 get()
/set()
)。这是为了选择事务运行的节点。
可计数的\可迭代的
Zenstruck\Redis
是可计数的和可迭代的。在计数/迭代时,根据底层客户端存在一些差异
\Redis
:计数始终为1,并对其自身迭代一次\RedisArray
:计数为主机数量,并迭代每个由代理包装的主机。\RedisCluser
:计数为主机数量,并迭代每个预先设置了节点参数的主节点。这允许在主节点上执行节点命令,而无需将这些命令的节点参数传递给这些命令(在迭代时)
/** @var Zenstruck\Redis $proxy */ $proxy->count(); // 1 if \Redis, # hosts if \RedisArray, # "masters" if \RedisCluster foreach ($proxy as $node) { $proxy->flushAll(); // this is permitted even for \RedisCluster (which typically requires a $nodeParams argument) }
注意:如果运行需要在每个主机/主节点上运行的命令,即使使用\Redis
也建议迭代并运行。这允许将来无缝过渡到\RedisArray
/\RedisCluster
。
实用工具
ExpiringSet
Zenstruck\Redis\Utility\ExpiringSet
封装了Redis过期集合的概念:一个集合(无序列表,无重复项),其成员在一段时间后过期。对集合的每次读写操作都会修剪过期的成员。
/** @var Zenstruck\Redis $client */ $set = $client->expiringSet('my-set'); // redis key to store the set $set->add('member1', 600); // set add "member1" that expires in 10 minutes $set->add('member1', new \DateInterval::createFromDateString('5 minutes')); // can use \DateInterval for the TTL $set->add('member1', new \DateTime('+5 minutes')); // use \DateTimeInterface to set specific expiry timestamp $set->remove('member1'); // explicitly remove a member $set->all(); // array - all unexpired members $set->contains('member'); // true/false $set->clear(); // clear all items $set->prune(); // explicitly "prune" the set (remove expired members) count($set); // int - number of unexpired members foreach ($set as $member) { // iterate over unexpired members } // fluent $set ->add('member1', 600) ->add('member2', 600) ->remove('member1') ->remove('member2') ->prune() ->clear() ;
注意:为了使用复杂类型(数组/对象)作为成员,您的Redis客户端必须配置了序列化器。
以下是使用此对象跟踪网站活跃用户的伪代码示例。当认证用户登录或请求页面时,将他们的用户名添加到集合中,具有5分钟的空闲生存时间(TTL)。用户在此时间内被认为是活跃的。在登出时,他们将从集合中移除。如果用户在其最后TTL内没有发出请求,他们将从不活跃集合中移除。
/** @var Zenstruck\Redis $client */ $set = $client->expiringSet('active-users'); $ttl = \DateInterval::createFromDateString('5 minutes'); // LOGIN EVENT: $set->add($event->getUsername(), $ttl); // LOGOUT EVENT: $set->remove($event->getUsername()); // REQUEST EVENT: $set->add($event->getUsername(), $ttl); // ADMIN MONITORING DASHBOARD WIDGET $activeUserCount = count($set); $activeUsernames = $set->all(); // [user1, user2, ...] // ADMIN USER CRUD LISTING foreach ($users as $user) { $isActive = $set->contains($user->getUsername()); // bool // ... }
集成
Symfony框架
添加支持的Redis DSN环境变量
# .env
REDIS_DSN=redis://localhost
配置服务
# config/packages/zenstruck_redis.yaml services: # Proxy that is autowireable Zenstruck\Redis: factory: ['Zenstruck\Redis', 'create'] arguments: ['%env(REDIS_DSN)%'] # Separate proxy's that have different prefixes redis1: class: Zenstruck\Redis factory: ['Zenstruck\Redis', 'create'] arguments: ['%env(REDIS_DSN)%', { prefix: 'prefix1:' }] redis2: class: Zenstruck\Redis factory: ['Zenstruck\Redis', 'create'] arguments: ['%env(REDIS_DSN)%', { prefix: 'prefix2:' }] # Separate proxy that uses PHP serialization serialization_redis: class: Zenstruck\Redis factory: ['Zenstruck\Redis', 'create'] arguments: ['%env(REDIS_DSN)%', { serializer: php }] # expiring set service active_users: class: Zenstruck\Redis\Utility\ExpiringSet factory: ['@Zenstruck\Redis', 'expiringSet'] arguments: - active_users # redis key # Specific clients that are autowireable Redis: class: Redis factory: ['Zenstruck\Redis', 'createClient'] arguments: ['%env(REDIS_DSN)%'] # note REDIS_DSN must be for \Redis client RedisArray: class: RedisArray factory: ['Zenstruck\Redis', 'createClient'] arguments: ['%env(REDIS_DSN)%'] # note REDIS_DSN must be for \RedisArray client RedisCluster: class: RedisCluster factory: ['Zenstruck\Redis', 'createClient'] arguments: ['%env(REDIS_DSN)%'] # note REDIS_DSN must be for \RedisCluster client
使用Zenstruck\Redis
进行会话存储(有关更多详细信息/选项,请参阅Symfony文档)
# config/services.yaml # Assumes "Zenstruck\Redis" is available as a service and symfony/expression-language is installed services: redis_session_handler: class: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler arguments: - "@=service('Zenstruck\\\\Redis').realClient()" # config/packages/framework.yaml framework: # ... session: handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler
贡献
运行测试套件
composer install
docker compose up -d # setup redis, redis-cluster, redis-sentinel
vendor/bin/phpunit -c phpunit.docker.xml
致谢
创建从DSN创建php-redis客户端的大部分代码已被从Symfony框架中提取和修改。