浅谈 Redis 数据类型
(一)String 类型
Redis 的 String 类型 是二进制安全的,可以用来存储 文本字符串、int 类型数据和 bitmap 位图 等数据。
1. 字符串操作
-  
适用于存储 文本、JSON、序列化数据 等任意二进制安全的内容
命令 作用 示例 SET设置键值 SET name "Alice"GET获取值 GET name→"Alice"APPEND追加内容 APPEND name " Smith"→"Alice Smith"STRLEN获取字节长度 STRLEN name→11GETRANGE截取子串 GETRANGE name 0 4→"Alice"SETRANGE覆盖部分内容 SETRANGE name 6 "Taylor"→"Alice Taylor"MSET/MGET批量操作 MSET k1 "v1" k2 "v2" -  
二进制安全:可存储图片、序列化对象等任意数据
 -  
自动编码:短字符串用
embstr编码(内存连续),长字符串用raw编码 
SETNX 操作实现分布式锁
SETNX key value
 
-  
当
key不存在 时,设置其值为value,并返回OK(成功);若key已存在,则不做任何操作,返回nil(失败) -  
为防止锁持有者崩溃后锁无法释放,需设置超时(通过
EXPIRE) -  
任务完成后,主动删除键以释放锁(通过
DEL) -  
**【风险1】**若
SETNX成功,但EXPIRE未执行(如客户端崩溃),锁会永久占用【解决方案】使用 
SET命令的NX和EX选项 原子性加锁和设置过期时间SET key value NX EX 10 # 原子操作:仅当不存在时设置,并过期时间为 10 秒 -  
**【风险2】**任务未在过期时间内完成,锁被提前释放
**【解决方案】**看门狗机制:启动后台线程定期续期
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) else return 0 end" 1 key value 10 # 每 10 秒执行一次该 LUA 脚本以续期 
2. 数值操作
- 当 String 的值是 整数或浮点数 时,Redis 提供专用命令进行原子增减
 
| 命令 | 作用 | 示例 | 
|---|---|---|
INCR | 原子 +1 | INCR counter → 1 | 
INCRBY | 原子 +N | INCRBY counter 5 → 6 | 
DECR | 原子 -1 | DECR counter → 5 | 
DECRBY | 原子 -N | DECRBY counter 3 → 2 | 
INCRBYFLOAT | 原子 +浮点数 | INCRBYFLOAT price 1.5 → "3.5" | 
- 编码优化:数值用 
int编码(固定 8 字节),浮点数用raw。 - 原子性:无需事务即可避免并发冲突。
 
3. bitmap 位图操作
- Bitmap 是 String 的扩展,通过 位(bit)操作 实现布尔统计,可用于布隆过滤器、用户状态统计等场景
 
| 命令 | 作用 | 示例 | 
|---|---|---|
SETBIT | 设置某位为 0/1 | SETBIT login:2023 100 1(用户 100 登录) | 
GETBIT | 获取某位的值 | GETBIT login:2023 100 → 1 | 
BITCOUNT | 统计 1 的数量 | BITCOUNT login:2023 → 50(50 人登录) | 
BITOP | 位运算(AND/OR/XOR) | BITOP AND result login:day1 login:day2 | 
BITPOS | 查找第一个 0/1 位 | BITPOS login:2023 1 → 100 | 
- 内存高效:1 亿用户登录状态仅需约 12MB
 - 高性能:位运算复杂度 O(n),适合批量处理
 
4. 二进制安全与 Encoding 加速机制
- Redis 的 String 类型基于简单动态字符串实现,数据按字节数组存储,不预设编码格式,读写时不会做任何转换
 - Redis 为 String 类型设计了多种编码格式(
encoding),根据数据内容动态选择最优编码以节省内存和提高性能。通过OBJECT ENCODING key可查看编码类型 
| 编码类型 | 适用场景 | 优化原理 | 
|---|---|---|
| int | 存储 64 位有符号整数(如 123) | 直接使用整数存储,避免字符串转换。 | 
| embstr | 短字符串(≤44 字节,Redis 7+) | 内存连续分配,减少碎片,CPU 缓存友好。 | 
| raw | 长字符串(>44 字节)或二进制数据 | 标准 SDS 动态分配,支持大容量数据。 | 
(二) List 类型
Redis 的 List 类型 是一个双向链表数据结构,支持在头部和尾部高效插入、删除元素,因此可以灵活实现 栈(Stack)、队列(Queue)、数组(Array) 和 阻塞队列(Blocking Queue) 的语义。
- 栈:
LPUSH+LPOP - 队列:
RPUSH+LPOP - 数组:
LINDEX+LSET - 阻塞队列:
RPUSH+BRPOP 
以下是详细用法解析:
1. List 作为栈(Stack)
特点:后进先出(LIFO),通过 LPUSH + LPOP 实现
 适用场景:函数调用栈、撤销操作(Undo)记录
 操作命令:
# 入栈(左侧插入)
LPUSH stack "task1"
LPUSH stack "task2"  # 栈内顺序: ["task2", "task1"]# 出栈(左侧弹出)
LPOP stack  # 返回 "task2",栈剩余: ["task1"]# 查看栈顶元素(不弹出)
LINDEX stack 0  # 返回 "task1"
 
2. List 作为队列(Queue)
特点:先进先出(FIFO),通过 RPUSH + LPOP 实现
 适用场景:任务队列、消息缓冲
 操作命令:
# 入队(尾部插入)
RPUSH queue "msg1"
RPUSH queue "msg2"  # 队列顺序: ["msg1", "msg2"]# 出队(头部弹出)
LPOP queue  # 返回 "msg1",队列剩余: ["msg2"]# 查看队列长度
LLEN queue  # 返回 1
 
3. List 作为数组(Array)
特点:支持按索引访问和修改,通过 LINDEX + LSET 实现
 适用场景:随机访问列表元素、动态数组
 操作命令:
# 初始化数组
RPUSH array "a" "b" "c"  # 数组: ["a", "b", "c"]# 按索引读取(索引从0开始)
LINDEX array 1  # 返回 "b"# 按索引修改
LSET array 1 "B"  # 数组变为: ["a", "B", "c"]# 获取全部元素
LRANGE array 0 -1  # 返回 ["a", "B", "c"]
 
4. List 作为阻塞队列(Blocking Queue)
特点:消费者阻塞等待新元素,通过 BRPOP/BLPOP 实现
 适用场景:实时消息系统、任务调度(避免轮询)
 操作命令:
# 生产者入队(尾部插入)
RPUSH bqueue "job1" "job2"# 消费者阻塞出队(从头部获取,超时时间5秒)
BRPOP bqueue 5  
# 1) 如果队列不为空,立即返回元素(如 "job1")
# 2) 如果队列为空,阻塞 5 秒后返回nil(若期间有新元素插入则立即返回)
 
(三)Hash 类型
Redis 的 Hash 类型 是一个 键值对集合,适合存储对象。它比 String 类型更节省内存,且支持 字段级操作(单独读写某个字段而无需序列化整个对象)。
1. Hash 的底层结构
ziplist(压缩列表):当字段数和字段值较小时使用,内存连续,节省空间hashtable(哈希表):字段较多或值较大时自动转换,查询效率 O(1)
2. 核心操作命令
(1)设置与获取字段值
| 命令 | 作用 | 示例 | 
|---|---|---|
HSET | 设置字段值 | HSET user:1 name "Alice" age 30 | 
HGET | 获取字段值 | HGET user:1 name → "Alice" | 
HMSET | 批量设置字段 | HMSET user:1 name "Alice" age 30 | 
HMGET | 批量获取字段 | HMGET user:1 name age → ["Alice", "30"] | 
HGETALL | 获取所有字段和值 | HGETALL user:1 → ["name", "Alice", "age", "30"] | 
- 示例:
 
HSET product:1001 name "iPhone" price 999 stock 50
HGET product:1001 price  # 返回 "999"
HGETALL product:1001     # 返回所有字段和值
 
(2)字段增减与删除
| 命令 | 作用 | 示例 | 
|---|---|---|
HINCRBY | 字段值整数增减 | HINCRBY user:1 age 1 | 
HINCRBYFLOAT | 字段值浮点数增减 | HINCRBYFLOAT account:1 balance 50.5 | 
HDEL | 删除字段 | HDEL user:1 age | 
HEXISTS | 判断字段是否存在 | HEXISTS user:1 name → 1(存在) | 
- 示例:
 
HINCRBY product:1001 stock -1  # 库存减1
HDEL product:1001 price        # 删除价格字段
 
(3)查询与统计
| 命令 | 作用 | 示例 | 
|---|---|---|
HKEYS | 获取所有字段名 | HKEYS user:1 → ["name", "age"] | 
HVALS | 获取所有字段值 | HVALS user:1 → ["Alice", "30"] | 
HLEN | 获取字段数量 | HLEN user:1 → 2 | 
HSTRLEN | 获取字段值的字节长度 | HSTRLEN user:1 name → 5("Alice"占 5 字节) | 
- 示例:
 
HKEYS product:1001   # 返回 ["name", "price", "stock"]
HLEN product:1001    # 返回 3
 
(4)原子操作
| 命令 | 作用 | 示例 | 
|---|---|---|
HSETNX | 字段不存在时才设置 | HSETNX user:1 email "alice@example.com" | 
HSCAN | 增量迭代字段(大数据量时避免阻塞) | HSCAN user:1 0 | 
- 示例:
 
HSETNX user:1 email "alice@example.com"  # 仅当 email 不存在时设置
 
3. 对象的三种存储策略:单 key 存储、多 key 存储、使用 hash 类型
(1)序列化为字符串(单 Key 存储)
SET user:1000 '{"name":"Alice","age":30,"email":"alice@example.com"}'
 
(2)每个字段单独存储(多 Key 存储)
SET user:1000:name "Alice"
SET user:1000:age 30
SET user:1000:email "alice@example.com"
 
(3)使用 Hash 类型
HSET user:1000 name "Alice" age 30 email "alice@example.com"
 
(4)三种策略比较
| 维度 | 序列化为字符串 | 多 Key 存储 | Hash 类型 | 
|---|---|---|---|
| 读写效率 | 整体读写快,部分更新慢 | 部分读写快,整体查询慢 | 部分和批量读写均快 | 
| 内存占用 | 低(单个 Key) | 高(每个 Key 有元数据) | 中(小 Hash 用 zip list) | 
| 原子性 | 高(整个对象原子操作) | 低(需事务) | 中(单字段操作原子) | 
| 字段级 TTL | 不支持 | 支持 | 不支持 | 
| 适用字段规模 | 任意 | 少量字段 | 中小规模字段 | 
| 复杂结构支持 | 支持(JSON 等序列化方式) | 需额外设计 | 需序列化字段值 | 
(四)Set 类型
Redis 的 Set 类型 是一个 无序、去重的集合,底层基于哈希表实现,支持插入、删除元素以及多个集合的交并差集运算,同时提供了随机返回指定个数元素的功能。
1. Set 的核心特性
- 无序性:元素没有固定顺序,遍历时顺序不确定
 - 唯一性:自动去重,重复插入的元素会被忽略
 
2. 常用命令
(1)基本操作
| 命令 | 作用 | 示例 | 
|---|---|---|
SADD | 添加元素(自动去重) | SADD tags "redis" "db" | 
SREM | 删除元素 | SREM tags "db" | 
SMEMBERS | 获取所有元素 | SMEMBERS tags | 
SISMEMBER | 判断元素是否存在 | SISMEMBER tags "redis" → 1(存在) | 
SCARD | 获取集合元素数量 | SCARD tags → 2 | 
SRANDMEMBER | 随机返回元素(可指定数量) | SRANDMEMBER tags 2 | 
- 示例:
 
SADD users:1000:followers "user1" "user2" "user3"
SMEMBERS users:1000:followers  # 返回 ["user1", "user2", "user3"]
SISMEMBER users:1000:followers "user1"  # 返回 1(存在)
 
(2)集合运算
| 命令 | 作用 | 示例 | 
|---|---|---|
SINTER | 返回多个集合的交集 | SINTER set1 set2 | 
SUNION | 返回多个集合的并集 | SUNION set1 set2 | 
SDIFF | 返回第一个集合与其他集合的差集 | SDIFF set1 set2 | 
SINTERSTORE | 存储交集到新集合 | SINTERSTORE result set1 set2 | 
SUNIONSTORE | 存储并集到新集合 | SUNIONSTORE result set1 set2 | 
SDIFFSTORE | 存储差集到新集合 | SDIFFSTORE result set1 set2 | 
- 示例:
 
SADD group1 "user1" "user2" "user3"
SADD group2 "user2" "user3" "user4"
SINTER group1 group2  # 返回 ["user2", "user3"](共同成员)
SDIFF group1 group2   # 返回 ["user1"](仅在 group1 中的成员)
 
(3)随机返回元素
- Redis 的 Set 类型提供了 随机返回元素 的功能,适用于 抽奖、随机推荐、随机选取样本 等场景
 
SRANDMEMBER key count
 
key:Set 的键名count:指定返回的元素数量count > 0:返回 不重复 的元素(最多返回整个 Set 的大小)count < 0:返回元素可能 重复(适用于允许重复抽奖的场景)
(五)Sorted Set 类型
Redis 中的 Sorted Set 类型 也是一种集合类型,集合中的元素可以按照自定义分值排序,底层基于跳表实现。
1. 特点
- 唯一性:成员(member)不可重复,但分数(score)可重复
 - 有序性:按 score 排序(默认升序),相同 score 按字典序排序
 
2. 核心命令
-  
成员操作命令
命令 功能 ZADD key score member添加/更新成员(支持 NX/XX/INCR选项)ZREM key member删除指定成员 ZINCRBY key increment member增加成员分数 ZSCORE key member获取成员分数  -  
查询命令
命令 功能 ZRANGE key start stop按分数升序返回成员( WITHSCORES显示分数)ZREVRANGE key start stop按分数降序返回成员 ZRANGEBYSCORE key min max返回分数在 [min,max]区间的成员 -  
排名统计命令
命令 功能 ZRANK key member获取成员升序排名(从 0 开始) ZREVRANK key member获取成员降序排名 ZCARD key返回成员总数 ZCOUNT key min max统计分数范围内的成员数  -  
集合运算命令
计算多个有序集合的并集存储(交集存储)
ZUNIONSTORE/ZINTERSTORE destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]-  
必选参数
destkey:存储结果的键名numkeys:参与计算的集合数量(后续需对应数量的key)key:参与计算的集合键名(至少一个)
 -  
可选参数
WEIGHTS:为每个集合的分值设置权重(默认权重为1)AGGREGATE:合并重复成员的分值策略(SUM求和、MIN取最小、MAX取最大,默认SUM)
 -  
示例
 
ZADD scores1 10 "Alice" 20 "Bob" ZADD scores2 5 "Alice" 30 "Charlie" ZUNIONSTORE result 2 scores1 scores2 WEIGHTS 2 3 SUM-  
Alice的分值:10 * 2 + 5 * 3 = 35 -  
Bob的分值 =20 * 2 = 40 -  
Charlie的分值 =30 * 3 = 90 
1) "Alice" 35 2) "Bob" 40 3) "Charlie" 90 -  
 
3. 底层实现
Sorted Set 是 Redis 中最复杂的数据结构之一。它通过 跳表和哈希表 的混合实现,兼顾了高效查询和动态排序的能力。
| 编码方式 | 数据结构 | 触发条件 | 特点 | 
|---|---|---|---|
ziplist | 压缩列表 [member1, score1, member2, score2, ...] | 元素数量 ≤ zset-max-ziplist-entries(默认128)且所有元素长度 ≤ zset-max-ziplist-value(默认64字节) | 内存紧凑,但增删效率低(O(n)) | 
skiplist + dict | 跳表 + 哈希表 | 不满足 ziplist 条件时自动转换 | 支持高效查询和范围操作(O(log n)) | 
-  
哈希表与跳表的协同
跳表保证有序性,哈希表加速单成员查询,两者互补
 -  
内存与 CPU 的权衡
小数据用
ziplist节省内存,大数据用skiplist提升操作效率 
(1)跳表的结构
-  
跳表(Skip List)是一种基于 多层有序链表 的数据结构,通过 概率平衡 实现高效的动态操作(平均 O(log n) 时间复杂度)
 -  
跳表由多层链表组成,每层链表都是有序的,但高层链表是低层链表的【快速通道】:
-  
最底层链表:包含所有元素的有序链表
 -  
高层链表:每层节点数约为下一层的一半,形成跳跃路径
 
 -  
 
(2)查找原理:平均 O(log n)(最坏 O(n))
-  
从最高层头节点开始,向右遍历:
-  
若当前节点的下一个节点值 ≤ 目标值,则继续向右
 -  
若下一个节点值 > 目标值,则向下移动到下一层
 
 -  
 -  
重复上述过程,直到最底层,找到目标节点或确认不存在
 
(3)插入原理:平均 O(log n)
- 查找插入位置,记录每一层的前驱节点,即插入位置左侧的节点
 - 随机生成层高,每个新插入节点的层数由 概率决定,通常采用 “抛硬币”策略: 
- 初始层高:新节点至少在第 
0层(最底层,包含所有节点) - 逐层晋升:每次“抛硬币” 若为“正面”(概率通常为 
50%)则层数+1;否则停止 - Redis 优化:降低晋升概率(
p=0.25)和限制最大层数(32),平衡性能与内存开销 
 - 初始层高:新节点至少在第 
 - 在每一层(从生成的最高层到最底层)链表中插入新节点,并更新前后节点的指针