目錄
sdsnewlen:
首頁 後端開發 php教程 Redis原始碼解析

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

熱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.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

redis集群模式怎麼搭建 redis集群模式怎麼搭建 Apr 10, 2025 pm 10:15 PM

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

redis底層怎麼實現 redis底層怎麼實現 Apr 10, 2025 pm 07:21 PM

Redis 使用哈希表存儲數據,支持字符串、列表、哈希表、集合和有序集合等數據結構。 Redis 通過快照 (RDB) 和追加只寫 (AOF) 機制持久化數據。 Redis 使用主從復制來提高數據可用性。 Redis 使用單線程事件循環處理連接和命令,保證數據原子性和一致性。 Redis 為鍵設置過期時間,並使用 lazy 刪除機制刪除過期鍵。

redis-server找不到怎麼辦 redis-server找不到怎麼辦 Apr 10, 2025 pm 06:54 PM

解決redis-server找不到問題的步驟:檢查安裝,確保已正確安裝Redis;設置環境變量REDIS_HOST和REDIS_PORT;啟動Redis服務器redis-server;檢查服務器是否運行redis-cli ping。

redis怎麼查看所有的key redis怎麼查看所有的key Apr 10, 2025 pm 07:15 PM

要查看 Redis 中的所有鍵,共有三種方法:使用 KEYS 命令返回所有匹配指定模式的鍵;使用 SCAN 命令迭代鍵並返回一組鍵;使用 INFO 命令獲取鍵的總數。

redis怎麼讀源碼 redis怎麼讀源碼 Apr 10, 2025 pm 08:27 PM

理解 Redis 源碼的最佳方法是逐步進行:熟悉 Redis 基礎知識。選擇一個特定的模塊或功能作為起點。從模塊或功能的入口點開始,逐行查看代碼。通過函數調用鏈查看代碼。熟悉 Redis 使用的底層數據結構。識別 Redis 使用的算法。

redis怎麼讀取隊列 redis怎麼讀取隊列 Apr 10, 2025 pm 10:12 PM

要從 Redis 讀取隊列,需要獲取隊列名稱、使用 LPOP 命令讀取元素,並處理空隊列。具體步驟如下:獲取隊列名稱:以 "queue:" 前綴命名,如 "queue:my-queue"。使用 LPOP 命令:從隊列頭部彈出元素並返回其值,如 LPOP queue:my-queue。處理空隊列:如果隊列為空,LPOP 返回 nil,可先檢查隊列是否存在再讀取元素。

redis計數器怎麼實現 redis計數器怎麼實現 Apr 10, 2025 pm 10:21 PM

Redis計數器是一種使用Redis鍵值對存儲來實現計數操作的機制,包含以下步驟:創建計數器鍵、增加計數、減少計數、重置計數和獲取計數。 Redis計數器的優勢包括速度快、高並發、持久性和簡單易用。它可用於用戶訪問計數、實時指標跟踪、遊戲分數和排名以及訂單處理計數等場景。

redis指令怎麼用 redis指令怎麼用 Apr 10, 2025 pm 08:45 PM

使用 Redis 指令需要以下步驟:打開 Redis 客戶端。輸入指令(動詞 鍵 值)。提供所需參數(因指令而異)。按 Enter 執行指令。 Redis 返迴響應,指示操作結果(通常為 OK 或 -ERR)。

See all articles