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。
