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健康监控

相关推荐
倔强的石头_6 分钟前
关系数据库替换用金仓:数据迁移过程中的完整性与一致性风险
数据库
Elastic 中国社区官方博客12 分钟前
使用 Groq 与 Elasticsearch 进行智能查询
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
LZL_SQ14 分钟前
HCCL测试框架中AllReduce边界条件测试设计深度剖析
wpf·cann
My LQS25 分钟前
使用 Redis Stack 向量索引构建大模型问答缓存系统
redis·缓存·ai
穿过锁扣的风29 分钟前
一文搞懂 SQL 五大分类:DQL/DML/DDL/DCL/TCL
数据库·microsoft·oracle
l1t30 分钟前
DeepSeek总结的SNKV — 无查询处理器的 SQLite 键值存储
数据库·sqlite·kvstore
洛豳枭薰32 分钟前
MySQL 梳理
数据库·mysql
九.九1 小时前
CANN 算子生态的底层安全与驱动依赖:固件校验与算子安全边界的强化
大数据·数据库·安全
蓝帆傲亦1 小时前
代码革命!我用Claude Code 3个月完成1年工作量,这些实战经验全给你
jvm·数据库·oracle
亓才孓1 小时前
[JDBC]事务
java·开发语言·数据库