[Java] Redis基础
更好的阅读体验
https://www.yuque.com/ahaccmt/dfvafl/fg66ezu0nmafakkq?singleDoc# 《Redis基础》
1 Redis 基础
1 通用命令
键(Key)相关命令
-
SET key value
设置一个键值对。如果键已经存在,会覆盖它。SET name "Alice"
-
GET key
获取指定键的值。如果键不存在,返回nil
。GET name
-
DEL key
删除指定的键。DEL name
-
EXISTS key
检查键是否存在,存在返回 1,否则返回 0。EXISTS name
-
EXPIRE key seconds
为指定键设置过期时间(秒)。EXPIRE name 60
-
TTL key
获取指定键的剩余过期时间。TTL name
-
PERSIST key
移除键的过期时间,使其变为持久化键。PERSIST name
-
RENAMENX old_key new_key
重命名一个键,如果新键不存在时才重命名成功。RENAMENX old_name new_name
2 String类型
1. 基本概念
Redis 的 String 类型是最简单的键值对存储方式,键和值均为字符串类型(注意:字符串不仅限于字面意义上的文本,也可以是数字、二进制数据等)。String 类型的值的最大长度是 512 MB。
string类型可以为:
- 字符串
- 整型
- 浮点型
2. 常见命令
Redis 提供了一些常用的命令来操作 String 类型,以下是一些重要的命令:
SET
-
命令:
SET key value
-
功能: 设置指定键(
key
)的值为指定的字符串(value
)。如果键已经存在,旧值会被覆盖。 -
示例:
SET name "Redis"
GET
-
命令:
GET key
-
功能: 获取指定键(
key
)对应的值。如果键不存在,返回nil
。 -
示例:
GET name
DEL
-
命令:
DEL key
-
功能: 删除指定的键。如果键存在,则删除成功,返回被删除的键的数量(1 或 0)。
-
示例:
DEL name
INCR / DECR
-
命令:
INCR key
和DECR key
-
功能: 将指定键的值按 1 增加或减少。如果键不存在,Redis 会创建该键,并初始化为 0,再执行加减操作。
-
示例:
INCR counter DECR counter
INCRBY / DECRBY
-
命令:
INCRBY key increment
和DECRBY key decrement
-
功能: 按指定的增量(
increment
)或减量(decrement
)对键的值进行加减操作。 -
示例:
INCRBY counter 5 DECRBY counter 2
APPEND
-
命令:
APPEND key value
-
功能: 将指定的字符串(
value
)追加到已存在的键(key
)值的末尾。如果键不存在,执行此命令相当于SET
操作。 -
示例:
APPEND name " is awesome"
SETNX
-
命令:
SETNX key value
-
功能: 仅在键(
key
)不存在时才设置该键的值。如果键已存在,什么都不做。这个命令的意思是“SET if Not eXists”。 -
示例:
SETNX name "Redis"
MS ET / MGET
-
命令:
MSET key value [key value ...]
和MGET key [key ...]
-
功能:
MSET
用来同时设置多个键值对;MGET
用来获取多个键的值。 -
示例:
MSET name "Redis" age 3 MGET name age
3. 字符串与数字的操作
Redis 的 String 类型不仅支持简单的字符串操作,还能处理数字:
字符串与整数运算
- Redis 支持通过命令直接对字符串值执行整数运算,如
INCRBY
、DECRBY
等。这样可以将字符串作为数字进行累加或减少。
浮点数支持
-
命令:
INCRBYFLOAT key increment
-
功能: 对指定键的值执行浮点数加法操作。
-
示例:
SET balance 100.5 INCRBYFLOAT balance 10.25
4. 超时和持久化
虽然 Redis 是一个内存存储系统,但它也提供了超时设置和持久化机制。
设置超时(EXPIRE)
-
命令:
EXPIRE key seconds
-
功能: 设置指定的键的过期时间,单位是秒。过期时间到达后,键会自动删除。
-
示例:
EXPIRE name 60
永久化(PERSIST)
-
命令:
PERSIST key
-
功能: 移除指定键的过期时间,令该键永久有效。
-
示例:
PERSIST name
5. 字符串的二进制安全
Redis 的 String 类型支持二进制安全,这意味着你可以存储任何二进制数据,比如图像、音频文件等。这是 Redis 的一个重要特性。
6. 常见的优化和技巧
- 内存优化: Redis 对内存管理非常高效,尤其是针对小字符串。对于某些非常小的字符串,Redis 会使用一种叫做 “shared strings” 的机制来减少内存占用。
- 管道技术(Pipeline): 在高并发场景下,可以使用 Redis 的管道功能(Pipeline)批量发送命令,而不需要等待每个命令的响应,显著提高性能。
- 事务支持: Redis 的事务支持通过 MULTI、EXEC、WATCH 等命令实现,可以将多个命令打包在一起原子执行。
3 Key的层级格式
1. 为什么使用层级格式?
由于 Redis 是一个键值存储数据库,所有的键都是存放在同一个命名空间中。如果项目规模较大,可能会有大量的键,容易造成命名冲突或管理混乱。使用层级结构可以:
- 组织数据:类似于文件夹路径,使键的结构更加清晰。
- 避免冲突:可以防止不同模块的数据相互覆盖。
- 便于查询:通过某些模式匹配命令(如
SCAN
或KEYS
)更方便地查询特定类型的数据。
2. 常见的层级格式
Redis 没有强制的层级格式,但通常会使用 冒号(:
) 作为分隔符,以模拟多层级的结构。常见格式如下:
(1)用户数据
user:1001:name -> "Alice"
user:1001:email -> "alice@example.com"
user:1002:name -> "Bob"
user:1002:email -> "bob@example.com"
user:1001:name
:表示用户1001
的名称user:1001:email
:表示用户1001
的邮箱- 适用于存储用户信息,便于按 ID 查询。
(2)会话管理
session:token:abc123 -> "1001"
session:token:def456 -> "1002"
session:token:abc123
代表用户1001
的会话 token。
(3)文章或博客
blog:post:20240212:title -> "Redis Key 层级格式"
blog:post:20240212:content -> "本篇文章介绍了 Redis Key 层级格式..."
- 适用于存储文章内容,方便按日期归档。
(4)应用缓存
cache:homepage -> "<html>...</html>"
cache:user:1001 -> "{user data}"
cache:product:sku123 -> "{product data}"
- 适用于缓存 HTML 页面或 JSON 数据,避免频繁查询数据库。
(5)订单系统
order:20240212:1001:items -> "[item1, item2, item3]"
order:20240212:1001:total -> "200.50"
- 适用于存储订单信息。
3. 层级格式的查询技巧
(1)匹配某一类键
-
使用
KEYS
命令(慎用)KEYS user:*
- 获取所有
user:
开头的键。 - ⚠ 注意:
KEYS
命令在大数据量下性能较差,可能会阻塞 Redis。
- 获取所有
-
使用
SCAN
命令(推荐)SCAN 0 MATCH user:* COUNT 10
SCAN
命令是渐进式的,不会阻塞 Redis,适合大数据集查询。
(2)获取子层级数据
如果想获取 user:1001:*
相关数据:
SCAN 0 MATCH user:1001:* COUNT 10
(3)统计某一层级的键
如果你想知道有多少个用户:
SCARD user:ids # 假设有个存所有用户 ID 的集合
4. 最佳实践
(1)保持命名一致
- 使用
冒号 :
作为分隔符。 - 避免大小写混用,推荐统一小写。
- 保持层级格式一致,例如
user:1001:email
,不要user_1001-email
。
(2)适当存储索引
-
例如存储所有用户 ID:
SADD user:ids 1001 1002 1003
这样可以通过
SMEMBERS user:ids
获取所有用户 ID,而不必扫描整个数据库。
(3)删除某个层级的数据
-
删除
user:1001
相关的所有键:
SCAN 0 MATCH user:1001:* COUNT 100 | xargs DEL
这可以批量删除所有
user:1001:*
相关数据。
5. Redis 提供的 HSCAN
方案
如果一个用户的数据较多,推荐使用 Redis Hash 存储,而不是多个 String:
HSET user:1001 name "Alice" email "alice@example.com"
HSET user:1002 name "Bob" email "bob@example.com"
然后可以用 HGETALL
取出完整信息:
HGETALL user:1001
4 Hash类型
4.1 Redis Hash 类型详解
1. Redis Hash 简介
Redis 的 Hash 是一个 键值对集合,类似于 Java 中的 HashMap
或 Python 中的 dict
。
适用于存储对象,例如用户信息(id
、name
、age
等),相比使用多个 String
类型的键存储不同字段,Hash 类型可以减少键的数量,节省内存。
2. Hash 相关命令
命令 | 描述 | 示例 |
---|---|---|
HSET key field value | 设置 Hash 中的字段值(若 field 不存在,则新增) | HSET user:1001 name "Alice" |
HGET key field | 获取 Hash 中指定字段的值 | HGET user:1001 name → "Alice" |
HMSET key field1 value1 field2 value2 ... | 一次性设置多个字段值 | HMSET user:1001 age 25 city "NY" |
HMGET key field1 field2 ... | 获取多个字段值 | HMGET user:1001 name age |
HGETALL key | 获取 Hash 中的所有字段和值 | HGETALL user:1001 |
HDEL key field1 field2 ... | 删除 Hash 中的一个或多个字段 | HDEL user:1001 age |
HLEN key | 获取 Hash 中字段的数量 | HLEN user:1001 |
HEXISTS key field | 判断字段是否存在 | HEXISTS user:1001 name |
HINCRBY key field increment | 使某个字段的值自增 | HINCRBY user:1001 age 1 |
HINCRBYFLOAT key field increment | 使某个字段的值自增(浮点数) | HINCRBYFLOAT user:1001 score 0.5 |
HKEYS key | 获取 Hash 中所有字段 | HKEYS user:1001 |
HVALS key | 获取 Hash 中所有值 | HVALS user:1001 |
3. Redis Hash 存储结构
两种存储方式
- 压缩列表(ziplist):当字段数量较少(默认
512
个以下)且每个字段值较小(默认64 字节以下
)时,Redis 采用 ziplist(压缩列表) 存储,节省内存。 - 哈希表(hashtable):如果 Hash 结构超过上述阈值,则转换为 hashtable(类似于普通的哈希表结构),提高查询效率。
可以通过 redis.conf
配置文件调整:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
4. Hash 适用场景
-
存储用户信息:
HMSET user:1001 name "Alice" age 25 city "NY"
这样可以避免创建多个 key,如
user:1001:name
、user:1001:age
,减少 key 数量。 -
计数器:
HINCRBY page_views home 1
统计网站某个页面的访问量。
-
缓存对象:
- 可以将数据库中的对象数据存入 Hash,提高访问速度,减少数据库查询压力。
5. Hash vs. String 对比
特性 | Hash | String |
---|---|---|
适合存储对象 | ✅(减少 key 数量) | ❌(需要多个 key) |
单个字段读取 | ✅(HGET) | ❌(需要整个值) |
内存占用 | 🚀(小对象时更节省) | 🛑(多个 key 时浪费) |
适用场景 | 结构化存储,如用户信息 | 简单数据,如计数、单值 |
示例对比: 使用 String
SET user:1001:name "Alice"
SET user:1001:age 25
SET user:1001:city "NY"
使用 Hash
HMSET user:1001 name "Alice" age 25 city "NY"
- Hash 方式减少了
key
的数量,提升查询效率,降低内存占用。
4.2 典型面试题
- Hash 是否适合存储大数据对象?
- 不适合,因为 Hash 不能进行部分字段模糊查询(如
HGETALL
需要获取所有字段)。 - 对于超大对象,建议使用 JSON 格式的 String 存储(如
RedisJSON
)。
- 不适合,因为 Hash 不能进行部分字段模糊查询(如
- 如何高效管理 Hash 结构?
- 避免大 Hash(单个 Hash 过大会影响
HGETALL
性能)。 - 定期删除无用字段(避免 Hash 过大)。
- 适当调整
ziplist
阈值 以优化小对象存储。
- 避免大 Hash(单个 Hash 过大会影响
5 Redis List 类型详解
1. Redis List 简介
Redis 的 List 类型是一个 双向链表,支持从两端插入、删除和读取数据,适用于 消息队列、任务队列、时间线存储等场景。
2. List 相关命令
命令 | 描述 | 示例 |
---|---|---|
LPUSH key value [value ...] | 从左侧插入一个或多个元素 | LPUSH tasks "task1" "task2" |
RPUSH key value [value ...] | 从右侧插入一个或多个元素 | RPUSH tasks "task3" "task4" |
LPOP key | 移除并返回左侧第一个元素 | LPOP tasks |
RPOP key | 移除并返回右侧第一个元素 | RPOP tasks |
LRANGE key start stop | 获取指定范围的元素 | LRANGE tasks 0 -1 (获取所有元素) |
LLEN key | 获取列表长度 | LLEN tasks |
LINDEX key index | 获取指定索引的元素 | LINDEX tasks 0 (获取第一个元素) |
LSET key index value | 修改指定索引的元素 | LSET tasks 0 "new_task" |
LREM key count value | 删除 count 个指定值的元素 | LREM tasks 2 "task1" |
LTRIM key start stop | 只保留指定区间的元素,删除其他元素 | LTRIM tasks 0 2 (保留前三个元素) |
BLPOP key timeout | 阻塞式弹出左侧元素 | BLPOP tasks 5 (最多等待 5 秒) |
BRPOP key timeout | 阻塞式弹出右侧元素 | BRPOP tasks 5 |
RPOPLPUSH source destination | 从 source 右侧弹出元素并推入 destination 左侧 | RPOPLPUSH queue1 queue2 |
3. List 的存储结构
Redis List 底层实现有两种:
- ziplist(压缩列表):
- 小规模列表(元素较少,默认
512
个以内,元素值小于64
字节)。 - 采用 连续内存存储,节省空间,但操作效率较低。
- 小规模列表(元素较少,默认
- quicklist(快速列表):
- Redis 3.2 之后,使用 quicklist(ziplist + 双向链表)。
- 当列表较大时,自动转换为 双向链表,支持高效插入和删除。
4. List 适用场景
-
消息队列(FIFO / LIFO)
LPUSH
+RPOP
:模拟 队列(FIFO,先进先出)。LPUSH
+LPOP
:模拟 栈(LIFO,后进先出)。
LPUSH queue "task1" LPUSH queue "task2" RPOP queue # 先取出 "task1"
-
任务调度
- 使用
BRPOP
实现任务消费者阻塞式读取:
BRPOP tasks 10 # 10秒内如果没有任务,返回空
- 使用
-
时间线存储(如微博、推特)
LPUSH
最新动态,LTRIM
保留最近N
条:
LPUSH timeline "Post1" LTRIM timeline 0 99 # 只保留最新100条
5. List vs. Set vs. Sorted Set
特性 | List | Set | Sorted Set |
---|---|---|---|
是否允许重复 | ✅ 允许 | ❌ 不允许 | **❌ ** 不允许 |
是否有序 | ✅ 按插入顺序 | ❌ 无序 | ✅ 自定义排序 |
适用场景 | 消息队列、时间线 | 标签、去重集合 | 排行榜、计分 |
6. List 的性能优化
-
避免超长列表
- 如果列表过长(超过百万级别),遍历和删除操作会变慢,应使用 LTRIM 限制长度:
LTRIM mylist 0 999 # 只保留最新 1000 条
-
使用
BLPOP/BRPOP
代替轮询- 轮询
LPOP
会不断占用 CPU,改用BLPOP
可避免高频查询。
- 轮询
-
合理选择
LPUSH
/RPUSH
LPUSH
适用于新数据插入(如时间线)。RPUSH
适用于队列消费(FIFO)。
7. 典型面试题
-
List 为什么能用于消息队列?
LPUSH
+RPOP
先进先出,BRPOP
阻塞式等待消息,适用于生产者-消费者模型。
-
如何删除 List 中的某个元素?
LREM
命令:
LREM mylist 2 "task1"
- 其中
2
代表 最多删除 2 个 “task1”。
-
Redis List 和 Kafka 有何不同?
- Redis List 适合小型队列,数据不会持久化,适用于临时任务。
- Kafka 适合高吞吐量日志流处理,支持分布式消费。
8. 总结
- Redis List 是双向链表,支持 左/右插入删除。
- 适用于消息队列、时间线存储,支持 FIFO / LIFO 访问模式。
- 使用
BLPOP
/BRPOP
处理任务队列,避免轮询消耗。 - 合理限制列表长度,避免超长列表影响性能。
Redis List 适用于 队列任务处理、微博时间线、消息流存储,但需要注意超长列表的性能问题。
6 Redis Set 类型详解
1. Redis Set 简介
Redis 的 Set 类型是一个 无序集合,它的特点是:
- 元素唯一(自动去重)。
- 支持常见的集合操作(如交集、并集、差集)。
- 适用于 去重、标签管理、社交关系(好友、关注)、排行榜 等场景。
2. Set 相关命令
命令 | 描述 | 示例 |
---|---|---|
SADD key member [member ...] | 添加一个或多个元素到 Set | SADD users "Alice" "Bob" |
SREM key member [member ...] | 删除指定元素 | SREM users "Alice" |
SISMEMBER key member | 判断元素是否存在 | SISMEMBER users "Bob" → 1 (存在) |
SCARD key | 获取 Set 的元素数量 | SCARD users |
SMEMBERS key | 获取所有元素 | SMEMBERS users |
SPOP key [count] | 随机移除并返回 count 个元素 | SPOP users 2 |
SRANDMEMBER key [count] | 随机获取 count 个元素(不删除) | SRANDMEMBER users 1 |
SMOVE source destination member | 从一个 Set 移动元素到另一个 Set | SMOVE group1 group2 "Bob" |
集合运算
命令 | 描述 | 示例 |
---|---|---|
SINTER key1 key2 ... | 计算 交集 | SINTER fans musicians |
SUNION key1 key2 ... | 计算 并集 | SUNION fans musicians |
SDIFF key1 key2 ... | 计算 差集(key1 - key2) | SDIFF fans musicians |
SINTERSTORE dest key1 key2 ... | 计算 交集 并存储到新 Set | SINTERSTORE common fans musicians |
SUNIONSTORE dest key1 key2 ... | 计算 并集 并存储到新 Set | SUNIONSTORE all_users set1 set2 |
SDIFFSTORE dest key1 key2 ... | 计算 差集 并存储到新 Set | SDIFFSTORE only_fans fans musicians |
3. Set 存储结构
Redis Set 采用两种存储方式:
- intset(整数集合):当 Set 里 所有元素都是整数,且数量较少(默认小于 512 个) 时,使用
intset
存储,节省内存。 - hashtable(哈希表):当 Set 存储字符串或元素较多 时,自动转换为哈希表(字典),查询速度快(O(1))。
可以调整 set-max-intset-entries
来控制 intset
转换阈值:
set-max-intset-entries 512
4. Set 适用场景
1️⃣ 去重
-
存储唯一值集合
-
例如去重存储用户 ID:
SADD unique_users 1001 1002 1003 1001
- 结果:
SMEMBERS unique_users
→1001 1002 1003
(自动去重)
- 结果:
2️⃣ 社交网络
-
共同关注
SINTER user:alice:followers user:bob:followers
- 查找 Alice 和 Bob 共同的关注者。
-
好友推荐
SDIFF user:alice:followers user:bob:followers
- 计算 Alice 关注但 Bob 没有关注的用户(推荐关注)。
3️⃣ 抽奖系统
-
随机抽取获奖者
SRANDMEMBER lottery_users 2
- 随机抽取 2 名获奖者。
4️⃣ 标签管理
-
用户感兴趣的标签
SADD user:1001:tags "Redis" "Python" "BigData"
- 存储用户感兴趣的技术标签。
-
查询共同兴趣
SINTER user:1001:tags user:1002:tags
- 找出两个用户的共同兴趣。
5. Set vs. List vs. Sorted Set
特性 | Set | List | Sorted Set |
---|---|---|---|
是否允许重复 | ❌ 不允许 | ✅ 允许 | ❌ 不允许 |
是否有序 | ❌ 无序 | ✅ 按插入顺序 | ✅ 按分数排序 |
适用场景 | 去重、标签、社交网络 | 消息队列、时间线 | 排行榜、积分排名 |
6. Set 的性能优化
-
避免存储超大 Set
- Redis 单个 key 不能超过 2^32(40 多亿)个元素,但大 Set 可能影响查询性能。
-
使用
SINTERSTORE
代替SINTER
-
交集计算开销较大,
SINTERSTORE
可避免重复计算:
SINTERSTORE result_set set1 set2
-
这样
result_set
可直接复用,减少 CPU 计算。
-
-
利用
SUNIONSTORE
预计算结果-
例如,提前计算所有用户 ID 组成的 Set:
SUNIONSTORE all_users users:region1 users:region2
-
后续查询可直接
SCARD all_users
获取总用户数。
-
7. 典型面试题
1️⃣ Redis Set 和 List 的区别?
- List 适用于有序数据(如消息队列),支持
LPUSH/RPUSH
头尾插入。 - Set 适用于去重存储,自动去重,不保证顺序。
2️⃣ 如何计算多个 Set 的交集?
-
使用
SINTER
:
SINTER set1 set2
-
如果交集计算频繁,使用
SINTERSTORE
存储结果,提高查询效率。
3️⃣ 如何判断某个值是否在 Set 中?
-
使用
SISMEMBER
:
SISMEMBER myset "Alice"
4️⃣ Redis Set 适用于排行榜吗?
- 不适合,Set 无序,无法存储分数。
- 适合 去重 ID,但排名建议使用 Sorted Set。
8. 总结
- Set 是无序集合,支持去重、交集、并集、差集计算。
- 适用于去重、社交关系、随机抽取、标签管理。
- 交集、并集计算可用
SINTERSTORE
、SUNIONSTORE
预计算结果,提高性能。 - 适当调整
set-max-intset-entries
以优化小整数集合存储。
Redis Set 是 处理去重、社交网络、标签系统的利器,合理使用可以大幅提升应用性能。
7 Redis Sorted Set(有序集合)详解
1. Redis Sorted Set 简介
Redis 的 Sorted Set(Zset,有序集合) 是 带有权重(分数)的集合,它的特点是:
- 元素唯一,但分数(score)可重复。
- 自动按分数排序,支持 排行榜、排序列表、时间序列数据等场景。
- 底层由 skiplist(跳表) 和 hash 表 组成,查询和排序效率高。
2. Sorted Set 相关命令
命令 | 描述 | 示例 |
---|---|---|
ZADD key score member [score member ...] | 添加元素,并设置分数 | ZADD leaderboard 100 "Alice" |
ZREM key member [member ...] | 删除元素 | ZREM leaderboard "Alice" |
ZSCORE key member | 获取元素的分数 | ZSCORE leaderboard "Alice" |
ZRANK key member | 获取元素的排名(从小到大) | ZRANK leaderboard "Alice" |
ZREVRANK key member | 获取元素的排名(从大到小) | ZREVRANK leaderboard "Alice" |
ZRANGE key start stop [WITHSCORES] | 获取指定区间的元素(从小到大) | ZRANGE leaderboard 0 2 WITHSCORES |
ZREVRANGE key start stop [WITHSCORES] | 获取指定区间的元素(从大到小) | ZREVRANGE leaderboard 0 2 WITHSCORES |
ZCARD key | 获取集合元素个数 | ZCARD leaderboard |
ZCOUNT key min max | 计算指定分数区间的元素数量 | ZCOUNT leaderboard 50 100 |
ZINCRBY key increment member | 增加某个元素的分数 | ZINCRBY leaderboard 10 "Alice" |
ZRANGEBYSCORE key min max [WITHSCORES] | 按分数范围获取元素 | ZRANGEBYSCORE leaderboard 50 100 WITHSCORES |
ZREMRANGEBYRANK key start stop | 删除指定排名范围的元素 | ZREMRANGEBYRANK leaderboard 0 2 |
ZREMRANGEBYSCORE key min max | 删除指定分数范围的元素 | ZREMRANGEBYSCORE leaderboard 50 100 |
3. Sorted Set 底层数据结构
1️⃣ SkipList(跳表)
- 支持快速查找、范围查询,时间复杂度
O(log N)
。 - 插入/删除元素时,维护多个层级的索引,提高查询速度。
2️⃣ Hash(哈希表)
- 用于 存储元素和分数的映射,时间复杂度
O(1)
。
⚠️ 默认情况下:
- 元素较少时,Redis 可能用 ziplist(压缩列表) 进行存储,提高效率。
- 当 元素较多 或 ziplist 不适用 时,会转换为 skiplist。
参数 zset-max-ziplist-entries
和 zset-max-ziplist-value
可调整 ziplist 存储上限:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
4. Sorted Set 适用场景
1️⃣ 排行榜
-
存储用户积分榜
ZADD leaderboard 100 "Alice" ZADD leaderboard 200 "Bob" ZADD leaderboard 150 "Charlie"
-
获取前 3 名
ZREVRANGE leaderboard 0 2 WITHSCORES
-
输出:
1) "Bob" 2) 200 3) "Charlie" 4) 150 5) "Alice" 6) 100
-
-
获取某用户排名
ZREVRANK leaderboard "Alice"
- 返回
2
(Alice 排名第 3)。
- 返回
2️⃣ 任务队列
-
按优先级存储任务
ZADD task_queue 10 "task1" ZADD task_queue 5 "task2"
-
获取最高优先级任务
ZRANGE task_queue 0 0 WITHSCORES
task2
优先级较高,优先执行。
3️⃣ 访问统计
-
存储网站访问次数
ZINCRBY site_visits 1 "google.com" ZINCRBY site_visits 1 "facebook.com"
-
获取访问量最高的网站
ZREVRANGE site_visits 0 2 WITHSCORES
4️⃣ 时间序列数据
-
存储日志时间戳
ZADD logs 1678439200 "log1" ZADD logs 1678439300 "log2"
-
获取最近 N 条日志
ZREVRANGE logs 0 4
5. Sorted Set vs. List vs. Set
特性 | Sorted Set | Set | List |
---|---|---|---|
是否有序 | ✅ 是 | ❌ 否 | ✅ 按插入顺序 |
是否允许重复 | ❌ 不能有重复元素 | ❌ 不能有重复元素 | ✅ 允许 |
能否按权重排序 | ✅ 可以(按 score 排序) | ❌ 不行 | ❌ 不能 |
适用场景 | 排行榜、任务队列、时间序列 | 标签、去重集合 | 消息队列、时间线 |
6. 性能优化
1️⃣ 避免大规模 ZREMRANGEBYRANK
操作
- 如果 元素较多,批量删除
ZRANGE
范围过大会影响性能。 - 解决方案:定期删除,避免瞬间大量操作。
2️⃣ 使用 ZINCRBY
而不是频繁 ZADD
ZINCRBY
直接修改分数,不会增加 key 数量,减少 Redis 内存占用。
3️⃣ 合理设置 zset-max-ziplist-entries
- 小数据量时,可以使用 ziplist 提高性能。
7. 典型面试题
1️⃣ Redis Sorted Set 和 Set 的区别?
特性 | Set | Sorted Set |
---|---|---|
是否有序 | ❌ 无序 | ✅ 按分数排序 |
是否支持重复元素 | ❌ 不允许 | ❌ 不允许 |
是否可排序 | ❌ 不可 | ✅ 可 |
2️⃣ 如何获取 Top 3 用户?
ZREVRANGE leaderboard 0 2 WITHSCORES
3️⃣ 如何存储最新 100 条日志?
ZADD logs 1678439200 "log1"
ZADD logs 1678439300 "log2"
ZREMRANGEBYRANK logs 0 -101 # 只保留最近 100 条
4️⃣ Redis Sorted Set 适用于排行榜吗?
- 非常适合,支持 自动排序、快速查询排名。
- 适用于高频更新数据,如游戏积分、微博热度排行等。
8. 总结
✅ Sorted Set 适用于排行榜、任务调度、时间序列数据
✅ 底层由 skiplist + hash 组成,查询高效(O(log N))
✅ 支持范围查询、自动排序,适合高频更新的数据存储
⚠️ 避免过多 ZREMRANGEBYRANK
,使用 ZINCRBY
进行优化
2 Spring操作Redis
1. RedisTemplate
概述
RedisTemplate
是 Spring Data Redis 提供的用于操作 Redis 的核心类,它封装了对 Redis 的各种操作,包括字符串、哈希、列表、集合、有序集合等数据结构。
1.1 主要特性
- 提供了一系列 Redis 操作方法,如
opsForValue()
、opsForList()
、opsForHash()
等。 - 支持事务(但 Redis 本身是单线程的,并不完全等同于数据库事务)。
- 支持 Redis 的发布/订阅功能。
- 支持不同的序列化方式,如 JDK 序列化、Jackson、String 以及 ProtoBuf 等。
1.2 主要组件
RedisConnectionFactory
:连接 Redis 服务器的工厂,常见实现:LettuceConnectionFactory
(推荐,基于 Netty,支持异步)JedisConnectionFactory
(基于 Jedis,线程不安全)
RedisSerializer
:定义 Redis 数据的序列化方式,如StringRedisSerializer
、GenericJackson2JsonRedisSerializer
等。
2. RedisTemplate
的常见操作
2.1 配置 RedisTemplate
Spring Boot 提供了默认的 RedisTemplate
,但为了更好的控制,我们通常需要自定义 RedisTemplate
。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 Jackson 序列化
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
// 设置 key 和 value 的序列化方式
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
2.2 opsForValue()
操作字符串
// 获取字符串操作对象
ValueOperations<String, String> ops = redisTemplate.opsForValue();
// 设置值
ops.set("name", "Redis学习");
// 获取值
String value = ops.get("name");
System.out.println(value);
2.3 opsForHash()
操作哈希
HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
hashOps.put("user:1001", "name", "张三");
hashOps.put("user:1001", "age", "30");
// 获取单个字段
String name = hashOps.get("user:1001", "name");
System.out.println(name);
// 获取所有字段
Map<String, String> entries = hashOps.entries("user:1001");
System.out.println(entries);
2.4 opsForList()
操作列表
ListOperations<String, String> listOps = redisTemplate.opsForList();
listOps.rightPush("list", "A");
listOps.rightPush("list", "B");
listOps.rightPush("list", "C");
// 获取列表元素
List<String> elements = listOps.range("list", 0, -1);
System.out.println(elements);
2.5 opsForSet()
操作集合
SetOperations<String, String> setOps = redisTemplate.opsForSet();
setOps.add("setKey", "1", "2", "3", "4");
// 获取所有成员
Set<String> members = setOps.members("setKey");
System.out.println(members);
2.6 opsForZSet()
操作有序集合
ZSetOperations<String, String> zSetOps = redisTemplate.opsForZSet();
zSetOps.add("zset", "A", 1);
zSetOps.add("zset", "B", 2);
zSetOps.add("zset", "C", 3);
// 获取有序集合中的元素
Set<String> range = zSetOps.range("zset", 0, -1);
System.out.println(range);
3. 序列化和反序列化
3.1 Redis 序列化方式
默认情况下,RedisTemplate
使用 JdkSerializationRedisSerializer
,即 Java 原生序列化,但这会导致 Redis 数据格式难以阅读,因此推荐使用其他序列化方式:
- StringRedisSerializer:适用于存储简单的字符串数据。
- Jackson2JsonRedisSerializer:使用 JSON 进行序列化,推荐存储对象数据。
- GenericJackson2JsonRedisSerializer:Jackson 序列化的增强版,可存储复杂对象结构。
- JdkSerializationRedisSerializer:Java 内置序列化方式,不推荐使用。
- FastJsonRedisSerializer(阿里巴巴 FastJSON 实现,性能优秀)。
3.2 常见序列化方式配置
(1)使用 StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
- 适用于存储简单的 Key-Value 结构,数据可读性高。
(2)使用 Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
- 适用于存储 JSON 结构的对象数据。
(3)使用 GenericJackson2JsonRedisSerializer
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
- 适用于存储泛型对象,避免 JSON 反序列化时丢失类型信息。
3.3 配置序列化的一些问题
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 配置key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 配置value的序列化器
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
按照上述方式配置完成后,我们尝试写入和读取一个对象User
:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test1() {
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("user", User.builder().name("zhangsan").phone("123456").build());
System.out.println(valueOperations.get("user"));
}
}
redis中存储的内容还包括class
信息,这样会浪费存储空间,因此不推荐这种方式来序列化。
在实际开发中,应使用 StringRedisSerializer
作为 Redis 的序列化方式或直接使用StringRedisTemplate
,之后在业务逻辑中手动 JSON 序列化和反序列化。
在业务层手动 JSON 序列化和反序列化
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 保存对象到 Redis
*/
public void saveObject(String key, Object object) {
try {
String json = objectMapper.writeValueAsString(object);
redisTemplate.opsForValue().set(key, json);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON 序列化失败", e);
}
}
/**
* 从 Redis 获取对象
*/
public <T> T getObject(String key, Class<T> clazz) {
try {
String json = redisTemplate.opsForValue().get(key);
return json == null ? null : objectMapper.readValue(json, clazz);
} catch (Exception e) {
throw new RuntimeException("JSON 反序列化失败", e);
}
}
}
4. StringRedisTemplate
StringRedisTemplate
是 RedisTemplate<String, String>
的特殊实现,主要用于字符串操作,不需要额外的序列化配置。
4.1 StringRedisTemplate
配置
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
4.2 使用示例
@Autowired
private StringRedisTemplate stringRedisTemplate;
stringRedisTemplate.opsForValue().set("message", "Hello, Redis!");
String msg = stringRedisTemplate.opsForValue().get("message");
System.out.println(msg);
5. 事务支持
Redis 事务是通过 multi
和 exec
命令实现的,可以使用 SessionCallback
进行事务操作。
redisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) {
operations.multi();
operations.opsForValue().set("key1", "value1");
operations.opsForValue().set("key2", "value2");
return operations.exec();
}
});
参考内容
黑马Redis课程
https://www.bilibili.com/video/BV1cr4y1671t