Redis 6.0ソースコードリーディングノート(4)-文字列データタイプのソースコード分析

1.ストレージ構造

導入Redisの文字列オブジェクトの文字列は、我々はRedisの文字列格納用の3つの記憶形式を持っていることを知って、そのストレージのメモリ構造は、次の画像の例で示されています:

  • OBJ_ENCODING_INT:保存された文字列の長さが20未満であり、long型の整数値として解析できる場合、保存方法は次のようになります。redisObjectのptrポインタをこの整数値に直接ポイントします
    ここに写真の説明を挿入

  • OBJ_ENCODING_EMBSTR:長さが44未満の文字列(OBJ_ENCODING_EMBSTR_SIZE_LIMIT)は、単純な動的文字列(SDS)の形式でredisObjectに格納されますが、redisObjectオブジェクトヘッダーはSDSオブジェクトとともに引き続き存在します
    ここに写真の説明を挿入

  • OBJ_ENCODING_RAW:文字列は単純動的文字列(SDS)の形式で保存されます。redisObjectオブジェクトヘッダーとSDSオブジェクトは、通常、メモリアドレス内の2つの不連続なメモリです。
    ここに写真の説明を挿入

2.データストレージソースコード分析

2.1データストレージプロセス

  1. では-Redisサーバ側(1)、ノートやコマンド実行を読み取るためのRedis 6.0のソースコードを開始し、我々はすでにの文字列保存するクライアントを知っているsetコマンドがにと呼ばれるt_string.c#setCommand()次のことを達成するための機能、そのソースコード:

    このメソッドでは、次の2つの主要な関数が呼び出されます。このセクションでは、主にtryObjectEncoding()関数に焦点を当てます。

    1. tryObjectEncoding()メモリを節約するためにクライアントから送信された保存が必要な文字列オブジェクトをエンコードしようとします
    2. setGenericCommand()はキー値をデータベースに保存します
    void setCommand(client *c) {
          
          
    
     ......
    
     c->argv[2] = tryObjectEncoding(c->argv[2]);
     setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
    }
    
  2. object.c#tryObjectEncoding() 機能ロジックは非常に明確で、次の操作が主に実行されていることがわかります。

    1. 文字列の長さが20未満で、長いタイプのデータとして解決される可能性がある場合、データは整数形式で保存され、robj->ptr = (void*) valueこの形式で直接割り当てストレージになります。
    2. 文字列の長さがOBJ_ENCODING_EMBSTR_SIZE_LIMIT構成以下であり、それがまだrawエンコーディングである場合は、createEmbeddedStringObject()関数を呼び出してembstrエンコーディングに変換します。
    3. この文字列オブジェクトはトランスコーディングできなくなったため、trimStringObjectIfNeeded()関数を呼び出して、文字列オブジェクトからすべての空き領域を削除する必要があります。
    robj *tryObjectEncoding(robj *o) {
          
          
     long value;
     sds s = o->ptr;
     size_t len;
    
     /* Make sure this is a string object, the only type we encode
      * in this function. Other types use encoded memory efficient
      * representations but are handled by the commands implementing
      * the type. */
     serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
    
     /* We try some specialized encoding only for objects that are
      * RAW or EMBSTR encoded, in other words objects that are still
      * in represented by an actually array of chars. */
     if (!sdsEncodedObject(o)) return o;
    
     /* It's not safe to encode shared objects: shared objects can be shared
      * everywhere in the "object space" of Redis and may end in places where
      * they are not handled. We handle them only as values in the keyspace. */
      if (o->refcount > 1) return o;
    
     /* Check if we can represent this string as a long integer.
      * Note that we are sure that a string larger than 20 chars is not
      * representable as a 32 nor 64 bit integer. */
     len = sdslen(s);
     if (len <= 20 && string2l(s,len,&value)) {
          
          
         /* This object is encodable as a long. Try to use a shared object.
          * Note that we avoid using shared integers when maxmemory is used
          * because every object needs to have a private LRU field for the LRU
          * algorithm to work well. */
         if ((server.maxmemory == 0 ||
             !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
             value >= 0 &&
             value < OBJ_SHARED_INTEGERS)
         {
          
          
             decrRefCount(o);
             incrRefCount(shared.integers[value]);
             return shared.integers[value];
         } else {
          
          
             if (o->encoding == OBJ_ENCODING_RAW) {
          
          
                 sdsfree(o->ptr);
                 o->encoding = OBJ_ENCODING_INT;
                 o->ptr = (void*) value;
                 return o;
             } else if (o->encoding == OBJ_ENCODING_EMBSTR) {
          
          
                 decrRefCount(o);
                 return createStringObjectFromLongLongForValue(value);
             }
         }
     }
    
     /* If the string is small and is still RAW encoded,
      * try the EMBSTR encoding which is more efficient.
      * In this representation the object and the SDS string are allocated
      * in the same chunk of memory to save space and cache misses. */
     if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
          
          
         robj *emb;
    
         if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
         emb = createEmbeddedStringObject(s,sdslen(s));
         decrRefCount(o);
         return emb;
     }
    
     /* We can't encode the object...
      *
      * Do the last try, and at least optimize the SDS string inside
      * the string object to require little space, in case there
      * is more than 10% of free space at the end of the SDS string.
      *
      * We do that only for relatively large strings as this branch
      * is only entered if the length of the string is greater than
      * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
     trimStringObjectIfNeeded(o);
    
     /* Return the original object. */
     return o;
    }
    
  3. object.c#createEmbeddedStringObject() embstrエンコーディングを実装する機能も非常にシンプルです。主な手順は次のとおりです。

    1. まず、zmalloc()関数を呼び出してメモリを適用します。ここでは、格納する文字列のメモリとredisObjectのメモリだけでなく、SDS実装構造の1つであるsdshdr8のメモリも適用されていることがわかります。これが、上記のembstrエンコーディングがメモリに1回だけ適用され、redisObjectオブジェクトヘッダーがSDSオブジェクトとともに存在し続ける理由です。
    2. redisObjectオブジェクトのptrポインターを、sdshdr8から始まるメモリアドレスにポイントします。
    3. len文字列の長さ、alloc文字配列の容量など各属性sdshdr8オブジェクトを埋めると、実際には文字列buf文字配列が格納されます。
    robj *createEmbeddedStringObject(const char *ptr, size_t len) {
          
          
     robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
     struct sdshdr8 *sh = (void*)(o+1);
    
     o->type = OBJ_STRING;
     o->encoding = OBJ_ENCODING_EMBSTR;
     o->ptr = sh+1;
     o->refcount = 1;
     if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
          
          
         o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
     } else {
          
          
         o->lru = LRU_CLOCK();
     }
    
     sh->len = len;
     sh->alloc = len;
     sh->flags = SDS_TYPE_8;
     if (ptr == SDS_NOINIT)
         sh->buf[len] = '\0';
     else if (ptr) {
          
          
         memcpy(sh->buf,ptr,len);
         sh->buf[len] = '\0';
     } else {
          
          
         memset(sh->buf,0,len+1);
     }
     return o;
    }
    
  4. 生のコード化された文字列の作成はobject.c#createRawStringObject()、2つのメモリアプリケーションに関連する関数を参照できます。これはsds.c#sdsnewlen()object.c#createObject()redisObjectで作成されたメモリオブジェクトに対して、オブジェクトSDSを作成するためのアプリケーションメモリです。

    robj *createRawStringObject(const char *ptr, size_t len) {
          
          
     return createObject(OBJ_STRING, sdsnewlen(ptr,len));
    }
    
  5. 容量の大きさを検出する機能からt_string.c#checkStringLength()文字列の最大長は512Mです、この値を超えるとエラーが報告されます

    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;
    }
    

2.2単純な動的文字列SDS

2.2.1SDS構造

SDS(简单动态字符串) Redisでは、文字列を保存するためのツールです。本質的には文字配列ですが、C言語の文字列のように「\ 0」で終わるわけではありません。

従来のC文字列はASCIIエンコーディングに準拠しています。このエンコーディング操作の特徴は、ゼロで停止することです。つまり、文字列を読み取るとき、「\ 0」に遭遇する限り、それは最後に到達したと見なされ、「\ 0」以降のすべての文字は無視されます。さらに、文字列の長さを取得する方法は、文字列をトラバースしてゼロで停止することです。時間の複雑さはO(n)であり、比較的非効率的です。

実装構造はsds.h、次のように定義されているSDSで定義されています。SDSは、ヘッダーのlen属性に基づいて文字列の最後に到達したかどうかを判断するため、文字列の長さを効率的に計算し、データをすばやく追加できます。

sds構造には5つのヘッダー定義があります。目的は、メモリを節約するために、さまざまな長さの文字列にさまざまなサイズのヘッダーを提供することです。sdshdr8を例にとると、そのlen属性はuint8_tタイプであり、メモリサイズは1バイトであるため、格納される文字列の最大長は256です。ヘッダーには主に次の属性が含まれています。

  1. len:ヌルターミネータを除く、文字列の実際の長さ
  2. alloc:最大容量であるヘッダーとターミネーターを除いたbufアレイの長さ
  3. flags:ロゴヘッダーの種類
  4. buf:文字配列、実際に文字を格納します
/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    
    
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    
    
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    
    
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    
    
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    
    
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

2.2.2SDS容量の調整
  1. SDS拡張の機能はsds.c#sdsMakeRoomFor()ストリングの長さが1M未満の場合、拡張は既存のスペースを2倍にします。1Mを超える場合、拡張は一度に1Mだけ多くのスペースを拡張します。以下は、ソースコードの実装です。

    文字列の長さがSDS_MAX_PREALLOC(1024 * 1024、つまりsds.hで定義されている1MB)未満になる前に、容量が2倍に拡張されます。つまり、100%の冗長スペースが予約されます。長さがSDS_MAX_PREALLOCを超える場合、各拡張はSDS_MAX_PREALLOCのサイズのより多くの冗長スペースのみを割り当て、拡張を2倍にして無駄を引き起こした後の過剰な冗長スペースを回避します。

    sds 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;
    
     /* Return ASAP if there is enough space left. */
     if (avail >= addlen) return s;
    
     len = sdslen(s);
     sh = (char*)s-sdsHdrSize(oldtype);
     newlen = (len+addlen);
     if (newlen < SDS_MAX_PREALLOC)
         newlen *= 2;
     else
         newlen += SDS_MAX_PREALLOC;
    
     type = sdsReqType(newlen);
    
     /* Don't use type 5: the user is appending to the string and type 5 is
      * not able to remember empty space, so sdsMakeRoomFor() must be called
      * at every appending operation. */
     if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    
     hdrlen = sdsHdrSize(type);
     if (oldtype==type) {
          
          
         newsh = s_realloc(sh, hdrlen+newlen+1);
         if (newsh == NULL) return NULL;
         s = (char*)newsh+hdrlen;
     } else {
          
          
         /* Since the header size changes, need to move the string forward,
          * and can't use realloc */
         newsh = s_malloc(hdrlen+newlen+1);
         if (newsh == NULL) return NULL;
         memcpy((char*)newsh+hdrlen, s, len+1);
         s_free(sh);
         s = (char*)newsh+hdrlen;
         s[-1] = type;
         sdssetlen(s, len);
     }
     sdssetalloc(s, newlen);
     return s;
    }
    
  2. SDSスケーリング関数はsds.c#sdsclear()、ソースコードの実装から、主に次の操作があります。ある種の怠惰な戦略を反映して、占有されている実際のメモリを解放しません。

    1. SDSヘッダーのlen属性値を0にリセットします
    2. ターミネータをbuf配列の先頭に配置することは、bufのコンテンツを遅延削除することと同じです。
    /* Modify an sds string in-place to make it empty (zero length).
    * However all the existing buffer is not discarded but set as free space
    * so that next append operations will not require allocations up to the
    * number of bytes previously available. */
    void sdsclear(sds s) {
          
          
     sdssetlen(s, 0);
     s[0] = '\0';
    }
    

おすすめ

転載: blog.csdn.net/weixin_45505313/article/details/108292168