搜索

Redis源码解析

Mar 22, 2018 am 09:56 AM
redis源码解析

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] = &#39;\0&#39;;  
        return s;  
    }

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

相关推荐:

Redis源码解析1

Redis源码解析2

Redis源码解析3

以上是Redis源码解析的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
PHP的目的:构建动态网站PHP的目的:构建动态网站Apr 15, 2025 am 12:18 AM

PHP用于构建动态网站,其核心功能包括:1.生成动态内容,通过与数据库对接实时生成网页;2.处理用户交互和表单提交,验证输入并响应操作;3.管理会话和用户认证,提供个性化体验;4.优化性能和遵循最佳实践,提升网站效率和安全性。

PHP:处理数据库和服务器端逻辑PHP:处理数据库和服务器端逻辑Apr 15, 2025 am 12:15 AM

PHP在数据库操作和服务器端逻辑处理中使用MySQLi和PDO扩展进行数据库交互,并通过会话管理等功能处理服务器端逻辑。1)使用MySQLi或PDO连接数据库,执行SQL查询。2)通过会话管理等功能处理HTTP请求和用户状态。3)使用事务确保数据库操作的原子性。4)防止SQL注入,使用异常处理和关闭连接来调试。5)通过索引和缓存优化性能,编写可读性高的代码并进行错误处理。

您如何防止PHP中的SQL注入? (准备的陈述,PDO)您如何防止PHP中的SQL注入? (准备的陈述,PDO)Apr 15, 2025 am 12:15 AM

在PHP中使用预处理语句和PDO可以有效防范SQL注入攻击。1)使用PDO连接数据库并设置错误模式。2)通过prepare方法创建预处理语句,使用占位符和execute方法传递数据。3)处理查询结果并确保代码的安全性和性能。

PHP和Python:代码示例和比较PHP和Python:代码示例和比较Apr 15, 2025 am 12:07 AM

PHP和Python各有优劣,选择取决于项目需求和个人偏好。1.PHP适合快速开发和维护大型Web应用。2.Python在数据科学和机器学习领域占据主导地位。

PHP行动:现实世界中的示例和应用程序PHP行动:现实世界中的示例和应用程序Apr 14, 2025 am 12:19 AM

PHP在电子商务、内容管理系统和API开发中广泛应用。1)电子商务:用于购物车功能和支付处理。2)内容管理系统:用于动态内容生成和用户管理。3)API开发:用于RESTfulAPI开发和API安全性。通过性能优化和最佳实践,PHP应用的效率和可维护性得以提升。

PHP:轻松创建交互式Web内容PHP:轻松创建交互式Web内容Apr 14, 2025 am 12:15 AM

PHP可以轻松创建互动网页内容。1)通过嵌入HTML动态生成内容,根据用户输入或数据库数据实时展示。2)处理表单提交并生成动态输出,确保使用htmlspecialchars防XSS。3)结合MySQL创建用户注册系统,使用password_hash和预处理语句增强安全性。掌握这些技巧将提升Web开发效率。

PHP和Python:比较两种流行的编程语言PHP和Python:比较两种流行的编程语言Apr 14, 2025 am 12:13 AM

PHP和Python各有优势,选择依据项目需求。1.PHP适合web开发,尤其快速开发和维护网站。2.Python适用于数据科学、机器学习和人工智能,语法简洁,适合初学者。

PHP的持久相关性:它还活着吗?PHP的持久相关性:它还活着吗?Apr 14, 2025 am 12:12 AM

PHP仍然具有活力,其在现代编程领域中依然占据重要地位。1)PHP的简单易学和强大社区支持使其在Web开发中广泛应用;2)其灵活性和稳定性使其在处理Web表单、数据库操作和文件处理等方面表现出色;3)PHP不断进化和优化,适用于初学者和经验丰富的开发者。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具