欢迎光临
我们一直在努力

【Redis合集-03】Redis常用数据结构选型指南|面试&生产场景全覆盖

目录

1.Redis7 数据结构概览

2. String 类型应用

2.1 内部结构:SDS(简单动态字符串)

2.2 常用命令实战

2.3 应用场景补充

缓存对象

分布式锁

计数器与限流

3. Hash 类型应用

3.1 常用命令

3.2 实战:购物车

3.3 底层优化

4. List 类型应用

4.1 常用命令

4.2 实战:消息队列(简易版)

4.3 实战:最新评论列表

5. Set 类型应用

5.1 内部编码

5.2 常用命令

5.3 实战场景补充

共同关注 / 推荐好友

抽奖系统

标签系统

6. Zset(Sorted Set)类型应用

6.1 常用命令

6.2 实战:排行榜

6.3 实战:延迟队列

7. Bitmap 数据类型详解

7.1 常用命令

7.2 实战:用户签到

7.3 实战:布隆过滤器

8. HyperLogLog 数据类型详解

8.1 常用命令

8.2 实战:页面 UV 统计

9. Geo 数据类型详解

9.1 常用命令

9.2 实战:附近的人

10. Stream 数据类型详解

10.1 核心概念

10.2 常用命令

10.3 实战:可靠的消息队列

11. Spring Boot 快速连接 Redis

11.1 引入依赖

11.2 配置文件

11.3 配置 RedisTemplate 序列化

11.4 使用示例

11.5 实战中的注意点

总结(个人心得)

1.Redis7 数据结构概览

Redis 7 在数据结构上延续了之前的丰富性,但底层有许多优化(例如用 listpack 替代 ziplist)。核心数据结构可以分为:

  • 基础类型:string、hash、list、set、zset

  • 扩展类型:bitmap、hyperloglog、geo、stream

  • 底层数据结构:SDS、listpack、quicklist、dict、skiplist、intset 等

每种类型都有自己最合适的应用场景,别什么都用 string,这会浪费内存并丧失 Redis 的能力。

2. String 类型应用

String 是 Redis 中最基础的类型。

2.1 内部结构:SDS(简单动态字符串)

Redis 的 string 不是直接用 C 语言的 char*,而是封装了 SDS(Simple Dynamic String)。好处:

  • O(1) 获取长度(len 字段)

  • 杜绝缓冲区溢出(自动扩容)

  • 减少内存重分配(预分配空间和惰性释放)

  • 二进制安全(可以存图片、序列化对象等)

在 Redis 7 里,string 的底层编码有 embstr 和 raw 两种:

  • 长度 ≤ 44 字节(不同版本阈值不同,7.x 为 44)时使用 embstr,内存连续,只读不修改时效率高。

  • 长度 > 44 字节或发生修改时转为 raw,即 SDS 和 RedisObject 分开存储。

2.2 常用命令实战

# 基本 SET / GET
SET user:1:name "zhangsan"
GET user:1:name

# 批量操作(减少网络 RTT)
MSET user:1:age 25 user:1:city "beijing"
MGET user:1:name user:1:age user:1:city

# 数值操作(计数器)
INCR article:100:views
INCRBY article:100:views 10
DECR article:100:views

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

# 不存在才设置(分布式锁的基石)
SETNX lock:order:123 unique_value
# Redis 7 推荐使用 SET … NX EX 原子命令
SET lock:order:123 unique_value NX EX 30

# 获取并设值
GETSET counter "0" # 旧版本,现推荐 GETEX 或组合命令
GETEX key EX 60 # 获取值同时设置过期时间

2.3 应用场景补充

缓存对象

把对象序列化成 JSON 存成 string。优点:简单。缺点:频繁修改某个字段时需要整体读出来再写回去,浪费流量和 CPU。如果对象修改频繁,后面讲的 hash 更适合。

// Spring Boot 示例
redisTemplate.opsForValue().set("user:1", JSON.toJSONString(user), 30, TimeUnit.MINUTES);

分布式锁

用 string 实现简单的分布式锁。Java 代码展示一个完整版:

String lockKey = "lock:order:123";
String clientId = UUID.randomUUID().toString();
// 加锁,同时设置过期时间防止死锁
Boolean lockSuccess = redisTemplate.opsForValue()
.setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lockSuccess)) {
try {
// 执行业务
} finally {
// 释放锁:lua 脚本保证原子性,判断 value 是否为当前客户端
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), clientId);
}
}

计数器与限流

文章阅读量、API 调用次数等直接用 INCR。滑动窗口限流可以基于 string + 过期时间实现:

String rateKey = "rate:api:" + userId;
Long count = redisTemplate.opsForValue().increment(rateKey);
if (count == 1) {
redisTemplate.expire(rateKey, 1, TimeUnit.MINUTES);
}
if (count > 100) {
throw new RuntimeException("请求过于频繁");
}

注意:这种简易限流不是滑动窗口的,更精确的需要用 zset 或 Redis-Cell 模块。

3. Hash 类型应用

Hash 适合存储对象,特别是那些字段经常单独更新的场景。内部编码有 listpack(Redis 7 中取代了 ziplist)和 hashtable。

3.1 常用命令

HSET user:1 name "lisi" age 28 city "shanghai" # 设置哈希表 user:1 中的多个字段:name="lisi", age=28, city="shanghai"。返回新添加的字段数量(此处为3)
HGET user:1 name # 获取哈希表 user:1 中字段 name 的值,返回 "lisi"
HMGET user:1 name age # 批量获取字段 name 和 age 的值,返回 ["lisi", "28"]
HINCRBY user:1 age 1 # 将字段 age 的值增加 1(原子操作),返回增加后的值(29)。要求原值可解析为整数
HGETALL user:1 # 获取哈希表 user:1 中所有字段和值,以列表形式返回,如 ["name","lisi","age","29","city","shanghai"]
HEXISTS user:1 name # 检查字段 name 是否存在,存在返回 1,不存在返回 0
HDEL user:1 city # 删除字段 city,返回成功删除的字段数量(1)
HLEN user:1 # 返回哈希表 user:1 中的字段数量(删除 city 后,还剩 name 和 age,所以返回 2)
HSCAN user:1 0 MATCH * # 大 hash 用 scan 避免阻塞:迭代哈希表 user:1 中的字段。游标从 0 开始,MATCH * 匹配所有字段。每次返回一批字段和值,用于安全遍历大哈希

3.2 实战:购物车

# 用户1 的购物车
HSET cart:1 1001 2 # 设置哈希表 cart:1 中商品1001的数量为2(如果字段不存在则创建,存在则覆盖)。返回新添加的字段数量(此处为1)
HSET cart:1 1002 1 # 添加商品1002,数量为1。返回新添加的字段数量(此处为1)
HINCRBY cart:1 1001 1 # 将商品1001的数量增加1(原子操作),返回增加后的数量(3)。可用于购物车加一件商品
HGETALL cart:1 # 获取购物车中所有商品ID和数量,以列表形式返回,如 ["1001","3","1002","1"]

用 hash 存购物车的好处是每个商品数量可以独立操作,不用像 string 那样序列化整个对象。

3.3 底层优化

当字段数量少且每个字段值短时,hash 用 listpack 紧凑存储,节省内存;当超过 hash-max-listpack-entries 或 hash-max-listpack-value 时转为 hashtable。Redis 7 用 listpack 替代了 ziplist,级联更新问题彻底解决。

4. List 类型应用

List 是简单的字符串列表,底层用 quicklist(Redis 7 仍是 quicklist,但内部节点用 listpack 替代了 ziplist)。它可以用作栈、队列、阻塞队列等。

4.1 常用命令

LPUSH queue "task1" "task2" # 将一个或多个值插入到列表 queue 的头部(左侧)。返回插入后列表的长度。此处插入 task1, task2,顺序为:先插入 task1,再插入 task2,因此 task2 在头部
RPUSH queue "task3" # 将一个或多个值插入到列表 queue 的尾部(右侧)。返回插入后列表的长度。此处 task3 追加到末尾
LPOP queue # 移除并返回列表 queue 头部(最左侧)的元素。如果列表为空,返回 nil。此处返回 "task2"(因为上一步 LPUSH 后头部是 task2)
RPOP queue # 移除并返回列表 queue 尾部(最右侧)的元素。如果列表为空,返回 nil。此处返回 "task3"(因为上一步 RPUSH 添加了 task3 到尾部)
LRANGE queue 0 -1 # 返回列表 queue 中指定区间的元素。索引从 0 开始,-1 表示最后一个元素。0 -1 表示返回所有元素。结果以数组形式返回
BLPOP queue 5 # 阻塞式左弹出(Blocking Left Pop):依次检查多个列表(此处只有一个 queue),如果指定列表非空则立即弹出头部元素;如果所有列表都为空,则阻塞等待最多 5 秒。超时后返回 nil。适用于生产者-消费者模式

BLPOP queue 5 之所以适用于生产者-消费者模式,是因为它解决了消费者端的关键问题:如何高效地等待新任务。

  • 传统轮询的缺陷:如果消费者不断用 LPOP 检查队列,队列为空时会产生大量无效请求(CPU 空转 + 网络开销)。

  • BLPOP 的阻塞机制:当队列为空时,消费者会阻塞等待,直到:

    • 生产者通过 LPUSH / RPUSH 向队列中推入新元素 → 消费者立即弹出该元素并继续处理。

    • 或者超过指定的超时时间(此处为 5 秒)→ 返回 nil,让消费者有机会做其他任务或重试。

优势总结:

  • 零无效轮询:不浪费 CPU 和 Redis 资源。

  • 即时响应:任务到达后立即被唤醒,延迟低。

  • 避免死等:超时机制防止消费者永久阻塞。

  • 典型场景:多个消费者进程/线程同时 BLPOP 同一个队列,Redis 保证每个任务只被一个消费者取走,实现天然的负载均衡和任务分发。

    4.2 实战:消息队列(简易版)

    利用 BLPOP(最左侧) 或 BRPOP (最右侧)可以实现简单的生产者-消费者模型:

    # 消费者1
    BRPOP task_queue 0 # 永久阻塞等待
    # 生产者
    LPUSH task_queue "send_email"

    这种队列没有 ack 机制,消息一旦弹出就没了,可靠性不如后面的 Stream。

    4.3 实战:最新评论列表

    用 LPUSH 插入新评论,LTRIM 保持固定长度:

    LPUSH comment:article:100 "comment1" # 将评论 "comment1" 插入到文章100的评论列表头部(左侧)。此时列表:["comment1"]
    LPUSH comment:article:100 "comment2" # 再将 "comment2" 插入头部,新评论在前。列表变为:["comment2", "comment1"]
    LTRIM comment:article:100 0 9 # 修剪列表,只保留索引0到9的元素(即最多10条)。由于当前只有2条,全部保留。常用于控制评论列表最多显示最新10条(最新的在头部)
    LRANGE comment:article:100 0 -1 # 返回列表所有元素(索引0到-1)。结果为 ["comment2", "comment1"],即最新评论在最前面

    5. Set 类型应用

    Set 是无序的字符串集合,支持交并差集。

    5.1 内部编码

    Set 有两种底层实现:

    • intset:当集合元素全是整数且数量较少时使用,有序存储,节省内存。

    • dict:当出现非整数元素或元素数量超过 set-max-intset-entries 时,转为哈希表,value 都为 NULL。

    Redis 7 还优化了 intset 的转换条件。

    5.2 常用命令

    SADD users:follow:1 "user2" "user3" # 将 user2 和 user3 添加到集合 users:follow:1 中(用户1的关注集合)。返回成功添加的元素数量(2)
    SREM users:follow:1 "user2" # 从集合中移除 user2。返回成功移除的元素数量(1)
    SMEMBERS users:follow:1 # 返回集合中的所有成员,结果为 ["user3"](无序)
    SISMEMBER users:follow:1 "user2" # 检查 user2 是否在集合中,存在返回1,不存在返回0(此处返回0)
    SCARD users:follow:1 # 返回集合中的元素个数,此处为1(只有 user3)

    # 集合运算
    SADD set1 a b c # 向集合 set1 中添加元素 a, b, c,返回添加数量(3)
    SADD set2 b c d # 向集合 set2 中添加元素 b, c, d,返回添加数量(3)
    SINTER set1 set2 # 返回两个集合的交集:["b", "c"]
    SUNION set1 set2 # 返回两个集合的并集:["a","b","c","d"](顺序可能不同)
    SDIFF set1 set2 # 返回差集(set1 中有但 set2 中没有的元素):["a"]
    SINTERSTORE dest set1 set2 # 计算 set1 和 set2 的交集,并将结果存储到新集合 dest 中(如果 dest 已存在则覆盖)。返回交集元素个数(2)。此命令原子完成,且避免网络传输大量数据

    5.3 实战场景补充

    共同关注 / 推荐好友

    # 用户1和用户2的关注列表
    SADD follow:1 userA userB userC
    SADD follow:2 userB userC userD

    # 共同关注
    SINTER follow:1 follow:2 # userB userC

    # 推荐给用户1:用户2关注了但用户1没关注的
    SDIFF follow:2 follow:1 # userD

    抽奖系统

    # 将所有参与抽奖的用户放入set
    SADD lottery "user1" "user2" "user3" "user4"

    # 随机抽取1名用户(不删除)
    SRANDMEMBER lottery 1

    # 随机抽取1名并移除(保证不重复中奖)
    SPOP lottery 1

    标签系统

    用 Set 给文章打标签,例如:

    SADD article:1:tags "redis" "database"
    SADD article:2:tags "redis" "cache"
    # 返回同时拥有两个标签的文章 → "article1"
    SINTER article:1:tags article:2:tags

    6. Zset(Sorted Set)类型应用

    Zset 是带分数的有序集合,内部使用 skiplist + dict 实现,兼顾范围查找和按成员查分的效率。Redis 7 在小数据量时先用 listpack 紧凑存储。

    6.1 常用命令

    ZADD rank 100 user1 90 user2 80 user3 # 向有序集合 rank 中添加三个成员,分数分别为 100(user1)、90(user2)、80(user3)。返回成功添加的数量(3)
    ZINCRBY rank 5 user2 # 给 user2 的分数增加 5 分(原子操作),返回增加后的分数(95)。若成员不存在则自动添加并设置分数为 5
    ZRANGE rank 0 -1 WITHSCORES # 按分数升序返回所有成员及其分数(索引 0 到 -1)。结果为 ["user3","80","user2","95","user1","100"]
    ZREVRANGE rank 0 -1 WITHSCORES # 按分数降序返回所有成员及其分数。结果为 ["user1","100","user2","95","user3","80"]
    ZSCORE rank user1 # 返回 user1 的分数,结果为 "100"
    ZRANK rank user1 # 返回 user1 在升序排名中的索引(分数最低的排名为 0)。user1 分数最高,排名为 2(因为 user3 索引0,user2索引1,user1索引2)
    ZREVRANK rank user1 # 返回 user1 在降序排名中的索引(分数最高的排名为 0)。user1 分数最高,所以返回 0
    ZCOUNT rank 80 100 # 返回分数在闭区间 [80, 100] 内的成员数量。所有三个成员分数均在区间内,返回 3
    ZREMRANGEBYRANK rank 0 2 # 移除升序排名索引从 0 到 2 的成员(即分数最低的前3个成员)。此处所有成员(索引0、1、2)均被删除,rank 变为空集合

    6.2 实战:排行榜

    # 游戏积分榜
    ZADD game_score 500 playerA 480 playerB 450 playerC
    # Top 3
    ZREVRANGE game_score 0 2 WITHSCORES
    # 查询玩家排名
    ZREVRANK game_score playerA

    6.3 实战:延迟队列

    利用 score 作为时间戳,消费者不断获取分数 ≤ 当前时间的元素并处理。

    # 生产者添加延迟任务
    ZADD delay_queue 1680000000 "order_timeout_123"
    # 消费者定期拉取到期的任务
    ZRANGEBYSCORE delay_queue 0 1680000000 LIMIT 0 10
    # 处理完删除
    ZREM delay_queue "order_timeout_123"

    7. Bitmap 数据类型详解

    Bitmap 不是一种新的数据类型,本质是 string,但提供位操作命令。适合只有两种状态的布尔型海量数据统计。

    7.1 常用命令

    SETBIT user:login:20220101 100 1 # 将 key 为 "user:login:20220101" 的位图(bitmap)的第 100 位设置为 1,表示 uid=100 的用户在 2022年1月1日 登录了。返回该位原来的值(之前是 0,则返回 0)
    GETBIT user:login:20220101 100 # 获取该位图中第 100 位的值,返回 1(表示已登录)。若用户未登录,返回 0
    BITCOUNT user:login:20220101 # 统计该位图中所有值为 1 的位的个数,即当天登录的总用户数(假设每个 uid 对应一个偏移量)。返回整数值
    BITOP AND result key1 key2 # 对 key1 和 key2 对应的位图执行按位与(AND)操作,结果存入 result 键。BITOP 还支持 OR、XOR、NOT 操作。返回结果位图的字节长度
    BITPOS key 1 # 返回 key 对应位图中第一个值为 1 的位的偏移量(从 0 开始计数)。若所有位都是 0,则返回 -1。可用于快速查找最早登录的用户等场景

    7.2 实战:用户签到

    # uid=1000 的用户在2022年第一天签到
    SETBIT sign:1000:2022 0 1
    # 第365天签到
    SETBIT sign:1000:2022 364 1
    # 统计该用户2022年签到总天数
    BITCOUNT sign:1000:2022

    7.3 实战:布隆过滤器

    虽然布隆过滤器在 Redis 4.0 之后有了模块(RedisBloom),但我们可以用 bitmap 自己实现一个简单的布隆过滤器,原理是通过多个哈希函数把元素映射到位图的几个位置并置 1,查询时如果所有位置都为 1 则可能存在。

    8. HyperLogLog 数据类型详解

    HyperLogLog 是一种基数统计算法,占用空间固定(12 KB),误差率 0.81%。适合 UV 统计这种不需要精确值的场景。

    8.1 常用命令

    PFADD uv:page1 user1 user2 user3 # 将元素 user1, user2, user3 添加到 HyperLogLog 结构 uv:page1 中。HyperLogLog 是一种概率数据结构,用于高效统计基数(唯一元素数量),内存占用固定(约12KB)。返回 1 表示底层估计值发生变化(通常是第一次添加新元素),返回 0 表示添加的元素可能已经存在过(不影响基数估计)
    PFCOUNT uv:page1 # 返回 uv:page1 的近似基数(唯一用户数)。由于使用概率算法,结果存在误差,默认标准误差为 0.81%(配置可调)。此处返回 3(因为有 3 个不同用户)。注意:PFCOUNT 可同时统计多个 HLL,如 PFCOUNT uv:page1 uv:page2
    PFMERGE dest uv:page1 uv:page2 # 合并多个 HyperLogLog 结构(uv:page1 和 uv:page2)到一个新的 HLL(dest)中。dest 会被创建或覆盖,合并后的基数估计等于多个集合的并集基数。常用于汇总多个维度的 UV 数据,比如合并不同日期的 HLL 得到月活 UV

    8.2 实战:页面 UV 统计

    不用为每个用户维护 set,直接用 HLL 节省内存。

    # 记录用户访问
    PFADD uv:homepage userId1 userId2 …
    # 获取访问量近似值
    PFCOUNT uv:homepage

    用 Set 存储用户 ID 和 HLL 的内存差异:当 UV 达到亿级时,Set 消耗 GB 级别内存,HLL 仍然 12KB。

    9. Geo 数据类型详解

    Geo 内部基于 zset 实现,将经纬度编码成一个 score,用来计算地理位置距离、范围查询等。

    9.1 常用命令

    GEOADD cities 116.40 39.90 "beijing" # 将北京(经度116.40,纬度39.90)添加到地理集合 cities 中。返回成功添加的元素数量(1)
    GEOADD cities 121.47 31.23 "shanghai" # 添加上海(经度121.47,纬度31.23)。返回成功添加的数量(1)
    GEODIST cities beijing shanghai km # 计算北京到上海的距离,单位 km。返回距离值(约1067.8 km,球面距离)。单位还可选 m(米)、mi(英里)、ft(英尺)
    GEORADIUS cities 116 39 500 km WITHCOORD # 从指定坐标(经度116,纬度39)为中心,半径500公里内查找城市,并返回坐标(WITHCOORD)。可加 WITHHASH、WITHDIST 等选项。结果可能包含北京(距离在500km内),上海超出范围
    GEORADIUSBYMEMBER cities beijing 1000 km # 以城市集合中的北京为中心,半径1000公里内查找其他城市。返回符合的城市名称(例如上海可能在此范围内,实际约1067km > 1000km,所以不返回上海;若半径设为1100km,则返回上海)
    GEOHASH cities beijing # 返回北京的地理位置 Geohash 字符串(如 "wx4g0s8p3t")。Geohash 是一种将二维经纬度编码为字符串的算法,可用于快速邻近查找

    9.2 实战:附近的人

    # 存储用户位置
    GEOADD user_location 116.38 39.92 user1
    # 查找5km范围内的用户
    GEORADIUS user_location 116.38 39.92 5 km

    可以配合移动端实时更新位置。

    10. Stream 数据类型详解

    Stream 是 Redis 5.0 引入的持久化消息队列,Redis 7 中功能更完善。它弥补了 List 做队列没有消费者组、没有 ACK 的缺陷。

    10.1 核心概念

    • 消息:键值对组成的条目。

    • 消息 ID:时间戳-序号形式,如 1680000000000-0。

    • 消费者组:组内消费者分摊消息,支持 ACK 和重试

    10.2 常用命令

    # 添加消息
    XADD mystream * field1 value1 field2 value2 # 向 Stream (消息队列) mystream 中添加一条消息,* 表示由 Redis 自动生成消息 ID(格式:<毫秒时间戳>-<序列号>),后续字段为 field1=value1, field2=value2。返回生成的消息 ID,如 "1680000000000-0"

    # 读取消息(从某个ID开始)
    XREAD COUNT 2 STREAMS mystream 0 # 从 Stream mystream 中读取最多 2 条消息,从消息 ID 大于 0 的位置开始(0 表示从最早的消息开始)。返回消息列表(包含消息 ID 和字段)。常用用法:XREAD BLOCK 0 STREAMS mystream $ 阻塞等待新消息

    # 创建消费者组
    XGROUP CREATE mystream mygroup 0 # 在 Stream mystream 上创建消费者组 mygroup,起始偏移量为 0(表示从 Stream 的开头开始消费,也可用 $ 表示只消费新消息)。返回 OK。若组已存在可加 MKSTREAM 选项自动创建 Stream

    # 消费者组读取(读取未确认的新消息)
    XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream > # 消费者 consumer1 从消费者组 mygroup 中读取 1 条尚未被任何消费者确认的消息。> 表示只读取从未投递给其他消费者的新消息(即待处理队列中的未分发消息)。返回消息及消息 ID,同时该消息会进入消费者的 Pending 状态,直到被 XACK 确认

    # 确认消息
    XACK mystream mygroup 1680000000000-0 # 确认消费者组 mygroup 已成功处理消息 ID 为 1680000000000-0 的消息。从 Pending 列表中移除该消息。返回成功确认的数量(1 或 0)

    # 查看挂起的消息(未ACK)
    XPENDING mystream mygroup # 查看消费者组 mygroup 中所有已投递但尚未确认的消息(即 Pending 条目)的统计信息。返回总条数、最小和最大 ID,以及各消费者的消费条目概览(消费者名及其挂起消息数)。用于监控消息处理进度

    10.3 实战:可靠的消息队列

    # 生产者
    XADD order_stream * order_id 123 action "create" # 向 Stream(消息队列)order_stream 中添加一条订单消息,* 表示自动生成消息 ID(格式:<毫秒时间戳>-<序列号>),消息包含两个字段:order_id=123,action="create"。返回生成的消息 ID,如 "1680000000000-0"

    # 消费者组处理
    XGROUP CREATE order_stream order_group 0 # 在 Stream order_stream 上创建消费者组 order_group,起始消费偏移量为 0(表示从 Stream 的最早消息开始读取)。如果 Stream 不存在,可加 MKSTREAM 选项自动创建。返回 OK

    XREADGROUP GROUP order_group order_consumer1 COUNT 1 BLOCK 5000 STREAMS order_stream > # 消费者 order_consumer1 从消费者组 order_group 中读取消息。COUNT 1 表示最多读取 1 条消息;BLOCK 5000 表示如果没有新消息,则阻塞等待最多 5000 毫秒(5 秒);> 表示只读取从未被任何消费者读取过的**新消息**(即消费组中尚未分发的消息)。返回消息内容(包含消息 ID 和字段),同时该消息会进入该消费者的 Pending 列表(待确认状态)

    # 处理成功后 ACK
    XACK order_stream order_group messageId # 消费者确认已成功处理指定消息(messageId 需替换为实际的消息 ID,如 "1680000000000-0")。确认后,消息会从消费者组的 Pending 列表中移除,避免重复消费。返回成功确认的消息数量(1 或 0)

    与 List 做队列的区别:

    • List 弹出后消息就没了,消费者崩溃会丢消息。

    • Stream 有 ACK 机制和待处理消息列表,保证消息被正确处理。

    11. Spring Boot 快速连接 Redis

    整合 Spring Boot 和 Redis 7,我将整理出核心配置和常见坑点。

    11.1 引入依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!– 连接池(推荐) –>
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    </dependency>

    11.2 配置文件

    spring:
    redis:
    host: 127.0.0.1
    port: 6379
    password: yourpassword
    database: 0
    lettuce: # 默认 lettuce 客户端
    pool:
    max-active: 10
    max-idle: 5
    min-idle: 2

    11.3 配置 RedisTemplate 序列化

    如果不配置,默认使用 Jdk 序列化,可读性差且占用大。建议改为 JSON 序列化:

    @Configuration
    public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);

    // Jackson 序列化
    Jackson2JsonRedisSerializer<Object> jacksonSerializer =
    new Jackson2JsonRedisSerializer<>(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
    ObjectMapper.DefaultTyping.NON_FINAL);
    jacksonSerializer.setObjectMapper(om);

    // String 序列化
    StringRedisSerializer stringSerializer = new StringRedisSerializer();

    template.setKeySerializer(stringSerializer);
    template.setHashKeySerializer(stringSerializer);
    template.setValueSerializer(jacksonSerializer);
    template.setHashValueSerializer(jacksonSerializer);
    template.afterPropertiesSet();
    return template;
    }
    }

    11.4 使用示例

    @RestController
    public class RedisController {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @GetMapping("/set")
    public String setValue() {
    redisTemplate.opsForValue().set("test_key", "hello redis7");
    return "ok";
    }

    @GetMapping("/zset")
    public void zsetTest() {
    redisTemplate.opsForZSet().add("rank", "user1", 100);
    Set<Object> top = redisTemplate.opsForZSet().reverseRange("rank", 0, 2);
    System.out.println(top);
    }
    }

    11.5 实战中的注意点

    • 大 Key 问题:用 HSCAN、SSCAN、ZSCAN 分批获取,别用 HGETALL、SMEMBERS 直接暴拉。

    • 热 Key 问题:高频访问的 key 加随机后缀分散到多个 key,或使用本地缓存缓解。

    • 连接池配置:根据并发量合理设置,避免连接耗尽。

    总结(个人心得)

    通过学习Redis 7 数据结构,最深的感受是:选对类型比优化代码更重要。下面是我自己的决策小抄:

    • 需要简单缓存 → String

    • 对象字段频繁独立更新 → Hash

    • 队列 / 栈 → List

    • 去重、标签、交并差 → Set

    • 排行榜、延迟队列 → Zset

    • 签到、大量布尔统计 → Bitmap

    • UV 统计 → HyperLogLog

    • 附近的人、距离计算 → Geo

    • 高可靠消息队列 → Stream(消息有专属中间件 如MQ等,感觉用处不大)

    在实际项目中,我习惯结合 Spring Boot + RedisTemplate,配合 lua 脚本保证原子性,基本上能满足分布式系统中绝大部分场景的需求。

    最后,如有改进之处欢迎指正,觉得有帮助的话不妨点赞收藏支持一下,感谢!

    赞(0)
    未经允许不得转载:171主机测评 » 【Redis合集-03】Redis常用数据结构选型指南|面试&生产场景全覆盖
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址