【3】为什么会有embstr,代码展示
//CPU读取数据的时候其实是会有一个缓存行的概念(cache line,通常是64byte的空间),也就是一次性读取的大小//而redisObject数据大小为16 bytetypedef struct redisObject {unsigned type:4;//占4 bitunsigned encoding:4;//占4 bitunsigned lru:LRU_BITS; // 占24 bitint refcount;// 4 bytevoid *ptr;// 8 byte} robj;//总空间:4 bit + 4 bit + 24 bit + 4 byte + 8 byte = 16 byte 所以读取是会读【redisObject 16 byte,及其后面的48byte的数据(但是用不到)】为了节约CPU成本,可不可以在创建的时候,将数据就存在后面呢?(为什么采用sdshdr8,因为最多存44个字符,sdshdr8可以容纳128个,满足条件 , 且消耗最?。?struct __attribute__ ((__packed__)) sdshdr8 { // 对应的字符串长度小于 1<<8uint8_t len;//占据1byte,表示128个uint8_t alloc;//占据1byteunsigned char flags;//占据1bytechar buf[];//以'\0'结尾,这个字符也会占据1byte};所以如果把他们都存于一个64byte的内存中是不是读取对象的时候顺便可以把值也拿出来了 , 减少了一次IO 。【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;
推荐阅读
- 七 Netty 学习:NioEventLoop 对应线程的创建和启动源码说明
- Spring mvc源码分析系列--Servlet的前世今生
- spring cron表达式源码分析
- 集合框架——LinkedList集合源码分析
- 含源码 手把手教你使用LabVIEW OpenCV DNN实现手写数字识别
- 补充部分---ScheduledThreadPoolExecutor类分析 线程池底层原理详解与源码分析
- 五 Netty 学习:服务端启动核心流程源码说明
- HashMap底层原理及jdk1.8源码解读
- 含源码 手把手教你使用LabVIEW人工智能视觉工具包快速实现传统Opencv算子的调用
- 含源码 手把手教你使用LabVIEW人工智能视觉工具包快速实现图像读取与采集