当前位置: 首页 > article >正文

[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 keyDECR key

  • 功能: 将指定键的值按 1 增加或减少。如果键不存在,Redis 会创建该键,并初始化为 0,再执行加减操作。

  • 示例:

    INCR counter
    DECR counter
    
INCRBY / DECRBY
  • 命令: INCRBY key incrementDECRBY 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 支持通过命令直接对字符串值执行整数运算,如 INCRBYDECRBY 等。这样可以将字符串作为数字进行累加或减少。
浮点数支持
  • 命令: 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 是一个键值存储数据库,所有的键都是存放在同一个命名空间中。如果项目规模较大,可能会有大量的键,容易造成命名冲突或管理混乱。使用层级结构可以:

  • 组织数据:类似于文件夹路径,使键的结构更加清晰。
  • 避免冲突:可以防止不同模块的数据相互覆盖。
  • 便于查询:通过某些模式匹配命令(如 SCANKEYS)更方便地查询特定类型的数据。

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
适用于存储对象,例如用户信息(idnameage等),相比使用多个 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 适用场景
  1. 存储用户信息

    HMSET user:1001 name "Alice" age 25 city "NY"
    

    这样可以避免创建多个 key,如 user:1001:nameuser:1001:age,减少 key 数量。

  2. 计数器

    • HINCRBY page_views home 1 统计网站某个页面的访问量。
  3. 缓存对象

    • 可以将数据库中的对象数据存入 Hash,提高访问速度,减少数据库查询压力。

5. Hash vs. String 对比
特性HashString
适合存储对象✅(减少 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 典型面试题

  1. Hash 是否适合存储大数据对象?
    • 不适合,因为 Hash 不能进行部分字段模糊查询(如 HGETALL 需要获取所有字段)。
    • 对于超大对象,建议使用 JSON 格式的 String 存储(如 RedisJSON)。
  2. 如何高效管理 Hash 结构?
    • 避免大 Hash(单个 Hash 过大会影响 HGETALL 性能)。
    • 定期删除无用字段(避免 Hash 过大)。
    • 适当调整 ziplist 阈值 以优化小对象存储。

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 底层实现有两种:

  1. ziplist(压缩列表)
    • 小规模列表(元素较少,默认 512 个以内,元素值小于 64 字节)。
    • 采用 连续内存存储,节省空间,但操作效率较低。
  2. quicklist(快速列表)
    • Redis 3.2 之后,使用 quicklist(ziplist + 双向链表)。
    • 当列表较大时,自动转换为 双向链表,支持高效插入和删除。

4. List 适用场景

  1. 消息队列(FIFO / LIFO)

    • LPUSH + RPOP:模拟 队列(FIFO,先进先出)
    • LPUSH + LPOP:模拟 栈(LIFO,后进先出)
    LPUSH queue "task1"
    LPUSH queue "task2"
    RPOP queue  # 先取出 "task1"
    
  2. 任务调度

    • 使用 BRPOP 实现任务消费者阻塞式读取:
    BRPOP tasks 10  # 10秒内如果没有任务,返回空
    
  3. 时间线存储(如微博、推特)

    • LPUSH 最新动态,LTRIM 保留最近 N 条:
    LPUSH timeline "Post1"
    LTRIM timeline 0 99  # 只保留最新100条
    

5. List vs. Set vs. Sorted Set

特性ListSetSorted Set
是否允许重复✅ 允许❌ 不允许**❌ ** 不允许
是否有序✅ 按插入顺序❌ 无序✅ 自定义排序
适用场景消息队列、时间线标签、去重集合排行榜、计分

6. List 的性能优化

  1. 避免超长列表

    • 如果列表过长(超过百万级别),遍历和删除操作会变慢,应使用 LTRIM 限制长度:
    LTRIM mylist 0 999  # 只保留最新 1000 条
    
  2. 使用 BLPOP/BRPOP 代替轮询

    • 轮询 LPOP 会不断占用 CPU,改用 BLPOP 可避免高频查询。
  3. 合理选择 LPUSH / RPUSH

    • LPUSH 适用于新数据插入(如时间线)。
    • RPUSH 适用于队列消费(FIFO)。

7. 典型面试题

  1. List 为什么能用于消息队列?

    • LPUSH + RPOP 先进先出,BRPOP 阻塞式等待消息,适用于生产者-消费者模型。
  2. 如何删除 List 中的某个元素?

    • LREM 命令:
    LREM mylist 2 "task1"
    
    • 其中 2 代表 最多删除 2 个 “task1”
  3. 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 ...]添加一个或多个元素到 SetSADD 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 移动元素到另一个 SetSMOVE 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 ...计算 交集 并存储到新 SetSINTERSTORE common fans musicians
SUNIONSTORE dest key1 key2 ...计算 并集 并存储到新 SetSUNIONSTORE all_users set1 set2
SDIFFSTORE dest key1 key2 ...计算 差集 并存储到新 SetSDIFFSTORE only_fans fans musicians

3. Set 存储结构

Redis Set 采用两种存储方式:

  1. intset(整数集合):当 Set 里 所有元素都是整数,且数量较少(默认小于 512 个) 时,使用 intset 存储,节省内存。
  2. 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_users1001 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

特性SetListSorted Set
是否允许重复❌ 不允许✅ 允许❌ 不允许
是否有序❌ 无序✅ 按插入顺序✅ 按分数排序
适用场景去重、标签、社交网络消息队列、时间线排行榜、积分排名

6. Set 的性能优化

  1. 避免存储超大 Set

    • Redis 单个 key 不能超过 2^32(40 多亿)个元素,但大 Set 可能影响查询性能。
  2. 使用 SINTERSTORE 代替 SINTER

    • 交集计算开销较大,

      SINTERSTORE
      

      可避免重复计算:

      SINTERSTORE result_set set1 set2
      
    • 这样 result_set 可直接复用,减少 CPU 计算。

  3. 利用 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 是无序集合,支持去重、交集、并集、差集计算
  • 适用于去重、社交关系、随机抽取、标签管理
  • 交集、并集计算可用 SINTERSTORESUNIONSTORE 预计算结果,提高性能
  • 适当调整 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-entrieszset-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 SetSetList
是否有序✅ 是❌ 否✅ 按插入顺序
是否允许重复❌ 不能有重复元素❌ 不能有重复元素✅ 允许
能否按权重排序✅ 可以(按 score 排序)❌ 不行❌ 不能
适用场景排行榜、任务队列、时间序列标签、去重集合消息队列、时间线

6. 性能优化

1️⃣ 避免大规模 ZREMRANGEBYRANK 操作

  • 如果 元素较多,批量删除 ZRANGE 范围过大会影响性能。
  • 解决方案:定期删除,避免瞬间大量操作。

2️⃣ 使用 ZINCRBY 而不是频繁 ZADD

  • ZINCRBY 直接修改分数,不会增加 key 数量,减少 Redis 内存占用。

3️⃣ 合理设置 zset-max-ziplist-entries

  • 小数据量时,可以使用 ziplist 提高性能。

7. 典型面试题

1️⃣ Redis Sorted Set 和 Set 的区别?
特性SetSorted 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 数据的序列化方式,如 StringRedisSerializerGenericJackson2JsonRedisSerializer 等。

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

StringRedisTemplateRedisTemplate<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 事务是通过 multiexec 命令实现的,可以使用 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


http://www.kler.cn/a/544462.html

相关文章:

  • LabVIEW与小众设备集成
  • #渗透测试#批量漏洞挖掘#致远互联AnalyticsCloud 分析云 任意文件读取
  • 【基于SprintBoot+Mybatis+Mysql】电脑商城项目之获取省市区列表名称及收货地址列表展示
  • 细胞计数专题 | LUNA-FX7™新自动对焦算法提高极低细胞浓度下的细胞计数准确性
  • 从设计到生产,3D技术如何改变鞋业生态
  • 基于Java SpringBoot以及vue前后端分离的旅游景区网站系统设计与实现
  • AI+智能中台企业架构设计_重新定义制造(46页PPT)
  • Javaweb中,使用Servlet编写简单的接口
  • 如何利用Spring的@Value注解实现配置信息的动态注入与管理?
  • flutter本地推送 flutter_local_notifications的使用记录
  • 十八、vben框架前端编码风格及标准
  • 2024年博客之星年度评选—主题文章创作评审文章得分公布
  • 苹果放弃DeepSeek选择阿里通义,近屿智能助您入局黄金AI领域
  • 本地部署DeepSeek后的调用与删除全攻略
  • AI前端开发:跨领域合作的新引擎
  • Linux运维——系统管理
  • DeepSeek批量生成全平台推广营销内容:高效提升营销效率
  • 【信息系统项目管理师-案例真题】2019下半年案例分析答案和详解
  • 在 python 中使用 toml
  • NLP Word Embeddings