redis创建像mysql表结构_Redis数据结构列表实现
双向链表linkedlist
Redis实现的是标准的双向链表。
链表节点定义:
链表定义:
总结链表实现:
1.每个节点有前后节点指针,且第一个节点的指针为NULL,最后一个节点的指针为NULL(无环)。
2.对双链表进行封装,链表第一个节点和最后一个节点指针,以及链表长度。
优点:
在链表两端进行push和pop操作都是O(1)。
获取链表的长度操作O(1)。
多态,可以存储c语言支持的任何数据类型,通过void *万能指针。
缺点:
内存开销比较大。每个节点上除了要保存数据之外,还要额外保存两个指针
各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
压缩列表转化成双向链表条件
创建新列表时 redis 默认使用 redis_encoding_ziplist 编码, 当以下任意一个条件被满足时, 列表会被转换成 redis_encoding_linkedlist 编码:
试图往列表新添加一个字符串值,且这个字符串的长度超过 server.list_max_ziplist_value (默认值为 64 )。
ziplist 包含的节点超过 server.list_max_ziplist_entries (默认值为 512 )。
注意:这两个条件是可以修改的,在 redis.conf 中
list-max-ziplist-value 64list-max-ziplist-entries 512
ziplist
压缩列表 ziplist 是为 Redis 节约内存而开发的。
ziplist 是由一系列特殊编码的内存块构成的列表(像内存连续的数组,但每个元素长度不同), 一个 ziplist 可以包含多个节点(entry)。
ziplist 将表中每一项存放在前后连续的地址空间内,每一项因占用的空间不同,而采用变长编码。
当元素个数较少时,Redis 用 ziplist 来存储数据,当元素个数超过某个值时,链表键中会把 ziplist 转化为 linkedlist,字典键中会把 ziplist 转化为 hashtable。
由于内存是连续分配的,所以遍历速度很快。
在3.2之后,ziplist被quicklist替代。但是仍然是zset底层实现之一。
ziplist内存布局
ziplist使用连续的内存块,每一个节点(entry)都是连续存储的;ziplist 存储分布如下:
常态的压缩列表内存布局如上图所示,整个内存块区域内分为五个部分,下面分别介绍着五个部分:
zlbytes:存储一个无符号整数,固定四个字节长度,用于存储压缩列表所占用的字节,当重新分配内存的时候使用,不需要遍历整个列表来计算内存大小。
zltail:存储一个无符号整数,固定四个字节长度,代表指向列表尾部的偏移量,偏移量是指压缩列表的起始位置到指定列表节点的起始位置的距离。
zllen:压缩列表包含的节点个数,固定两个字节长度,源码中指出当节点个数大于2^16-2个数的时候,该值将无效,此时需要遍历列表来计算列表节点的个数。
entries:列表节点区域,长度不定,由列表节点紧挨着组成。每个节点可以保存一个字节数组或者是一个整数值。
zlend:一字节长度固定值为255,用于表示列表结束。
上面介绍了压缩列表的总体内存布局,对于初entries区域以外的四个区域的长度都是固定的,下面再看看此区域中每个节点的布局情况。
每个列表节点由三部分组成:
previous length:记录前一个节点所占有的内存字节数,通过该值,我们可以从当前节点计算前一个节点的地址,可以用来实现从表尾向表头节点遍历;
len/encoding:记录了当前节点content占有的内存字节数及其存储类型,用来解析content用;
content:保存了当前节点的值。
最关键的是prevrawlen和len/encoding,content只是实际存储数值的比特位。
为了节省内存,根据上一个节点的长度prevlength 可以将entry节点分为两类:
entry的前8位小于254,则这8位就表示上一个节点的长度
entry的前8位等于254,则意味着上一个节点的长度无法用8位表示,后面32位才是真实的prevlength。用254 不用255(11111111)作为分界是因为255是zlend的值,它用于判断ziplist是否到达尾部。
根据当前节点存储的数据类型及长度,可以将ziplist节点分为9类:
其中整数节点分为6类:
整数节点的encoding的长度为8位,其中高2位用来区分整数节点和字符串节点(高2位为11时是整数节点),低6位用来区分整数节点的类型,定义如下:
#define ZIP_INT_16B (0xc0 | 0<<4)//整数data,占16位(2字节)
#define ZIP_INT_32B (0xc0 | 1<<4)//整数data,占32位(4字节)
#define ZIP_INT_64B (0xc0 | 2<<4)//整数data,占64位(8字节)
#define ZIP_INT_24B (0xc0 | 3<<4)//整数data,占24位(3字节)
#define ZIP_INT_8B 0xfe //整数data,占8位(1字节)
/*4 bit integer immediate encoding*/
//整数值1~13的节点没有data,encoding的低四位用来表示data
#define ZIP_INT_IMM_MASK 0x0f
#define ZIP_INT_IMM_MIN 0xf1 /* 11110001 */
#define ZIP_INT_IMM_MAX 0xfd /* 11111101 */
值得注意的是 最后一种encoding是存储整数0~12的节点的encoding,它没有额外的data部分,encoding的高4位表示这个类型,低4位就是它的data。这种类型的节点的encoding大小介于ZIP_INT_24B与ZIP_INT_8B之间(1~13),但是为了表示整数0,取出低四位xxxx之后会将其-1作为实际的data值(0~12)。在函数zipLoadInteger中,我们可以看到这种类型节点的取值方法:
当data小于63字节时(2^6),节点存为上图的第一种类型,高2位为00,低6位表示data的长度。
当data小于16383字节时(2^14),节点存为上图的第二种类型,高2位为01,后续14位表示data的长度。
当data小于4294967296字节时(2^32),节点存为上图的第二种类型,高2位为10,下一字节起连续32位表示data的长度。
字符串节点分为3类:
当data小于63字节时(2^6),节点存为上图的第一种类型,高2位为00,低6位表示data的长度。
当data小于16383字节时(2^14),节点存为上图的第二种类型,高2位为01,后续14位表示data的长度。
当data小于4294967296字节时(2^32),节点存为上图的第二种类型,高2位为10,下一字节起连续32位表示data的长度。
上图可以看出:不同于整数节点encoding永远是8位,字符串节点的encoding可以有8位、16位、40位三种长度
相同encoding类型的整数节点 data长度是固定的,但是相同encoding类型的字符串节点,data长度取决于encoding后半部分的值。
#define ZIP_STR_06B (0 << 6)//字符串data,最多有2^6字节(encoding后半部分的length有6位,length决定data有多少字节)
#define ZIP_STR_14B (1 << 6)//字符串data,最多有2^14字节
#define ZIP_STR_32B (2 << 6)//字符串data,最多有2^32字节
如何通过一个节点向前跳转到另一个节点?
从尾部向头部遍历(利用 ztail 和privious_entry_length),用指向当前节点的指针 e , 减去前一个 entry的长度, 得出的结果就是指向前一个节点的地址 p 。
已知节点的位置,求data的值
entry布局 可以看出,若要算出data的偏移量,得先计算出prevlength所占内存大小(1字节和5字节):
//根据ptr指向的entry,返回这个entry的prevlensize
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \
if ((ptr)[0]
(prevlensize)= 1; \
}else{ \
(prevlensize)= 5; \
} \
}while(0);
接着再用ZIP_DECODE_LENGTH(ptr + prevlensize, encoding, lensize, len)算出encoding所占的字节,返回给lensize;data所占的字节返回给len
//根据ptr指向的entry求出该entry的len(encoding里存的 data所占字节)和lensize(encoding所占的字节)
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \ZIP_ENTRY_ENCODING((ptr), (encoding)); \if ((encoding)
(lensize)= 1; \
(len)= (ptr)[0] & 0x3f; \
}else if ((encoding) ==ZIP_STR_14B) { \
(lensize)= 2; \
(len)= (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \
}else if (encoding ==ZIP_STR_32B) { \
(lensize)= 5; \
(len)= ((ptr)[1] << 24) |\
((ptr)[2] << 16) |\
((ptr)[3] << 8) |\
((ptr)[4]); \
}else{ \
assert(NULL); \
} \
}else{ \
(lensize)= 1; \
(len)=zipIntSize(encoding); \
} \
}while(0);//将ptr的encoding解析成1个字节:00000000、01000000、10000000(字符串类型)和11??????(整数类型)//如果是整数类型,encoding直接照抄ptr的;如果是字符串类型,encoding被截断成一个字节并清零后6位
#define ZIP_ENTRY_ENCODING(ptr, encoding) do { \(encoding)= (ptr[0]); \if ((encoding) < ZIP_STR_MASK) (encoding) &=ZIP_STR_MASK; \
}while(0)//根据encoding返回数据(整数)所占字节数
unsigned int zipIntSize(unsigned charencoding) {switch(encoding) {case ZIP_INT_8B: return 1;case ZIP_INT_16B: return 2;case ZIP_INT_24B: return 3;case ZIP_INT_32B: return 4;case ZIP_INT_64B: return 8;default: return 0; /*4 bit immediate*/}
assert(NULL);return 0;
}
完成以上步骤之后,即可算出data的位置:ptr+prevlensize+lensize,以及data的长度len
连锁更新
每个节点的previous_entry_length属性都记录了前一个节点的长度
如果前一个节点的长度小于254,那么previous_entry_length属性需要用1字节长的空间来保存这个长度值
如果前一个节点的长度大于等于254,那么previous_entry_length属性需要5字节长的空间来保存这个长度值
考虑这样一种情况:在一个压缩列表中,有多个连续的、长度介于250字节到253字节之间的节点e1至eN
|zlbytes|zltail|zllen|e1|e2|e3|...|eN|zlend|
因为e1至eN的所有节点的长度都小于254字节,所以记录这些节点的长度只需要1字节长的previous_entry_length属性,换句话说,e1至eN的所有节点的previous_entry_length属性都是1字节长的。
如果我们将一个长度大于等于254字节的新节点new设置到压缩列表的表头节点,那么new将成为e1的潜质节点。
此时e1到eN的每个节点的previous_entry_length属性都要扩展为5字节以符合压缩列表对节点的要求,程序需要不断的对压缩列表进行空间重分配操作。
Redis将这种在特殊情况下产生的多次空间扩展操作称之为“连锁更新”。
除了添加新节点可能会引发连锁更新之外,删除节点也可能会连锁更新。
因为连锁更新在最坏情况下需要对压缩列表执行N次空间重分配操作,而每次空间重分配的最坏复杂度为O(N),所以连锁更新的最坏复杂度为O(N2)。
注意的是,尽管连锁更新的复杂度较高,但它真正赵成性能问题的几率是很低的:
首先,压缩列表里要恰好有多个连续的、长度介于250字节至253字节之间的节点,连锁更新才有可能被引发,在实际中,这种情况并不多见;
其次,即使出现连锁更新,但只要更新的节点数量不多,就不会对性能造成任何影响:比如说,对三五个节点进行连锁更新是绝对不会影响性能的;
Redis中压缩列表的应用
Redis中,不同的数据类型广泛地应用了压缩列表编码,整理如下表:
ziplist总结
ziplist的主要优点是节省内存,且ziplist存储在一段连续的内存上,所以存储效率很高。但是,它不利于修改操作,插入和删除操作需要频繁的申请和释放内存。
查找操作只能按顺序查找(可以是从前往后、也可以从后往前)
一旦数据发生改动,就会引发内存realloc,可能导致内存拷贝。当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝。
quickList
可以认为quickList,是ziplist和linkedlist二者的结合;quickList将二者的优点结合起来。
官方给出的定义:
A generic doubly linked quicklist implementation
A doubly linked list of ziplists
quickList是一个ziplist组成的双向链表。每个节点使用ziplist来保存数据。
本质上来说,quicklist里面保存着一个一个小的ziplist。结构如下:
/*quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
* We use bit fields keep the quicklistNode at 32 bytes.
* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, NONE=1, ZIPLIST=2.
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
* extra: 12 bits, free for future use; pads out the remainder of 32 bits*/typedefstructquicklistNode {struct quicklistNode *prev; //上一个node节点
struct quicklistNode *next; //下一个node
unsigned char *zl; //保存的数据 压缩前ziplist 压缩后压缩的数据
unsigned int sz; /*ziplist size in bytes*/unsignedint count : 16; /*count of items in ziplist*/unsignedint encoding : 2; /*RAW==1 or LZF==2*/unsignedint container : 2; /*NONE==1 or ZIPLIST==2*/unsignedint recompress : 1; /*was this node previous compressed?*/unsignedint attempted_compress : 1; /*node can't compress; too small*/unsignedint extra : 10; /*more bits to steal for future usage*/} quicklistNode;/*quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
* 'sz' is byte length of 'compressed' field.
* 'compressed' is LZF data with total (compressed) length 'sz'
* NOTE: uncompressed length is stored in quicklistNode->sz.
* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF*/typedefstructquicklistLZF {
unsignedint sz; /*LZF size in bytes*/
charcompressed[];
} quicklistLZF;/*quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.
* 'count' is the number of total entries.
* 'len' is the number of quicklist nodes.
* 'compress' is: -1 if compression disabled, otherwise it's the number
* of quicklistNodes to leave uncompressed at ends of quicklist.
* 'fill' is the user-requested (or default) fill factor.*/typedefstructquicklist {
quicklistNode*head; //头结点
quicklistNode *tail; //尾节点
unsigned long count; /*total count of all entries in all ziplists*/unsignedint len; /*number of quicklistNodes*/
int fill : 16; /*fill factor for individual nodes*///负数代表级别,正数代表个数
unsigned int compress : 16; /*depth of end nodes not to compress;0=off*///压缩级别
} quicklist;
quickList就是一个标准的双向链表的配置,有head 有tail;
每一个节点是一个quicklistNode,包含prev和next指针。
每一个quicklistNode 包含 一个ziplist,*zp 压缩链表里存储键值。
所以quicklist是对ziplist进行一次封装,使用小块的ziplist来既保证了少使用内存,也保证了性能。
refer:
《Redis设计与实现》
redis创建像mysql表结构_Redis数据结构列表实现相关推荐
- redis创建像mysql表结构_如何给redis添加新数据结构
前言 作为一款缓存型nosql数据库,redis在诞生之初就以高性能.丰富的数据结构等特性获得业界的青睐.redis默认提供了五种数据类型的支持:string.list.set.zset.hash.针 ...
- JAVA实现导出mysql表结构到Word详细注解版
JAVA实现导出mysql表结构到Word详细注解版 转自https://blog.csdn.net/weixin_42041153/article/details/109739073 本文在原文中一 ...
- 修改mysql表结构语句
昨天在执行碰到几个报错,总提示缺少mysql表结构里的字段什么的,当时有点没头绪不知道从哪里着手,再次记录一下修改表结构的语句,保证下次不会在出现此类问题; mysql 修改表结构语句 ALTER T ...
- mysql schema 同步_GitHub - naryn/mysql-schema-sync: mysql表结构自动同步工具
mysql-schema-sync mysql表结构自动同步工具 用于将 线上 数据库结构变化同步到 本地环境! 支持功能: 同步新表 同步字段 变动:新增.修改 同步索引 变动:新增.修改 支持预览 ...
- nagios监控 mysql 表结构
引言 为了给客户演示对mysql表结构的监控,在搜了很久之后发现不得不自己写一个脚本了.percona这么牛B的公司竟然没有提供一个这方面的工具,看来客户的要求有点花儿不实啊...这个问题一共花费了我 ...
- MySQL表结构导出Excel
MySQL表结构导出Excel 在写设计文档时,需要把MySQL中的表结构按要求导出.MySQL客户端默认的字段不满足需求时,可通过MySQL的information_schema.COLUMNS表, ...
- 查看修改MySQL表结构命令
查看修改MySQL表结构命令 简述 小编经常会遇到一些数据库编码不对得问题,好TM头疼,这里做一个记录,供大家参考. 修改数据库字符集: ALTER DATABASE db_name DEFAULT ...
- c 自动生成mysql表结构_EntityFrameworkCore 根据实体类自动创建数据库
1.首先新建 Asp.Net Core WebApi 项目 2.添加一下引用 : 2.1 Pomelo.EntityFrameworkCore.MySql(我用的Mysql 根据自己情况引用就行) ...
- mysql导出结构及数据结构,Mysql导出数据结构 or 数据
如果我们单单只想导出mysql数据表结构,通过navcat工具还不行,这时我们可以用mysqldump工具 在mysql server的安装目录:C:\Program Files\MySQL\MySQ ...
最新文章
- 面向自动驾驶车辆的高效激光里程计(ICRA2021)
- 计算机导论与计算机组成原理关系,计算机组成原理
- 如何自学python到做项目-总算明白如何通过项目学习python
- 搞不懂SDN和SD-WAN?那是因为你没看这个小故事—Vecloud微云
- 有了malloc/free,为什么还要new/delete?
- 【最新合集】编译原理习题(含答案)_答案全集_MOOC慕课 哈工大陈鄞
- 制作 mysql的rpm文件_自制mysql.rpm安装包
- 一文搞懂浏览器同源策略
- OpenJ_Bailian 3151 Pots (BFS)
- Linux下iptables屏蔽IP和端口号
- 台式计算机怎么设置屏幕常亮,怎么设置电脑屏幕一直亮着
- 硬件工程师有没有35岁危机?
- 软件测试学习 之 QA、QC与QM
- 自学考试计算机实践课,河南大学自学考试计算机实践课考试须知
- 解决word转PDF文件时图片位置改变和字体格式改变的问题
- java实现图片滚动_怎么用Java代码使图片自行滚动浏览
- SpringFox 学习
- 使用gulp编译 sass和less
- JavaScript 教程「6」:数组
- vb 使用IAccessible接口获取QQ聊天记录
热门文章
- LeetCode 284. 顶端迭代器
- insert和update 锁等待_黑龙F5智感双全智能锁全球首发,掀起惊艳风潮
- mysql pdo 事务处理_php中pdo的mysql事务处理实例
- axure选中后横线切换_Axure8.0|动态面板内容简单切换技巧
- Notepad++的json 格式化
- Linux无法连接远程仓库,ssh无法连接到远端Ubuntu的解决方法
- 解开玻尔兹曼机的封印会发生什么?
- 论文浅尝 | 区分概念和实例的知识图谱嵌入方法
- 【小程序】微信小程序开发实践
- 使用less实现自适应宽度