深入底层C源码 Redis核心设计原理( 六 )

【4】而raw便是表示:字符串将以简单动态字符串(SDS)的形式存储 , 需要?两次 malloc 分配内存? , redisObject 对象头和 SDS 对象在内存地址上一般是不连续的 。
5)发现说明
【1】会有人疑问为什么DB默认是16?
因为Redis的配置文件redis/redis.conf中的databases属性默认是16 。所以Redis启动的时候默认会创建16个数据库且拿数据库索引为0的数据库作为默认数据库 。这些都是可以通过配置调整的 。
 4.List 数据结构(Redis采用quicklist(双端链表) 和 ziplist 作为List的底层实现)1)介绍
【1】List是一个有序(按加入的时序排序)的数据结构 , Redis采用quicklist(双端链表) 和 ziplist 作为List的底层实现 。以通过设置每个ziplist的最大容量 , quicklist的数据压缩范围 , 提升数据存取效率 。
//当值为正数时 , 表示quicklistNode节点上的ziplist的长度 。比如当这个值为5时 , 每个quicklistNode节点的ziplist最多包含5个数据项//当值为负数时 , 表示按照字节数来限制quicklistNode节点上的ziplist的的长度 , 可选值为-1到-5 , 每个值的含义如下//-1ziplist节点最大为4kb//-2ziplist节点最大为8kb//-3ziplist节点最大为16kb//-4ziplist节点最大为32kb//-5ziplist节点最大为64kblist-max-ziplist-size-2//单个ziplist节点最大能存储8kb,超过则进行分裂,将数据存储在新的ziplist节点中//对节点中间的数据进行压缩 , 进一步节省内存//0 特殊值 , 表示都不压缩//1quicklist两端各有1个节点不压缩 , 中间的节点压缩//2quicklist两端各有2个节点不压缩 , 中间的节点压缩//nquicklist两端各有n个节点不压缩 , 中间的节点压缩list-compress-depth1//0 代表所有节点 , 都不进行压缩 , 1 ,  代表从头节点往后走一个 , 尾节点往前走一个不用压缩 , 其他的全部压缩 , 以此类推2)ziplist 分析详解
【1】介绍
1.ziplist是一个经过特殊编码的双向链表 , 它的设计目标就是为了提高存储效率;
2.ziplist可以用于存储字符串或整数 , 其中整数是按真正的二进制表示进行编码的 , 而不是编码成字符串序列 。它能以O(1)的时间复杂度在表的两端提供push和pop操作;
3.因为ziplist是一个内存连续的集合 , 所以ziplist遍历只要通过当前节点的指针 加上 当前节点的长度 或 减去 上一节点的长度  , 即可得到下一个节点的数据或上一个节点的数据 , 这样就省去的指针从而节省了存储空间 , 又因为内存连续所以在数据读取上的效率也远高于普通的链表 。
【2】代码展示
robj *createZiplistObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_LIST,zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o;}robj *createObject(int type, void *ptr) {robj *o = zmalloc(sizeof(*o));o->type = type;o->encoding = OBJ_ENCODING_RAW;o->ptr = ptr;o->refcount = 1;if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;} else {o->lru = LRU_CLOCK();// 获取 24bit 当前时间秒数}return o;}//以下为ziplist.c文件中#define ZIPLIST_BYTES(zl)(*((uint32_t*)(zl)))//获取ziplist的zlbytes的指针(ziplist 所占空间字节数)#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+ZIPLIST_END_SIZE;// zip头加结束标识位数unsigned char *zl = zmalloc(bytes);ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);// 大小端转换ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);ZIPLIST_LENGTH(zl) = 0;// len赋值为0zl[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;

经验总结扩展阅读