keys *:查看所有的键 O(n)
dbsize:键总数 O(1)
exists key :检查键是否存在
del key:删除键
expire key seconds:键过期
ttl : 返回键的剩余时间 (
)
type key:键的类型
object encoding:查询内部编码
每种数据结构都有两种以上的内部编码实现,好处:
为什么单线程还能这么快?
缺点:如果某个命令执行时间过长,会造成其他命令的阻塞。
字符串类型的值可以是普通字符串,也可以是复杂字符串(Json, Xml), 数字(整数、浮点数),二进制的图片、音频、视频,但值最大不能超过512MB
set key value [ex seconds] [px milliseconds] [nx | xx]
get key
mset key value [key value ……]
mget key [key……]
incr key 自增
decr、incrby(自增指定数字)、decrby、incrbyfloat
append key value 向字符串尾部追加值
strlen key 字符串长度
每个中文占用3个字节
getset key value 设置并返回原值
setrange key offset value 设置指定位置的字符
getrange key start end 获取部分字符串,下标从0开始,包括右下标
有3种编码:
通过object encoding key 查看内部编码
有2种:
ziplist (压缩列表):当元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节),redis使用ziplist作为内部编码。ziplist 使用紧凑的结构实现多个元素的连续存储,在节省内存方面比hashtable更加好。
hashtable (哈希表):当上述条件无法满足时,redis使用hashtable,因为此时ziplist 的读写效率会下降,而hashtable 的读写时间复杂度为O(1)
缓存:用户的id作为键后缀,多对f-v对应每个用户的属性
一个列表最多可以存储232-1 个元素,列表中的元素是有序的且可以重复的
lpush key value 从左插入元素
rpush key value 从右插入元素
lrange 0 -1 从左到右获取列表中的所有元素
linsert key before | after pivot value 从列表中找到等于pivot的元素,插入
lrange key start end 获取指定索引范围的所有元素,索引下标:从左到右是0 - N-1,从右到左是-1 - -N;lrange中的end元素包括自身。
lindex key index 获取指定索引下标
llen key 获取列表长度
lpop key 从左弹出元素
lrem key count value
ltrim key start end 按照索引范围剪列表
lset key index newValue 修改指定索引下标的元素
blpop/brpop key timeout 阻塞弹出
key 可以多个
timeout 如果列表为空,则等待timeout时间返回。timeout = 0时,则一直等待。
如果列表不为空,立即返回
注意:
ziplist:当列表元素个数小于list - max - ziplist - entries(默认512个),同时列表中每个元素的值小于list - max - ziplist - values(默认64字节)
linkedlist:无法满足ziplist条件,则用linkedlist
集合不允许有重复元素,且元素是无序的,不能通过索引获取。
sadd key element[...] 添加元素
srem key element[...] 删除元素
scard key 计算元素个数
sismember key element 判断元素是否在集合中,返回1存在,0不存在
srandmember key 【count】 随机返回集合中指定个数的元素
spop key 随机弹出
smembers key 获取所有元素,可能会造成阻塞
sinter key [key...] 求交集
sunion key [key...] 求并集
sdiff key [key...] 求差集
sinterstore / sunionstore / sdiffstore destination key [key...] 求结果并保存
intset(整数集合):元素都是整数并且元素个数小于 set - max - intset - entries (默认512个),减少内存的使用
hashtable(哈希表):无法满足上述,则用hashtable
没有重复元素,给每个元素设置一个分数,作为排序的依据;提供了获取指定元素分数和元素范围查询、计算成员排名等功能
zadd key score member [....] 添加成员
zcard key 计算成员个数
zscore key memeber 计算某个成员分数
zrank key memeber 从低到高排名
zrevrank key member 从高到底排名
zrem key member [....] 删除成员
zincrby key increment member 增加成员分数
zrange key start end [withscores] 返回指定排名范围成员
zrevrange key start end [withscores] 返回指定排名范围成员
zrangebyscore key min max 返回指定分数范围成员
zcount key min max 返回指定分数范围成员个数
zremrangebyrank key start end 删除指定排名范围内的元素
zremrangebyscore key min max 删除指定分数范围内的元素
zinterstore destination numkeys key [key...] [weights [weight ...]] [aggregate sum|min|max] 求交集
zunionstore......
ziplist:元素个数小于zset - max - ziplist - entries(默认128个),值小于zset - max - ziplist - value(默认64字节)
skiplist:不满足上述条件
排行榜:比如对用户上传的视频做排行榜,可以按照时间、播放量、赞数排行。
比如赞数:
添加用户赞数
zadd ...
之后再获得一个赞:zincrby ....
取消用户赞数
zrem ...
展示获取赞数最多的10个用户
zrevrangebyrank ...
压缩列表是列表键和哈希键的底层实现之一,键值要么是小整数值,要么是短字符串
压缩列表是为了节约内存,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值
压缩列表组成:
属性 | 类型 | 长度 | 用途 |
---|---|---|---|
zlbytes | uint32_t | 4字节 | 记录整个压缩列表占用的字节数:进行内存重分配或者计算zlend位置时使用 |
zltail | uint32_t | 4字节 | 记录表尾节点距离压缩列表的起始地址有多少字节:可以无须遍历就确定尾节点地址 |
zllen | uint16_t | 2字节 | 记录列表包含的节点数量:当小于65535时,节点数量;否则,只能遍历算出数量 |
entryX | 列表节点 | 不定 | 压缩列表节点,节点的长度由节点内容决定 |
zlend | uint8_t | 1字节 | 特殊值OxFF(十进制255),用于标记压缩列表的末端 |
节点组成:
每个节点可以保存一个字节数组或一个整数值:
字节数组:
整数值:
previous_entry_length:以字节为单位,可以是1字节或5字节,记录前一个节点的长度
如果前一个节点的长度小于254字节,该值为1字节
如果前一个节点的长度大于等于254字节,该值为5字节,第一字节会被设置为0xFE(254),后4个字节表示前一节点的长度
作用:可以根据当前节点的起始地址计算前一节点的起始地址,压缩列表从表尾到表头的遍历操作就是这样实现的。
encoding:记录content属性保存的数据类型及长度
字节数组:1字节、2字节或5字节长,值最高位为00,01或者10
编码 | 编码长度 | content属性保存的值 |
---|---|---|
00bbbbbbb | 1字节 | 长度小于等于26 - 1字节的字节数组 |
01bbbbbbb xxxxxxxx | 2字节 | 长度小于等于214 - 1字节的字节数组 |
10___ aaa…… | 5字节 | 长度小于等于232 - 1字节的字节数组 |
整数:1字节长,值的最高位以11开头
编码 | 编码长度 | content属性保存的值 |
---|---|---|
11000000 | 1字节 | int16_t类型的整数 |
11010000 | 1字节 | int32_t类型的整数 |
11100000 | 1字节 | int64_t类型的整数 |
11110000 | 1字节 | 1字节 |
11111110 | 1字节 | 8位有符号整数 |
1111xxxx | 1字节 | 没有content属性,本身包含了0-12的值 |
content:负责保存节点的值,节点值可以是一个整数或字节数组
连锁更新:压缩列表恰有好多个连续的、长度介于250字节至253字节的节点,当添加一个长度大于等于254字节的节点时,可能会导致后续的节点的previous_entry_length属性从1字节扩展为5字节。删除节点也可能产生更新。时间复杂度O(n2)
整数集合是集合键的底层实现之一,集合只包含整数值元素并且元素数量不多。
encoding:contents数组中存储值的类型
length:集合包含的元素个数
contents:按照从小到大的顺序保存元素
升级:当添加一个新元素到整数集合时,并且新元素的类型比整数集合中所有元素类型都要长时,要先进行升级,然后才将新元素添加到整数集合中。
升级整数集合的三步骤:
根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间
将底层数组现有的所有元素都转换成与新元素相同的类型,然后将元素放到正确的位置上,需要维持底层数组的有序性
将新元素添加到底层数组
每次升级都需要对底层数组的元素进行类型转换,因此向整数集合添加新元素的时间复杂度为O(N)
升级后新元素的位置:
引发升级的新元素的长度总是比整数集合所有的元素长度大,
升级的好处:提升整数集合的灵活性,尽可能节约内存
提升灵活性:整数集合通过自动升级底层数组来适应新元素,因此可以随意将不同类型的整数添加到集合中,灵活性较好。
节约内存:整数集合既能保存三种不同类型的值,又能确保升级操作只会在有需要的时候进行,可以尽量节省内存。
整数集合不支持降级,一旦升级,就会一直保持升级后的状态
跳表是一种有序的数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的
跳表是有序集合键的底层实现之一,当有序集合中包含的元素个数较多或者元素的成员是比较长的字符串时
redis用到跳表的地方:有序集合键、集群节点的内部数据结构
表头节点的结构:表头节点也有后退指针、分值和成员对象
跳表节点的结构:
level数组:level数组可以包含多个元素,每个元素都包含前进指针和跨度。
前进指针用于从表头向表尾访问其他节点,跨度则记录了前进指针所指向节点和当前节点的距离。
层的数量越多,访问其他节点的速度就更快。
每次创建一个新节点时,会随机生成一个1-32之间的值作为level数组的大小。
跨度实际上是用来排位的,而不是遍历:在查找某个节点的过程中,将沿途访问过的所有层的跨度累计得到的结 果就是目标节点在跳表中的排位。
后退指针:指向位于当前节点的前一个节点,可以用于从表尾向表头遍历时使用,每次只能后退至前一个节点
分值:在跳表中,节点按各自所保存的分值从小到大排列
成员对象:节点所保存的成员对象
各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值可以是相同的,如果分值相同,则按照成员对象的字典序来排序,较小的排前面。
Redis数据库使用字典作为底层实现,字典也是哈希键的底层实现之一,当包含的键值对比较多或者键值对中的元素都是比较长的字符串时
字典组成:
type:一个指向dictType结构的指针,每个dictType保存了一组用于操作特定类型键值对的函数
dictType包含的函数:计算hash值;复制键;复制值;销毁键;销毁值;对比键
privdata:传给函数的可选参数
ht:包含两个项的数组,每个项都是一个哈希表,一般只是用ht[0],ht[1]只会在对ht[0]哈希表进行rehash使用
rehashidx:rehash索引,记录当前rehash的进度,如果当前没有进行rehash,值为-1
哈希表组成:
哈希表节点组成:
哈希算法:
使用MurmurHash2算法,调用type中的计算哈希值的函数算出hash,然后和sizemask进行按位&运算
键冲突:
采用链地址法,为了速度考虑,采用头插法,总是将新节点添加到链表的表头
rehash:
当保存的键值对太多或太少,需要通过rehash对哈希表的大小进行相应的扩展或者收缩,让负载因子维持在一个合理范围内。
rehash步骤:
哈希表的扩展与收缩:
扩展:
当前没有在执行 BGSAVE 或 BGREWRITEAOF ,并且负载因子大于等于1
当前有在执行 BGSAVE 或 BGREWRITEAOF,并且负载因子大于等于5
负载因子 = (哈希表已保存的节点数量)/ 哈希表大小
在执行BGSAVE 或 BGREWRITEAOF 时,需要创建子进程,大多数操作系统都是采用写时复制来优化子进程使用效率,所以在子进程存在期间,提高执行扩展所需负载因子,避免子进程存在期间进行扩展操作,避免不必要的内存写入,最大限度节约内存。
收缩:负载因子小于0.1时,执行收缩操作。
渐进式rehash:
rehash并不是一次性完成,而是分多次、渐进式完成,如果键值对数量比较多,要一次性将全部键值对rehash到ht[1],可能会导致服务器在一段时间内停止服务
rehash步骤:
rehash期间操作:
链表是列表键的底层实现之一,除此之外,发布与订阅、慢查询、监视器等功能也用到了链表,Redis本身也使用了链表保存多个客户端的状态信息
链表组成:
节点组成:
特性:
Redis中,C字符串只会作为字符串字面量,用在一些无须对字符串值进行修改的地方,比如打印日志
字符串值的键值对都是SDS实现的,SDS还可以被用作缓冲区:AOF缓冲区、客户端输入缓冲区
len:字符串的长度
free:未使用的字节数量
buf:字节数组,用于保存字符串
SDS遵循C字符串以空字符结尾的惯例,空字符的1字节空间不计算在len中,好处:可以直接重用一部分C字符串函数。
SDS与C字符串的区别:
常数复杂度获取字符串长度
C字符串不记录自身的长度,要获取长度,必须遍历,O(N);SDS只要反问len属性就可以知道长度,即使对一个非常长的字符串键反复执行获取长度的命令,也不会对系统造成任何影响。
杜绝缓冲区溢出
C字符串如果没有分配足够的内存空间,在执行修改字符串时可能会溢出。SDS杜绝了缓冲区溢出的可能性:当SDS修改时,会先检查SDS空间是否满足修改所需的要求,不满足自动将SDS扩展至执行修改所需的大小,然后执行实际修改操作。
减少修改字符串时的内存重分配次数
每次增长或缩短一个C字符串,都要进行一次内存重分配操作:
如果是增长操作,需要先通过内存重分配来扩展底层数组,忘了这一步会产生缓冲区溢出
如果是缩短操作,需要先通过内存重分配来释放不再使用的空间,忘 了这一步会产生内存泄漏
内存重分配是一个耗时的操作,如果每次修改字符串都要进行内存重分配,在修改频繁的情况下,会对性能造成影响。
SDS的空间预分配和惰性空间两种优化策略:
空间预分配:每次修改并需要对SDS进行扩展时,除了分配修改所需要的空间,还需要分配额外未使用的空间
如果修改之后,SDS的长度小于1MB,将分配和len属性同样大小的未使用空间,这时buf实际长度等于len字节+free字节+1字节
如果修改之后,SDS的长度大于等于1MB,将分配1MB未使用空间,buf实际长度等于lenMB+1MB+1字节
通过空间预分配,可以减少连续执行字符串增长操作所需的内存重分配次数,和C字符串对比,将连续增长N次所需的内存重分配次数从必定的N次降低为最多N次
惰性空间释放:优化缩短SDS操作,需要缩短SDS时,不是立即用内存重分配来回收缩短后多出来的字节,而是使用free记录这些字节的数量,并等待将来使用。
通过惰性空间释放,SDS避免了缩短字符串所需的内存重分配操作,并为将来可能有的增长操作提供了优化
二进制安全
C字符串除了字符串末尾的空字符外,里面不能包含空字符,使得只能保存文本数据,不能保存图片、音频、视频等二进制文件。
SDS的buf属性是保存二进制数据,SDS会以处理二进制的方式处理buf数组,数据写入时什么样,读出时就什么样。
重用部分C字符串函数
SDS遵循C字符串以空字符结尾,可以让保存文本数据的SDS重用一部分C字符串函数。
联系客服