ziplist数据结构
内存会一次性开辟一块大的连续的空间,来存放ziplist。
-
• zlbytes
32bit内存空间,表示ziplist占用的字节总数
-
• zltail
32bit内存空间,表示ziplist表中最后一项(entry)在ziplist中的偏移字节数,通过zltail可以很方便的找到最后一项,从而可以在ziplist尾端快速的执行push或pop操作。
-
• zllen
16bit内存空间,表示ziplist中数据项entry的个数
-
• zlend:255
ziplist最后一个字节,是一个结束标记,值固定等于255
-
• entry
表示真正存放数据的数据项,长度不定
-
• prerawlen
表示上一个节点的数据长度信息。
如果前一个元素的大小prerawlen等于254,用一个字节作为标记项,在用4个字节描述前一个元素大小。
ziplist额外的信息最多需要5个字节存储,相比quicklist双端链表的一个元素需要2个指针16个字节来说节省很多内存开销。
-
• len
entry中数据的长度
-
• data
表示当前元素里面的真实数据,业务数据存储,这是一个非常紧凑的二进制数据结构。
连续的内存空间也会存在弊端?
ziplist放入n多个元素的时候,再往里面添加元素,都需要分配新的内存空间,并且完成数据的完整拷贝。元素比较少还好,但如果元素很多的情况下,会很消耗性能。
那么就需要借助双端链表来控制ziplist的大小,如果超过一定的大小,则分裂成两个,保证每个ziplist不能太大。
quicklist的底层是quicklistNode,quicklistNode指向ziplist。
加一些数据,比如加入到ziplist里面去,不需要对数据整个做偏移,因为数据已经分配到不同的ziplist里面去了,修改数据,只需要修改某一个ziplist就可以了。
这是基于双端链表做的优化,可以从前往后遍历,也可以从后往前遍历。
接下来看下lpush源码
pushGenericCommand
先从redis数据库获取数据
-
• c->db
db就是要操作的redis数据库
-
• c->argv[1]
比如执行lpush命令
lpush alist a b c argv[0]就是lpush argv[1]就是alist
根据key去db中查找数据
-
• key->ptr
key真正所执行的字符串
进行rehash操作
-1表示没有进行rehash,不等于-1表示正在进行rehash。
做rehash的方法
筛选非空的hash槽
有时候,hash槽上不一定有数据,因为hashtable散列之后有的是空的,如果循环的时候发现有empty_visits=10个hash槽是空的,就不做rehash了。
这个while循环结束之后,剩下的都是非空的hash槽。
获取非空hash槽上链表的头节点
开始遍历链表,重新计算hash值。
这里是进行与运算,而不是求模运算,性能更高。
-
• size
size大小为2^n
-
• sizemask
sizemask=size-1=2^n-1
公式
任意数 % 2^n <=> 任意数 & (2^n-1)
即累除法求余数,一次位运算就能计算出结果,更加高效。
计算出key在新的hashtable上的位置之后,将老的hash槽索引指向新的hashtable的表头,并将老的hash槽上的链表迁移到新的hashtable上去,完成了数据迁移操作。
迁移过来之后,老的hashtable上的元素个数减1,新的加1。
一个hash槽一个hash槽的迁移数据。
判断老的hashtable中还有没有元素,如果没有的话,就释放掉老的hashtable。
将老的hashtable(ht[0])指向新的hashtable(ht[1]),新的hashtable(ht[1])就释放掉了。
至此,就完成了rehash的过程。
rehash完之后,接下来就是查找key的过程
先从老的hashtable查找(ht[0]),有的话就返回,没有的话再查找新的hashtable(ht[1]),还没有的话就返回null。
接下来就是检查查询到数据的类型
看是否为List数据类型
lpush alist a b c
如果alist不是List而是string 则会报异常提示类型不匹配