redis 数据类型详解
本文主要介绍Redis 五种数据结构字符串、哈希、列表、集合、有序集合,本文主要从下面几个方面进行介绍
- 使用方式
- 使用场景
- 内部编码
redis 字符串
命令格式
SET key value [EX seconds] [PX milliseconds] [NX|XX]
key:要设置的键名。
value:与键关联的新值。
[EX seconds]:设置键的过期时间(以秒为单位)。
[PX milliseconds]:设置键的过期时间(以毫秒为单位)。
[NX]:仅在键不存在时设置键。
[XX]:仅在键已经存在时设置键。
批量命令
使用 MSET
和 MGET
批量多个键值对操作,批量操作可以减少网络传输时间,一定程度能提升性能,注意由于redis是单线程执行命令,如果一次批量执行命令过多,可能会阻塞其他命令。
使用场景
-
分布式锁
通过 NX 选项可以实现分布式锁, 当然如果考虑锁续期,以及分布式下单点故障,可以使用redission 替代。
-
缓存
比如缓存用户信息,登录态、
使用demo
> SET user:1000 "Alice"
OK
> GET user:1000
"Alice"
# 批量命令
> MSET user:1001 "Bob" user:1002 "Charlie" user:1003 "Diana"
OK
# 同时获取多个用户的姓名
> MGET user:1001 user:1002 user:1003
1) "Bob"
2) "Charlie"
3) "Diana"
内部编码
-
int:
当字符串是整数,并且可以由 long 类型表示时,Redis 直接将这个整数存储为一个整数值,这种编码非常节省内存,因为不需要额外的 SDS 头部信息。
-
embstr:
当字符串较短时,Redis 会使用 embstr 编码。,它将 SDS 的头部和数据放在一块连续的内存区域中,这样做可以减少内存碎片,提高缓存局部性。
-
raw:
对于较长的字符串,Redis 会使用 raw 编码。
在这种编码下,SDS 的头部和数据是分开分配的。这种方式更灵活,允许对大容量数据进行高效的管理。
综上 Redis字符串类型底层结构不仅仅是SDS,根据情况对空间与时间的折中选择合适的编码格式。
哈希
redis 哈希可以用来存储多个键值对,例如存储用户信息、商品信息等结构化数据
使用demo
# 使用HSET命令存储用户信息
HSET user:1000 username "codetonignt"
HSET user:1000 age 18
HSET user:1000 email "codetonignt@example.com"
# 使用HGET命令获取用户信息
HGET user:1000 username # 返回 "codetonignt"
HGET user:1000 age # 返回 "18"
HGET user:1000 email # 返回 "codetonignt@example.com"
# 使用HGETALL命令获取用户的所有信息
HGETALL user:1000
# 返回 "username" "codetonignt" "age" "18" "email" "codetonignt@example.com"
# HMSET 设置多个字段值
HMSET user:1000 sex man nationality china
# HMGET 获取多个字段值
HMGET user:1000 sex nationality
# 返回 "man" "china"
性能
hash 可以最多存储(2^32 - 1)个键值对,大多数hash操作时间复杂度是O(1)。HKEYS, HVALS, HGETALL时间复杂度是O(N);
内部编码
-
ziplist:
当哈希中的字段数量较少,并且每个字段和值的大小都较小时,Redis 会使用 ziplist 编码,它更加节约内存。
-
hashtable:
当哈希中的字段数量较多或单个字段/值的大小较大时,Redis 会切换到 hashtable 编码。哈希表提供 O(1) 平均时间复杂度的操作性能,但存储空间比ziplist消耗更多。
列表
Redis列表是简单的字符串列表,按照插入顺序排序。支持在两端进行添加或移除元素,列表的最大长度是 2^32 - 1 个元素。
Redis 列表的主要特性包括:
- 可以在两端插入和删除
- 列表中元素可以重复
- 元素是有顺序的
- BLPOP 和 BRPOP 操作是阻塞的
常见命令
LPUSH key value [value ...]:将一个或多个值插入到列表 key 的头部。
RPUSH key value [value ...]:将一个或多个值插入到列表 key 的尾部。
LPOP key:移除并返回列表 key 的头元素。
RPOP key:移除并返回列表 key 的尾元素。
LRANGE key start stop:获取列表 key 中指定范围内的元素,索引从 0 开始。
LLEN key:返回列表 key 的长度。
LINDEX key index:返回列表 key 中,下标为 index 的元素。
LSET key index value:设置列表 key 中下标为 index 的元素的值为 value。
LTRIM key start stop:修剪列表 key,使之只保留指定范围内的元素。
BLPOP key [key ...] timeout:移除并返回第一个非空列表的第一个元素,或者阻塞直到有元素可取或超时。
BRPOP key [key ...] timeout:与 BLPOP 类似,但是是从列表的尾部弹出元素。
内部编码
-
ziplist
当列表中元素较少,且每个元素都是小正数或者是比较短字符串时,使用ziplist存储,它将多个元素紧凑地存储在一起,减少了内存碎片
-
双向链表
否则使用 双向链表实现的:Redis 使用的是双向链表,维护一个长度计数器,因此LLEN key时间复杂度O(1)
相关配置
如果列表中的元素数量超过了 list-max-ziplist-entries 参数设置的值(默认为512)。
或者如果列表中的任何一个元素的大小超过了 list-max-ziplist-value 参数设置的值(默认为64字节)
使用场景
- 最新消息排行榜
- 消息队列实现 (利用列表阻塞操作)
性能
list 最多可以有 2^32 - 1 个元素。
LPUSH、LPOP、RPUSH、RPOP 头部或尾部操作时间复杂度是O(1),
LINDEX、 LINSERT、 LSET 时间复杂度是O(N);
同样的如果链表中元素数量过多,同样存在大key问题 ,
(比如使用 LRANGE key start stop 等命令取出所有元素)
集合
Redis 集合是一种无序且不重复的数据结构,类似于Java中的HashSet,它支持交集、并集、差集等功能,用于存储一组字符串元素。
限制:
集合最多可以有 2^32 - 1 (4,294,967,295)个元素
使用示例
# 为用户 user1001 添加标签
> SADD user1001:tags "photography" "travel" "food"
(integer) 3
# 为用户 user1001 添加更多标签
> SADD user1001:tags "music"
(integer) 1
# 删除用户 user1001 的某个标签
> SREM user1001:tags "food"
(integer) 1
# 获取用户 user1001 的所有标签
> SMEMBERS user1001:tags
1) "photography"
2) "travel"
3) "music"
# 检查用户 user1001 是否有特定标签
> SISMEMBER user1001:tags "photography"
(integer) 1
> SISMEMBER user1001:tags "sports"
(integer) 0
性能
大多数集合操作包括SADD、SREM和SISMEMBER 时间复杂度都是O(1),SMEMBERS 返回所有元素如果集合中元素过多时应谨慎使用,考虑使用SSCAN替代,对于大数据场景,判断元素是否在集合中可能占大量内存,可以考虑使用bloom过滤器替代。
内部编码
-
intset:
当集合中的所有元素都是整数,并且集合的大小较小(默认少于512个元素)时,Redis 会使用 intset 编码。intset 是一个有序的整数数组,支持快速查找和插入操作。它在内存中占用的空间较少,并且对小规模的数据集非常高效。
-
hashtable:
当集合中的元素较多或包含非整数元素时,Redis 会切换到 hashtable 编码。哈希表提供 O(1) 平均时间复杂度的操作性能。
使用场景
可以用集合存储用户标签、好友关系等。
有序集合
Redis有序集合可以存储不重复的字符串成员,并为每个成员关联一个分数(score),以便按分数对成员进行排序。
因此它可以用来实现排行榜、优先级队列等业务场景
使用举例
> ZADD leaderboard 100 "user1"
(integer) 1
> ZADD leaderboard 200 "user2" 150 "user3"
(integer) 2
# ZRANGE key start stop [WITHSCORES] 按照分数从小到大获取指定范围内的成员
> ZRANGE leaderboard 0 2 WITHSCORES
1) "user1"
2) "100"
3) "user3"
4) "150"
5) "user2"
6) "200"
# ZREVRANGE key start stop [WITHSCORES] 按照分数从大到小获取指定范围内的成员
> ZREVRANGE leaderboard 0 2 WITHSCORES
1) "user2"
2) "200"
3) "user3"
4) "150"
5) "user1"
6) "100"
性能
大部分操作时间复杂度是 O(log(n))
内部编码
-
ziplist:
当有序集合中的元素数量较少,并且每个元素的大小也较小时,Redis 会使用 ziplist 编码。ziplist 非常适合小规模的数据集,因为它在内存中占用的空间较小,并且访问速度较快。
-
skiplist:
当有序集合中的元素数量较多或单个元素的大小较大时,Redis 会切换到 skiplist 编码。跳表支持高效的范围查询和插入/删除操作,适用于大规模的数据集。
总结
- 时间与空间兼顾
Redis 底层使用不仅仅考虑效率(时间复杂度),同时也兼顾空间复杂度,因此同一种数据类型,Redis底层编码因存储内容,大小等因素不同,会采用不同的编码进行存储。
- 合理使用批量命令
Redis 基本是内存操作,因此性能很高,通常多个操作耗时主要在网络传输上,因此redis支持一些批量命令,如mget ,mset 可以有效减少网络传输时间,由于redis 是当线程模型,批量命令也不能滥用,一次批量键值对也不能过多,否则会阻塞其他命令。
- 大Key问题
大key指占用大量内存的键,如很长的字符串,列表元素过多。访问或操作大Key,会导致 Redis 服务器阻塞一段时间,同时会消耗大量的 CPU 和内存资源,因此需要尽量避免。