1. 程式人生 > >Redis原始碼閱讀筆記—sds

Redis原始碼閱讀筆記—sds

Redis系統當中,針對字串進行的更加完善的封裝,建立了一個動態字串,並構建了大量的實用api。相關的實現程式碼為sds.h及sds.c,以下為我的原始碼閱讀筆記。內容較多,逐步更新

  1. typedefchar *sds;  
  2. struct __attribute__ ((__packed__)) sdshdr5 {  
  3.     usigned char flags;  
  4.     char buf[];  
  5. };  
  6. struct __attribute__ ((__packed__)) sdshdr8 {  
  7.     uint8_t len;  
  8.     uint8_t alloc;  
  9.     unsigned char
     flags;  
  10.     char buf[];  
  11. };  
  12. struct __attribute__ ((__packed__)) sdshdr16 {  
  13.     uint16_t len;  
  14.     uint16_t alloc;  
  15.     unsigned char flags;  
  16.     char buf[];  
  17. };  
  18. struct __attribute__ ((__packed__)) sdshdr32 {  
  19.     uint32_t len;  
  20.     uint32_t alloc;  
  21.     unsigned char flags;  
  22.     char buf[];  
  23. };  
  24. struct __attribute__ ((__packed__)) sdshdr64 {  
  25.     uint64_t len;  
  26.     uint64_t alloc;  
  27.     unsigned char flags;  
  28.     char buf[];  
  29. };  
  30. #define SDS_TYPE_5 0
  31. #define SDS_TYPE_8 1
  32. #define SDS_TYPE_16 2
  33. #define SDS_TYPE_32 3
  34. #define SDS_TYPE_64 4
  35. #define SDS_TYPE_MASK 7
  36. #define SDS_TYPE_BITS 3
  37. #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
  38. #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
  39. #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. //獲取動態字串長度
  2. staticinlinesize_t  sdslen(const sds s) {  
  3.     unsigned char flags = s[-1];//獲取頭部資訊中的flags屬性,因記憶體緊密相連,可以直接通過這種方式獲取
  4.     switch(flags&SDS_TASK_MASK) {//獲取型別,SDS_TASK_MASK為7,所以flags&SDS_TASK_MASK等於flags
  5.         case SDS_TYPE_5:  
  6.             return SDS_TYPE_5_LEN(flags);//SDS_TYPE_5型別的長度獲取稍微不同,它的長度被定義在flags的高5位當中,具體可檢視之後的sdsnewlen函式,或者下面的sdssetlen函式
  7.         case SDS_TYPE_8:  
  8.             return SDS_HDR(8,s)->len;//SDS_HDR函式通過型別與字串開始位元組獲取頭部,以此獲取字串的長度
  9.         case SDS_TYPE_16:  
  10.             return SDS_HDR(16,s)->len;  
  11.         case SDS_TYPE_32:  
  12.             return SDS_HDR(32,s)->len;  
  13.         case SDS_TYPE_64:  
  14.             RETURN SDS_HDR(64,S)->len;  
  15.     }  
  16.     return 0;  
  17. }  
  18. //獲取動態字串的剩餘記憶體
  19. staticinlinesize_t sdsavail(const sds s) {  
  20.     unsigned char flags = s[-1];//獲取flags
  21.     switch(flags&SDS_TYPE_MASK) {  
  22.         case SDS_TYPE_5://SDS_TYPE_5直接返回0,
  23.             return 0;  
  24.         case SDS_TYPE_8: {  
  25.             SDS_HDR_VAR(8,s);//通過SDS_HDR_VAR函式,將頭部指標放置在sh變數
  26.             return sh->alloc - sh->len;//總記憶體大小 - 字串長度,獲取可用記憶體大小
  27.         }  
  28.         case SDS_TYPE_16: {  
  29.             SDS_HDR_VAR(16,s);  
  30.             return sh->alloc - sh->len;  
  31.         }  
  32.         case SDS_TYPE_32: {  
  33.             SDS_HDR_VAR(32,s);  
  34.             return sh->alloc - sh->len;  
  35.         }  
  36.         case SDS_TYPE_64: {  
  37.             SDS_HDR_VAR(64,s);  
  38.             return sh->alloc - sh-len;  
  39.         }  
  40.     }  
  41.     return 0;  
  42. }  
  43. //重置字串長度
  44. staticinlinevoid sdssetlen(sds s, size_t newlen) {  
  45.     unsigned char flags = s[-1];//獲取flags
  46.     switch(flags&SDS_TASK_MASK) {  
  47.         //SDS_TYPE_5的長度設定較為特殊,長度資訊寫在flags的高5位
  48.         case SDS_TYPE_5:  
  49.             {  
  50.                 unsigned char *fp = ((unsigned char*)s)-1;  
  51.                 *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);  
  52.             }  
  53.             break;  
  54.         //其他型別則是統一修改len屬性的值
  55.         case SDS_TYPE_8:  
  56.             SDS_HDR(8,s)->len = newlen;  
  57.             break;  
  58.         case SDS_TYPE_16:  
  59.             SDS_HDR(16,s)->len = newlen;  
  60.             break;  
  61.         case SDS_TYPE_32:  
  62.             SDS_HDR(32,s)->len = newlen;  
  63.             break;  
  64.         case SDS_TYPE_64:  
  65.             SDS_HDR(64,s)->len = newlen;  
  66.             break

    相關推薦

    Redis原始碼閱讀筆記sds

    Redis系統當中,針對字串進行的更加完善的封裝,建立了一個動態字串,並構建了大量的實用api。相關的實現程式碼為sds.h及sds.c,以下為我的原始碼閱讀筆記。內容較多,逐步更新typedefchar *sds;  struct __attribute__ ((__pac

    Redis原始碼閱讀筆記--資料庫redisDb

    一. 資料庫 Redis的資料庫使用字典作為底層實現,資料庫的增、刪、查、改都是構建在字典的操作之上的。 redis伺服器將所有資料庫都儲存在伺服器狀態結構redisServer(redis.h/redisServer)的db陣列(應該是一個連結串列)裡:

    redis原始碼閱讀筆記-dict.h

    dict.h 在redis中,dict.h主要是hash的底層實現方式。 在dict.h中主要是一些資料結構的定義,以及一些巨集函式的定義相關的內容。 雜湊節點定義 原始碼中的dictEntry就是雜湊節點的相關定義 typedef struct dictEnt

    原始碼閱讀筆記——Tablib

    文章目錄 Tablib 用法示例 程式碼結構 程式碼結構與風格 細節 Reference Tablib Tablib是一個支援多格式資料轉換的庫,支援的格式包括XLSX、XLS、JSON、YA

    原始碼閱讀筆記——HowDoI

    文章目錄 HowDoI 用法示例 程式碼結構 主要依賴 程式碼結構與風格 細節 Reference HowDoI HowDoI是命令列應用,用以搜尋程式設計問題的答案,原始碼十分簡短,

    Redis原始碼閱讀(六)叢集-故障遷移(下)

    Redis原始碼閱讀(六)叢集-故障遷移(下)   最近私人的事情比較多,沒有抽出時間來整理部落格。書接上文,上一篇裡總結了Redis故障遷移的幾個關鍵點,以及Redis中故障檢測的實現。本篇主要介紹叢集檢測到某主節點下線後,是如何選舉新的主節點的。注意到Redis叢集是無中心的,那麼使用分散式一

    jdk原始碼閱讀筆記-LinkedHashMap

      Map是Java collection framework 中重要的組成部分,特別是HashMap是在我們在日常的開發的過程中使用的最多的一個集合。但是遺憾的是,存放在HashMap中元素都是無序的,原因是我們在put或get資料的時候都是根據key的hash值來確定元素的位置。在具體的業務場景中,我們更

    jdk原始碼閱讀筆記-ArrayList

    一、ArrayList概述 首先我們來說一下ArrayList是什麼?它解決了什麼問題?ArrayList其實是一個數組,但是有區別於一般的陣列,它是一個可以動態改變大小的動態陣列。ArrayList的關鍵特性也是這個動態的特性了,ArrayList的設計初衷就是為了解決Java陣列長度不可變的

    .NetCore原始碼閱讀筆記系列之HttpAbstractions(五) Authentication

    說道認證&授權其實這塊才是核心,這款跟前面Security這塊有者緊密的聯絡,當然 HttpAbstractions 不光是認證、授權、還包含其他Http服務和中間價 接下來先就認證這塊結合前面的Security作一個補充說明 前面 AddCookie 、OpenIdConnect、Go

    Redis原始碼閱讀(四)叢集-請求分配

        叢集搭建好之後,使用者傳送的命令請求可以被分配到不同的節點去處理。那Redis對命令請求分配的依據是什麼?如果節點數量有變動,命令又是如何重新分配的,重分配的過程是否會阻塞對外提供的服務?接下來會從這兩個問題入手,分析Redis3.0的原始碼實現。 1. 分配依據—

    mxnet原始碼閱讀筆記之include

    寫在前面 mxnet程式碼的規範性比Caffe2要好,看起來核心程式碼量也小很多,但由於對dmlc其它庫的依賴太強,程式碼的獨立性並不好。依賴的第三方庫包括: cub dlpack dmlc-core googletest mkldnn mshadow onnx-tensorrt openmp ps-lite

    JAVA 10原始碼閱讀筆記之JEP-307(G1的並行Full GC)

    # 1. 背景 JEP-307解決了G1垃圾回收器的一個嚴重的問題,截止到Java 9,G1的Full GC採用的是單執行緒演算法,嚴重影響效能,無法利用到多核能力進行垃圾回收。JEP-307修復了此問題,發生Full GC時允許使用多個執行緒進行並行回收。 # 2. G1

    base_local_planner原始碼閱讀筆記

    ROS的navigation stack中區域性規劃器的介面plugin類為nav_core::BaseLocalPlanner,給出的包有4種: base_local_planner - 提供了 D

    hashMap原始碼閱讀筆記1.7

    這幾天一直在看hashMap的原始碼,也借鑑了很多大佬的文章以便更好的理解,也從大佬文章中借鑑了很多的內容,如果侵權,請告知,我將立刻刪除。 hashMap繼承AbstractMap,實現了map介面。 public class HashMap<K,V> extends

    Disruptor原始碼閱讀筆記

    Disruptor是什麼 關於 Disruptor,網路上有很多的解釋和說法。這裡簡單的概括下。Disruptor 是一個消費者生產者佇列框架,據官網介紹,可以提供非常強大的效能。Disruptor 與其說為我們帶來了一個框架,更多的是為我們帶來了一個獨特思路的程式設計實踐

    spark原始碼閱讀筆記Dataset(二)Dataset中Actions、function、transformations

    package Dataset import org.apache.spark.sql.functions._ import org.apache.spark.sql.{DataFrame, Dataset, SparkSession} /** * Cr

    Flask 原始碼閱讀筆記 開篇

    Flask 是一個 Python 實現的 Web 開發微框架, 有豐富的生態資源。本文從一段官方的示例程式碼通過一步步打斷點方式解釋 Flask 內部的執行機制,在一些關鍵概念會有相關解釋,這些前提概念對整體理解 Flask框架十分重要,本文基於flask 0

    jdk原始碼閱讀筆記-LinkedList

      一、LinkedList概述   LinkedList的底層資料結構為雙向連結串列結構,與ArrayList相同的是LinkedList也可以儲存相同或null的元素。相對於ArrayList來說,LinkedList的插入與刪除的速度更快,時間複雜度為O(1),查詢的速度就相對比較慢了,因為每次遍歷的時

    spark原始碼閱讀筆記Dataset(三)structField、structType、schame

    StructType(fields: Seq[StructField]) 一個StructType物件,可以有多個StructField,同時也可以用名字(name)來提取,就想當於Map可以用key來提取value,但是他StructType提取的是整條欄位的資訊 在原始碼中structType是一個cas

    jvm原始碼閱讀筆記[3]:從記憶體分配到觸發GC的細節

             除了第一篇說到的,對於使用cms回收的應用,會有執行緒輪詢判斷老年代是否滿足GC的條件,若滿足,則會觸發一次cms老年代的回收。     針對年輕代,更常見的是,執行緒優先在eden區分配物件的時候,若eden區空間不足,則會觸發一次y