Redis中List列表中的每个字符串成为元素,一个列表最多可以存储2^32 - 1个元素。
在Redis中,可以对列表两端插入(LPUSH)和弹出(LPOP),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,可以充当 栈 和 队列 的角色,在实际开发中有很多应用场景。
数据插入时即确定了顺序
,可以通过索引下标获取某个元素或者某个范围内的元素列表;最左边、头插法
最右边、尾插法
/*
* ZIPLIST OVERALL LAYOUT
* ======================
*
* ziplist的大体布局如下:
*
* <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
*
* 注意:如果没有另外指定,所有字段都以小端存储。
*
* <uint32_t zlbytes> 是一个无符号整数,32bit。用于表示ziplist占用的字节总数。
* ziplist占用字节总数,包括 zlbytes 字段本身的四个字节。
* 这个值主要用于在ziplist中 entry 元素变化导致ziplist进行内存重分配时使用,或者计算 zlend的位置时使用
* 遍历集合中数据时,无需先遍历它。
*
* <uint32_t zltail> 32bit,表示ziplist表中最后一项(entry)在ziplist中的偏移字节数。
* 通过zltail我们可以很方便地找到最后一项,从而可以在ziplist尾端快速地执行push或pop操作,无需遍历整个集合。
*
* <uint16_t zllen> 16bit,表示ziplist中数据项(entry)的个数。
*
* <uint8_t zlend> 8bit,ziplist最后1个字节,是一个结束标记,值固定等于0xFF,转换为十进制则为:255。
* entry 表示真正存放数据的数据项,长度不定,下文中进行详解
* ZIPLIST ENTRIES
* ===============
*/
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl))) //获取ziplist的zlbytes的指针
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) //获取ziplist的zltail的指针
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2))) //获取ziplist的zllen的指针
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) //ziplist头大小
#define ZIPLIST_END_SIZE (sizeof(uint8_t)) // ziplist结束标志位大小
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE) // 获取第一个元素的指针
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) // 获取最后一个元素的指针
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1) // 获取结束标志位指针
unsigned char *ziplistNew(void) { // 创建一个压缩表
unsigned int bytes = ZIPLIST_HEADER_SIZE+1; // zip头加结束标识位数
unsigned char *zl = zmalloc(bytes);
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes); // 大小端转换
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
ZIPLIST_LENGTH(zl) = 0; // len赋值为0
zl[bytes-1] = ZIP_END; // 结束标志位赋值
return zl;
}
/*
* 压缩列表节点 对应 上文中 Ziplist 中的 entry
* zlentry每个节点由三部分组成:Previous entry len、encoding、data
* prevlengh: 记录上一个节点的长度,为了方便反向遍历ziplist
* encoding: 编码,由于 ziplist 就是用来节省空间的,所以 ziplist 有多种编码,用来表示不同长度的字符串或整数。
* data: 用于存储 entry 真实的数据
* 结构体定义了7个字段,主要还是为了满足各种可变因素
*/
typedef struct zlentry {
unsigned int prevrawlensize; //prevrawlensize是描述prevrawlen的大小,有1字节和5字节两种
unsigned int prevrawlen; //prevrawlen是前一个节点的长度,
unsigned int lensize; //lensize为编码len所需的字节大小
unsigned int len; //len为当前节点长度
unsigned int headersize; //当前节点的header大小
unsigned char encoding; //节点的编码方式
unsigned char *p; //指向节点的指针
} zlentry;
如上Ziplist的设计结构,保障了空间的节省与查询的高效,但是当出现zlentry增加或删除时,Ziplist是不能直接在原有空间上进行修改,每一次变动都需要重新开辟空间去拷贝、修改。这样的场景下Ziplist一旦内部元素过多,将会导致性能的急剧下滑。因此Redis 在实现上做了一层优化,当Ziplist过大时,会将其分割成多个Ziplist,然后再通过一个双向链表将其串联起来。
Redis quicklist是Redis 3.2版本以后针对链表和压缩列表进行改造的一种数据结构,是 zipList 和 linkedList 的混合体,相对于链表它压缩了内存,进一步的提高了效率。
/*
* quicklist 是一个 40 字节的结构(在 64 位系统上),描述了一个快速列表。
* 'count' 所有ziplist数据项的个数总和。
* 'len' quicklist节点的个数。
* 'compress' 16bit,节点压缩深度设置,存放list-compress-depth参数的值。
* -1:禁止压缩
* >0:头/尾结点的前n个结点不压缩,其它结点压缩
* 'fill' 16bit,ziplist大小设置,存放list-max-ziplist-size参数的值。
*/
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 */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
/*
* quicklistNode 是一个 32 字节的结构,描述了一个快速列表的 ziplist。
* count: 16 bits, 表示ziplist里面包含的数据项个数。
* encoding: 2 bits, 1:RAW 未压缩, 2:LZF 被压缩了使用LZF压缩算法
* container: 2 bits, 1:NONE, 2:ZIPLIST。目前这个值只可能是 2:ZIPLIST.
* recompress: 1 bit, boolean,如果节点临时解压缩以供使用,则为 true。等待有机会了再把它重新压缩
* attempted_compress: 1 bit, boolean,用于测试期间的验证,不用关注
* extra: 10 bits, 预留字段
*/
typedef struct quicklistNode {
struct quicklistNode *prev; /* 指向链表前一个节点的指针。 */
struct quicklistNode *next; /* 指向链表后一个节点的指针。 */
unsigned char *zl; /* 数据指针。如果当前节点的数据没有压缩,那么它指向一个ziplist结构;否则,它指向一个quicklistLZF结构。 */
unsigned int sz; /* ziplist size in bytes */
unsigned int count : 16; /* count of items in ziplist */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
/*
* quicklistLZF 是一个 4+N 字节结构,表示一个被压缩过的ziplist
* sz: 表示压缩后的ziplist大小。
* compressed: 是个柔性数组(flexible array member),存放压缩后的ziplist字节数组。
*/
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
通过控制ziplist 的大小,则很好的解决了超大ziplist 的拷贝情况下对性能的影响。每次改动只需要针对具体的小段ziplist 进行操作。
你的点赞就是我创作的最大动力,如果写的不错,来个三连行不行