Skip to content

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.2

php.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";
    }
}

参考链接