京东云开发者| Redis数据结构(二)-List、Hash、Set及Sorted Set的结构实现
1 引言
之前介绍了Redis的数据存储及String类型的实现,接下来再来看下List、Hash、Set及Sorted Set的数据结构的实现。
2 List
List类型通常被用作异步消息队列、文章列表查询等;存储有序可重复数据或做为简单的消息推送机制时,可以使用Redis的List类型。对于这些数据的存储通常会使用链表或者数组作为存储结构。
使用数组存储,随机访问节点通过索引定位时间复杂度为O(1)。但在初始化时需要分配连续的内存空间;在增加数据时,如果超过当前分配空间,需要将数据整体搬迁移到新数组中。
使用链表存储,在进行前序遍历或后续遍历,当前节点中要存储前指针和后指针,这两个指针在分别需要8byte共16byte空间存储,存在大量节点会因指针占用过多空间。链表虽然不需要连续空间存储可以提高内存利用率,但频繁的增加和删除操作会使内存碎片化,影响数据读写速率。
如果我们能够将链表和数组的特点结合起来就能够很好处理List类型的数据存储。
2.1 ZipList
3.2之前Redis使用的是ZipList,具体结构如下:
zlbytes: 4byte 记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配, 或者计算 zlend 的位置时使用。
zltail:4byte 记录压缩列表表尾节点距离压缩列表的起始地址有多少字节: 通过这个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址。
zllen:2byte 记录了压缩列表包含的节点数量: 当这个属性的值小于 UINT16_MAX(65535)时, 这个属性的值就是压缩列表包含节点的数量; 当这个值等于UINT16_MAX 时,节点的真实数量需要遍历整个压缩列表才能计算得出。
entry X:压缩列表包含的各个节点,节点的长度由节点保存的内容决定。包含属性如下:
prerawlen:记录前一个节点所占内存的字节数,方便查找上一个元素地址
len:data根据len的首个byte选用不同的数据类型来存储data
data:本元素的信息
zlend: 尾节点 恒等于255
ziplist是一个连续的内存块,由表头、若干个entry节点和压缩列表尾部标识符zlend组成,通过一系列编码规则,提高内存的利用率,使用于存储整数和短字符串。每次增加和删除数据时,所有数据都在同一个ziplist中都会进行搬移操作。如果将一个组数据按阈值进行拆分出多个数据,就能保证每次只操作某一个ziplist。3.2之后使用的quicklist与ziplist。
2.2 QuickList
quicklist就是维护了一种宏观上的双端链表(类似于B树),链表的节点为对ziplist包装的quicklistNode,每个quciklistNode都会通过前后指针相互指向,quicklist包含头、尾quicklistNode的指针。
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* total count of all entries in all ziplists */
unsigned long len; /* number of quicklistNodes */
int fill : QL_FILL_BITS; /* fill factor for individual nodes */
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
…
} quicklist;
*head:表头节点
*tail:表尾节点
count:节点包含entries数量
len:quicklistNode节点计数器
fill:保存ziplist的大小,配置文件设定
compress:保存压缩程度值,配置文件设定
基本概念:
Redis 散列可以存储多个键值对之间的映射。和字符串一样,散列存储的值既可以是字符串又可以是数值,并且用户同样可以对散列存储的数字值执行自增或自减操作。这个和 Java 的 HashMap 很像,每个 HashMap 有自己的名字,同时可以存储多个 k/v 对。
底层实现:
哈希对象的编码可以是 ziplist 或者 hashtable 。
哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节并且保存的键值对数量小于 512 个,使用ziplist 编码;否则使用 hashtable;
应用场景:
Hash 更适合存储结构化的数据,比如 Java 中的对象;其实 Java 中的对象也可以用 string 进行存储,只需要将 对象 序列化成 json 串就可以,但是如果这个对象的某个属性更新比较频繁的话,那么每次就需要重新将整个对象序列化存储,这样消耗开销比较大。可如果用 hash 来存储 对象的每个属性,那么每次只需要更新要更新的属性就可以。
购物车场景:可以以用户的 id 为 key ,商品的 id 为存储的 field ,商品数量为键值对的value,这样就构成了购物车的三个要素。
Set(集合)
基本概念:
Redis 的 set 和 list 都可以存储多个字符串,他们之间的不同之处在于,list是有序可重复,而set是无序不可重复。
底层实现:
集合对象的编码可以是 intset 或者 hashtable 。
如果集合对象保存的所有元素都是整数值并且保存的元素数量不超过 512 个,则使用 intset 编码;否则使用 hashtable;
应用场景:
标签:可以将博客网站每个人的标签用 set 集合存储,然后还按每个标签 将用户进行归并。
存储好友/粉丝:set 具有去重功能;还可以利用set并集功能得到共同好友之类的功能。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/291864.html