浅谈 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
→11
GETRANGE
截取子串 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 "[email protected]" |
HSCAN |
增量迭代字段(大数据量时避免阻塞) | HSCAN user:1 0 |
-
示例:
HSETNX user:1 email "[email protected]" # 仅当 email 不存在时设置
3. 对象的三种存储策略:单 key 存储、多 key 存储、使用 hash 类型
(1)序列化为字符串(单 Key 存储)
SET user:1000 '{"name":"Alice","age":30,"email":"[email protected]"}'
(2)每个字段单独存储(多 Key 存储)
SET user:1000:name "Alice"
SET user:1000:age 30
SET user:1000:email "[email protected]"
(3)使用 Hash 类型
HSET user:1000 name "Alice" age 30 email "[email protected]"
(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
- "Alice" 35
- "Bob" 40
- "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
),平衡性能与内存开销
- 初始层高 :新节点至少在第
- 在每一层(从生成的最高层到最底层)链表中插入新节点,并更新前后节点的指针