PHP与Redis缓存实践完整方案

📅 2026/7/17 22:23:35 👁️ 阅读次数 📝 编程学习
PHP与Redis缓存实践完整方案

PHP与Redis缓存实践完整方案

Redis在PHP项目里太常用了。缓存、队列、计数器、排行榜,各种场景都能用上。今天写一份完整的Redis实践指南。

先从最基础的连接和基本操作说起。

```php
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 如果有密码
// $redis->auth('password');
// 选择数据库
// $redis->select(1);

echo "连接状态: " . ($redis->ping() ? '正常' : '异常') . "\n";

// 字符串操作
$redis->set('key1', 'value1');
echo $redis->get('key1') . "\n";

// 带过期时间的设置
$redis->setex('session:123', 3600, 'user_data');
$redis->set('temp', '临时数据', 60); // 60秒过期

// 批量操作
$redis->mSet(['k1' => 'v1', 'k2' => 'v2', 'k3' => 'v3']);
$values = $redis->mGet(['k1', 'k2', 'k3']);
print_r($values);
?>
```

List可以用作队列或栈。

```php
// 列表操作
$redis->del('queue');

// 从左边推入
$redis->lPush('queue', '任务C');
$redis->lPush('queue', '任务B');
$redis->lPush('queue', '任务A');

// 从右边弹出(先进先出)
while ($task = $redis->rPop('queue')) {
echo "处理: $task\n";
}

// 队列长度
$redis->lPush('queue', 'task1', 'task2', 'task3');
echo "队列长度: " . $redis->lLen('queue') . "\n";

// 范围获取
$range = $redis->lRange('queue', 0, -1);
print_r($range);

// 阻塞弹出(等待队列有数据)
// $task = $redis->brPop(['queue'], 5); // 等待5秒
?>
```

Hash适合存储对象类型的数据。

```php
$redis->del('user:1001');

// 存储用户信息
$redis->hSet('user:1001', 'name', '张三');
$redis->hSet('user:1001', 'age', 28);
$redis->hSet('user:1001', 'email', 'zhangsan@test.com');

// 获取单个字段
echo "姓名: " . $redis->hGet('user:1001', 'name') . "\n";

// 获取所有字段
$user = $redis->hGetAll('user:1001');
print_r($user);

// 批量设置
$redis->hMSet('user:1002', [
'name' => '李四',
'age' => 35,
'email' => 'lisi@test.com',
]);

// 字段是否存在
echo "存在name: " . ($redis->hExists('user:1002', 'name') ? '是' : '否') . "\n";

// 获取所有键和值
$fields = $redis->hKeys('user:1001');
$values = $redis->hVals('user:1001');
print_r($fields);
print_r($values);

// 计数器
$redis->hIncrBy('user:1001', 'login_count', 1);
echo "登录次数: " . $redis->hGet('user:1001', 'login_count') . "\n";
?>
```

Set适合做集合运算。

```php
// 用户标签
$redis->sAdd('user:1:tags', 'PHP', 'JavaScript', 'MySQL', 'Redis');
$redis->sAdd('user:2:tags', 'PHP', 'Python', 'Docker', 'Redis');

// 共同标签(交集)
$commonTags = $redis->sInter('user:1:tags', 'user:2:tags');
echo "共同标签: " . implode(', ', $commonTags) . "\n";

// 所有标签(并集)
$allTags = $redis->sUnion('user:1:tags', 'user:2:tags');
echo "所有标签: " . implode(', ', $allTags) . "\n";

// 差异标签
$diffTags = $redis->sDiff('user:1:tags', 'user:2:tags');
echo "User1特有: " . implode(', ', $diffTags) . "\n";

// 随机获取成员
$random = $redis->sRandMember('user:1:tags', 2);
echo "随机标签: " . implode(', ', $random) . "\n";

// 集合大小
echo "User1标签数: " . $redis->sCard('user:1:tags') . "\n";
?>
```

Sorted Set(有序集合)适合排行榜场景。

```php
// 游戏排行榜
$redis->zAdd('leaderboard', 9500, '张三');
$redis->zAdd('leaderboard', 8800, '李四');
$redis->zAdd('leaderboard', 9200, '王五');
$redis->zAdd('leaderboard', 7800, '赵六');
$redis->zAdd('leaderboard', 9900, '钱七');

// 获取前3名(从高到低)
$top3 = $redis->zRevRange('leaderboard', 0, 2, true);
echo "排行榜前三:\n";
foreach ($top3 as $player => $score) {
echo " $player: $score分\n";
}

// 获取某人的排名
$rank = $redis->zRevRank('leaderboard', '李四');
echo "李四排名: 第" . ($rank + 1) . "名\n";

// 获取某人的分数
echo "张三分数: " . $redis->zScore('leaderboard', '张三') . "\n";

// 增加分数
$redis->zIncrBy('leaderboard', 100, '王五');
echo "王五加分后: " . $redis->zScore('leaderboard', '王五') . "\n";

// 统计分数区间人数
$count = $redis->zCount('leaderboard', 8000, 9500);
echo "8000-9500分人数: $count\n";
?>
```

Redis的发布订阅模式可以用于消息通知。

```php
// 发布者
function publishMessage(Redis $redis, string $channel, string $message): void
{
$redis->publish($channel, $message);
echo "已发布到频道 $channel: $message\n";
}

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
publishMessage($redis, 'notifications', '有新订单');
publishMessage($redis, 'notifications', '用户注册成功');
?>
```

Redis的Lua脚本可以在服务端执行原子操作。

```php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// Lua脚本:限流
$rateLimitScript = '
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])

local current = redis.call("GET", key)
if current and tonumber(current) >= limit then
return 0
end

redis.call("INCR", key)
redis.call("EXPIRE", key, window)
return 1
';

$redis->eval($rateLimitScript, ['rate_limit:api', 100, 60], 1);
echo "请求通过\n";
?>

分布式锁也是Redis的常见用途。

class RedisLock
{
private Redis $redis;
private string $prefix = 'lock:';
private int $defaultTTL = 10;

public function __construct(Redis $redis)
{
$this->redis = $redis;
}

public function acquire(string $key, int $ttl = null): ?string
{
$ttl = $ttl ?: $this->defaultTTL;
$lockKey = $this->prefix . $key;
$token = bin2hex(random_bytes(16));

// SET NX EX 原子操作
$result = $this->redis->set($lockKey, $token, ['NX', 'EX' => $ttl]);

if ($result) {
return $token;
}

return null;
}

public function release(string $key, string $token): bool
{
$lockKey = $this->prefix . $key;

// Lua脚本保证原子性:只有持有锁的人才能释放
$script = '
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
';

return (bool)$this->redis->eval($script, [$lockKey, $token], 1);
}

public function withLock(string $key, callable $callback, int $ttl = null): mixed
{
$token = $this->acquire($key, $ttl);

if ($token === null) {
throw new RuntimeException("无法获取锁: $key");
}

try {
return $callback();
} finally {
$this->release($key, $token);
}
}
}

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$lock = new RedisLock($redis);

try {
$lock->withLock('process:order:123', function () {
echo "处理订单中...\n";
sleep(1);
echo "订单处理完成\n";
return ['status' => 'success'];
});
} catch (RuntimeException $e) {
echo "错误: {$e->getMessage()}\n";
}
?>
```

Redis在实际项目中的性能优势很明显。但要注意内存管理,设置合理的过期策略。还有持久化配置,RDB和AOF各有优劣。数据结构的选用也要看具体场景,不是所有数据都适合放Redis。