欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

Redis源码解析

程序员文章站 2022-03-24 19:29:27
...
Redis系统当中,针对字符串进行的更加完善的封装,建立了一个动态字符串,并构建了大量的实用api。相关的实现代码为sds.h及sds.c,以下为我的源码阅读笔记。内容较多,逐步更新

  1. typedef char *sds;  
      
    struct __attribute__ ((__packed__)) sdshdr5 {  
        usigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr8 {  
        uint8_t len;  
        uint8_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr16 {  
        uint16_t len;  
        uint16_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr32 {  
        uint32_t len;  
        uint32_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr64 {  
        uint64_t len;  
        uint64_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
      
    #define SDS_TYPE_5 0  
    #define SDS_TYPE_8 1  
    #define SDS_TYPE_16 2  
    #define SDS_TYPE_32 3  
    #define SDS_TYPE_64 4  
    #define SDS_TYPE_MASK 7  
    #define SDS_TYPE_BITS 3  
    #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));  
    #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))  
    #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

以上是动态字符串的结构体声明及define声明的函数。动态字符串一共有5种类型,分别为不同长度的字符串所实用。我在这里称之为动态字符串的头部。

sdshdr5:长度为小于32的字符串

sdshdr8:长度为小于256的字符串

sdshdr16:长度为小于2^16的字符串

sdshdr32:长度为小于2^32的字符串。这里有一点注意,若是机器的LONG_MAXbu不等于LLONG_MAX,则返回sdshdr64类型。

sdshdr64:其他所有长度都实用此类。

sdshdr5这个类型不同于其他类型,它缺少len成员与alloc成员,它的判断与处理都比较特别。但是官方在代码有过一段注释,如下:

/* Note: sdshdr5 is never used, we just access the flags byte directly.

* However is here to document the layout of type 5 SDS strings. */

注释中说明,这个类型从未被使用,所以我们在这里姑且不考虑它,而实际上,它的处理操作本质上与其他类型并没有什么区别。方便起见,我们以通用的类型进行研究。

结构体当中,有四个类,分别为len、alloc、flags与buf。

len:字符串的长度。

alloc:字符串内存总大小,注意,alloc不同于len。len是实际字符串的长度,而alloc,则是实际分配的内存大小(不包含sds头与结尾的'\0'的大小)。为了减少字符串内容增加时反复的重新申请内存,redis当中会申请更多内存以备使用。当字符串大小小于1MB的时候,申请两倍大小的内存使用,当字符串大小大于等于1MB的时候,多申请1MB的内存以备使用。详细的设定可以看之后的sdsMakeRoomFor函数的解析。

flags:作为区分不同类型的标记使用,暂时只使用了低3位来标记,高5位暂未使用,也供以后增加新功能时使用。上面代码中define声明的SDS_TYPE_*类型为相应的标记内容,用于区分不同的字符串类型。比如flags等于SDS_TYPE_8时,则可以从字符串开始字节在向前17字节,或者字符串头部开始获取8字节数据获取当前字符串的实际长度。等于SDS_TYPE_16时,从字符串开始字节向前33字节,或者字符串头部开始获取16自己获取当前字符串的实际长度。flags与头部信息的配合使用,将在之后的函数解析里面大量出现。

buf:实际存储字符串内容的数组,同传统数组一样,结尾需要'\0'字符。

在sdshdr的声明当中,我们可以看到 __attribute__ ((__packed__)) 关键字,这将使这个结构体在内存中不再遵守字符串对齐规则,而是以内存紧凑的方式排列。所以可以从字符串位置开始向前一个字节便可以获取flags信息了,比如buf[-1]。具体__attribute__ ((__packed__))与字符串对齐的内容请查看另一篇博客。

SDS_HDR_VAR函数则通过结构体类型与字符串开始字节,获取到动态字符串头部的开始位置,并赋值给sh指针。SDS_HDR函数则通过类型与字符串开始字节,返回动态字符串头部的指针。使用方式为可在之后的代码当中看到,具体define声明中的双'#'号的使用方式与意义,请看草另一篇博客。

sds比起传统的字符串,有着自己优势跟便利之处。

1、内存预分配:sds通过预先分配了更多的内存量,来避免频繁的申请、分配内存,无端的浪费了性能

2、惰性释放:sds作为普通字符串使用之时,可以通过在不同字节打上'\0'字符,来代表字符串的截断及减少长度,而不是需要清空多余的字节并释放它们,那些内存再之后的操作当中可以当做空闲内存继续使用。

3、二进制安全:作为非字符串使用存储数据之时,通过善用头部的len属性,可以存储一些包含'\0'字符的数据。当然,一定要善用len属性,api当中,如长度更新的函数,同样通过'\0'字符来判断结尾!

接下来开始介绍sds相关的api函数,第一批是声明、定义在sds.h文件内的静态函数,这些函数都是针对动态字符串头部的属性的获取与修改,简单易懂

  1. //获取动态字符串长度  
    static  inline  size_t  sdslen(const sds s) {  
        unsigned char flags = s[-1];//获取头部信息中的flags属性,因内存紧密相连,可以直接通过这种方式获取  
        switch(flags&SDS_TASK_MASK) {//获取类型,SDS_TASK_MASK为7,所以flags&SDS_TASK_MASK等于flags  
            case SDS_TYPE_5:  
                return SDS_TYPE_5_LEN(flags);//SDS_TYPE_5类型的长度获取稍微不同,它的长度被定义在flags的高5位当中,具体可查看之后的sdsnewlen函数,或者下面的sdssetlen函数  
            case SDS_TYPE_8:  
                return SDS_HDR(8,s)->len;//SDS_HDR函数通过类型与字符串开始字节获取头部,以此获取字符串的长度  
            case SDS_TYPE_16:  
                return SDS_HDR(16,s)->len;  
            case SDS_TYPE_32:  
                return SDS_HDR(32,s)->len;  
            case SDS_TYPE_64:  
                RETURN SDS_HDR(64,S)->len;  
        }  
        return 0;  
    }  
      
    //获取动态字符串的剩余内存  
    static inline size_t sdsavail(const sds s) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TYPE_MASK) {  
            case SDS_TYPE_5://SDS_TYPE_5直接返回0,  
                return 0;  
            case SDS_TYPE_8: {  
                SDS_HDR_VAR(8,s);//通过SDS_HDR_VAR函数,将头部指针放置在sh变量  
                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;  
    }  
      
    //重置字符串长度  
    static inline void sdssetlen(sds s, size_t newlen) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TASK_MASK) {  
            //SDS_TYPE_5的长度设置较为特殊,长度信息写在flags的高5位  
            case SDS_TYPE_5:  
                {  
                    unsigned char *fp = ((unsigned char*)s)-1;  
                    *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);  
                }  
                break;  
            //其他类型则是统一修改len属性的值  
            case SDS_TYPE_8:  
                SDS_HDR(8,s)->len = newlen;  
                break;  
            case SDS_TYPE_16:  
                SDS_HDR(16,s)->len = newlen;  
                break;  
            case SDS_TYPE_32:  
                SDS_HDR(32,s)->len = newlen;  
                break;  
            case SDS_TYPE_64:  
                SDS_HDR(64,s)->len = newlen;  
                break;  
        }  
    }  
      
    //按照指定数值,增加字符串长度  
    static inline void sdsinclen(sds s, size_t inc) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TYPE_MASK) {  
            //SDS_TYPE_5类型使用上面的函数,获取长度、更新、设置  
            case SDS_TYPE_5:  
                {  
                    unsigned char *fp = ((unsigned char*)s)-1;  
                    unsigned char newlen = SDS_TYPE_LEN(flags)+inc;  
                    *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);  
                }  
                break;  
            //其他类型则直接通过SDS_HDR函数,更新len值  
            case SDS_TYPE_8:  
                SDS_HDR(8,s)->len += inc;  
                break;  
            case SDS_TYPE_16:  
                SDS_HDR(16,s)->len += inc;  
                break;  
            case SDS_TYPE_32:  
                SDS_HDR(32,s)->len += inc;  
                break;  
            case SDS_TYPE_64:  
                SDS_HDR(64,s)->len += inc;  
                break;  
        }  
    }  
      
    //获取动态字符串的总内存  
    static inline size_t sdsalloc(const sds s) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TASK_MASK) {  
            //SDS_TYPE_5直接通过SDS_TYPE_5_LEN函数返回  
            case SDS_TYPE_5:  
                return SDS_TYPE_5_LEN(flags);  
            //其他类型则返回头部信息中的alloc属性  
            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;  
    }  
      
    //重置字符串内存大小  
    static inline size_t sdssetalloc(sds s, size_t newlen) {  
        unsigned cahr flags = s[-1];//获取flags  
        switch(flags&SDS_TASK_MASK) {  
            case SDS_TYPE_5:  
                //官方注释,SDS_TYPE_5不做任何操作  
                /*Nothing to do, this type has no total allocation info. */  
                break;  
            //其他类型直接修改头部信息中的alloc属性  
            case SDS_TYPE_8:  
                SDS_HDR(8,s)->alloc = newlen;  
                break;  
            case SDS_TYPE_16:  
                SDS_HDR(16,s)->alloc = newlen;  
                break;  
            case SDS_TYPE_32:  
                SDS_HDR(32,s)->alloc = newlen;  
                break;  
            case SDS_TYPE_64:  
                SDS_HDR(64,s)->alloc = newlen;  
                break;  
        }  
    }

上述的几个函数,sdslen,sdsavail,sdssetlen,sdsinclen,sdsalloc,sdssetalloc函数,都是基本的头部属性操作函数。代码的难度也不大,可以直观的阅读、理解。

接下来的sds的相关api,数量有点多,之后的dict、zskiplist也是有大量api,挑部分代码较多,需要逐行理解的函数来记录、分析。

sdsnewlen:

  1. //创建一个新的sds对象  
    sds sdsnewlen(const void *init, size_t initlen) {  
        void *sh;  
        sds s;  
        char type = sdsReqType(initlen);//根据初始化长度获取对应结构体类型  
        if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//若长度为0的则初始化为SDS_TYPE_8类型  
        int hdrlen = sdsHdrSize(type);  
        unsigned char *fp;  
          
        sh = s_malloc(hdrlen+initlen+1);//申请足够的内存,头部大小+初始化大小+结尾符  
        if (!init)  
            memset(sh, 0, hdrlen+initlen+1);  
        if (sh == NULL) return NULL;  
        s = (char*)sh+hdrlen;//获取字符串起始字节  
        fp = ((unsigned char*)s)-1;//获取flags字节  
        switch(type) {  
            //SDS_TYPE_5又是特立独行了,有自己的初始化方案,我都懒得说明了。。。  
            case SDS_TYPE_5: {  
                *fp = type | (initlen << SDS_TYPE_BITS);  
                break;  
            }  
            //其他类型通过SDS_HDR_VAR函数获取头部信息,并逐步初始化  
            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与initlen,将内容复制给字符串  
        if (initlen && init)  
            memcpy(s, init, initlen);  
        //打上结尾符  
        s[initlen] = '\0';  
        return s;  
    }

sdsnewlen根据参数给予的init字符串与initlen初始长度,生成并返回一个动态字符串。代码都打上了注释,阅读已经没什么难度了。

相关推荐:

Redis源码解析1

Redis源码解析2

Redis源码解析3

以上就是Redis源码解析的详细内容,更多请关注其它相关文章!