Redis 基础数据结构(三)

这一节我们介绍Redis的列表(list)

一、list(列表)简单介绍

1、Redis列表相当于java语言里面的LinkedList,注意它是链表而不是数组。这意味着list的插入和删除操作的时间复杂度为O(1),但是查找时间复杂度为O(n)

2、列表中每个元素使用双向指针,支持前向后向遍历。列表最后一个元素弹出之后,该数据结构被自动删除,内存回收。

3、Redis列表结构可以用来做异步队列使用

4、支持的操作

右边进左边出:队列

com.xiaozhameng.aliyun:6379> rpush books python java golang
(integer) 3
com.xiaozhameng.aliyun:6379> lpop books
"python"
com.xiaozhameng.aliyun:6379> lpop books
"java"
com.xiaozhameng.aliyun:6379> lpop books
"golang"
com.xiaozhameng.aliyun:6379> lpop books
(nil)
com.xiaozhameng.aliyun:6379> 

右边进右边出:栈

com.xiaozhameng.aliyun:6379> rpush books python java golang
(integer) 3
com.xiaozhameng.aliyun:6379> rpop books
"golang"
com.xiaozhameng.aliyun:6379> rpop books
"java"
com.xiaozhameng.aliyun:6379> rpop books
"python"
com.xiaozhameng.aliyun:6379> rpop books
(nil)
com.xiaozhameng.aliyun:6379> 

当然列表的操作不止于此,小伙伴们可以继续探索,列表在实际应用中也是一种常用的数据结构。

5、慢操作

Redis列表数据结构我们已经知道是基于链表实现的,有些指令的时间复杂度比较高,要慎用。

lindex 相当于java里面的get(int index) 方法,它需要遍历链表。时间复杂度较高

ltrim 的含义是保留其后两个参数区间内的值。可以通过ltrim实现一个定长链表

index 可以为负数,index=-1 标识倒数第一个元素,同理index=-2表示倒数第二个元素

com.xiaozhameng.aliyun:6379> rpush books python java golang
(integer) 3
com.xiaozhameng.aliyun:6379> lindex books 1       // 时间复杂度为O(n),慎用
"java"
com.xiaozhameng.aliyun:6379> lrange books 0 -1    // 时间复杂度为O(n),慎用
1) "python"
2) "java"
3) "golang"
com.xiaozhameng.aliyun:6379> lrange books 0 3     // 时间复杂度为O(n),慎用
1) "python"
2) "java"
3) "golang"
com.xiaozhameng.aliyun:6379> ltrim books 1 -1
OK
com.xiaozhameng.aliyun:6379> lrange books 0 -1
1) "java"
2) "golang"
com.xiaozhameng.aliyun:6379> llen books
(integer) 2
com.xiaozhameng.aliyun:6379> 

二、list(列表)内部实现

上面提到list底层是基于链表实现的,实际上,Redis的list结构底层不仅仅是一个linkedList,而是快速列表的数据结构。在数据元素较少的情况下,Redis会使用一块连续的内存存储。这个结构是ziplist,也就是压缩列表,他将所有的元素都紧挨着一起存储,分配连续的内存空间,当数据量比较大的时候,才会修改成quicklist。因为普通的链表需要附加指针空间,还会加重内存的碎片化,Redis将链表和压缩列表结合使用,quicklist既满足了快速的新增删除性能,也不会出现太大的空间浪费。

三、压缩列表

上面提到list结构在数据较少的时候使用了一种叫做压缩列表(ziplist)的数据结构,实际上,Redis为了节约内存,zset,hash,list等几种类型的数据结构在存储元素较少时,都使用了ziplist,这种基于数据规模大小选择合适数据结构的思想,正体现了Redis对于性能的机制追求。压缩列表(ziplist)是一块连续的内存空间,元素之间紧挨着,没有冗余。我们看一下ziplist结构的定义

struck ziplist<T> {
	int32 zlbytes;			// 整个压缩列表占用的字节数
	int32 zltail_offset; 	// 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位最后一个节点
	int16 zllength;			// 元素个数
	T[] entries;			// 元素内容列表,依次紧凑存储
	int8 zlend;				// 标志压缩列表结束,其值恒为0xFF
}

entry块随着容纳元素的类型不同,也会有不同的结构。对于entry的介绍,这里不再赘述,如果有同学还想深入了解,可以参考这篇博客:https://juejin.im/post/5caaab786fb9a05e6538bb18 

因为ziplist都是紧凑存储的,没有冗余空间,这就意味着,新增一个元素就要调用realloc扩展内存,这也取决于分配器算法和当前的ziplist内存大小,realloc可能会重新分配新的内存空间,并将之前的内容一次性拷贝到新的地址,也有可能在原有地址上进行扩展,这是就不需要进行旧内容的拷贝。但是如果ziplist占据内存地址较大,重新分配内存和拷贝就会有很大的消耗,所以ziplist不适合存储大型字符串,存储的元素也不宜过多。

四、快速列表(quicklist)

Redis早起的版本里面存储list列表数据使用的压缩列表和普通的双向链表linkedList,当元素较少的时候使用ziplist,当元素较多时,使用linkedList,但是考虑到双向链表要维护prev和next指针需要占去16字节的空间,另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响了整体内存的管理效率,所以在后来的版本中,Redis对列表进行了改造,使用quicklist。

com.xiaozhameng.aliyun:6379> debug object books
Value at:0x7f62a76665c0 refcount:1 encoding:quicklist serializedlength:27 lru:3998069 lru_seconds_idle:1879 ql_nodes:1 ql_avg_node:2.00 ql_ziplist_max:-2 ql_compressed:0 ql_uncompressed_size:25
com.xiaozhameng.aliyun:6379>

注意以上输出的encoding字段,quicklist是ziplist和linkedlist的结合,他将linkedlist分成一段一段的,每一段使用ziplist让存储更加紧凑,多个ziplist之间使用双向指针串起来。

为了进一步节约空间,Redis还会对ziplist进一步压缩存储,使用LZF算法进行压缩。可以选择压缩深度

quicklist内部每个ziplist长度为8kb,如果超过了这个字节数,就会另起一个ziplist,ziplist的长度由配置参数list-max-ziplist-size来决定。quicklist默认的压缩深度是0,也就是不压缩,

总结一下

这一节里面主要介绍了Redis的list数据类型和对应的底层数据结构实现。Redis的list是一个双向快速链表,为了节约内存空间,底层使用ziplist,默认情况下,单个ziplist的长度是8kb。Redis的列表数据类型可以当做先进先出的队列和先进后出的栈来使用。另外我们注意到不管是String类型,list,底层的quicklist结构,Redis在实际存储数据的时候,会考虑数据规模选用不同的数据结构来存储,可见redis对于性能要求的极致设计

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值