上一篇我们讲解了Redis中SDS的组成以及优势,这一篇我们讨论下Redis中的Hash数据类型是怎么构成的呢?
Java中存在HashMap和HashTable的数据类型。而Hash的数据结构可以近似于HashTable,依据数组+链表的形式构成。
在Redis中,Hash在元素对象个数较少的时候采用的是压缩链表的形式
(ziplist),压缩链表是一块连续的内存空间,元素之间紧紧的挨着,不存在任何的冗余空间。
压缩链表的内部结构图:
struct ziplist{ int32 zlbytes; //压缩列表占用的字节数 int32 zltail_offset; //压缩列表最后一个元素到起始位置的偏移量 int16 zllength; //元素的个数 T[] entries; //元素列表 int8 zlend; //压缩列表的末尾 常设置为0xFF} ;
然后让我们看一下entry的内部结构:
struct entry{ int<var> prevlen; //前一个entry字节长度 int<var> encoding; //元素编码类型 optional byte[] content; //元素内容}
prelen存放的是上一个entry的字节长度,压缩列表倒着遍历的时候,需要这个字段快速的定位下一个元素的位置。其是一个变长的整数,小于254的时候用一个字节存放,大于254的时候用的是5个字节存放。
encoding 代表的是压缩表的编码类型,其是根据encoding的前缀位进行区分的。(有兴趣的同事可以看一下redis的深度历险)
当插入元素的时候,ziplist每次都需要进行内存的重新分配,所以不适合存储大元素或者过多的元素。
于是存储过多的元素或者大元素的时候,hash也就自动转化为字典的方式进行存储。字典的方式可以根据hashtable的结构进行理解,数组+链表。数组存放的是链表的第一个元素的指针。
提到hash,就不得不提渐进式rehash。
当hashtable需要扩容的时候怎么办呢?Redis维护了两个Hashtable。需要扩容的时候就会将旧的字典所有的链表元素挂到新的链表下面。但是不是一次性立马转移完成,毕竟redis是单线程的,需要保证性能。
渐进式 rehash式一个数组内容一个数组内容进行元素的转移。当查询触及到当前链表的时候,查询完成后会将链表的元素转移到新的字典链表下。