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


【1】为什么要对原本的数据结构进行修改?(改版后的优化在哪里)
因为int占据4个字节(8bit),也就是能存42亿左右的,但是在我们实际上 , 存储的数据大概率都是小数据,所以它存在浪费资源的嫌疑 。
所以进行优化的思维就是根据不同的数据范围,设置不同容量 , 如,uint8_t 表示占据1字节(8bit , 在二进制中最大可以表示255),uint16_t 表示占据2字节(16bit , 在二进制中最大可以表示65535)
【2】官网上说String类型限制大小512M , 是怎么限制的?
//位于t_string.c文件中//为什么要限制 , 要知道512M已经是一个很大的值了(已经是一个bigkey了),在redis单线程操作中已经很容易阻塞线程//故在追加命令appendCommand和设置命令setrangeCommand中都会进行校验static int checkStringLength(client *c, long long size) {if (size > 512*1024*1024) {addReplyError(c,"string exceeds maximum allowed size (512MB)");return C_ERR;}return C_OK;}3)分析是怎么创建的
//在sds.c文件内//sds在创建的时候 , buf数组初始大小为:struct结构体大小 + 字符串的长度+1, +1是为了在字符串末尾添加一个\0 。//在完成字符串到字符数组的拷贝之后,会在字符串末尾加一个\0 , 这样可以复用C语言的一些函数 。sds sdsnewlen(const void *init, size_t initlen) {void *sh;sds s;// 根据长度计算sds类型char type = sdsReqType(initlen);if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//为空时强制用sdshdr8// 获取结构体大小int hdrlen = sdsHdrSize(type);unsigned char *fp; /* flags pointer. */// 分配内存空间,初始大小为:struct结构体大小+字符串的长度+1,+1是为了在字符串末尾添加一个\0,兼容传统C语言sh = s_malloc(hdrlen+initlen+1);// sh在这里指向了这个刚刚分配的内存地址if (sh == NULL) return NULL;// 判断是否是init阶段if (!init)//init 不为空的话 , 将sh这块内存全部设置为0memset(sh, 0, hdrlen+initlen+1);// 指向buf数组的指针s = (char*)sh+hdrlen;//因为可以看到地址的顺序是 len,alloc,flag,buf,目前s是指向buf,那么后退1位,fp 正好指向了flag对应的地址fp = ((unsigned char*)s)-1;// 类型选择switch(type) {case SDS_TYPE_5: {*fp = type | (initlen << SDS_TYPE_BITS);break;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}}//如果两者都不为空,则init 这个对应的字符串,赋值给sif (initlen && init)memcpy(s, init, initlen); // 将字符串拷贝到buf数组s[initlen] = '\0';// 字符串末尾添加一个\0return s;}// 获取结构体大小static inline int sdsHdrSize(char type) {switch(type&SDS_TYPE_MASK) {case SDS_TYPE_5:return sizeof(struct sdshdr5);case SDS_TYPE_8:return sizeof(struct sdshdr8);case SDS_TYPE_16:return sizeof(struct sdshdr16);case SDS_TYPE_32:return sizeof(struct sdshdr32);case SDS_TYPE_64:return sizeof(struct sdshdr64);}return 0;}4)怎么防止操作时缓冲区溢出
//先检查 SDS 的空间是否满足修改所需的要求//如果不满足要求的话,API 会自动将 SDS 的空间扩展到执行修改所需的大小//最后才是返回,去执行实际的修改操作sds sdscatlen(sds s, const void *t, size_t len) {size_t curlen = sdslen(s);//获取s已经使用过的空间字符数s = sdsMakeRoomFor(s,len);//扩大s的空闲空间if (s == NULL) return NULL;memcpy(s+curlen, t, len);//拷贝数据sdssetlen(s, curlen+len);//设置s的lens[curlen+len] = '\0'; //最后加上空字符串return s;}5)分析是怎么扩容的
代码展示
// 扩容sdssds sdsMakeRoomFor(sds s, size_t addlen) {void *sh, *newsh;//获取剩余可用的空间size_t avail = sdsavail(s);size_t len, newlen;char type, oldtype = s[-1] & SDS_TYPE_MASK;int hdrlen;//如果可用空间大于需要增加的长度,那么直接返回if (avail >= addlen) return s;//len 已使用长度len = sdslen(s);//sh 回到指向了这个sds的起始位置 。sh = (char*)s-sdsHdrSize(oldtype);// newlen 代表最小需要的长度newlen = (len+addlen);//Redis认为一旦被扩容了,那这个字符串被再次扩容的几率就很大 , 所以会在此基础上多加一些空间,防止频繁扩容if (newlen < SDS_MAX_PREALLOC)newlen *= 2;elsenewlen += SDS_MAX_PREALLOC;//获取新长度的类型type = sdsReqType(newlen);//如果是SDS_TYPE_5会被强行转为SDS_TYPE_8if (type == SDS_TYPE_5) type = SDS_TYPE_8;hdrlen = sdsHdrSize(type);if (oldtype==type) {//sh是开始地址,在开始地址的基础上,分配更多的空间 , 逻辑如同初始化部分,hdrlen 是head的长度,即struct本身大小 。后面newlen 是buf 大小 ,  +1 是为了结束符号,sds 通常情况下是可以直接打印的newsh = s_realloc(sh, hdrlen+newlen+1);if (newsh == NULL) {s_free(sh);return NULL;}s = (char*)newsh+hdrlen;} else {//如果类型发生变化,地址内容不可复用,所以找新的空间 。newsh = s_malloc(hdrlen+newlen+1);if (newsh == NULL) return NULL;//复制原来的str到新的sds 上面,newsh+hdrlen 等于sds buf 地址开始的位置,s 原buf的位置,len+1 把结束符号也复制进来memcpy((char*)newsh+hdrlen, s, len+1);//释放前面的内存空间s_free(sh);//调整s开始的位置,即地址空间指向新的buf开始的位置s = (char*)newsh+hdrlen;//-1 正好到了flag的位置s[-1] = type;//分配len的值sdssetlen(s, len);}sdssetalloc(s, newlen);//返回新的sdsreturn s;}// 给len 设值static inline size_t sdsavail(const sds s) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5: {return 0;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);return sh->alloc - sh->len;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);return sh->alloc - sh->len;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);return sh->alloc - sh->len;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);return sh->alloc - sh->len;}}return 0;}// 获取当前sds,可用的长度 。static inline void sdssetlen(sds s, size_t newlen) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:{unsigned char *fp = ((unsigned char*)s)-1;*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));}break;case SDS_TYPE_8:SDS_HDR(8,s)->len = (uint8_t)newlen;break;case SDS_TYPE_16:SDS_HDR(16,s)->len = (uint16_t)newlen;break;case SDS_TYPE_32:SDS_HDR(32,s)->len = (uint32_t)newlen;break;case SDS_TYPE_64:SDS_HDR(64,s)->len = (uint64_t)newlen;break;}}// 获取alloc的长度/* sdsalloc() = sdsavail() + sdslen() */static inline size_t sdsalloc(const sds s) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:return SDS_TYPE_5_LEN(flags);case SDS_TYPE_8:return SDS_HDR(8,s)->alloc;case SDS_TYPE_16:return SDS_HDR(16,s)->alloc;case SDS_TYPE_32:return SDS_HDR(32,s)->alloc;case SDS_TYPE_64:return SDS_HDR(64,s)->alloc;}return 0;}// 给 alloc 设值static inline void sdssetalloc(sds s, size_t newlen) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:/* Nothing to do, this type has no total allocation info. */break;case SDS_TYPE_8:SDS_HDR(8,s)->alloc = (uint8_t)newlen;break;case SDS_TYPE_16:SDS_HDR(16,s)->alloc = (uint16_t)newlen;break;case SDS_TYPE_32:SDS_HDR(32,s)->alloc = (uint32_t)newlen;break;case SDS_TYPE_64:SDS_HDR(64,s)->alloc = (uint64_t)newlen;break;}}

推荐阅读