小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Redis源碼學(xué)習(xí):字符串

 WindySky 2016-02-18

Redis源碼學(xué)習(xí):字符串

1.初識(shí)SDS

1.1 SDS定義

Redis定義了一個(gè)叫做sdshdr(SDS or simple dynamic string)的數(shù)據(jù)結(jié)構(gòu)。SDS不僅用于 保存字符串,還用來(lái)當(dāng)做緩沖區(qū),例如AOF緩沖區(qū)或輸入緩沖區(qū)等。如下所示,整數(shù)len和free分別表示buf數(shù)組中已使用的長(zhǎng)度和剩余可用的長(zhǎng)度,buf是一個(gè)原生C字符串,以\0結(jié)尾。

sds就是sdshdr中char buf[]的別名,后面能看到,各種操作函數(shù)的入?yún)⒑头祷刂刀际莝ds而非sdshdr。那sdshdr中的len和free如何訪問(wèn)到?不然不就白定義了嗎?答案就是靈活的指針,各種SDS的函數(shù)都是:struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr))),通過(guò)指針偏移計(jì)算出sdshdr的首地址。

關(guān)于字符串末尾的“\0”始終要注意:len和free中都沒把“\0”算進(jìn)去,例如為字符串”hello”創(chuàng)建sds,最終buf總長(zhǎng)度是6,len為5,free為0。

/* 類型別名,用于指向 sdshdr 的 buf 屬性 */
typedef char *sds;

/* 保存字符串對(duì)象的結(jié)構(gòu) */
struct sdshdr {

    // buf 中已占用空間的長(zhǎng)度
    int len;

    // buf 中剩余可用空間的長(zhǎng)度
    int free;

    // 數(shù)據(jù)空間
    char buf[];
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

1.2 SDS操作

有了SDS就可以統(tǒng)一接口,所有字符串函數(shù)的操作對(duì)象都是SDS而非原生的C字符串。下面分析一些典型的字符串操作,看看SDS是如何實(shí)現(xiàn)的。代碼注釋來(lái)自于huangz所著Redis 3.0詳細(xì)注釋版

1.2.1 新建SDS

Redis內(nèi)部使用sdsnew()函數(shù),并在其基礎(chǔ)上提供了sdsnew()和sdsempty()兩個(gè)函數(shù)。在sdsnew()中,若傳入字符串init若為NULL,則創(chuàng)建一個(gè)buf只有1個(gè)字節(jié)的空SDS,否則buf長(zhǎng)度是字符串長(zhǎng)度加1。len置為字符串長(zhǎng)度initlen,free置為0。注意最終返回buf部分,而不是整個(gè)sdshdr,這在前面介紹過(guò)的sdshdr.h中有定義:typedef char *sds。

/* 根據(jù)給定字符串 init ,創(chuàng)建一個(gè)包含同樣字符串的 sds */
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

/* 創(chuàng)建并返回一個(gè)只保存了空字符串 "" 的 sds */
sds sdsempty(void) {
    return sdsnewlen("", 0);
}

/* 根據(jù)給定的初始化字符串 init 和字符串長(zhǎng)度 initlen 創(chuàng)建一個(gè)新的 sds */
sds sdsnewlen(const void *init, size_t initlen) {

    struct sdshdr *sh;

    // 根據(jù)是否有初始化內(nèi)容,選擇適當(dāng)?shù)膬?nèi)存分配方式
    // T = O(N)
    if (init) {
        // zmalloc 不初始化所分配的內(nèi)存
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        // zcalloc 將分配的內(nèi)存全部初始化為 0
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }

    // 內(nèi)存分配失敗,返回
    if (sh == NULL) return NULL;

    // 設(shè)置初始化長(zhǎng)度
    sh->len = initlen;
    // 新 sds 不預(yù)留任何空間
    sh->free = 0;

    // 如果有指定初始化內(nèi)容,將它們復(fù)制到 sdshdr 的 buf 中
    // 以 \0 結(jié)尾,T = O(N)
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';

    // 返回 buf 部分,而不是整個(gè) sdshdr
    return (char*)sh->buf;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

1.2.2 擴(kuò)展SDS空間

Redis需要的是一個(gè)可以修改的字符串值,所以SDS是可變的。當(dāng)對(duì)SDS中的字符串修改時(shí),長(zhǎng)度難免會(huì)長(zhǎng)了或者短了,這時(shí)就要根據(jù)需要對(duì)SDS進(jìn)行擴(kuò)展。Redis的算法比較簡(jiǎn)單,甚至與Java中ArrayList有些相像。空間不足時(shí)重新分配一塊大小翻倍的內(nèi)存,縮小時(shí)則維持不變。有一點(diǎn)區(qū)別是:在Redis中,如果要擴(kuò)展的長(zhǎng)度超過(guò)1MB,那么只會(huì)額外分配1MB而不會(huì)翻倍。比如修改一個(gè)字符串到10MB了,那最終SDS占用空間只是 10MB + 1MB + 1B(“\0”)。

/* 最大預(yù)分配長(zhǎng)度 */
#define SDS_MAX_PREALLOC (1024*1024)

/* 對(duì) sds 中 buf 的長(zhǎng)度進(jìn)行擴(kuò)展,確保在函數(shù)執(zhí)行之后,
    buf 至少會(huì)有 addlen + 1 長(zhǎng)度的空余空間(額外的 1 字節(jié)是為 \0 準(zhǔn)備的)*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;

    // 獲取 s 目前的空余空間長(zhǎng)度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // s 目前的空余空間已經(jīng)足夠,無(wú)須再進(jìn)行擴(kuò)展,直接返回
    if (free >= addlen) return s;

    // 獲取 s 目前已占用空間的長(zhǎng)度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的長(zhǎng)度
    newlen = (len + addlen);

    // 根據(jù)新長(zhǎng)度,為 s 分配新空間所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新長(zhǎng)度小于 SDS_MAX_PREALLOC,那么為它分配兩倍于所需長(zhǎng)度的空間
        newlen *= 2;
    else
        // 否則,分配長(zhǎng)度為目前長(zhǎng)度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr) + newlen + 1);

    // 內(nèi)存不足,分配失敗,返回
    if (newsh == NULL) return NULL;

    // 更新 sds 的空余長(zhǎng)度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}

/* 返回 sds 實(shí)際保存的字符串的長(zhǎng)度,T = O(1) */
static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
    return sh->len;
}

/* 返回 sds 可用空間的長(zhǎng)度,T = O(1) */
static inline size_t sdsavail(const sds s) {
    struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
    return sh->free;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

1.2.3 收縮SDS空間

類似ArrayList的shrink方法,SDS也能收縮,重新分配一塊僅僅能容納字符串的空間。

/* 回收 sds 中的空閑空間,回收不會(huì)對(duì) sds 中保存的字符串內(nèi)容做任何修改 */
sds sdsRemoveFreeSpace(sds s) {
    struct sdshdr *sh;

    sh = (void*) (s-(sizeof(struct sdshdr)));

    // 進(jìn)行內(nèi)存重分配,讓 buf 的長(zhǎng)度僅僅足夠保存字符串內(nèi)容,T = O(N)
    sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);

    // 空余空間為 0
    sh->free = 0;

    return sh->buf;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

1.2.4 字符串操作

SDS也有類似原生C字符串的拼接、拷貝等函數(shù)。這里就不一一列舉了。

2.SDS設(shè)計(jì)總結(jié)

2.1 O(1)取字符串長(zhǎng)度

C標(biāo)準(zhǔn)庫(kù)的strlen函數(shù)的時(shí)間復(fù)雜度是O(n),因?yàn)樗闅v到字符串末尾的“\0”才會(huì)停止計(jì)數(shù),將當(dāng)前計(jì)數(shù)器的值作為字符串的長(zhǎng)度返回。這在要求高性能的Redis中是無(wú)法容忍的。手頭正好有g(shù)libc-2.15的源碼,打開string文件夾下的strlen.c,發(fā)現(xiàn)前面的說(shuō)法并不準(zhǔn)確,標(biāo)準(zhǔn)庫(kù)還是盡可能做了優(yōu)化,總的思想就是利用數(shù)據(jù)對(duì)齊做快速的“字”查找:

  1. C標(biāo)準(zhǔn)庫(kù)要求有很好的移植性,在絕大部分系統(tǒng)體系結(jié)構(gòu)下都應(yīng)該能正確運(yùn)行。那么每次拿出4個(gè)字節(jié)比較(unsigned long int),就需要考慮內(nèi)存對(duì)齊問(wèn)題,傳入的字符串的首字符地址可不一定在4對(duì)齊的地址上。如果在內(nèi)存對(duì)齊之前就遇到了’\0’ 則直接return推出。否則到下一步;
  2. 一次讀入并判斷一個(gè)4字節(jié)(sizeof(unsigned long int))大小的內(nèi)存塊,如果4字節(jié)中沒有為0的字節(jié),則繼續(xù)下一個(gè)4字節(jié),否則到下一步;
  3. 這里4字節(jié)中至少有一個(gè)字節(jié)為0,最后要做的就是找出這個(gè)字節(jié)的位置并return退出。
/* Return the length of the null-terminated string STR.  Scan for
   the null terminator quickly by testing four bytes at a time.  */
size_t
strlen (str)
     const char *str;
{
  const char *char_ptr;
  const unsigned long int *longword_ptr;
  unsigned long int longword, himagic, lomagic;

  /* Handle the first few characters by reading one character at a time.
     Do this until CHAR_PTR is aligned on a longword boundary.  */
  for (char_ptr = str; 
        ((unsigned long int) char_ptr & (sizeof (longword) - 1)) != 0;
            ++char_ptr)
    if (*char_ptr == '\0')
      return char_ptr - str;

  /* All these elucidatory comments refer to 4-byte longwords,
     but the theory applies equally well to 8-byte longwords.  */

  longword_ptr = (unsigned long int *) char_ptr;

  /* Bits 31, 24, 16, and 8 of this number are zero.  Call these bits
     the "holes."  Note that there is a hole just to the left of
     each byte, with an extra at the end:

     bits:  01111110 11111110 11111110 11111111
     bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD

     The 1-bits make sure that carries propagate to the next 0-bit.
     The 0-bits provide holes for carries to fall into.  */
  himagic = 0x80808080L;
  lomagic = 0x01010101L;
  if (sizeof (longword) > 4)
  {
      /* 64-bit version of the magic.  */
      /* Do the shift in two steps to avoid a warning if long has 32 bits.  */
      himagic = ((himagic << 16) << 16) | himagic;
      lomagic = ((lomagic << 16) << 16) | lomagic;
  }
  if (sizeof (longword) > 8)
    abort ();

  /* Instead of the traditional loop which tests each character,
     we will test a longword at a time.  The tricky part is testing
     if *any of the four* bytes in the longword in question are zero.  */
  for (;;)
  {
      longword = *longword_ptr++;

      if (((longword - lomagic) & ~longword & himagic) != 0)
      {
      /* Which of the bytes was the zero?  If none of them were, it was
         a misfire; continue the search.  */

          const char *cp = (const char *) (longword_ptr - 1);

          if (cp[0] == 0)
            return cp - str;
          if (cp[1] == 0)
            return cp - str + 1;
          if (cp[2] == 0)
            return cp - str + 2;
          if (cp[3] == 0)
            return cp - str + 3;
          if (sizeof (longword) > 4)
          {
              if (cp[4] == 0)
            return cp - str + 4;
              if (cp[5] == 0)
            return cp - str + 5;
              if (cp[6] == 0)
            return cp - str + 6;
              if (cp[7] == 0)
            return cp - str + 7;
          }
      }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

2.2 重用C標(biāo)準(zhǔn)庫(kù)函數(shù)

SDS中的buf并沒有“標(biāo)新立異”,而是老老實(shí)實(shí)地按照原生C字符串的要求保存,末尾放置“\0”。這樣做最大的好處就是標(biāo)準(zhǔn)庫(kù)中許許多多好用的字符串操作函數(shù)又都可以重用了,避免這繁瑣的工作(就算Redis作者有代碼“潔癖”,也不會(huì)到標(biāo)準(zhǔn)庫(kù)都不用的程度吧…)。

2.3 避免緩沖區(qū)溢出

緩沖區(qū)溢出問(wèn)題可是個(gè)安全界的老問(wèn)題了。因?yàn)镃字符串不記錄自身長(zhǎng)度,所以strcat()一旦拷貝過(guò)多的字節(jié)就會(huì)導(dǎo)致原始字符串空間被覆蓋??紤]下面的例子,字符串s1為”Redis”,字符串s2為”MongoDB”,拼接”hello”到s1末尾時(shí)會(huì)發(fā)生什么?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char s1[] = "Redis";
char s2[] = "MongoDB";

int main(int argc, char const *argv[])
{
    int s2len, i;

    // Save original length of s2
    s2len = strlen(s2);

    // Cancat to s1
    strcat(s1, "hello");

    // Print s1 and s2
    printf("s1 = [%s]\n", s1);  
    printf("s2 = [");
    for (i = 0; i < s2len; ++i)
        printf("%c", s2[i]);
    printf("]\n");

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

因?yàn)閟1和s2編譯鏈接后是相鄰的(保存在.data段),所以拼接”hello”到s1末尾時(shí)會(huì)導(dǎo)致s1溢出,覆蓋了s2的部分內(nèi)容。最終打印出的s1和s2就是下面這個(gè)樣子:

[root@vm Temp]# gcc -o bufoverflow bufoverflow.c 
[root@vm Temp]# ./bufoverflow 
s1 = [Redishello]
s2 = [elloDB]
  • 1
  • 2
  • 3
  • 4

這里順帶復(fù)習(xí)一個(gè)關(guān)于字符串常量和數(shù)組的基礎(chǔ)知識(shí)。當(dāng)我們將s1從char s1[]改為char *s1再編譯運(yùn)行時(shí),就會(huì)報(bào)錯(cuò)異常終止,這又是為什么?對(duì)比bufoverflow-old(s1是數(shù)組)和bufoverflow(s1是指針),通過(guò)objdump查看.data段,發(fā)現(xiàn)char *的s1并沒有保存在.data里,而是放在了.rodata中,因?yàn)閏har *是字符串常量不允許修改。所以 修改只讀段.rodata導(dǎo)致了段錯(cuò)誤。但是當(dāng)我們對(duì)可讀寫區(qū)域,如各種buf數(shù)據(jù),“搞破壞”時(shí)錯(cuò)誤就悄悄發(fā)生了:

[root@vm Temp]# ./buffoverflow
Segmentation fault (core dumped)

[root@vm Temp]# objdump -j .data -s bufoverflow-old
Contents of section .data:
 600a40 00000000 52656469 73004d6f 6e676f44  ....Redis.MongoD
 600a50 42000000                             B...            

[root@vm Temp]# objdump -j .data -s bufoverflow
Contents of section .data:
 600a58 00000000 00000000 88074000 00000000  ..........@.....
 600a68 4d6f6e67 6f444200                    MongoDB.        

[root@BC-VM-edce4ac67d304079868c0bb265337bd4 Temp]# objdump -s bufoverflow
    ...
Contents of section .rodata:
 400778 01000200 00000000 00000000 00000000  ................
 400788 52656469 73006865 6c6c6f00 7331203d  Redis.hello.s1 =
 400798 205b2573 5d0a0073 32203d20 5b005d00   [%s]..s2 = [.].
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.4 減少內(nèi)存申請(qǐng)和釋放次數(shù)

前面在[1.2.2 擴(kuò)展SDS空間](###1.2.2 擴(kuò)展SDS空間)中分析過(guò),新建的SDS會(huì)分配能恰好容納字符串的空間。但后續(xù)對(duì)字符串修改導(dǎo)致SDS擴(kuò)容時(shí),Redis會(huì)為其留出一部分的預(yù)留空間。這樣就避免了頻繁調(diào)用zmalloc()和zfree()。

2.5 保證二進(jìn)制安全

C字符串因?yàn)橐阅┪驳摹癨0”空字符作為標(biāo)記,所以C字符串只能保存文本數(shù)據(jù)。若用來(lái)保存圖片、音視頻、壓縮文件等二進(jìn)制數(shù)據(jù)的話,若其中含有哪怕一個(gè)空字符,就會(huì)導(dǎo)致錯(cuò)誤。盡管Redis重用C標(biāo)準(zhǔn)庫(kù)中字符串的基本操作函數(shù),但為了不限制自身的應(yīng)用場(chǎng)景,Redis保證SDS中的字符串是二進(jìn)制安全的,即可以保存各種二進(jìn)制數(shù)據(jù)的。

SDS中已經(jīng)用單獨(dú)的變量len保存了字符串的實(shí)際長(zhǎng)度,所以解決方法就很簡(jiǎn)單了:所有SDS的API都使用len屬性的值而不是“\0”來(lái)判斷字符串是否結(jié)束。也就是說(shuō)不管SDS的buf中保存的是不是普通文本數(shù)據(jù),Redis都會(huì)假設(shè)它是二進(jìn)制數(shù)據(jù),以最安全的方式處理它們。同時(shí),當(dāng)SDS保存的確實(shí)是文本數(shù)據(jù)時(shí),Redis還能享受到C標(biāo)準(zhǔn)庫(kù)帶來(lái)的便捷。

3.SDS應(yīng)用

3.1 AOF緩沖區(qū)

redisServer結(jié)構(gòu)中保存了當(dāng)前Redis實(shí)例的所有全局狀態(tài),其中一項(xiàng)aof_buf就是用作AOF緩沖區(qū)的SDS。

struct redisServer {

    /* General */
    ...
    // 數(shù)據(jù)庫(kù)
    redisDb *db;
    // 事件狀態(tài)
    aeEventLoop *el;
    ...

    /* Networking */
    ...
    /* Fields used only for stats */
    ...
    /* slowlog */
    ...
    /* Configuration */
    ...

    /* AOF persistence */
    ...
    // AOF 緩沖區(qū)
    sds aof_buf;      /* AOF buffer, written before entering the event loop */
    ...    

    /* RDB persistence */
    ...
    /* Logging */
    ...
    /* Replication (master) */
    ...
    /* Replication (slave) */
    ...
    /* Replication script cache. */
    ...
    /* Pubsub */
    ...
    /* Cluster */
    ...
    /* Scripting */
    ...
    /* Assert & bug reporting */
    ...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

3.2 客戶端輸入緩沖區(qū)

在表示客戶端連接的redisClient結(jié)構(gòu)中,有一個(gè)叫做queryBuf的SDS屬性,保存的就是來(lái)自遠(yuǎn)程客戶端的命令請(qǐng)求。因?yàn)橐粋€(gè)redisClient反復(fù)使用這一個(gè)緩沖區(qū),所以SDS會(huì)根據(jù)每次請(qǐng)求的內(nèi)容動(dòng)態(tài)地縮小或擴(kuò)大。

/* 因?yàn)?I/O 復(fù)用的緣故,需要為每個(gè)客戶端維持一個(gè)狀態(tài)。
    多個(gè)客戶端狀態(tài)被服務(wù)器用鏈表連接起來(lái)。*/
typedef struct redisClient {

    // 套接字描述符
    int fd;

    // 當(dāng)前正在使用的數(shù)據(jù)庫(kù)
    redisDb *db;

    // 當(dāng)前正在使用的數(shù)據(jù)庫(kù)的 id (號(hào)碼)
    int dictid;

    // 客戶端的名字
    robj *name;             /* As set by CLIENT SETNAME */

    // 查詢緩沖區(qū)
    sds querybuf;

    ...
} redisClient;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

下面簡(jiǎn)單分析一下Redis是如何使用查詢緩沖區(qū)SDS的。首先,main()函數(shù)中調(diào)用initServer()初始化epoll創(chuàng)建連接的事件函數(shù)(都在redis.c中)acceptTcpHandler()負(fù)責(zé)接收TCP請(qǐng)求,它調(diào)用acceptCommonHandler()->createClient()創(chuàng)建出redisClient(在networking.c中)。因?yàn)椴捎玫氖钱惒降膃poll機(jī)制,所以此時(shí),queryBuf屬性只是初始化為sdsempty(),同時(shí)綁定讀事件處理函數(shù)readQueryFromClient(),這才是真正解析請(qǐng)求的地方!

/* 讀取客戶端的查詢緩沖區(qū)內(nèi)容 */
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    redisClient *c = (redisClient*) privdata;
    int nread, readlen;
    size_t qblen;
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(mask);

    // 設(shè)置服務(wù)器的當(dāng)前客戶端
    server.current_client = c;

    // 讀入長(zhǎng)度(默認(rèn)為 16 MB)
    readlen = REDIS_IOBUF_LEN;

    /* If this is a multi bulk request, and we are processing a bulk reply
     * that is large enough, try to maximize the probability that the query
     * buffer contains exactly the SDS string representing the object, even
     * at the risk of requiring more read(2) calls. This way the function
     * processMultiBulkBuffer() can avoid copying buffers to create the
     * Redis Object representing the argument. */
    if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1
        && c->bulklen >= REDIS_MBULK_BIG_ARG)
    {
        int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf);

        if (remaining < readlen) readlen = remaining;
    }

    // 獲取查詢緩沖區(qū)當(dāng)前內(nèi)容的長(zhǎng)度
    // 如果讀取出現(xiàn) short read ,那么可能會(huì)有內(nèi)容滯留在讀取緩沖區(qū)里面
    // 這些滯留內(nèi)容也許不能完整構(gòu)成一個(gè)符合協(xié)議的命令,
    qblen = sdslen(c->querybuf);
    // 如果有需要,更新緩沖區(qū)內(nèi)容長(zhǎng)度的峰值(peak)
    if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
    // 為查詢緩沖區(qū)分配空間
    c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
    // 讀入內(nèi)容到查詢緩存
    nread = read(fd, c->querybuf+qblen, readlen);

    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多