Memcached Hash算法

Hash算法

1. Memcached Hash介紹

? 我們在前面的文章中已經(jīng)介紹過了Memcached的內(nèi)存管理方式,LRU的策略邓梅。由于Memcached的數(shù)據(jù)存儲方式基本上是基于雙向鏈表來實現(xiàn)的围苫,而鏈表實現(xiàn)的最大好處在于可以快速的進行增刪改冲杀,但其最大的不足在于其數(shù)據(jù)的獲取只能通過遍歷鏈表的方式來進行公罕。而Memcached使用了Hash算法來進行數(shù)據(jù)的快速讀取。

2. Hash算法

? Memcached的Hash算法原理上非常簡單韧拒。我們用下面的圖來說明。

Alt pic

? 這個數(shù)據(jù)結(jié)構(gòu)跟我們熟知的HashMap是一致的十性,數(shù)據(jù)hash到不同的桶中叛溢,當(dāng)Hash發(fā)生沖突的時候,采用了鏈表來記錄相同Hash值的數(shù)據(jù)劲适。使用Hash算法最重要的一點是如何解決Hash沖突楷掉,Memcached采用的鏈表來解決Hash沖突是較為基本的方式。這種方式的缺陷是當(dāng)數(shù)據(jù)量增多霞势,Hash沖突增多時烹植,會發(fā)生鏈表過長的情況。Memcached在這種情況下愕贡,會采用擴大桶數(shù)量的方式來優(yōu)化草雕。Memcached的Hash算法本身并不復(fù)雜,這里也不再花大篇幅來介紹其Hash算法固以。

3. 源代碼分析

? 首先我們來看看Memcached的Hash算法:

unsigned int hashpower = HASHPOWER_DEFAULT;

/* 這里的hash算法采用的還是按位與的方式來定位Bucket墩虹,1<<(n)表示hash桶的數(shù)量 */
#define hashsize(n) ((ub4)1<<(n))
/* 這里是Hash的掩碼,數(shù)據(jù)的hash值與掩碼取與操作可以定位到唯一的Hash桶 */
#define hashmask(n) (hashsize(n)-1)

? 下面我們來看看Memcached的增刪查找操作:

/* hash列表中Item元素的查找 */
item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
    item *it;
    unsigned int oldbucket;
    /* 這一步是找到hash的桶號 */
    if (expanding &&
        /* 在Hash列表進行rehash的時候,是按照桶號順序進行的败晴,所以如果該桶號>=目前正在處理的桶號時浓冒,意味著該數(shù)據(jù)還是舊Hash表中*/
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        it = old_hashtable[oldbucket];
    } else {
        it = primary_hashtable[hv & hashmask(hashpower)];
    }

    item *ret = NULL;
    int depth = 0;
    /* 這一步是Hash沖突列表的遍歷查找 */
    while (it) {
        /* Item值匹配的標準:
            1. key的長度相等
            2. key值相等
        */
        if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
            ret = it;
            break;
        }
        it = it->h_next;
        ++depth;
    }
    MEMCACHED_ASSOC_FIND(key, nkey, depth);
    return ret;
}

/* 該方法是插入操作,該Key值必須是不存在才行 */
int assoc_insert(item *it, const uint32_t hv) {
    unsigned int oldbucket;

    /* 這一步是找到該數(shù)據(jù)應(yīng)存儲的桶號 */
    if (expanding &&
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        it->h_next = old_hashtable[oldbucket];
        old_hashtable[oldbucket] = it;
    } else {
        it->h_next = primary_hashtable[hv & hashmask(hashpower)];
        primary_hashtable[hv & hashmask(hashpower)] = it;
    }

    pthread_mutex_lock(&hash_items_counter_lock);
    hash_items++;
    /* 進行rehash的條件判斷尖坤,滿足rehash的條件如下:
        1. 目前不是正處在rehash中
        2. hash表中的所有數(shù)據(jù)量>hash表容量的1.5倍
    */
    if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) {
        assoc_start_expand();
    }
    pthread_mutex_unlock(&hash_items_counter_lock);

    MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items);
    return 1;
}

/* hash表中元素的刪除 */
void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {
    /* 指針的指針稳懒,要刪除元素的地址指針*/
    item **before = _hashitem_before(key, nkey, hv);

    if (*before) {
        item *nxt;
        pthread_mutex_lock(&hash_items_counter_lock);
        hash_items--;
        pthread_mutex_unlock(&hash_items_counter_lock);
        /* The DTrace probe cannot be triggered as the last instruction
         * due to possible tail-optimization by the compiler
         */
        MEMCACHED_ASSOC_DELETE(key, nkey, hash_items);
        nxt = (*before)->h_next;
        (*before)->h_next = 0;   /* probably pointless, but whatever. */
        *before = nxt;
        return;
    }
    /* Note:  we never actually get here.  the callers don't delete things
       they can't find. */
    assert(*before != 0);
}

static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {
    item **pos;
    unsigned int oldbucket;

    if (expanding &&
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        pos = &old_hashtable[oldbucket];
    } else {
        pos = &primary_hashtable[hv & hashmask(hashpower)];
    }

    while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
        pos = &(*pos)->h_next;
    }
    return pos;
}

? 在看過了Memcached Hash表中數(shù)據(jù)的增刪查,下面來看看Hash表的擴容實現(xiàn):

/* 該方法只是Hash擴容的初始化方法 */
static void assoc_expand(void) {
    old_hashtable = primary_hashtable;
    
    /* 從這里可以看出慢味,Hash擴容的方式是重新申請兩倍大小的Hash表*/
    primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));
    if (primary_hashtable) {
        if (settings.verbose > 1)
            fprintf(stderr, "Hash table expansion starting\n");
        hashpower++;
        expanding = true;
        expand_bucket = 0;
        STATS_LOCK();
        stats.hash_power_level = hashpower;
        stats.hash_bytes += hashsize(hashpower) * sizeof(void *);
        stats.hash_is_expanding = 1;
        STATS_UNLOCK();
    } else {
        primary_hashtable = old_hashtable;
        /* Bad news, but we can keep running. */
    }
}

static volatile int do_run_maintenance_thread = 1;

#define DEFAULT_HASH_BULK_MOVE 1
int hash_bulk_move = DEFAULT_HASH_BULK_MOVE;

/* ReHash的線程任務(wù) */
static void *assoc_maintenance_thread(void *arg) {

    mutex_lock(&maintenance_lock);
    while (do_run_maintenance_thread) {
        int ii = 0;

        /* There is only one expansion thread, so no need to global lock. */
        /* 這里的hash_bulk_move標記一次rehash的桶的最小個數(shù)*/
        for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {
            item *it, *next;
            int bucket;
            void *item_lock = NULL;

            /* bucket = hv & hashmask(hashpower) =>the bucket of hash table
             * is the lowest N bits of the hv, and the bucket of item_locks is
             *  also the lowest M bits of hv, and N is greater than M.
             *  So we can process expanding with only one item_lock. cool! */
            /*這里對整個桶進行加鎖*/
            if ((item_lock = item_trylock(expand_bucket))) {
                    for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
                        next = it->h_next;
                        bucket = hash(ITEM_key(it), it->nkey) & hashmask(hashpower);
                        it->h_next = primary_hashtable[bucket];
                        primary_hashtable[bucket] = it;
                    }
                    /* 已經(jīng)處理掉的桶置為NULL */
                    old_hashtable[expand_bucket] = NULL;

                    expand_bucket++;
                    /* rehash完成的標記 */
                    if (expand_bucket == hashsize(hashpower - 1)) {
                        expanding = false;
                        free(old_hashtable);
                        STATS_LOCK();
                        stats.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);
                        stats.hash_is_expanding = 0;
                        STATS_UNLOCK();
                        if (settings.verbose > 1)
                            fprintf(stderr, "Hash table expansion done\n");
                    }

            } else {
                usleep(10*1000);
            }

            if (item_lock) {
                item_trylock_unlock(item_lock);
                item_lock = NULL;
            }
        }

        if (!expanding) {
            /* We are done expanding.. just wait for next invocation */
            started_expanding = false;
            pthread_cond_wait(&maintenance_cond, &maintenance_lock);
            /* assoc_expand() swaps out the hash table entirely, so we need
             * all threads to not hold any references related to the hash
             * table while this happens.
             * This is instead of a more complex, possibly slower algorithm to
             * allow dynamic hash table expansion without causing significant
             * wait times.
             */
            pause_threads(PAUSE_ALL_THREADS);
            assoc_expand();
            pause_threads(RESUME_ALL_THREADS);
        }
    }
    return NULL;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末场梆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纯路,更是在濱河造成了極大的恐慌或油,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驰唬,死亡現(xiàn)場離奇詭異顶岸,居然都是意外死亡,警方通過查閱死者的電腦和手機叫编,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門辖佣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搓逾,你說我怎么就攤上這事卷谈。” “怎么了霞篡?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵世蔗,是天一觀的道長。 經(jīng)常有香客問我朗兵,道長污淋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任余掖,我火速辦了婚禮芙沥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浊吏。我一直安慰自己而昨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布找田。 她就那樣靜靜地躺著歌憨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪墩衙。 梳的紋絲不亂的頭發(fā)上务嫡,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天甲抖,我揣著相機與錄音,去河邊找鬼心铃。 笑死准谚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的去扣。 我是一名探鬼主播柱衔,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼愉棱!你這毒婦竟也來了唆铐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤奔滑,失蹤者是張志新(化名)和其女友劉穎艾岂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朋其,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡王浴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了梅猿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叼耙。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粒没,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情簇爆,我是刑警寧澤癞松,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站入蛆,受9級特大地震影響响蓉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哨毁,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一枫甲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扼褪,春花似錦想幻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幔崖,卻和暖如春食店,著一層夾襖步出監(jiān)牢的瞬間渣淤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工吉嫩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留价认,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓自娩,卻偏偏與公主長得像用踩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子椒功,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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