Redis 数据结构详解

Redis 五种数据结构详解

Redis 不只是缓存,它的五种数据结构各有各的用处,搞懂了才能用对地方。

1. String(字符串)

最基础的数据结构,存字符串、数字都行。

SET name "张三"
GET name
INCR count
SET token "abc123" EX 3600

应用场景:缓存用户信息、计数器、分布式锁、会话存储。

2. Hash(哈希)

存键值对,适合存对象。

HSET user:1 name "张三" age 25 city "北京"
HGET user:1 name
HGETALL user:1

应用场景:存用户信息、商品信息,比 String 存 JSON 更省空间。

3. List(列表)

有序列表,可以从头或尾加元素。

LPUSH messages "hello"
LPUSH messages "world"
LRANGE messages 0 -1

应用场景:消息队列、最新消息列表、朋友圈时间线。

4. Set(集合)

无序不重复集合。

SADD tags "PHP"
SADD tags "MySQL"
SMEMBERS tags
SISMEMBER tags "PHP"

应用场景:标签、共同好友、去重。

5. ZSet(有序集合)

每个元素带分数,按分数排序。

ZADD rank 100 "张三"
ZADD rank 90 "李四"
ZREVRANGE rank 0 9 WITHSCORES

应用场景:排行榜、计分板、优先级队列。

总结

五种数据结构各有用处:

  • String:简单缓存、计数器
  • Hash:存对象
  • List:队列、时间线
  • Set:去重、标签
  • ZSet:排行榜

选对数据结构,性能才能发挥出来。


emer 发布于  2026-10-4 10:50 

Redis 缓存穿透解决

Redis 缓存穿透解决方案

缓存穿透是指查询一个数据库里根本不存在的数据,因为缓存里也没有,所以每次请求都打到数据库。如果有人恶意攻击,数据库可能被打挂。

什么是缓存穿透

正常流程:先查缓存,缓存有就直接返回,没有再查数据库,然后写入缓存。

如果查询的数据数据库里也没有,那缓存永远都不会命中,每次请求都会查数据库,这就是缓存穿透。

解决方案一:缓存空值

最简单的办法,数据库查不到也往缓存里写一个空值,设个短过期时间。

$data = $redis->get($key);
if ($data === null) {
    $data = $db->query($sql);
    if ($data === null) {
        // 缓存空值,60秒过期
        $redis->setex($key, 60, '');
    } else {
        $redis->setex($key, 3600, $data);
    }
}

优点:简单直接
缺点:会占用更多缓存空间,大量空值缓存会浪费内存

解决方案二:布隆过滤器

在缓存前面加一层布隆过滤器,先判断数据是否存在,不存在直接返回。

布隆过滤器的原理是用一个大的位数组和多个哈希函数,把存在的数据标记为1。查询时如果所有哈希位置都是1,说明可能存在;只要有一个位置是0,说明一定不存在。

优点:内存占用小,效率高
缺点:有一定误判率(说存在可能不存在,说不存在一定不存在)

两种方案对比

方案 优点 缺点 适用场景
缓存空值 简单 浪费内存 数据量小,穿透不严重
布隆过滤器 内存效率高 有误差 数据量大,穿透严重

总结

缓存穿透是常见的缓存问题,小项目用缓存空值就够了,大项目数据量大就上布隆过滤器。两种方案都能解决问题,根据实际场景选就行。


emer 发布于  2026-10-4 10:43 

Redis 持久化详解

Redis 两种持久化机制对比

Redis 是内存数据库,重启数据就没了,所以持久化很重要。Redis 有两种持久化方式:RDB 和 AOF。

1. RDB 快照

RDB 就是把整个内存数据快照存成文件。

优点:

  • 文件小,恢复快
  • 适合备份和灾难恢复

缺点:

  • 可能丢数据,两次快照之间的写入会丢

配置:

save 900 1        # 900秒内至少1个key变化就快照
save 300 10       # 300秒内至少10个key变化
save 60 10000     # 60秒内至少10000个key变化

2. AOF 日志

AOF 把每个写操作都记录到日志文件,重启时重放日志恢复数据。

优点:

  • 数据更安全,最多丢1秒
  • 日志可读,误删可以手动修复

缺点:

  • 文件比 RDB 大
  • 恢复慢

配置:

appendonly yes
appendfsync everysec   # 每秒刷盘一次

3. 混合持久化

Redis 4.0 支持混合持久化,RDB 做全量,AOF 做增量。

aof-use-rdb-preamble yes

生产环境推荐用这个,兼顾性能和数据安全。

4. 怎么选

  • 纯缓存场景:不要持久化,性能最好
  • 数据不能丢:用 AOF 或混合持久化
  • 需要快速恢复:用 RDB

总结

Redis 持久化没有完美方案,根据业务场景选。生产环境建议混合持久化,兼顾性能和数据安全。


emer 发布于  2026-10-4 10:30 

Redis 数据结构:五种常用类型和应用场景实战

前言

Redis 不只是简单的缓存,它有 5 种常用数据结构,每种都有特定的应用场景。用对了能解决很多问题,用错了就是浪费内存。本文讲清楚这 5 种数据结构。

一、String(字符串)

是什么

最简单的 key-value 结构,value 可以是字符串、数字、二进制。

常用命令

# 设置值
SET name "张三"

# 获取值
GET name

# 数字自增
INCR count
INCRBY count 10

# 设置过期时间
SETEX session:token 3600 "abc123"

# 批量操作
MSET name "张三" age 18 city "北京"
MGET name age city

应用场景

  1. 缓存:缓存用户信息、配置
  2. 计数器:文章阅读数、点赞数
  3. 分布式锁:SETNX 命令
  4. Session 存储:用户登录状态

二、Hash(哈希)

是什么

一个 key 对应一个 map,适合存储对象。

常用命令

# 设置单个字段
HSET user:1 name "张三"
HSET user:1 age 18

# 获取单个字段
HGET user:1 name

# 获取所有字段
HGETALL user:1

# 批量设置
HMSET user:1 name "张三" age 18 city "北京"

# 字段自增
HINCRBY user:1 age 1

应用场景

  1. 存储对象:用户信息、商品信息
  2. 购物车:key 是用户 ID,field 是商品 ID,value 是数量
# 购物车
HSET cart:1001 商品A 2
HSET cart:1001 商品B 1

三、List(列表)

是什么

有序列表,底层是双向链表,可以从左右两边 push/pop。

常用命令

# 左边插入
LPUSH messages "hello"
LPUSH messages "world"

# 右边插入
RPUSH messages "end"

# 左边弹出
LPOP messages

# 右边弹出
RPOP messages

# 查看列表范围
LRANGE messages 0 -1

# 列表长度
LLEN messages

应用场景

  1. 消息队列:LPUSH + RPOP
  2. 最新消息:比如最新评论、最新文章
  3. 朋友圈时间线:LPUSH 新内容,LRANGE 分页查看

四、Set(集合)

是什么

无序且不重复的集合,自动去重。

常用命令

# 添加元素
SADD tags "PHP" "MySQL" "Redis"

# 查看所有元素
SMEMBERS tags

# 判断元素是否存在
SISMEMBER tags "PHP"

# 集合运算
SINTER set1 set2  # 交集
SUNION set1 set2  # 并集
SDIFF set1 set2   # 差集

# 集合大小
SCARD tags

应用场景

  1. 标签系统:一篇文章的标签
  2. 共同好友:SINTER 算交集
  3. 去重:UV 统计、防止重复提交
  4. 抽奖:SRANDMEMBER 随机抽取

五、ZSet(有序集合)

是什么

每个元素都有一个 score,按 score 排序,不重复。

常用命令

# 添加元素
ZADD rank 100 "张三"
ZADD rank 90 "李四"
ZADD rank 95 "王五"

# 按分数从低到高排序
ZRANGE rank 0 -1 WITHSCORES

# 按分数从高到低排序
ZREVRANGE rank 0 -1 WITHSCORES

# 查看元素排名
ZREVRANK rank "张三"

# 查看元素分数
ZSCORE rank "张三"

# 分数范围查询
ZRANGEBYSCORE rank 90 100

应用场景

  1. 排行榜:直播间送礼排行、游戏排行
  2. 延迟队列:score 存时间戳,到点的任务处理
  3. 加权排序:搜索结果排序

六、怎么选数据结构

场景 用什么
简单 key-value String
存对象 Hash
有序列表/队列 List
去重/标签 Set
排序/排行 ZSet

七、常见坑

  1. List 太多元素:链表太长,范围查询慢
  2. Hash 字段太多:小对象还好,大对象不适合
  3. Set 当 List 用:Set 无序,要顺序用 List 或 ZSet
  4. 大 key:一个 key 存几百万数据,会阻塞

总结

Redis 5 种数据结构的核心要点:

  1. String:最简单,缓存、计数器
  2. Hash:存对象,用户信息、购物车
  3. List:有序队列,消息、时间线
  4. Set:去重集合,标签、共同好友
  5. ZSet:排序集合,排行榜、延迟队列

选对数据结构,Redis 才能发挥最大威力。


emer 发布于  2026-10-4 10:14 

Redis 缓存策略:穿透、击穿、雪崩的完整解决方案

前言

Redis 缓存是高并发系统的标配,但用不好反而会出大问题。缓存穿透、击穿、雪崩这三个经典问题,几乎每个后端开发都踩过坑。本文用实战的方式,带你彻底搞懂这三个问题的原理和解决方案。

一、缓存穿透

什么是穿透

查询一个数据库里根本不存在的数据,缓存里也没有,每次请求都打到数据库上。这就是缓存穿透。

比如有人恶意请求 id = -1 的用户数据,数据库里永远查不到,缓存里也永远没有,每次请求都要查一次数据库。

解决方案

1. 缓存空值

function getUserById($id) {
    $user = $redis->get("user:{$id}");

    if ($user === false) {
        // 数据库查询
        $user = $db->query("SELECT * FROM users WHERE id = ?", [$id])->fetch();

        if ($user) {
            $redis->setex("user:{$id}", 3600, json_encode($user));
        } else {
            // 空值也缓存,短时间过期
            $redis->setex("user:{$id}", 60, 'NULL');
        }
    }

    return $user === 'NULL' ? null : $user;
}

优点:简单直接
缺点:如果恶意请求的 id 非常多,会占用大量缓存空间

2. 布隆过滤器

在缓存前面加一层布隆过滤器,先判断这个 id 是否存在,不存在直接返回。

// 初始化布隆过滤器,把所有存在的 id 放进去
$bloomFilter = new RedisBloomFilter($redis);
$bloomFilter->add($allUserIds);

function getUserById($id) {
    // 先查布隆过滤器,不存在直接返回
    if (!$bloomFilter->exists($id)) {
        return null;
    }

    // 再走缓存查询...
}

优点:占用空间小,效率高
缺点:有一定误判率,实现复杂

二、缓存击穿

什么是击穿

某个热点 key 突然过期了,这一瞬间大量请求同时打到数据库上,就像被击穿了一样。

和穿透的区别:穿透是数据不存在,击穿是数据存在但缓存过期了。

解决方案

1. 互斥锁

function getHotProduct($id) {
    $product = $redis->get("product:{$id}");

    if ($product === false) {
        // 尝试获取锁
        $lockKey = "lock:product:{$id}";
        $isLocked = $redis->set($lockKey, 1, ['NX', 'EX' => 10]);

        if ($isLocked) {
            // 拿到锁,查数据库
            $product = $db->query("SELECT * FROM products WHERE id = ?", [$id])->fetch();
            $redis->setex("product:{$id}", 3600, json_encode($product));
            $redis->del($lockKey);
        } else {
            // 没拿到锁,等一会再查缓存
            usleep(10000);
            return getHotProduct($id);
        }
    }

    return $product;
}

优点:保证只有一个请求查数据库
缺点:实现复杂,有死锁风险

2. 热点数据永不过期

对真正的热点数据,直接设置永不过期,由后台异步更新。

// 热点数据不设过期时间,后台定时更新
$redis->set("hot_product:{$id}", json_encode($product));

三、缓存雪崩

什么是雪崩

大量缓存 key 在同一时间集体过期,或者 Redis 整个宕机了,所有请求瞬间全部打到数据库上,数据库直接被压垮。

和击穿的区别:击穿是一个热点 key,雪崩是大量 key 同时失效。

解决方案

1. 过期时间加随机值

// 不要所有 key 都设成 3600 秒过期
$ttl = 3600 + rand(0, 600);  // 在 1 小时基础上加 0-10 分钟随机值
$redis->setex("product:{$id}", $ttl, json_encode($product));

这样就不会出现大量 key 同一时间过期的情况。

2. Redis 集群高可用

部署 Redis 主从 + 哨兵,或者 Redis Cluster,确保 Redis 本身不会单点故障。

3. 服务降级限流

当数据库压力过大时,主动限流,返回兜底数据。

// 简单的降级逻辑
if ($redis->ping() === false) {
    // Redis 挂了,直接返回默认数据
    return getDefaultProduct();
}

四、三种问题对比

问题 原因 特点 解决方案
穿透 数据不存在 查不到的数据一直被请求 缓存空值 / 布隆过滤器
击穿 热点 key 过期 单个热点 key 瞬间失效 互斥锁 / 永不过期
雪崩 大量 key 同时过期 大面积缓存失效 随机过期时间 / 集群 / 限流

总结

缓存三件套的核心思路:

  1. 穿透:不让不存在的数据反复打数据库
  2. 击穿:热点数据过期时只让一个请求去查数据库
  3. 雪崩:让缓存失效时间错开,同时保证 Redis 高可用

搞懂这三个问题,你的缓存方案才算真正入门了。


emer 发布于  2026-10-4 09:58