Redis数据类型

引言

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存对象

  1. List当队列,Set做去重

  2. SortedSet排榜单,BitMap搞统计

  3. 批量用Pipeline,原子用Lua

  4. 监控不能少,过期必须设

  5. 连接要复用,内存要优化
    立刻实施的5个优化

  6. 给所有缓存设置过期时间

  7. 使用Pipeline批量操作

  8. 大Hash拆分成小Hash

  9. 实现多级缓存策略

  10. 添加Redis健康监控

相关推荐
_F_y2 小时前
MySQL表的增删查改
android·数据库·mysql
@我不是大鹏2 小时前
3、Spring AI Alibaba(SAA)零基础速通实战之Ollama私有化部署和对接本地大模型
数据库·人工智能·spring
Linging_242 小时前
PGSQL与Mysql对比学习
数据库·学习·mysql·postgresql
Anarkh_Lee2 小时前
【免费开源】MCP 数据库万能连接器:用自然语言查询和分析数据
数据库·开源·ai编程·claude·自然语言·mcp·cherry studio
Getgit2 小时前
mysql批量更新语句
java·数据库·mysql·udp·eclipse
资深web全栈开发2 小时前
分布式锁的陷阱:Redlock 真的安全吗?
分布式·安全·wpf
alex18012 小时前
nginx配置图片静态路由
数据库·nginx·postgresql
明天…ling2 小时前
sql注入(1-10关)
java·数据库·sql
快快起来写代码2 小时前
Jenkins学习
数据库·学习·jenkins