引言
redis在PHP生态中的地位是非常高的,几乎90%的高并发PHP项目都在用Redis,不是因为它时髦,而是因为它真能解决问题。"PHP+Redis ≈ 高性能Web应用的基石"。
一、Redis五种数据结构
1. String:远不止是字符串
php
// 实际应用场景1:分布式ID生成器
class DistributedIdGenerator {
// 每日订单号:20240115000001
public function generateOrderId(): string {
$today = date('Ymd');
$key = "counter:order:{$today}";
// 原子操作,避免重复
$seq = $redis->incr($key);
$redis->expire($key, 86400); // 当天有效
return $today . str_pad($seq, 8, '0', STR_PAD_LEFT);
}
}
// 实际应用场景2:API调用次数限制(分钟级)
class ApiRateLimiter {
public function checkLimit($apiKey, $limit = 60): bool {
$minute = date('YmdHi'); // 精确到分钟
$key = "api:limit:{$apiKey}:{$minute}";
$current = $redis->incr($key);
if ($current == 1) {
$redis->expire($key, 60); // 自动过期
}
return $current <= $limit;
}
}
2. Hash:对象存储的最佳选择
php
// 场景1:用户画像存储(电商推荐系统)
class UserProfile {
public function updateUserBehavior($userId, $productId, $action) {
$key = "user:profile:{$userId}";
// 记录浏览历史(保留最近50个)
$redis->hSet($key, "view:{$productId}", time());
// 更新标签偏好(加权)
$tag = $this->getProductTag($productId);
$redis->hIncrByFloat($key, "tag:{$tag}", 0.1);
// 购买行为权重更高
if ($action === 'purchase') {
$redis->hIncrByFloat($key, "tag:{$tag}", 1.0);
}
// 清理过期数据
$this->cleanOldViews($key);
}
// 获取用户推荐标签
public function getUserTags($userId): array {
$key = "user:profile:{$userId}";
$all = $redis->hGetAll($key);
return array_filter($all, function($k) {
return strpos($k, 'tag:') === 0;
}, ARRAY_FILTER_USE_KEY);
}
}
// 场景2:购物车实现(比MySQL快100倍)
class ShoppingCart {
public function addToCart($userId, $productId, $quantity = 1) {
$cartKey = "cart:{$userId}";
$productKey = "product:{$productId}";
// 检查库存(原子操作)
$stock = $redis->hGet($productKey, 'stock');
if ($stock < $quantity) {
throw new Exception('库存不足');
}
// 添加到购物车
$redis->hIncrBy($cartKey, $productId, $quantity);
// 设置购物车7天过期
$redis->expire($cartKey, 604800);
// 实时统计购物车商品总数
$totalItems = array_sum($redis->hVals($cartKey));
$redis->zAdd("cart:stats:daily", $totalItems, date('Ymd'));
}
}
3. List:不只是队列
php
// 场景1:消息队列(支持优先级)
class PriorityMessageQueue {
private $highQueue = "queue:high";
private $normalQueue = "queue:normal";
private $lowQueue = "queue:low";
public function push($message, $priority = 'normal') {
$queue = $this->getQueueName($priority);
// 添加消息ID和时间戳
$message['id'] = uniqid();
$message['timestamp'] = microtime(true);
$redis->lPush($queue, json_encode($message));
}
public function pop() {
// 先尝试高优先级队列
foreach (['high', 'normal', 'low'] as $priority) {
$queue = $this->getQueueName($priority);
if ($message = $redis->rPop($queue)) {
return json_decode($message, true);
}
}
return null;
}
}
php
// 场景2:用户操作时间线(类似朋友圈)
class UserTimeline {
public function postFeed($userId, $content) {
$feedId = $this->generateFeedId();
$feed = [
'id' => $feedId,
'user_id' => $userId,
'content' => $content,
'time' => time()
];
// 1. 添加到自己的时间线
$redis->lPush("timeline:user:{$userId}", json_encode($feed));
$redis->lTrim("timeline:user:{$userId}", 0, 999); // 只保留1000条
// 2. 推送给粉丝(延迟推送,避免大V发帖时压力)
$this->pushToFollowers($userId, $feedId);
}
public function getTimeline($userId, $page = 1, $size = 20) {
$start = ($page - 1) * $size;
$end = $start + $size - 1;
// 使用管道提高性能
$pipe = $redis->pipeline();
$pipe->lRange("timeline:user:{$userId}", $start, $end);
$pipe->lLen("timeline:user:{$userId}");
list($feeds, $total) = $pipe->exec();
return [
'feeds' => array_map('json_decode', $feeds),
'total' => $total,
'page' => $page
];
}
}
4. Set:去重的艺术
php
// 场景1:共同好友/兴趣匹配
class SocialNetwork {
public function findCommonInterests($user1, $user2): array {
return $redis->sInter(
"user:{$user1}:interests",
"user:{$user2}:interests"
);
}
// 你可能认识的人(差集运算)
public function recommendFriends($userId): array {
$myFriends = "user:{$userId}:friends";
// 获取朋友的朋友
$friends = $redis->sMembers($myFriends);
$pipe = $redis->pipeline();
foreach ($friends as $friendId) {
$pipe->sMembers("user:{$friendId}:friends");
}
$allFriendsOfFriends = $pipe->exec();
$flatArray = array_merge(...$allFriendsOfFriends);
// 去掉已经是好友的和自己
$candidates = array_diff(
array_unique($flatArray),
$friends,
[$userId]
);
return array_slice($candidates, 0, 10);
}
}
// 场景2:抽奖系统(公平随机)
class LotterySystem {
public function draw($lotteryId, $count = 3) {
$key = "lottery:{$lotteryId}:participants";
$total = $redis->sCard($key);
if ($total < $count) {
throw new Exception('参与人数不足');
}
// 随机抽取(保证公平)
$winners = [];
for ($i = 0; $i < $count; $i++) {
$winner = $redis->sPop($key);
if ($winner) {
$winners[] = $winner;
// 记录中奖者
$redis->sAdd("lottery:{$lotteryId}:winners", $winner);
}
}
return $winners;
}
}
5. Sorted Set:排行榜的终极武器
// 场景1:游戏实时排行榜(带时间维度)
class GameLeaderboard {
public function updateScore($playerId, $score) {
$today = date('Ymd');
$thisWeek = date('YW');
// 更新多个维度的排行榜
$pipe = $redis->pipeline();
// 总榜(永久)
$pipe->zAdd("leaderboard:all", $score, $playerId);
// 日榜(24小时过期)
$pipe->zAdd("leaderboard:daily:{$today}", $score, $playerId);
$pipe->expire("leaderboard:daily:{$today}", 86400);
// 周榜(7天过期)
$pipe->zAdd("leaderboard:weekly:{$thisWeek}", $score, $playerId);
$pipe->expire("leaderboard:weekly:{$thisWeek}", 604800);
$pipe->exec();
}
// 获取玩家在所有榜单的位置
public function getPlayerRank($playerId): array {
$today = date('Ymd');
$thisWeek = date('YW');
$pipe = $redis->pipeline();
$pipe->zRevRank("leaderboard:all", $playerId);
$pipe->zRevRank("leaderboard:daily:{$today}", $playerId);
$pipe->zRevRank("leaderboard:weekly:{$thisWeek}", $playerId);
list($allRank, $dailyRank, $weeklyRank) = $pipe->exec();
return [
'all' => $allRank !== false ? $allRank + 1 : null,
'daily' => $dailyRank !== false ? $dailyRank + 1 : null,
'weekly' => $weeklyRank !== false ? $weeklyRank + 1 : null,
];
}
}
// 场景2:延迟队列(比List更精确)
class DelayedJobQueue {
public function delay($job, $executeAt) {
$redis->zAdd("delayed:jobs", $executeAt, json_encode($job));
}
public function processReadyJobs() {
$now = time();
// 获取到期的任务
$jobs = $redis->zRangeByScore("delayed:jobs", 0, $now);
if (empty($jobs)) {
return;
}
// 原子性移除并处理
$script = "
local jobs = redis.call('zrangebyscore', KEYS[1], 0, ARGV[1])
if #jobs > 0 then
redis.call('zremrangebyscore', KEYS[1], 0, ARGV[1])
end
return jobs
";
$readyJobs = $redis->eval($script, ["delayed:jobs", $now], 1);
foreach ($readyJobs as $job) {
$this->executeJob(json_decode($job, true));
}
}
}
二、生产环境最佳实践
1. 缓存策略:多级缓存架构
// 三级缓存系统(内存 > Redis > DB)
php
class MultiLevelCache {
private $localCache = []; // 本地内存缓存
private $redis;
private $localTTL = 5; // 本地缓存5秒
private $redisTTL = 3600; // Redis缓存1小时
public function get($key, callable $loader = null) {
// 1. 检查本地内存
if (isset($this->localCache[$key])) {
$item = $this->localCache[$key];
if (time() - $item['time'] < $this->localTTL) {
return $item['data'];
}
unset($this->localCache[$key]);
}
// 2. 检查Redis
$data = $this->redis->get($key);
if ($data !== false) {
// 更新本地缓存
$this->localCache[$key] = [
'data' => $data,
'time' => time()
];
return $data;
}
// 3. 从数据源加载(DB)
if ($loader) {
$data = $loader();
// 写入缓存
$this->redis->setex($key, $this->redisTTL, $data);
$this->localCache[$key] = [
'data' => $data,
'time' => time()
];
return $data;
}
return null;
}
}
2. 监控与告警:实时掌握Redis健康状态
/
php
/ Redis健康监控系统
class RedisMonitor {
private $redis;
private $alerts = [];
public function collectMetrics(): array {
$info = $this->redis->info();
$metrics = [
// 内存使用
'memory' => [
'used' => $info['used_memory_human'],
'peak' => $info['used_memory_peak_human'],
'fragmentation' => $info['mem_fragmentation_ratio'],
'rss' => $info['used_memory_rss_human'],
],
// 客户端连接
'clients' => [
'connected' => $info['connected_clients'],
'blocked' => $info['blocked_clients'],
'max_connections' => $info['maxclients'],
],
// 命令统计
'commands' => [
'processed' => $info['total_commands_processed'],
'ops_per_sec' => $info['instantaneous_ops_per_sec'],
'rejected' => $info['rejected_connections'],
],
// 持久化
'persistence' => [
'rdb_last_save' => date('Y-m-d H:i:s', $info['rdb_last_save_time']),
'aof_size' => $info['aof_current_size_human'],
],
// 主从复制
'replication' => [
'role' => $info['role'],
'connected_slaves' => $info['connected_slaves'],
],
];
// 关键指标检查
$this->checkCriticalMetrics($metrics);
return $metrics;
}
private function checkCriticalMetrics(array $metrics): void {
// 内存使用超过90%告警
if ($metrics['memory']['used_percent'] > 90) {
$this->alerts[] = '内存使用超过90%';
}
// 连接数超过最大80%告警
$connPercent = ($metrics['clients']['connected'] / $metrics['clients']['max_connections']) * 100;
if ($connPercent > 80) {
$this->alerts[] = '连接数超过80%';
}
// 内存碎片率过高
if ($metrics['memory']['fragmentation'] > 1.5) {
$this->alerts[] = '内存碎片率过高,建议重启';
}
}
public function getSlowLogs($count = 10): array {
return $this->redis->slowLog('get', $count);
}
public function getBigKeys($threshold = 10000): array {
$bigKeys = [];
// 使用SCAN迭代所有key(避免阻塞)
$iterator = null;
do {
$keys = $this->redis->scan($iterator, '*', 100);
foreach ($keys as $key) {
$type = $this->redis->type($key);
$size = $this->getKeySize($key, $type);
if ($size > $threshold) {
$bigKeys[] = [
'key' => $key,
'type' => $type,
'size' => $size,
'ttl' => $this->redis->ttl($key)
];
}
}
} while ($iterator > 0);
return $bigKeys;
}
}
三、高级技巧与性能优化
1. Pipeline性能优化实战
php
// 批量操作优化对比
class PerformanceOptimizer {
// 场景:初始化10000个用户缓存
public function initUserCache(array $userIds) {
$start = microtime(true);
// 慢的方式:10000次网络请求
// foreach ($userIds as $userId) {
// $user = $db->getUser($userId);
// $redis->setex("user:{$userId}", 3600, json_encode($user));
// }
// 耗时:~10秒
// 快的方式:Pipeline批量操作
$pipe = $redis->pipeline();
foreach ($userIds as $userId) {
$user = $db->getUser($userId);
$pipe->setex("user:{$userId}", 3600, json_encode($user));
}
$pipe->exec();
// 耗时:~0.5秒
$time = microtime(true) - $start;
echo "优化后速度提升:" . (10 / $time) . "倍";
}
// 场景:批量获取用户信息
public function batchGetUsers(array $userIds): array {
// 使用mGet替代多个get
$keys = array_map(fn($id) => "user:{$id}", $userIds);
$results = $redis->mGet($keys);
// 处理缓存未命中
$missing = [];
foreach ($results as $i => $result) {
if ($result === false) {
$missing[] = $userIds[$i];
}
}
if (!empty($missing)) {
// 批量从数据库加载
$users = $db->getUsersByIds($missing);
// 批量写入缓存
$pipe = $redis->pipeline();
foreach ($users as $user) {
$pipe->setex("user:{$user['id']}", 3600, json_encode($user));
$results[array_search($user['id'], $userIds)] = $user;
}
$pipe->exec();
}
return array_filter($results); // 移除false值
}
}
2. Lua脚本保证原子性
php
// 分布式锁进阶版(支持可重入、自动续期)
class AdvancedDistributedLock {
private $redis;
private $lockKey;
private $lockValue;
private $expireTime;
private $renewInterval = 10; // 10秒续期一次
public function __construct($redis, $lockKey, $expireTime = 30) {
$this->redis = $redis;
$this->lockKey = $lockKey;
$this->lockValue = uniqid(gethostname(), true);
$this->expireTime = $expireTime;
}
// 获取锁(支持阻塞和非阻塞)
public function acquire($timeout = 10, $blocking = true): bool {
$startTime = microtime(true);
while (true) {
// Lua脚本保证原子性
$script = "
if redis.call('exists', KEYS[1]) == 0 then
return redis.call('setex', KEYS[1], ARGV[1], ARGV[2])
elseif redis.call('get', KEYS[1]) == ARGV[2] then
redis.call('expire', KEYS[1], ARGV[1])
return 'OK'
else
return false
end
";
$result = $this->redis->eval(
$script,
[$this->lockKey, $this->expireTime, $this->lockValue],
1
);
if ($result) {
// 启动看门狗线程自动续期
$this->startWatchDog();
return true;
}
if (!$blocking || (microtime(true) - $startTime) > $timeout) {
return false;
}
usleep(100000); // 等待100ms再试
}
}
private function startWatchDog(): void {
// 使用协程或单独进程实现锁续期
Swoole\Timer::tick($this->renewInterval * 1000, function() {
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('expire', KEYS[1], ARGV[2])
end
return 0
";
$this->redis->eval(
$script,
[$this->lockKey, $this->lockValue, $this->expireTime],
1
);
});
}
}
3. 内存优化:数据压缩与编码
php
// 内存优化策略
class MemoryOptimizer {
// 1. 使用整数编码(内存减少75%)
public function optimizeNumbers() {
// Redis会自动将小整数编码为特殊格式
$redis->set("small:number", 100); // 使用8字节
$redis->set("large:number", 1000000); // 使用32字节
// 尽量使用小整数ID
$userId = 123; // 好
$userId = "user_123"; // 差(占用更多内存)
}
// 2. Hash的内存优化
public function optimizeHashes() {
// 小Hash使用特殊编码
// 当field数量 <= hash-max-ziplist-entries (默认512)
// 且每个value大小 <= hash-max-ziplist-value (默认64字节)时
// Redis会使用ziplist编码,内存更小
$user = [
'id' => '123', // 8字节
'name' => '张三', // 6字节
'email' => 'a@b.com', // 8字节
];
$redis->hMSet("user:123", $user);
// 检查编码类型
$encoding = $redis->object('encoding', 'user:123');
// 可能是 'ziplist' 或 'hashtable'
}
// 3. 使用压缩
public function compressLargeData($key, $data) {
// 对大文本进行压缩
if (strlen($data) > 1024) { // 大于1KB
$compressed = gzcompress($data, 9);
$redis->set($key, $compressed);
// 添加标记,读取时解压
$redis->set("{$key}:compressed", 1);
} else {
$redis->set($key, $data);
}
}
public function getCompressedData($key) {
$data = $redis->get($key);
if ($redis->get("{$key}:compressed")) {
return gzuncompress($data);
}
return $data;
}
}
四、性能对比数据
Redis操作性能对比(单位:微秒)
| 操作类型 | 单次耗时 | 批量(100次) | 性能提升 |
|---|---|---|---|
| String SET | 50μs | 500μs | 10倍 |
| Pipeline SET | - | 50μs | 100倍 |
| Hash HMSet | 60μs | 600μs | 10倍 |
| List LPush | 55μs | 550μs | 10倍 |
| SortedSet ZAdd | 70μs | 700μs | 10倍 |
五、总结
Redis使用黄金法则
1.String做缓存,Hash存对象
-
List当队列,Set做去重
-
SortedSet排榜单,BitMap搞统计
-
批量用Pipeline,原子用Lua
-
监控不能少,过期必须设
-
连接要复用,内存要优化
立刻实施的5个优化 -
给所有缓存设置过期时间
-
使用Pipeline批量操作
-
大Hash拆分成小Hash
-
实现多级缓存策略
-
添加Redis健康监控