天天看點

Redis(五):hash/hset/hget 指令源碼解析

  Redis作為nosql資料庫,kv string型資料的支援是最基礎的,但是如果僅有kv的操作,也不至于有redis的成功。(memcache就是個例子)

  Redis除了string, 還有hash,list,set,zset。

  是以,我們就來看看hash的相關操作實作吧。

  首先,我們從作用上了解hash存在的意義:Redis hash 是一個 string 類型的 field 和 value 的映射表,hash 特别适合用于存儲對象。從另一個方面來說是,hash可以聚合很多類似的屬性,這是string中難以實作的。

是以,總體來說,hash的指令與string的指令差不太多。其操作手冊如下:

1> hdel 指令:删除一個或多個哈希表字段

格式:HDEL key field2 [field2]

傳回值:被成功删除字段的數量,不包括被忽略的字段。

2> hexists 指令:檢視哈希表 key 中,指定的字段是否存在

格式:HEXISTS key field

傳回值:如果哈希表含有給定字段,傳回 1 。 如果哈希表不含有給定字段,或 key 不存在,傳回 0 。

3> hget 指令:擷取存儲在哈希表中指定字段的值

格式:HGET key field

傳回值:傳回給定字段的值。如果給定的字段或 key 不存在時,傳回 nil 。

4> hgetall 指令:擷取在哈希表中指定 key 的所有字段和值

格式:HGETALL key

傳回值:以清單形式傳回哈希表的字段及字段值。 若 key 不存在,傳回空清單。

5> hincrby 指令:為哈希表 key 中的指定字段的整數值加上增量 increment

格式:HINCRBY key field increment

傳回值:執行 HINCRBY 指令之後,哈希表中字段的值。

6> hincrbyfloat 指令:為哈希表 key 中的指定字段的浮點數值加上增量 increment

格式:HINCRBYFLOAT key field increment

傳回值:執行 Hincrbyfloat 指令之後,哈希表中字段的值。

7> hkeys 指令:擷取所有哈希表中的字段

格式:HKEYS key

傳回值:包含哈希表中所有字段的清單。 當 key 不存在時,傳回一個空清單。

8> hlen 指令:擷取哈希表中字段的數量

格式:HLEN key

傳回值:哈希表中字段的數量。 當 key 不存在時,傳回 0 。

9> hmget 指令:擷取所有給定字段的值

格式:HMGET key field1 [field2]

傳回值:一個包含多個給定字段關聯值的表,表值的排列順序和指定字段的請求順序一樣。

10> hmset 指令:同時将多個 field-value (域-值)對設定到哈希表 key 中

格式:HMSET key field1 value1 [field2 value2 ]

傳回值:如果指令執行成功,傳回 OK 。

11> hset 指令:将哈希表 key 中的字段 field 的值設為 value

格式:HSET key field value

傳回值:如果字段是哈希表中的一個建立字段,并且值設定成功,傳回 1 。 如果哈希表中域字段已經存在且舊值已被新值覆寫,傳回 0 。

12> hsetnx 指令:隻有在字段 field 不存在時,設定哈希表字段的值

格式:HSETNX key field value

傳回值:設定成功,傳回 1 。 如果給定字段已經存在且沒有操作被執行,傳回 0 。

13> hvals 指令:擷取哈希表中所有值

格式:HVALS key

傳回值:一個包含哈希表中所有值的表。 當 key 不存在時,傳回一個空表。

14> hscan 指令:疊代哈希表中的鍵值對

格式:HSCAN key cursor [MATCH pattern] [COUNT count]

  其中,有的是單kv操作有的是指量操作,有的是寫操作有的是讀操作。從實作上看,大體上很多指令是類似的:

  比如: hset/hmset/hincrbyXXX 可以是一類的

  比如:hget/hgetall/hexists/hkeys/hmget 可以是一類

  注意:以上分法僅是為了讓我們看清本質,對實際使用并無實際參考意義。

是以,我們就挑幾個方法來解析下 hash 的操作實作吧。

零、hash資料結構

  hash相關的指令定義如下:

{"hset",hsetCommand,4,"wmF",0,NULL,1,1,1,0,0},
    {"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},
    {"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0},
    {"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},
    {"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},
    {"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
    {"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0},
    {"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
    {"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0},
    {"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0},
    {"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
    {"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
    {"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
    {"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0},
    {"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},      

  ziplist 資料結構

typedef struct zlentry {
    unsigned int prevrawlensize, prevrawlen;
    unsigned int lensize, len;
    unsigned int headersize;
    unsigned char encoding;
    unsigned char *p;
} zlentry;
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
#define ZIPLIST_END_SIZE        (sizeof(uint8_t))
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
          

  hashtable 資料結構:

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;
typedef struct dictEntry {
    void *key;
    void *val;
    struct dictEntry *next;
} dictEntry;      

一、hset 設定單個 field -> value

  “增删改查”中的“增改” 就是它了。

// t_hash.c, set key field value
void hsetCommand(client *c) {
    int update;
    robj *o;
    // 1. 查找hash的key是否存在,不存在則建立一個,然後在其上進行資料操作
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
    // 2. 檢查2-3個參數是否需要将簡單版(ziplist)hash表轉換為複雜的hash表,轉換後的表通過 o->ptr 展現
    hashTypeTryConversion(o,c->argv,2,3);
    // 3. 添加kv到 o 的hash表中
    update = hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);
    addReply(c, update ? shared.czero : shared.cone);
    // 變更指令傳播
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
    server.dirty++;
}

// 1. 擷取db外部的key, 即整體hash資料執行個體
// t_hash.c
robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
    robj *o = lookupKeyWrite(c->db,key);
    if (o == NULL) {
        // 此處建立的hashObject是以 ziplist 形式的
        o = createHashObject();
        dbAdd(c->db,key,o);
    } else {
        // 不是hash類型的鍵已存在,不可覆寫,傳回錯誤
        if (o->type != OBJ_HASH) {
            addReply(c,shared.wrongtypeerr);
            return NULL;
        }
    }
    return o;
}
// object.c, 建立hashObject, 以 ziplist 形式建立
robj *createHashObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_HASH, zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}
// ziplist.c
static unsigned char *createList() {
    unsigned char *zl = ziplistNew();
    zl = ziplistPush(zl, (unsigned char*)"foo", 3, ZIPLIST_TAIL);
    zl = ziplistPush(zl, (unsigned char*)"quux", 4, ZIPLIST_TAIL);
    zl = ziplistPush(zl, (unsigned char*)"hello", 5, ZIPLIST_HEAD);
    zl = ziplistPush(zl, (unsigned char*)"1024", 4, ZIPLIST_TAIL);
    return zl;
}

// 2. 檢查參數,是否需要将 ziplist 形式的hash表轉換為真正的hash表
/* Check the length of a number of objects to see if we need to convert a
 * ziplist to a real hash. Note that we only check string encoded objects
 * as their string length can be queried in constant time. */
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
    int i;

    if (o->encoding != OBJ_ENCODING_ZIPLIST) return;

    for (i = start; i <= end; i++) {
        // 參數大于設定的 hash_max_ziplist_value (預設: 64)時,會直接将 ziplist 轉換為 ht
        // OBJ_ENCODING_RAW, OBJ_ENCODING_EMBSTR
        // 循環檢查參數,隻要發生了一次轉換就結束檢查(沒必要繼續了)
        if (sdsEncodedObject(argv[i]) &&
            sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
        {
            // 這個轉換過程很有意思,我們深入看看
            hashTypeConvert(o, OBJ_ENCODING_HT);
            break;
        }
    }
}
// t_hash.c, 轉換編碼方式 (如上, ziplist -> ht)
void hashTypeConvert(robj *o, int enc) {
    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        // 此處我們隻處理這種情況
        hashTypeConvertZiplist(o, enc);
    } else if (o->encoding == OBJ_ENCODING_HT) {
        serverPanic("Not implemented");
    } else {
        serverPanic("Unknown hash encoding");
    }
}
// t_hash.c, 轉換編碼 ziplist 為目标 enc (實際隻能是 OBJ_ENCODING_HT) 
void hashTypeConvertZiplist(robj *o, int enc) {
    serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);

    if (enc == OBJ_ENCODING_ZIPLIST) {
        /* Nothing to do... */

    } else if (enc == OBJ_ENCODING_HT) {
        hashTypeIterator *hi;
        dict *dict;
        int ret;
        // 疊代器建立
        hi = hashTypeInitIterator(o);
        // 一個hash的資料結構就是一個 dict, 從這個級别來說, hash 與 db 是一個級别的
        dict = dictCreate(&hashDictType, NULL);
        // 依次疊代 o, 指派到 hi->fptr, hi->vptr
        // 依次添加到 dict 中
        while (hashTypeNext(hi) != C_ERR) {
            sds key, value;
            // 從 hi->fptr 中擷取key
            // 從 hi->vptr 中擷取value
            key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
            value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
            // 添加到 dict 中
            ret = dictAdd(dict, key, value);
            if (ret != DICT_OK) {
                serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
                    o->ptr,ziplistBlobLen(o->ptr));
                serverPanic("Ziplist corruption detected");
            }
        }
        // 釋放疊代器
        hashTypeReleaseIterator(hi);
        zfree(o->ptr);
        // 将變更反映到o對象上傳回
        o->encoding = OBJ_ENCODING_HT;
        o->ptr = dict;
    } else {
        serverPanic("Unknown hash encoding");
    }
}
// 2.1. 疊代ziplist元素
// t_hash.c, 疊代器
/* Move to the next entry in the hash. Return C_OK when the next entry
 * could be found and C_ERR when the iterator reaches the end. */
int hashTypeNext(hashTypeIterator *hi) {
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl;
        unsigned char *fptr, *vptr;
        // 每次都是基于原始字元器進行計算偏移
        // 疊代的是 fptr,vptr
        zl = hi->subject->ptr;
        fptr = hi->fptr;
        vptr = hi->vptr;
        // 第一次查找時使用index查找,後續則使用 fptr,vptr 進行疊代
        if (fptr == NULL) {
            /* Initialize cursor */
            serverAssert(vptr == NULL);
            fptr = ziplistIndex(zl, 0);
        } else {
            /* Advance cursor */
            serverAssert(vptr != NULL);
            fptr = ziplistNext(zl, vptr);
        }
        if (fptr == NULL) return C_ERR;

        /* Grab pointer to the value (fptr points to the field) */
        vptr = ziplistNext(zl, fptr);
        serverAssert(vptr != NULL);

        /* fptr, vptr now point to the first or next pair */
        hi->fptr = fptr;
        hi->vptr = vptr;
    } else if (hi->encoding == OBJ_ENCODING_HT) {
        if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR;
    } else {
        serverPanic("Unknown hash encoding");
    }
    return C_OK;
}
// ziplist.c, 查找 index 的元素
/* Returns an offset to use for iterating with ziplistNext. When the given
 * index is negative, the list is traversed back to front. When the list
 * doesn't contain an element at the provided index, NULL is returned. */
unsigned char *ziplistIndex(unsigned char *zl, int index) {
    unsigned char *p;
    unsigned int prevlensize, prevlen = 0;
    if (index < 0) {
        // 小于0時,反向查找
        index = (-index)-1;
        p = ZIPLIST_ENTRY_TAIL(zl);
        if (p[0] != ZIP_END) {
            ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
            while (prevlen > 0 && index--) {
                p -= prevlen;
                ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
            }
        }
    } else {
        p = ZIPLIST_ENTRY_HEAD(zl);
        while (p[0] != ZIP_END && index--) {
            p += zipRawEntryLength(p);
        }
    }
    // 疊代完成還沒找到元素 p[0]=ZIP_END
    // index 超出整體ziplist大小則周遊完成後 index>0
    return (p[0] == ZIP_END || index > 0) ? NULL : p;
}
// ziplist.c, 由 fptr,vptr 進行疊代元素
/* Return pointer to next entry in ziplist.
 *
 * zl is the pointer to the ziplist
 * p is the pointer to the current element
 *
 * The element after 'p' is returned, otherwise NULL if we are at the end. */
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
    ((void) zl);

    /* "p" could be equal to ZIP_END, caused by ziplistDelete,
     * and we should return NULL. Otherwise, we should return NULL
     * when the *next* element is ZIP_END (there is no next entry). */
    if (p[0] == ZIP_END) {
        return NULL;
    }
    // 目前指針偏移目前元素長度(根據ziplist協定),即到下一進制素指針位置
    p += zipRawEntryLength(p);
    if (p[0] == ZIP_END) {
        return NULL;
    }

    return p;
}
/* Return the total number of bytes used by the entry pointed to by 'p'. */
static unsigned int zipRawEntryLength(unsigned char *p) {
    unsigned int prevlensize, encoding, lensize, len;
    ZIP_DECODE_PREVLENSIZE(p, prevlensize);
    ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
    return prevlensize + lensize + len;
}

// 2.2. t_hash.c, 擷取 hashTypeIterator 的具體值,寫入 vstr, vlen 中
/* Return the key or value at the current iterator position as a new
 * SDS string. */
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what) {
    unsigned char *vstr;
    unsigned int vlen;
    long long vll;

    hashTypeCurrentObject(hi,what,&vstr,&vlen,&vll);
    if (vstr) return sdsnewlen(vstr,vlen);
    return sdsfromlonglong(vll);
}
/* Higher level function of hashTypeCurrent*() that returns the hash value
 * at current iterator position.
 *
 * The returned element is returned by reference in either *vstr and *vlen if
 * it's returned in string form, or stored in *vll if it's returned as
 * a number.
 *
 * If *vll is populated *vstr is set to NULL, so the caller
 * can always check the function return by checking the return value
 * type checking if vstr == NULL. */
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) {
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
        *vstr = NULL;
        hashTypeCurrentFromZiplist(hi, what, vstr, vlen, vll);
    } else if (hi->encoding == OBJ_ENCODING_HT) {
        sds ele = hashTypeCurrentFromHashTable(hi, what);
        *vstr = (unsigned char*) ele;
        *vlen = sdslen(ele);
    } else {
        serverPanic("Unknown hash encoding");
    }
}

// t_hash.c, 從ziplist中擷取某個 hashTypeIterator 的具體值,結果定稿 vstr, vlen
/* Get the field or value at iterator cursor, for an iterator on a hash value
 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
                                unsigned char **vstr,
                                unsigned int *vlen,
                                long long *vll)
{
    int ret;

    serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST);
    // OBJ_HASH_KEY 從 fptr 中擷取, 否則從 vptr 中擷取
    if (what & OBJ_HASH_KEY) {
        ret = ziplistGet(hi->fptr, vstr, vlen, vll);
        serverAssert(ret);
    } else {
        ret = ziplistGet(hi->vptr, vstr, vlen, vll);
        serverAssert(ret);
    }
}
// ziplist.c, 
/* Get entry pointed to by 'p' and store in either '*sstr' or 'sval' depending
 * on the encoding of the entry. '*sstr' is always set to NULL to be able
 * to find out whether the string pointer or the integer value was set.
 * Return 0 if 'p' points to the end of the ziplist, 1 otherwise. */
unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval) {
    zlentry entry;
    if (p == NULL || p[0] == ZIP_END) return 0;
    if (sstr) *sstr = NULL;
    // 按照ziplist的編碼協定, 擷取頭部資訊
    zipEntry(p, &entry);
    if (ZIP_IS_STR(entry.encoding)) {
        if (sstr) {
            *slen = entry.len;
            *sstr = p+entry.headersize;
        }
    } else {
        if (sval) {
            *sval = zipLoadInteger(p+entry.headersize,entry.encoding);
        }
    }
    return 1;
}
// ziplist.c, 解析原始字元串為 zlentry
/* Return a struct with all information about an entry. */
static void zipEntry(unsigned char *p, zlentry *e) {
    // 按照ziplist的編碼協定,依次讀取 prevrawlensize, prevrawlen
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    // 指向下一位置偏移,按照ziplist的編碼協定,依次讀取 encoding, lensize, len
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    // 除去header得到 body偏移
    e->headersize = e->prevrawlensize + e->lensize;
    e->p = p;
}      

  具體header解析如下, 有興趣的點開瞅瞅:

Redis(五):hash/hset/hget 指令源碼解析
Redis(五):hash/hset/hget 指令源碼解析
// ziplist.c
/* Decode the length of the previous element, from the perspective of the entry
 * pointed to by 'ptr'. */
#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do {                     \
    // 解析第1個字元為 prevlensize
    ZIP_DECODE_PREVLENSIZE(ptr, prevlensize);                                  \
    if ((prevlensize) == 1) {                                                  \
        (prevlen) = (ptr)[0];                                                  \
    } else if ((prevlensize) == 5) {                                           \
        assert(sizeof((prevlensize)) == 4);                                    \
        // 當ptr[0]>254時,代表内容有點大,需要使用 5個字元儲存上一字元長度
        memcpy(&(prevlen), ((char*)(ptr)) + 1, 4);                             \
        memrev32ifbe(&prevlen);                                                \
    }                                                                          \
} while(0);
/* Decode the number of bytes required to store the length of the previous
 * element, from the perspective of the entry pointed to by 'ptr'. */
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do {                          \
    if ((ptr)[0] < ZIP_BIGLEN) {                                               \
        (prevlensize) = 1;                                                     \
    } else {                                                                   \
        (prevlensize) = 5;                                                     \
    }                                                                          \
} while(0);
/* Decode the length encoded in 'ptr'. The 'encoding' variable will hold the
 * entries encoding, the 'lensize' variable will hold the number of bytes
 * required to encode the entries length, and the 'len' variable will hold the
 * entries length. */
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do {                    \
    // 解析第1個字元為 編碼格式 &ZIP_STR_MASK=0xc0
    ZIP_ENTRY_ENCODING((ptr), (encoding));                                     \
    if ((encoding) < ZIP_STR_MASK) {                                           \
        // 0 << 6 =0
        // 具體解析如下代碼,
        if ((encoding) == ZIP_STR_06B) {                                       \
            (lensize) = 1;                                                     \
            (len) = (ptr)[0] & 0x3f;                                           \
        } 
        // 1 << 6 =64
        else if ((encoding) == ZIP_STR_14B) {                                  \
            (lensize) = 2;                                                     \
            (len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1];                       \
        }
        // 2 << 6 =128
        else if (encoding == ZIP_STR_32B) {                                    \
            (lensize) = 5;                                                     \
            (len) = ((ptr)[1] << 24) |                                         \
                    ((ptr)[2] << 16) |                                         \
                    ((ptr)[3] <<  8) |                                         \
                    ((ptr)[4]);                                                \
        } else {                                                               \
            assert(NULL);                                                      \
        }                                                                      \
    } else {                                                                   \
        // 超過 0xc0 的長度了,直接使用 1,2,3,4 表示len
        (lensize) = 1;                                                         \
        (len) = zipIntSize(encoding);                                          \
    }                                                                          \
} while(0);
/* Extract the encoding from the byte pointed by 'ptr' and set it into
 * 'encoding'. */
#define ZIP_ENTRY_ENCODING(ptr, encoding) do {  \
    (encoding) = (ptr[0]); \
    if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
} while(0)

/* Different encoding/length possibilities */
#define ZIP_STR_MASK 0xc0
#define ZIP_INT_MASK 0x30
#define ZIP_STR_06B (0 << 6)        // 0x00
#define ZIP_STR_14B (1 << 6)        // 0x40
#define ZIP_STR_32B (2 << 6)        // 0x80
#define ZIP_INT_16B (0xc0 | 0<<4)    // 0xc0
#define ZIP_INT_32B (0xc0 | 1<<4)    // 0xd0
#define ZIP_INT_64B (0xc0 | 2<<4)    // 0xe0
#define ZIP_INT_24B (0xc0 | 3<<4)    // 0xf0
#define ZIP_INT_8B 0xfe                // 0xfe      

View Code

  添加kv到對應的key執行個體中:

// 3. 添加kv到 hash表中, 稍微複雜
// t_hash.c, 做變更到hash表中
int hashTypeSet(robj *o, sds field, sds value, int flags) {
    int update = 0;
    // 針對ziplist 的添加, 與 ht 編碼的添加, 自然是分别處理
    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl, *fptr, *vptr;

        zl = o->ptr;
        // 找到ziplist 的頭節點指針
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if (fptr != NULL) {
            // 嘗試查找該 field 對應的元素(從1開始),如果找到則先删除原值,然後統一添加
            fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
            if (fptr != NULL) {
                /* Grab pointer to the value (fptr points to the field) */
                // value 不可以為null, 否則 ziplist 将無法工作
                vptr = ziplistNext(zl, fptr);
                serverAssert(vptr != NULL);
                update = 1;

                /* Delete value */
                // 先删除舊的 value, 再以插入的形式更新, 後續講删除時再詳解
                zl = ziplistDelete(zl, &vptr);

                /* Insert new value */
                // 重點,将value添加到 ziplist 中
                zl = ziplistInsert(zl, vptr, (unsigned char*)value,
                        sdslen(value));
            }
        }
        // 沒有找到對應元素,則直接将元素添加到尾部即可
        if (!update) {
            /* Push new field/value pair onto the tail of the ziplist */
            zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
                    ZIPLIST_TAIL);
            zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
                    ZIPLIST_TAIL);
        }
        o->ptr = zl;

        /* Check if the ziplist needs to be converted to a hash table */
        // 大于設定的閥值後,轉換ziplist為ht(預設: 512)
        if (hashTypeLength(o) > server.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);
    } else if (o->encoding == OBJ_ENCODING_HT) {
        dictEntry *de = dictFind(o->ptr,field);
        if (de) {
            sdsfree(dictGetVal(de));
            if (flags & HASH_SET_TAKE_VALUE) {
                dictGetVal(de) = value;
                value = NULL;
            } else {
                dictGetVal(de) = sdsdup(value);
            }
            update = 1;
        } else {
            sds f,v;
            if (flags & HASH_SET_TAKE_FIELD) {
                f = field;
                field = NULL;
            } else {
                f = sdsdup(field);
            }
            if (flags & HASH_SET_TAKE_VALUE) {
                v = value;
                value = NULL;
            } else {
                v = sdsdup(value);
            }
            dictAdd(o->ptr,f,v);
        }
    } else {
        serverPanic("Unknown hash encoding");
    }

    /* Free SDS strings we did not referenced elsewhere if the flags
     * want this function to be responsible. */
    if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field);
    if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value);
    return update;
}
// 3.1. 使用ziplist進行儲存 field -> value
// ziplist.c, 查找某個 field 是否存在于ziplist中
/* Find pointer to the entry equal to the specified entry. Skip 'skip' entries
 * between every comparison. Returns NULL when the field could not be found. */
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) {
    int skipcnt = 0;
    unsigned char vencoding = 0;
    long long vll = 0;

    while (p[0] != ZIP_END) {
        unsigned int prevlensize, encoding, lensize, len;
        unsigned char *q;
        // 解析整個字元串p的 prevlensize,encoding,lensize,len
        ZIP_DECODE_PREVLENSIZE(p, prevlensize);
        ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
        q = p + prevlensize + lensize;
        // 傳入1, 代表要跳過一個元素, 比如: 查找key時,跳過1個v,然後繼續疊代
        // 跳過了n個元素後,再從此開始key的比對過程
        if (skipcnt == 0) {
            /* Compare current entry with specified entry */
            // 針對不同的編碼使用不同的比較方式
            if (ZIP_IS_STR(encoding)) {
                // 找到相應的元素,直接傳回 p 指針
                if (len == vlen && memcmp(q, vstr, vlen) == 0) {
                    return p;
                }
            } else {
                /* Find out if the searched field can be encoded. Note that
                 * we do it only the first time, once done vencoding is set
                 * to non-zero and vll is set to the integer value. */
                if (vencoding == 0) {
                    if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) {
                        /* If the entry can't be encoded we set it to
                         * UCHAR_MAX so that we don't retry again the next
                         * time. */
                        vencoding = UCHAR_MAX;
                    }
                    /* Must be non-zero by now */
                    assert(vencoding);
                }

                /* Compare current entry with specified entry, do it only
                 * if vencoding != UCHAR_MAX because if there is no encoding
                 * possible for the field it can't be a valid integer. */
                if (vencoding != UCHAR_MAX) {
                    long long ll = zipLoadInteger(q, encoding);
                    if (ll == vll) {
                        return p;
                    }
                }
            }

            /* Reset skip count */
            // 查找一次,跳過skip次
            skipcnt = skip;
        } else {
            /* Skip entry */
            skipcnt--;
        }

        /* Move to next entry */
        p = q + len;
    }

    return NULL;
}
// ziplist.c, 添加value到ziplist中
// zl:ziplist執行個體, p:要插入的key字串, s:要插入的value字串, len:要插入的value的長度
/* Insert an entry at "p". */
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    return __ziplistInsert(zl,p,s,slen);
}
/* Insert item at "p". */
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
    unsigned int prevlensize, prevlen = 0;
    size_t offset;
    int nextdiff = 0;
    unsigned char encoding = 0;
    long long value = 123456789; /* initialized to avoid warning. Using a value
                                    that is easy to see if for some reason
                                    we use it uninitialized. */
    zlentry tail;

    /* Find out prevlen for the entry that is inserted. */
    if (p[0] != ZIP_END) {
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
    } else {
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        if (ptail[0] != ZIP_END) {
            prevlen = zipRawEntryLength(ptail);
        }
    }

    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&encoding)) {
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding);
    } else {
        /* 'encoding' is untouched, however zipEncodeLength will use the
         * string length to figure out how to encode it. */
        reqlen = slen;
    }
    /* We need space for both the length of the previous entry and
     * the length of the payload. */
    // 加上prevlen,encoding,slen 的長度,以計算value的存放位置
    reqlen += zipPrevEncodeLength(NULL,prevlen);
    reqlen += zipEncodeLength(NULL,encoding,slen);

    /* When the insert position is not equal to the tail, we need to
     * make sure that the next entry can hold this entry's length in
     * its prevlen field. */
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;

    /* Store offset because a realloc may change the address of zl. */
    // 存儲目前偏移位置,以便在擴容之後,還能找到相應位置
    // p = p -zl + zl
    offset = p-zl;
    zl = ziplistResize(zl,curlen+reqlen+nextdiff);
    p = zl+offset;

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) {
        /* Subtract one because of the ZIP_END bytes */
        // 字元拷貝
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        /* Encode this entry's raw length in the next entry. */
        zipPrevEncodeLength(p+reqlen,reqlen);

        /* Update offset for tail */
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);

        /* When the tail contains more than one entry, we need to take
         * "nextdiff" in account as well. Otherwise, a change in the
         * size of prevlen doesn't have an effect on the *tail* offset. */
        zipEntry(p+reqlen, &tail);
        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
        }
    } else {
        /* This element will be the new tail. */
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
    }

    /* When nextdiff != 0, the raw length of the next entry has changed, so
     * we need to cascade the update throughout the ziplist */
    if (nextdiff != 0) {
        // 如果本次更新後資料位置變化,則需要更新後續的元素位置
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }

    /* Write the entry */
    // 将 value 寫入 p 中, 即寫入了 ziplist 中
    p += zipPrevEncodeLength(p,prevlen);
    p += zipEncodeLength(p,encoding,slen);
    if (ZIP_IS_STR(encoding)) {
        memcpy(p,s,slen);
    } else {
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1);
    return zl;
}
// 另外,如果沒有舊的元素值時,直接在hash表的末尾添加對應的field->value 即可
// ziplist.c, 在尾部進行添加元素,沒有許多的情況要考慮,但是代碼完全複用 __ziplistInsert()
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
    unsigned char *p;
    p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
    return __ziplistInsert(zl,p,s,slen);
}      

  鑒于插入過程稍微複雜,咱們畫個圖重新理一下思路:

Redis(五):hash/hset/hget 指令源碼解析

   看起來沒ziplist好像沒那麼簡單呢,為啥還要搞這麼複雜呢?其實以上代碼,僅是在人看來複雜,對機器來說就是更多的移位計算操作,多消耗點cpu就換來了空間上的節省,是可以的。軟體本身的複雜性帶來了效益,是軟體的價值展現,是以,并非所有的東西都是簡單即美。

  接下來,我們來看一下使用 HT 的編碼又如何存儲field->value呢?

// 3.2. OBJ_ENCODING_HT 的 field -> value 的添加
    if (o->encoding == OBJ_ENCODING_HT) {
        // hash 表中查找對應的 field
        dictEntry *de = dictFind(o->ptr,field);
        if (de) {
            sdsfree(dictGetVal(de));
            // hset 時使用 HASH_SET_COPY, 是以直接使用 sdsdup() 即可
            if (flags & HASH_SET_TAKE_VALUE) {
                dictGetVal(de) = value;
                value = NULL;
            } else {
                dictGetVal(de) = sdsdup(value);
            }
            update = 1;
        } else {
            // 新增 field -> value
            sds f,v;
            if (flags & HASH_SET_TAKE_FIELD) {
                f = field;
                field = NULL;
            } else {
                f = sdsdup(field);
            }
            if (flags & HASH_SET_TAKE_VALUE) {
                v = value;
                value = NULL;
            } else {
                v = sdsdup(value);
            }
            // 添加到 hash 表中,前些篇章講解過,大概就是計算hash,放入v的過程
            dictAdd(o->ptr,f,v);
        }
    }      

  如此看來,OBJ_ENCODING_HT 的實作反而簡單了哦。

  總結下 hash的插入過程,hash 初始建立時都是使用ziplist 進行容納元素的,在特定情況下會觸發 ziplist 為 ht 的編碼方式, 比如:

    1. hset時自身的參數大于設定值(預設: 64)時直接轉換 ziplist -> ht;

    2. hash表的元素數量大于設定值(預設: 512)時轉換 ziplist -> ht;

  這麼設計的原因是,元素較少且占用空間較小時,使用ziplist會節省空間,且時間消耗與hash表相關并不大,是以 ziplist 是優先的選擇了。但是大量資料還是必須要使用hash表存儲的。

二、hmset 批量添加元素

  hset 和 hmset 在實作上基本如出一轍,是以簡單瞅瞅就得了。

// t_hash.c, hmset key f1 v1 f2 v2
void hmsetCommand(client *c) {
    int i;
    robj *o;
    // 參數個數檢查,必定是2n
    if ((c->argc % 2) == 1) {
        addReplyError(c,"wrong number of arguments for HMSET");
        return;
    }
    // 插入方式與 hset 一毛一樣,差别在于批量插入時,會循環向 key-hash表中添加field->value
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
    hashTypeTryConversion(o,c->argv,2,c->argc-1);
    // 循環insert
    for (i = 2; i < c->argc; i += 2) {
        hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
    }
    addReply(c, shared.ok);
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
    server.dirty++;
}      

三、hget 擷取某字段值

  這種指令的時間複雜度都是 O(1), 是以一般是簡單至上。

// t_hash.c    
void hgetCommand(client *c) {
    robj *o;
    // 查找key, 不存在或者類型不一緻則直接傳回
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
        checkType(c,o,OBJ_HASH)) return;
    // 基于o, 傳回 field 對應的元素值即可
    addHashFieldToReply(c, o, c->argv[2]->ptr);
}
// t_hash.c
static void addHashFieldToReply(client *c, robj *o, sds field) {
    int ret;

    if (o == NULL) {
        addReply(c, shared.nullbulk);
        return;
    }

    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;
        // 基于 ziplist, 
        ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
        if (ret < 0) {
            // 響應為空
            addReply(c, shared.nullbulk);
        } else {
            // 添加到輸出緩沖
            if (vstr) {
                addReplyBulkCBuffer(c, vstr, vlen);
            } else {
                addReplyBulkLongLong(c, vll);
            }
        }

    } else if (o->encoding == OBJ_ENCODING_HT) {
        // hash 表類型則查找 hash 表即可
        sds value = hashTypeGetFromHashTable(o, field);
        // 添加到輸出緩沖
        if (value == NULL)
            // 響應為空
            addReply(c, shared.nullbulk);
        else
            addReplyBulkCBuffer(c, value, sdslen(value));
    } else {
        serverPanic("Unknown hash encoding");
    }
}
// t_hash.c, 從 ziplist 中查找 field 值
/* Get the value from a ziplist encoded hash, identified by field.
 * Returns -1 when the field cannot be found. */
int hashTypeGetFromZiplist(robj *o, sds field,
                           unsigned char **vstr,
                           unsigned int *vlen,
                           long long *vll)
{
    unsigned char *zl, *fptr = NULL, *vptr = NULL;
    int ret;

    serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);

    zl = o->ptr;
    fptr = ziplistIndex(zl, ZIPLIST_HEAD);
    if (fptr != NULL) {
        fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
        if (fptr != NULL) {
            /* Grab pointer to the value (fptr points to the field) */
            vptr = ziplistNext(zl, fptr);
            serverAssert(vptr != NULL);
        }
    }

    if (vptr != NULL) {
        ret = ziplistGet(vptr, vstr, vlen, vll);
        serverAssert(ret);
        return 0;
    }

    return -1;
}

// t_hash.c, 從hash表中查找 field 字段的值
/* Get the value from a hash table encoded hash, identified by field.
 * Returns NULL when the field cannot be found, otherwise the SDS value
 * is returned. */
sds hashTypeGetFromHashTable(robj *o, sds field) {
    dictEntry *de;

    serverAssert(o->encoding == OBJ_ENCODING_HT);

    de = dictFind(o->ptr, field);
    if (de == NULL) return NULL;
    return dictGetVal(de);
}      

四、hmget 批量擷取值

  與hget如出一轍。

// t_hash.c
void hmgetCommand(client *c) {
    robj *o;
    int i;

    /* Don't abort when the key cannot be found. Non-existing keys are empty
     * hashes, where HMGET should respond with a series of null bulks. */
    o = lookupKeyRead(c->db, c->argv[1]);
    if (o != NULL && o->type != OBJ_HASH) {
        addReply(c, shared.wrongtypeerr);
        return;
    }
    // 循環輸出值
    addReplyMultiBulkLen(c, c->argc-2);
    for (i = 2; i < c->argc; i++) {
        addHashFieldToReply(c, o, c->argv[i]->ptr);
    }
}      

五、hgetall 擷取所有hash的kv

  hgetall 和 hmget 方式稍微有點不一樣,原因是為了讓 hkeysCommand/hvalsCommand 進行複用。

// t_hash.c
void hgetallCommand(client *c) {
    genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE);
}
void genericHgetallCommand(client *c, int flags) {
    robj *o;
    hashTypeIterator *hi;
    int multiplier = 0;
    int length, count = 0;

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
        || checkType(c,o,OBJ_HASH)) return;

    if (flags & OBJ_HASH_KEY) multiplier++;
    if (flags & OBJ_HASH_VALUE) multiplier++;

    length = hashTypeLength(o) * multiplier;
    addReplyMultiBulkLen(c, length);

    hi = hashTypeInitIterator(o);
    while (hashTypeNext(hi) != C_ERR) {
        if (flags & OBJ_HASH_KEY) {
            addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
            count++;
        }
        if (flags & OBJ_HASH_VALUE) {
            addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE);
            count++;
        }
    }

    hashTypeReleaseIterator(hi);
    serverAssert(count == length);
}
static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int what) {
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
        if (vstr)
            addReplyBulkCBuffer(c, vstr, vlen);
        else
            addReplyBulkLongLong(c, vll);
    } else if (hi->encoding == OBJ_ENCODING_HT) {
        sds value = hashTypeCurrentFromHashTable(hi, what);
        addReplyBulkCBuffer(c, value, sdslen(value));
    } else {
        serverPanic("Unknown hash encoding");
    }
}      

六、hincrby 增加x某字段

  hincrby key field 1

// t_hash.c, 
void hincrbyCommand(client *c) {
    long long value, incr, oldvalue;
    robj *o;
    sds new;
    unsigned char *vstr;
    unsigned int vlen;
    // 解析增加字段值到 incr 中
    if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
    // 擷取原值或者設定為0
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
    if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&value) == C_OK) {
        if (vstr) {
            if (string2ll((char*)vstr,vlen,&value) == 0) {
                addReplyError(c,"hash value is not an integer");
                return;
            }
        } /* Else hashTypeGetValue() already stored it into &value */
    } else {
        value = 0;
    }

    oldvalue = value;
    if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
        (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
        addReplyError(c,"increment or decrement would overflow");
        return;
    }
    // 将相加後的值重置設定回hash表中
    value += incr;
    new = sdsfromlonglong(value);
    hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
    addReplyLongLong(c,value);
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
    server.dirty++;
}      

七、hdel 删除某字段

  hdel key field

// t_hash.c, 
void hdelCommand(client *c) {
    robj *o;
    int j, deleted = 0, keyremoved = 0;

    if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,OBJ_HASH)) return;
    // 循環删除給定字段清單
    for (j = 2; j < c->argc; j++) {
        if (hashTypeDelete(o,c->argv[j]->ptr)) {
            deleted++;
            // 當沒有任何元素後,直接将key删除
            if (hashTypeLength(o) == 0) {
                dbDelete(c->db,c->argv[1]);
                keyremoved = 1;
                break;
            }
        }
    }
    if (deleted) {
        signalModifiedKey(c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
        if (keyremoved)
            notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
                                c->db->id);
        server.dirty += deleted;
    }
    addReplyLongLong(c,deleted);
}
// 具體删除 field, 同樣區分編碼類型,不同處理邏輯
/* Delete an element from a hash.
 * Return 1 on deleted and 0 on not found. */
int hashTypeDelete(robj *o, sds field) {
    int deleted = 0;

    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl, *fptr;

        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if (fptr != NULL) {
            // ziplist 删除,依次删除 field, value
            fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
            if (fptr != NULL) {
                // ziplistDelete 為原地删除,是以隻要調用2次,即把kv删除
                zl = ziplistDelete(zl,&fptr);
                zl = ziplistDelete(zl,&fptr);
                o->ptr = zl;
                deleted = 1;
            }
        }
    } else if (o->encoding == OBJ_ENCODING_HT) {
        if (dictDelete((dict*)o->ptr, field) == C_OK) {
            deleted = 1;

            /* Always check if the dictionary needs a resize after a delete. */
            // hash 删除的,可能需要進行縮容操作,這種處理方法相對特殊些
            if (htNeedsResize(o->ptr)) dictResize(o->ptr);
        }

    } else {
        serverPanic("Unknown hash encoding");
    }
    return deleted;
}
// server.c, 是否需要進行 resize
int htNeedsResize(dict *dict) {
    long long size, used;

    size = dictSlots(dict);
    used = dictSize(dict);
    // HASHTABLE_MIN_FILL=10, 即使用率小于 1/10 時,可以進行縮容操作了
    return (size && used && size > DICT_HT_INITIAL_SIZE &&
            (used*100/size < HASHTABLE_MIN_FILL));
}
          

  至此,整個hash資料結構的解析算是完整了。總體來說,hash由兩種資料結構承載,ziplist在小資料量時使用,稍微複雜,但對于昂貴的記憶體來說是值得的。hash表在資料量大時使用,容易了解。通過本文的講解,相信可以驗證了你對redis hash 的實作的猜想了。

不要害怕今日的苦,你要相信明天,更苦!