Redis用到的所有主要数据结构,简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合、跳跃表。
Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,而每种对象又通过不同地编码映射到不同的底层数据结构。
Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、 encoding属性和ptr属性:
Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的健(键对象),另一个对象用作键值对的值(值对象)。
typedef struct redisObiect{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
}
其中Redis的键对象都是字符串对象,而Redis的值对象主要有字符串、哈希、列表、集合、有序集合几种。其分别对应的内部编码和底层数据结构如下图所示:
一、字符串(String)
字符串对象是 Redis 中最基本的数据类型,也是我们工作中最常用的数据类型。redis中的键都是字符串对象,而且其他几种数据结构都是在字符串对象基础上构建的。字符串对象的值实际可以是字符串、数字、甚至是二进制,最大不能超过512MB 。
内部实现
Redis字符串对象底层的数据结构实现主要是int和简单动态字符串SDS,其通过int、raw和embstr三种不同的编码方式映射到不同的数据结构。Redis会根据当前值的类型和长度来决定使用哪种编码来实现。
1.如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为int
2.如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw。
3.如果字符串对象保存的是一个字符串值,并且这个字符申值的长度小于等于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为embstr
embstr编码是专门用于保存短字符串的一种优化编码方式,我们可以看到embstr和raw编码都会使用SDS来保存值,但不同之处在于embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObject和SDS。而raw编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObject和SDS。Redis这样做会有很多好处。
-
embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次
-
释放 embstr编码的字符串对象同样只需要调用一次内存释放函数
-
因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用CPU缓存提升性能。
常用命令
命令 | 描述 | 时间复杂度 |
---|---|---|
set key value [ex seconds] [px milliseconds] [nx xx] | 设置值 | O(1) |
get key | 获取值 | O(1) |
del key [key …] | 删除key | O(N)(N是键的个数) |
mset key [key value …] | 批量设置值 | O(N)(N是键的个数) |
mget key [key …] | 批量获取值 | O(N)(N是键的个数) |
incr key | 将 key 中储存的数字值增一 | O(1) |
decr key | 将 key 中储存的数字值减一 | O(1) |
incrby key increment | 将 key 所储存的值加上给定的增量值(increment) | O(1) |
decrby key increment | key 所储存的值减去给定的减量值(decrement) | O(1) |
incrbyfloat key increment | 将 key 所储存的值加上给定的浮点增量值(increment) | O(1) |
append key value | 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾 | O(1) |
strlen key | 返回 key 所储存的字符串值的长度。 | O(1) |
setrange key offset value | 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始 | O(1) |
getrange key start end | 返回 key 中字符串值的子字符 | O(N)(N是字符串的长度) |
二、哈希(Hash)
在redis中,哈希类型是指Redis键值对中的值本身又是一个键值对结构,形如value=[{field1,value1},…{fieldN,valueN}]。
内部实现
哈希类型的内部编码有两种:ziplist(压缩列表),hashtable(哈希表)。只有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件:
-
当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)
-
所有值都小于hash-max-ziplist-value配置(默认64字节)
ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
有关ziplist的底层数据结构的具体实现参考:Redis数据结构——压缩列表
常用命令
命令 | 说明 | 时间复杂度 |
---|---|---|
HDEL key field [field …] | 删除一个或多个Hash的field | O(N) N是被删除的字段数量。 |
HEXISTS key field | 判断field是否存在于hash中 | O(1) |
HGET key field | 获取hash中field的值 | O(1) |
HGETALL key | 从hash中读取全部的域和值 | O(N) N是Hash的长度 |
HINCRBY key field increment | 将hash中指定域的值增加给定的数字 | O(1) |
HINCRBYFLOAT key field increment | 将hash中指定域的值增加给定的浮点数 | O(1) |
HKEYS key | 获取hash的所有字段 | O(N) N是Hash的长度 |
HLEN key | 获取hash里所有字段的数量 | O(1) |
HMGET key field [field …] | 获取hash里面指定字段的值 | O(N) N是请求的字段数 |
HMSET key field value [field value …] | 设置hash字段值 | O(N) N是设置的字段数 |
HSET key field value | 设置hash里面一个字段的值 | O(1) |
HSETNX key field value | 设置hash的一个字段,只有当这个字段不存在时有效 | O(1) |
HSTRLEN key field | 获取hash里面指定field的长度 | O(1) |
HVALS key | 获得hash的所有值 | O(N) N是Hash的长度 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代hash里面的元素 |
三、列表(List)
列表(list)类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),一个列表最多可以存储232-1个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
列表类型有两个特点:
-列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
-列表中的元素可以是重复的.
内部实现
在Redis3.2版本以前列表类型的内部编码有两种。
-
ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
-
linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
而在Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.
有关quicklist的底层数据结构的具体实现参考: Redis数据结构——quicklist
常用命令
命令 | 说明 | 时间复杂度 |
---|---|---|
BLPOP key [key …] timeout | 删除,并获得该列表中的第一元素,或阻塞,直到有一个可用 | O(1) |
BRPOP key [key …] timeout | 删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用 | O(1) |
BRPOPLPUSH source destination timeout | 弹出一个列表的值,将它推到另一个列表,并返回它;或阻塞,直到有一个可用 | O(1) |
LINDEX key index | 获取一个元素,通过其索引列表 | O(N) |
LINSERT key BEFORE AFTER pivot value | 在列表中的另一个元素之前或之后插入一个元素 | O(N) |
LLEN key | 获得队列(List)的长度 | O(1) |
LPOP key | 从队列的左边出队一个元素 | O(1) |
LPUSH key value [value …] | 从队列的左边入队一个或多个元素 | O(1) |
LPUSHX key value | 当队列存在时,从队到左边入队一个元素 | O(1) |
LRANGE key start stop | 从列表中获取指定返回的元素 | O(S+N) |
LREM key count value | 从列表中删除元素 | O(N) |
LSET key index value | 设置队列里面一个元素的值 | O(N) |
LTRIM key start stop | 修剪到指定范围内的清单 | O(N) |
RPOP key | 从队列的右边出队一个元 | O(1) |
RPOPLPUSH source destination | 删除列表中的最后一个元素,将其追加到另一个列表 | O(1) |
RPUSH key value [value …] | 从队列的右边入队一个元素 | O(1) |
RPUSHX key value | 从队列的右边入队一个元素,仅队列存在时有效 | O(1) |
四、集合
集合类型 (Set) 是一个无序并唯一的键值集合。它的存储顺序不会按照插入的先后顺序进行存储。
集合类型和列表类型的区别如下:
-
列表可以存储重复元素,集合只能存储非重复元素;
-
列表是按照元素的先后顺序存储元素的,而集合则是无序方式存储元素的。
一个集合最多可以存储232-1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。
内部实现
集合类型的内部编码有两种:
-
intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
-
hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
常用命令
命令 | 说明 | 时间复杂度 |
---|---|---|
SADD key member [member …] | 添加一个或者多个元素到集合(set)里 | O(N) |
SCARD key | 获取集合里面的元素数量 | O(1) |
SDIFF key [key …] | 获得队列不存在的元素 | O(N) |
SDIFFSTORE destination key [key …] | 获得队列不存在的元素,并存储在一个关键的结果集 | O(N) |
SINTER key [key …] | 获得两个集合的交集 | O(N*M) |
SINTERSTORE destination key [key …] | 获得两个集合的交集,并存储在一个关键的结果集 | O(N*M) |
SISMEMBER key member | 确定一个给定的值是一个集合的成员 | O(1) |
SMEMBERS key | 获取集合里面的所有元素 | O(N) |
SMOVE source destination member | 移动集合里面的一个元素到另一个集合 | O(1) |
SPOP key [count] | 删除并获取一个集合里面的元素 | O(1) |
SRANDMEMBER key [count] | 从集合里面随机获取一个元素 | |
SREM key member [member …] | 从集合里删除一个或多个元素 | O(N) |
SUNION key [key …] | 添加多个set元素 | O(N) |
SUNIONSTORE destination key [key …] | 合并set元素,并将结果存入新的set里面 | O(N) |
SSCAN key cursor [MATCH pattern] [COUNT count] | 迭代set里面的元素 | O(1) |
五、有序集合(ZSet)
有序集合类型 (Sorted Set或ZSet) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QuUZtMwF-1666147245780)(…/static/有序集合.png)]
内部实现
有序集合是由 ziplist (压缩列表) 或 skiplist (跳跃表) 组成的。当数据比较少时,有序集合使用的是 ziplist 存储的,有序集合使用 ziplist 格式存储必须满足以下两个条件:
-
有序集合保存的元素个数要小于 128 个;
-
有序集合保存的所有元素成员的长度都必须小于 64 字节。
如果不能满足以上两个条件中的任意一个,有序集合将会使用 skiplist 结构进行存储。
常用命令
命令 | 说明 | 时间复杂度 |
---|---|---|
BZPOPMAX key [key …] timeout | 从一个或多个排序集中删除并返回得分最高的成员,或阻塞,直到其中一个可用为止 | O(log(N)) |
BZPOPMIN key [key …] timeout | 从一个或多个排序集中删除并返回得分最低的成员,或阻塞,直到其中一个可用为止 | O(log(N)) |
ZADD key [NXXX] [CH] [INCR] score member [score member …] | 添加到有序set的一个或多个成员,或更新的分数,如果它已经存在 | O(log(N)) |
ZCARD key | 获取一个排序的集合中的成员数量 | O(1) |
ZCOUNT key min max | 返回分数范围内的成员数量 | O(log(N)) |
ZINCRBY key increment member | 增量的一名成员在排序设置的评分 | O(log(N)) |
ZINTERSTORE | 相交多个排序集,导致排序的设置存储在一个新的关键 | O(NK)+O(Mlog(M)) |
ZLEXCOUNT key min max | 返回成员之间的成员数量 | O(log(N)) |
ZPOPMAX key [count] | 删除并返回排序集中得分最高的成员 | O(log(N)*M) |
ZPOPMIN key [count] | 删除并返回排序集中得分最低的成员 | O(log(N)*M) |
ZRANGE key start stop [WITHSCORES] | 根据指定的index返回,返回sorted set的成员列表 | O(log(N)+M) |
ZRANGEBYLEX key min max [LIMIT offset count] | 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。 | O(log(N)+M) |
ZREVRANGEBYLEX key max min [LIMIT offset count] | 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同 | O(log(N)+M) |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] | 返回有序集合中指定分数区间内的成员,分数由低到高排序。 | O(log(N)+M) |
ZRANK key member | 确定在排序集合成员的索引 | O(log(N)) |
ZREM key member [member …] | 从排序的集合中删除一个或多个成员 | O(M*log(N)) |
ZREMRANGEBYLEX key min max | 删除名称按字典由低到高排序成员之间所有成员。 | O(log(N)+M) |
ZREMRANGEBYRANK key start stop | 在排序设置的所有成员在给定的索引中删除 | O(log(N)+M) |
ZREMRANGEBYSCORE key min max | 删除一个排序的设置在给定的分数所有成员 | O(log(N)+M) |
ZREVRANGE key start stop [WITHSCORES] | 在排序的设置返回的成员范围,通过索引,下令从分数高到低 | O(log(N)+M) |
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] | 返回有序集合中指定分数区间内的成员,分数由高到低排序。 | O(log(N)+M) |
ZREVRANK key member | 确定指数在排序集的成员,下令从分数高到低 | O(log(N)) |
ZSCORE key member | 获取成员在排序设置相关的比分 | O(1) |
ZUNIONSTORE | 添加多个排序集和导致排序的设置存储在一个新的关键 | O(N)+O(M log(M)) |
ZSCAN key cursor [MATCH pattern] [COUNT count] | 迭代sorted sets里面的元素 | O(1) |