Redis PHP 扩展
概述
phpredis 是 PHP 与 Redis 交互的原生 C 扩展,提供了高性能、低延迟的 Redis 客户端实现。相比基于 PHP 实现的 predis 库,phpredis 具有更好的性能和更完整的功能支持。
phpredis vs predis
| 特性 | phpredis (ext-redis) | predis |
|---|---|---|
| 实现方式 | C 扩展 | 纯 PHP |
| 性能 | 更高 | 较低 |
| 安装 | 需编译 | Composer 安装 |
| 集群支持 | RedisCluster 类 | Predis\Client |
| 序列化 | 内置支持 | 手动处理 |
| 推荐场景 | 生产环境 | 无扩展权限环境 |
基础概念
扩展安装
bash
# PECL 安装
pecl install redis
# 编译安装
git clone https://github.com/phpredis/phpredis.git
cd phpredis && phpize && ./configure && make && make install
# Docker 中安装
docker-php-ext-install redis
# 验证安装
php -m | grep redis
# 输出: redis
php -r 'echo phpversion("redis");'
# 输出: 6.0.2php.ini 配置
ini
; 加载扩展
extension=redis.so
; Redis Session Handler(可选)
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=yourpassword&database=1"
; Redis Sentinel(高可用配置)
; session.save_path = "tcp://sentinel1:26379?sentinel=1&sentinel=2"Redis 类层次
| 类名 | 说明 | 用途 |
|---|---|---|
Redis | 单节点客户端 | 连接单个 Redis 服务器 |
RedisArray | 分片客户端 | 数据分片到多个节点 |
RedisCluster | 集群客户端 | 连接 Redis Cluster |
RedisSentinel | 哨兵客户端 | 高可用监控与故障转移 |
语法与代码
基本连接与认证
php
<?php
declare(strict_types=1);
// 创建 Redis 实例
$redis = new Redis();
// 连接服务器
$connected = $redis->connect('127.0.0.1', 6379, 2.0); // 2秒超时
if (!$connected) {
throw new RuntimeException('Redis 连接失败');
}
// 设置密码认证
$redis->auth('your_password');
// 选择数据库(0-15)
$redis->select(1);
// Ping 测试
$pong = $redis->ping(); // 返回 "PONG" 或 true
// 查看服务器信息
$info = $redis->info('server');
echo "Redis 版本: {$info['redis_version']}\n";
echo "已用内存: {$info['used_memory_human']}\n";连接选项与配置
php
<?php
declare(strict_types=1);
$redis = new Redis();
// 连接参数
$host = '127.0.0.1';
$port = 6379;
$timeout = 2.0; // 连接超时(秒)
$retryInterval = 100; // 重试间隔(毫秒)
$readTimeout = 5.0; // 读取超时(秒)
$redis->connect($host, $port, $timeout, $retryInterval, $readTimeout);
// 客户端设置
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
$redis->setOption(Redis::OPT_PREFIX, 'app:'); // 键前缀
$redis->setOption(Redis::OPT_READ_TIMEOUT, 10.0); // 读取超时
$redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_LZF);
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_REUSE); // SCAN 不重置迭代器序列化选项
php
<?php
declare(strict_types=1);
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 序列化器选项
$serializers = [
'NONE' => Redis::SERIALIZER_NONE, // 不序列化(默认)
'PHP' => Redis::SERIALIZER_PHP, // PHP serialize()
'IGBINARY' => Redis::SERIALIZER_IGBINARY, // igbinary(高性能二进制)
'JSON' => Redis::SERIALIZER_JSON, // json_encode
'MSGPACK' => Redis::SERIALIZER_MSGPACK, // msgpack
];
// 推荐: 使用 igbinary(需安装扩展)
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
// 存储 PHP 数组 — 自动序列化
$redis->set('user:1', [
'id' => 1,
'name' => '张三',
'email' => 'zhangsan@example.com',
]);
// 读取时自动反序列化
$user = $redis->get('user:1');
// $user 是 PHP 数组
// JSON 序列化(跨语言兼容)
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_JSON);
$redis->set('config:app', ['debug' => false, 'cache_ttl' => 3600]);序列化注意事项
使用序列化器后,存储的值无法被其他语言客户端正确读取。在多语言环境中使用 JSON 序列化器,或手动 json_encode() / json_decode()。
持久连接
php
<?php
declare(strict_types=1);
$redis = new Redis();
// pconnect — 持久连接(不会在脚本结束时关闭)
$redis->pconnect('127.0.0.1', 6379);
// 持久连接也可以设置超时
$redis->pconnect('127.0.0.1', 6379, 2.0);
// 注意: 持久连接可能在 select() 时共享状态
// 使用前先 ping 确保连接仍然有效
try {
$redis->ping();
} catch (RedisException $e) {
// 连接已断开,重新连接
$redis->connect('127.0.0.1', 6379);
}实战示例
Redis 连接池管理
php
<?php
declare(strict_types=1);
class RedisPool
{
private array $connections = [];
private string $host;
private int $port;
private int $maxConnections;
private string $password;
private int $database;
public function __construct(
string $host = '127.0.0.1',
int $port = 6379,
int $maxConnections = 10,
string $password = '',
int $database = 0
) {
$this->host = $host;
$this->port = $port;
$this->maxConnections = $maxConnections;
$this->password = $password;
$this->database = $database;
}
public function getConnection(): Redis
{
if (!empty($this->connections)) {
return array_pop($this->connections);
}
$redis = new Redis();
$redis->connect($this->host, $this->port, 2.0);
if ($this->password) {
$redis->auth($this->password);
}
if ($this->database > 0) {
$redis->select($this->database);
}
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
$redis->setOption(Redis::OPT_PREFIX, 'app:');
return $redis;
}
public function releaseConnection(Redis $redis): void
{
if (count($this->connections) < $this->maxConnections) {
// 检查连接是否仍然有效
try {
$redis->ping();
$this->connections[] = $redis;
} catch (RedisException) {
// 连接已断开,不回收到池中
}
} else {
$redis->close();
}
}
}
// 使用示例
$pool = new RedisPool('127.0.0.1', 6379, 10, 'secret', 1);
$redis = $pool->getConnection();
try {
$redis->set('key', 'value');
$result = $redis->get('key');
echo $result . "\n";
} finally {
$pool->releaseConnection($redis);
}Redis 连接工厂(单例)
php
<?php
declare(strict_types=1);
class RedisFactory
{
private static ?Redis $instance = null;
private static array $config = [];
public static function configure(array $config): void
{
self::$config = array_merge([
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => 2.0,
'password' => '',
'database' => 0,
'prefix' => 'app:',
'serializer' => Redis::SERIALIZER_IGBINARY,
], $config);
}
public static function getInstance(): Redis
{
if (self::$instance === null || !self::isConnected(self::$instance)) {
$redis = new Redis();
$redis->connect(
self::$config['host'],
self::$config['port'],
self::$config['timeout']
);
if (!empty(self::$config['password'])) {
$redis->auth(self::$config['password']);
}
if (self::$config['database'] > 0) {
$redis->select(self::$config['database']);
}
$redis->setOption(Redis::OPT_SERIALIZER, self::$config['serializer']);
$redis->setOption(Redis::OPT_PREFIX, self::$config['prefix']);
$redis->setOption(Redis::OPT_READ_TIMEOUT, 10.0);
self::$instance = $redis;
}
return self::$instance;
}
private static function isConnected(Redis $redis): bool
{
try {
return (bool) $redis->ping();
} catch (RedisException) {
return false;
}
}
public static function disconnect(): void
{
if (self::$instance !== null) {
self::$instance->close();
self::$instance = null;
}
}
}Redis Sentinel 高可用
php
<?php
declare(strict_types=1);
// Sentinel 连接 — 自动故障转移
$sentinel = new RedisSentinel('127.0.0.1', 26379, 2.0);
// 获取主节点地址
$masterInfo = $sentinel->getMasterAddrByName('mymaster');
// ['127.0.0.1', 6379]
// 连接主节点
$redis = new Redis();
$redis->connect($masterInfo[0], (int) $masterInfo[1]);
// 监听 Sentinel 事件
$sentinel->setOption(Redis::OPT_READ_TIMEOUT, 5.0);
// 获取所有从节点
$slaves = $sentinel->slaves('mymaster');
foreach ($slaves as $slave) {
echo "Slave: {$slave[0]}:{$slave[1]} ({$slave[7]})\n";
}注意事项
连接异常处理
php
<?php
try {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 2.0);
$redis->auth('wrong_password');
} catch (RedisException $e) {
// RedisException 是所有 Redis 相关异常的基类
// 连接失败: Connection refused
// 认证失败: NOAUTH Authentication required
// 操作失败: ERR ...
error_log("Redis 错误: " . $e->getMessage());
// 降级处理: 使用文件缓存替代
$fallback = new FileCache('/tmp/cache');
$fallback->set('key', 'value', 3600);
}内存管理
php
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 监控内存使用
$info = $redis->info('memory');
$usedMemory = $info['used_memory_human'];
$maxMemory = $info['maxmemory'] ?: '0';
echo "已用内存: {$usedMemory}\n";
echo "最大内存: {$maxMemory}\n";
// 设置最大内存(在 redis.conf 中配置更合适)
// $redis->config('SET', 'maxmemory', '256mb');
// 设置淘汰策略
// allkeys-lru — 所有键中淘汰最近最少使用的
// volatile-lru — 只淘汰设置了过期时间的键
// allkeys-random — 随机淘汰
// volatile-ttl — 淘汰 TTL 最小的键
// noeviction — 不淘汰,写入时返回错误
// $redis->config('SET', 'maxmemory-policy', 'allkeys-lru');生产环境建议
- 不要在 PHP 代码中通过
config SET修改 Redis 配置 - 配置应写入
redis.conf文件,重启生效 - 密码和敏感信息不应硬编码在代码中
最佳实践
1. 扩展版本选择
bash
# 推荐使用最新稳定版
pecl install redis
# 查看已安装版本
php -r 'phpinfo();' | grep redis
# 常见版本对应关系:
# Redis 扩展 5.x → PHP 7.x
# Redis 扩展 6.x → PHP 8.0+
# Redis 扩展 7.x → PHP 8.1+ (最新)2. 连接健康检查
php
<?php
class RedisHealthCheck
{
private Redis $redis;
private int $retryCount = 3;
private int $retryDelay = 100; // 毫秒
public function __construct(Redis $redis)
{
$this->redis = $redis;
}
public function check(): bool
{
for ($i = 0; $i < $this->retryCount; $i++) {
try {
return (bool) $this->redis->ping();
} catch (RedisException) {
if ($i < $this->retryCount - 1) {
usleep($this->retryDelay * 1000);
}
}
}
return false;
}
public function getStats(): array
{
try {
$info = $this->redis->info();
return [
'version' => $info['redis_version'],
'uptime' => $info['uptime_in_seconds'],
'connections' => $info['connected_clients'],
'memory_used' => $info['used_memory_human'],
'hit_rate' => $this->calculateHitRate($info),
];
} catch (RedisException $e) {
return ['error' => $e->getMessage()];
}
}
private function calculateHitRate(array $info): float
{
$hits = (int) ($info['keyspace_hits'] ?? 0);
$misses = (int) ($info['keyspace_misses'] ?? 0);
$total = $hits + $misses;
return $total > 0 ? round($hits / $total * 100, 2) : 0.0;
}
}3. 多数据库使用规范
php
<?php
// Redis 有 16 个数据库(0-15),推荐分配方式:
// DB 0: 应用缓存(默认)
// DB 1: Session 存储
// DB 2: 队列
// DB 3: 限流计数器
// DB 4: 临时数据
// 但更推荐使用前缀来隔离,而非不同数据库
// 原因: 多数据库共享连接池,select() 切换有风险
$prefixes = [
'cache' => 'cache:',
'session' => 'session:',
'queue' => 'queue:',
'rate' => 'rate:',
'temp' => 'temp:',
];
// 使用 SCAN 时注意 OPT_SCAN 选项
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_REUSE);
$iterator = null;
while ($keys = $redis->scan($iterator, 'cache:*', 100)) {
foreach ($keys as $key) {
echo $key . "\n";
}
}