redis string底層數(shù)據(jù)結(jié)構(gòu)

系列

redis數(shù)據(jù)淘汰原理
redis過(guò)期數(shù)據(jù)刪除策略
redis server事件模型
redis cluster mget 引發(fā)的討論
redis 3.x windows 集群搭建
redis 命令執(zhí)行過(guò)程
redis string底層數(shù)據(jù)結(jié)構(gòu)
redis list底層數(shù)據(jù)結(jié)構(gòu)
redis hash底層數(shù)據(jù)結(jié)構(gòu)
redis set底層數(shù)據(jù)結(jié)構(gòu)
redis zset底層數(shù)據(jù)結(jié)構(gòu)
redis 客戶(hù)端管理
redis 主從同步-slave端
redis 主從同步-master端
redis 主從超時(shí)檢測(cè)
redis aof持久化
redis rdb持久化
redis 數(shù)據(jù)恢復(fù)過(guò)程
redis TTL實(shí)現(xiàn)原理
redis cluster集群建立
redis cluster集群選主

redis數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)

?redis的內(nèi)部整體的存儲(chǔ)結(jié)構(gòu)就是一個(gè)大的hashmap剃盾,內(nèi)部實(shí)現(xiàn)是數(shù)組實(shí)現(xiàn)hash,沖突通過(guò)掛鏈去實(shí)現(xiàn)与涡,然后每個(gè)dictEntry就是一個(gè)key/value對(duì)象踱讨。dictEntry的key指向set key value命令中的key對(duì)應(yīng)的對(duì)象魏蔗,dictEntry的v指向set key value命令中的value對(duì)應(yīng)的對(duì)象。

redis存儲(chǔ)結(jié)構(gòu)



?dictEntry 內(nèi)部包含數(shù)據(jù)存儲(chǔ)的key和v變量痹筛,同時(shí)包含一個(gè)dictEntry的next指針連接落入同一個(gè)hash桶的對(duì)象沫勿。dictEntry當(dāng)中的key和v的指針指向的是redisObject。

/*
 * 哈希表節(jié)點(diǎn)
 */
typedef struct dictEntry {
    
    // 鍵
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下個(gè)哈希表節(jié)點(diǎn)味混,形成鏈表
    struct dictEntry *next;

} dictEntry;



?redisObject是redis server存儲(chǔ)最原子數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)产雹,其中的void *ptr會(huì)指向真正的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu),我們set key value中的key和value其實(shí)由ptr指向真正保存的位置翁锡。

typedef struct redisObject {

    // 類(lèi)型
    unsigned type:4;

    // 編碼
    unsigned encoding:4;

    // 對(duì)象最后一次被訪(fǎng)問(wèn)的時(shí)間
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用計(jì)數(shù)
    int refcount;

    // 指向?qū)嶋H值的指針
    void *ptr;

} robj;


redis string類(lèi)型轉(zhuǎn)換

?我們可能以為redis在內(nèi)部存儲(chǔ)string都是用sds的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的蔓挖,其實(shí)在整個(gè)redis的數(shù)據(jù)存儲(chǔ)過(guò)程中為了提高性能,內(nèi)部做了很多優(yōu)化馆衔。整體選擇順序應(yīng)該是:

  • 整數(shù)瘟判,存儲(chǔ)字符串長(zhǎng)度小于21且能夠轉(zhuǎn)化為整數(shù)的字符串。

  • EmbeddedString角溃,存儲(chǔ)字符串長(zhǎng)度小于39的字符串(REDIS_ENCODING_EMBSTR_SIZE_LIMIT)拷获。

  • SDS,剩余情況使用sds進(jìn)行存儲(chǔ)减细。

?embstr和sds的區(qū)別在于內(nèi)存的申請(qǐng)和回收

  • embstr的創(chuàng)建只需分配一次內(nèi)存匆瓜,而raw為兩次(一次為sds分配對(duì)象,另一次為redisObject分配對(duì)象未蝌,embstr省去了第一次)驮吱。相對(duì)地,釋放內(nèi)存的次數(shù)也由兩次變?yōu)橐淮巍?/p>

  • embstr的redisObject和sds放在一起萧吠,更好地利用緩存帶來(lái)的優(yōu)勢(shì)

  • 缺點(diǎn):redis并未提供任何修改embstr的方式左冬,即embstr是只讀的形式。對(duì)embstr的修改實(shí)際上是先轉(zhuǎn)換為raw再進(jìn)行修改纸型。


string編碼轉(zhuǎn)換源碼分析

?通過(guò)redis 內(nèi)部的命令映射表我們找到set對(duì)應(yīng)的處理函數(shù)為setCommand拇砰,相當(dāng)于這個(gè)是處理set命令的入口函數(shù),關(guān)注下tryObjectEncoding狰腌,內(nèi)部對(duì)其實(shí)對(duì)Object進(jìn)行轉(zhuǎn)換除破。

/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
void setCommand(redisClient *c) {
    int j;
    robj *expire = NULL;
    int unit = UNIT_SECONDS;
    int flags = REDIS_SET_NO_FLAGS;

    // 設(shè)置選項(xiàng)參數(shù)
    for (j = 3; j < c->argc; j++) {
        char *a = c->argv[j]->ptr;
        robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];

        if ((a[0] == 'n' || a[0] == 'N') &&
            (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
            flags |= REDIS_SET_NX;
        } else if ((a[0] == 'x' || a[0] == 'X') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
            flags |= REDIS_SET_XX;
        } else if ((a[0] == 'e' || a[0] == 'E') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
            unit = UNIT_SECONDS;
            expire = next;
            j++;
        } else if ((a[0] == 'p' || a[0] == 'P') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
            unit = UNIT_MILLISECONDS;
            expire = next;
            j++;
        } else {
            addReply(c,shared.syntaxerr);
            return;
        }
    }

    // 嘗試對(duì)值對(duì)象進(jìn)行編碼
    c->argv[2] = tryObjectEncoding(c->argv[2]);

    setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}



?整個(gè)嘗試編碼轉(zhuǎn)換的邏輯過(guò)程通過(guò)代碼的注釋?xiě)?yīng)該是比較清楚了,過(guò)程如下:

  • 只對(duì)長(zhǎng)度小于或等于 21 字節(jié)癌别,并且可以被解釋為整數(shù)的字符串進(jìn)行編碼皂岔,使用整數(shù)存儲(chǔ)
  • 嘗試將 RAW 編碼的字符串編碼為 EMBSTR 編碼,使用EMBSTR 編碼
  • 這個(gè)對(duì)象沒(méi)辦法進(jìn)行編碼展姐,嘗試從 SDS 中移除所有空余空間躁垛,使用SDS編碼
/* Try to encode a string object in order to save space */
// 嘗試對(duì)字符串對(duì)象進(jìn)行編碼剖毯,以節(jié)約內(nèi)存。
robj *tryObjectEncoding(robj *o) {
    long value;

    sds s = o->ptr;
    size_t len;
    redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);

    // 只在字符串的編碼為 RAW 或者 EMBSTR 時(shí)嘗試進(jìn)行編碼
    if (!sdsEncodedObject(o)) return o;

     // 不對(duì)共享對(duì)象進(jìn)行編碼
     if (o->refcount > 1) return o;

    // 對(duì)字符串進(jìn)行檢查
    // 只對(duì)長(zhǎng)度小于或等于 21 字節(jié)教馆,并且可以被解釋為整數(shù)的字符串進(jìn)行編碼
    len = sdslen(s);
    if (len <= 21 && string2l(s,len,&value)) {
        if (server.maxmemory == 0 &&
            value >= 0 &&
            value < REDIS_SHARED_INTEGERS)
        {
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } else {
            if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
            o->encoding = REDIS_ENCODING_INT;
            o->ptr = (void*) value;
            return o;
        }
    }

    // 嘗試將 RAW 編碼的字符串編碼為 EMBSTR 編碼
    if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;

        if (o->encoding == REDIS_ENCODING_EMBSTR) return o;
        emb = createEmbeddedStringObject(s,sdslen(s));
        decrRefCount(o);
        return emb;
    }

    // 這個(gè)對(duì)象沒(méi)辦法進(jìn)行編碼逊谋,嘗試從 SDS 中移除所有空余空間
    if (o->encoding == REDIS_ENCODING_RAW &&
        sdsavail(s) > len/10)
    {
        o->ptr = sdsRemoveFreeSpace(o->ptr);
    }

    /* Return the original object. */
    return o;
}


redis sds的介紹

?在C語(yǔ)言中,字符串可以用'\0'結(jié)尾的char數(shù)組標(biāo)示土铺。這種簡(jiǎn)單的字符串表示胶滋,在大多數(shù)情況下都能滿(mǎn)足要求,但是不能高效的計(jì)算length和append數(shù)據(jù)悲敷。所以Redis自己實(shí)現(xiàn)了SDS(簡(jiǎn)單動(dòng)態(tài)字符串)的抽象類(lèi)型究恤。
?SDS的數(shù)據(jù)結(jié)構(gòu)如下,len表示sdshdr中數(shù)據(jù)的長(zhǎng)度后德,free表示sdshdr中剩余的空間部宿,buf表示實(shí)際存儲(chǔ)數(shù)據(jù)的空間。
?sdslen的函數(shù)有一個(gè)細(xì)節(jié)需要我們注意瓢湃,那就是通過(guò)(s-(sizeof(struct sdshdr)))來(lái)計(jì)算偏移量理张,之所以需要這么計(jì)算是因?yàn)閟ds的指針指向的是char buf[]位置,所以我們需要訪(fǎng)問(wèn)sdshdr的首地址的時(shí)候需要減去偏移量绵患。

/*
 * 保存字符串對(duì)象的結(jié)構(gòu)
 */
struct sdshdr {
    
    // buf 中已占用空間的長(zhǎng)度
    int len;

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

    // 數(shù)據(jù)空間
    char 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;
}


sds對(duì)象創(chuàng)建

?在創(chuàng)建sds對(duì)象的時(shí)候雾叭,我們上面提到過(guò)的涉及兩次內(nèi)存分配的過(guò)程,從下面的代碼可以看出來(lái):

  • sds對(duì)象創(chuàng)建sdsnewlen分配了一次內(nèi)存落蝙。
  • robj對(duì)象的創(chuàng)建又分配了一次內(nèi)存织狐。
    整個(gè)sds對(duì)象的創(chuàng)建其實(shí)就是分配內(nèi)存并初始化len和free字段。

/* Create a string object with encoding REDIS_ENCODING_RAW, that is a plain
 * string object where o->ptr points to a proper sds string. */
// 創(chuàng)建一個(gè) REDIS_ENCODING_RAW 編碼的字符對(duì)象
// 對(duì)象的指針指向一個(gè) sds 結(jié)構(gòu)
robj *createRawStringObject(char *ptr, size_t len) {
    return createObject(REDIS_STRING, sdsnewlen(ptr,len));
}
/*
 * 根據(jù)給定的初始化字符串 init 和字符串長(zhǎng)度 initlen
 * 創(chuàng)建一個(gè)新的 sds
 *
 * 參數(shù)
 *  init :初始化字符串指針
 *  initlen :初始化字符串的長(zhǎng)度
 *
 * 返回值
 *  sds :創(chuàng)建成功返回 sdshdr 相對(duì)應(yīng)的 sds
 *        創(chuàng)建失敗返回 NULL
 *
 * 復(fù)雜度
 *  T = O(N)
 */
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 中
    // T = O(N)
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    // 以 \0 結(jié)尾
    sh->buf[initlen] = '\0';

    // 返回 buf 部分奏寨,而不是整個(gè) sdshdr
    return (char*)sh->buf;
}
robj *createObject(int type, void *ptr) {

    robj *o = zmalloc(sizeof(*o));

    o->type = type;
    o->encoding = REDIS_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* Set the LRU to the current lruclock (minutes resolution). */
    o->lru = LRU_CLOCK();
    return o;
}


sds內(nèi)存擴(kuò)容

?當(dāng)字符串長(zhǎng)度小于SDS_MAX_PREALLOC (1024*1024),那么就以2倍的速度擴(kuò)容鹰服,當(dāng)字符串長(zhǎng)度大于SDS_MAX_PREALLOC病瞳,那么就以+SDS_MAX_PREALLOC的速度擴(kuò)容。

/*
 * 對(duì) sds 中 buf 的長(zhǎng)度進(jìn)行擴(kuò)展悲酷,確保在函數(shù)執(zhí)行之后套菜,
 * buf 至少會(huì)有 addlen + 1 長(zhǎng)度的空余空間
 * (額外的 1 字節(jié)是為 \0 準(zhǔn)備的)
 *
 * 返回值
 *  sds :擴(kuò)展成功返回?cái)U(kuò)展后的 sds
 *        擴(kuò)展失敗返回 NULL
 *
 * 復(fù)雜度
 *  T = O(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內(nèi)存縮容

?釋放內(nèi)存的過(guò)程中修改len和free字段渣蜗,并不釋放實(shí)際占用內(nèi)存。

/*
 * 在不釋放 SDS 的字符串空間的情況下旷祸,
 * 重置 SDS 所保存的字符串為空字符串耕拷。
 *
 * 復(fù)雜度
 *  T = O(1)
 */
void sdsclear(sds s) {

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

    // 重新計(jì)算屬性
    sh->free += sh->len;
    sh->len = 0;

    // 將結(jié)束符放到最前面(相當(dāng)于惰性地刪除 buf中的內(nèi)容)
    sh->buf[0] = '\0';
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市托享,隨后出現(xiàn)的幾起案子骚烧,更是在濱河造成了極大的恐慌,老刑警劉巖闰围,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赃绊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡羡榴,警方通過(guò)查閱死者的電腦和手機(jī)凭戴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)炕矮,“玉大人么夫,你說(shuō)我怎么就攤上這事》羰樱” “怎么了档痪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)邢滑。 經(jīng)常有香客問(wèn)我腐螟,道長(zhǎng),這世上最難降的妖魔是什么困后? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任乐纸,我火速辦了婚禮,結(jié)果婚禮上摇予,老公的妹妹穿的比我還像新娘汽绢。我一直安慰自己,他們只是感情好侧戴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布宁昭。 她就那樣靜靜地躺著,像睡著了一般酗宋。 火紅的嫁衣襯著肌膚如雪积仗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天蜕猫,我揣著相機(jī)與錄音寂曹,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛隆圆,可吹牛的內(nèi)容都是我干的漱挚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼匾灶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棱烂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起阶女,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤颊糜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后秃踩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衬鱼,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年憔杨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸟赫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡消别,死狀恐怖抛蚤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寻狂,我是刑警寧澤岁经,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站蛇券,受9級(jí)特大地震影響缀壤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纠亚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一塘慕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蒂胞,春花似錦图呢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蚊锹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間稚瘾,已是汗流浹背牡昆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丢烘。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓柱宦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親播瞳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掸刊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 Redis是目前最火爆的內(nèi)存數(shù)據(jù)庫(kù)之一,通過(guò)在內(nèi)存中讀寫(xiě)數(shù)據(jù)赢乓,大大提高了讀寫(xiě)速度忧侧,可以說(shuō)Redis是實(shí)現(xiàn)網(wǎng)站...
    小陳阿飛閱讀 805評(píng)論 0 1
  • 參考來(lái)源 Redis的內(nèi)存優(yōu)化 Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常寶貴的資源牌芋。對(duì)于如何優(yōu)化內(nèi)存使用一直...
    秦漢郵俠閱讀 1,288評(píng)論 0 2
  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來(lái)自《Redis開(kāi)發(fā)與運(yùn)維》一書(shū)第八章蚓炬,如轉(zhuǎn)載請(qǐng)聲明。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 18,888評(píng)論 2 29
  • 歲月流逝躺屁,送走童稚——失去了天真與活潑肯夏; 走出青春——褪去了熱情與夢(mèng)想; 有所成就——還怕再走入失斚睢驯击; 經(jīng)歷挫折—...
    帥氣的多哈閱讀 220評(píng)論 0 0
  • 漢武帝時(shí)期有兩大外戚集團(tuán),就是田蚡和竇嬰耐亏。田蚡是個(gè)真小人徊都,連個(gè)偽君子都算不上,竇嬰是個(gè)有自己主見(jiàn)的正人君子苹熏。只是這...
    夏言baby閱讀 673評(píng)論 0 2