【Redis学习笔记】2018-03-20 压缩列表概念

生成对抗
• 阅读 2050

作者:李乐 顺风车运营研发团队
压缩列表是列表键和哈希键的底层实现之一。

当一个列表键只包含少量列表项,并且每个列表项要么是小整数,要么是长度较短的字符串时,Redis就会使用压缩列表来作为列表键的底层实现;

当一个哈希键只包含少量键值对,并且每个键值对的键和值要么就是小整数值要么就是长度较短的字符串时,Redis就会使用压缩列表来作为哈希键的底层实现;

  1. 压缩列表结构(ziplist)

压缩列表是为了节约内存而开发的,其就是一个字节数组(char *);

而一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或一个整数;

 【Redis学习笔记】2018-03-20 压缩列表概念

zlbytes:4字节,压缩列表的长度;因此压缩列表最大(2^32)-1字节;

zltail:4字节,尾节点偏移量;

zllen:2字节,压缩列表节点数目,其最大只能表示65535个节点;当压缩列表节点数目超过65535后,此字段无就没有任何意义了;

entryX:节点

zlend:压缩列表结尾标志,固定为0xFF;

假设char * zl指向压缩列表首地址;

注意:zl指针的类型为char*,因此通过zl获取相应字段时,首先需要强制类型转换;

zl指向zlbytes字段;((uint32_t) zl)取出zlbytes字段内容;

zl+4指向zltail字段;((uint32_t) (zl+4))取出zltail字段内容;

((uint32_t) (zl+4)) + zl (就是zl+zltail)指向最后一个节点首地址

zl+8指向zllen字段;((uint16_t) (zl+8))取出zllen字段内容;

zl + ((uint32_t) zl) (就是zl+zlbytes)指向zlend字段

  1. 节点(entry)结构

了解了压缩列表结构,我们可以很容易获得压缩列表空间大小,压缩列表拥有节点数目,压缩列表开始和结束位置指针;

那么如何遍历压缩列表的所有节点呢?

对于每一个entry节点,存储的可能是字节数组或整数值;

假设我们知道节点首地址指针,我们如何知道存储的是什么类型?对于字节数组,我们又如何知道字节数组的长度?

redis是对每一个entry是这样编码的:

 【Redis学习笔记】2018-03-20 压缩列表概念

2.1 首先回答上面一个问题:压缩列表如何遍历所有节点?

答案就在previous_entry_length字段,其表示前一个节点的长度(单位字节);

假如我知道当前节点的首地址为p,那么(p-previous_entry_length)就是前一个节点的首地址;通过这种方式实现了从尾到头的遍历;

previous_entry_length字段为1个或者5个字段(为了节约内存);

当前一个节点的长度小于254字节时,previous_entry_length字段用一个字段表示;

当前一个节点的长度大于等于254时,previous_entry_length字段用5个字节来表示;而这时候previous_entry_length的第一个字节是固定的标志0xFE,后面4个字节才真正表示前一个节点的长度;

假设当前节点首地址为p;p[0]为第一个字节内容;

当p[0]<0xFE时,说明previous_entry_length字段只占一个字节,p[0]就是前一个节点的长度;

当p[0]=0xFE时,说明previous_entry_length字段占5个字节,p[1]~p[4]表示前一个节点的长度;而p+5则只encoding字段首地址;

2.2 下面回答第二个问题:

如何区分当前节点存储数据是什么类型,字节数组还是整数?字节数组长度?

最简单的方法:使用1个比特表示数据是字节数组还是整数,假如是字节数组,再用7+4*8表示字节数组的长度;

但是,redis为了节约内存并没有这么做;(减少encoding字段长度)

字节数组分为三种,最大长度63字节,最大长度(2^14)-1,最大长度(2^32)-1;

整数分为6种:8比特整数,24比特整数,int16,int32,int64,0~12立即数;

而具体的数据内容存储在content字段;

 【Redis学习笔记】2018-03-20 压缩列表概念

 【Redis学习笔记】2018-03-20 压缩列表概念

我们发现encoding第一个字节的前2比特可以区分是字节数组(以及字节数组类型)还是整数;

是整数时,第3、4比特可以区分整数的类型;当content的前4个比特都是1时,后4个比特才能区分整数类型;

假设encoding字段首地址为p;p[0]为第一个字节内容;

p[0] & 0xc0 可以获得前两个比特bit[1:2],当其不等于11B时,说明content是字节数组;再根据其是00B、01B还是10B可以知道字节数组类型,从而取出字节数组实际长度;

整数类型的判断同理可得;

  1. 预备知识

2.1 大端小端

 【Redis学习笔记】2018-03-20 压缩列表概念

redis在存取压缩列表字段(如zlbytes、zltail时,会进行大小端转换;如果是小端不做处理,如果是大端,会转换为小端字节顺序);

大小端转换其实就是交换字节顺序;

void memrev32(void *p) {
    unsigned char *x = p, t;
    t = x[0];
    x[0] = x[3];
    x[3] = t;
    t = x[1];
    x[1] = x[2];
    x[2] = t;
}

问题:为什么在存取压缩列表字段时需要做大小端转换?

解答:redis集群,各机器的CPU架构可能不相同;有些机器是大端,有些机器是小端;假如不进行大小端转换,当压缩列表数据在集群中机器间传递时,不同机器解析情况会不相同。

  1. 连锁更新

如图,位置p处的节点为X;其previous_entry_length字段为1个字节,0x80,表明前一个节点长度为128;假设位置p之后的所有节点的长度为253字节;

现在往位置p新添加一个节点,其长度为1024字节;显然节点X的previous_entry_length需要改变为5个字节,那么此时节点X的长度为257字节;

节点X的长度从253改变为257字节;那么节点X的后驱节点的previous_entry_length也需要从一个字节改变为5个字节;

以此类推;因为在位置P新添加了一个节点,可能导致P后面得所有节点都需要依次更新previous_entry_length字段长度;

这就是连锁更新;他会导致N次内存分配,效率很低;

但是需要指出的是,这种情况出现的概率是很低的;而且一般情况下压缩列表存储的节点数目比较少;因此redis并没有对这种情况做特殊处理;

 【Redis学习笔记】2018-03-20 压缩列表概念

点赞
收藏
评论区
推荐文章
陈占占 陈占占
3年前
python 字典
字典(dict):以键值对的方式存在,以大括号为标志、在字典里面键是不能修改的,值可以修改语法格式:字典名{key1:value1,key2:value2,.......}note:是无序的类型,建必须唯一,值不必。索引是以键为下标,不能索引键对应的值,键不能为列表特点:1.键值之间必须用冒号(:)隔开2.项与项之间必须用逗号(,)隔开3.字典中的键必须
Stella981 Stella981
3年前
Redis 列表(List)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)一个列表最多可以包含2321个元素(4294967295,每个列表超过40亿个元素)。实例redis127.0.0.1:6379LPUSHw3ckeyredis(integer)1
Stella981 Stella981
3年前
Redis 数据类型及操作
前言作为Keyvalue型数据库,Redis也提供了键(Key)和键值(Value)的映射关系。但是,除了常规的数值或字符串,Redis的键值还可以是以下形式之一:<!more\Lists(可重复列表)\(Lists(可重复列表))\Sets(不可重复集合)\(Sets(不可重复集合)
Stella981 Stella981
3年前
Redis 对象系统
Redis为了更好的实现键值对数据库,创建了一个对象系统,以下为Redis对象系统的相关知识简介。redisObjectRedis使用对象来表示数据库中的键和值,每次在Redis数据库中创建一个键值对时,至少会创建两个对象。一个为键对象,一个为值对象。Redis对象的定义如下:typedef
Wesley13 Wesley13
3年前
Java集合之Map接口
Map使用键值对来存储数据,将键映射到值对象,一个映射不能包含重复的键,每一个键最多只能映射到一个值。Map接口的具体实现类:HashMap,Hashtable,TreeMap,LinkedHashMap  1)HashMap  基于哈希表(哈希表学习地址)的Map接口实现。允许使用null值和null键,不保证映射的顺序,特别是不保证顺序恒
Stella981 Stella981
3年前
Redis实现之对象(一)
对象前面我们介绍了Redis的主要数据结构,如:简单动态字符串SDS、双端链表、字典、压缩列表、整数集合等。Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们之前介绍的数据结构通过这五种
Stella981 Stella981
3年前
C#创建安全的字典(Dictionary)存储结构
  在上面介绍过栈(Stack)的存储结构,接下来介绍另一种存储结构字典(Dictionary)。 字典(Dictionary)里面的每一个元素都是一个键值对(由二个元素组成:键和值) 键必须是唯一的,而值不需要唯一的,键和值都可以是任何类型。字典(Dictionary)是常用于查找和排序的列表。 接下来看一下Dictionary的部分方法和类的底
Stella981 Stella981
3年前
Redis面试:八问字典内部构造与rehash,这谁顶的住啊!
字典是一种用于保存键值对的抽象数据结构,也被称为查找表、映射或关联表。在字典中,一个键(key)可以和一个值(value)进行关联,这些关联的键和值就称之为键值对。抽象数据结构,啥意思?就是可以需要实际的数据结构是实现这个功能。抽象,意味着它这是实现功能的标准,凡是能够完成这些功能的都可以是其实现。redis的字典
小万哥 小万哥
2年前
Redis数据结构:高频面试题及解析
概述Redis是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。Redis支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩
小万哥 小万哥
1年前
Java HashMap 和 HashSet 的高效使用技巧
JavaHashMapHashMap是一种哈希表,它存储键值对。键用于查找值,就像数组中的索引一样。HashMap的优势在于它可以使用任何类型作为键,并且查找速度很快。创建HashMapjava//导入HashMap类importjava.util.Has
贾蔷 贾蔷
2个月前
哈希表实现指南:从原理到C++实践
一、简介和应用哈希表(HashTable)是一种高效的数据结构,通过键值对(keyvalue)存储数据,提供快速的插入、删除和查找操作。它使用哈希函数将键映射到表中的位置,使得平均时间复杂度可以达到O(1)。‌应用场景‌:数据库索引、缓存实现(如Redis
生成对抗
生成对抗
Lv1
我住长江头,君住长江尾;日日思君不见君,共饮长江水。
文章
4
粉丝
0
获赞
0