Google Maglev Hashing实现

背景

Maglev是Google开发的基于kernal bypass技术实现的4层负载均衡,它具有非常强大的负载性能,承载了Google绝大部分接入流量。Maglev在负载均衡算法上采用自行开发的一致性哈希算法被称为Maglev Hashing,该哈希算法在节点变化时能够尽量少的影响其他几点,且尽可能的保证负载的均衡,是一个非常优秀的一致性哈希算法,Google的技术都是自带光环哈!下面想用Golang做一个简单的实现。

原理说明

Maglev Hashing的基本思想是为每一个节点生成一个优先填充表,列表的值就是该节点想要填充查询表的位置.
lookup table

如Table1所示,节点B0,会按照顺序3,0,4,...依次去尝试填充查询表。实际上,所有的节点会轮流按照各自优先列表的值填充查询表。也就是说,每个节点都有几乎均等的机会根据优先表来填充查询表,直到查询表被填满。

当出现节点变化,如B1宕机时,查询表会重新生成,因为节点的优先填充表不变,所以B0和B2原来的填充位置不变,B1宕机后确实的位置被B0和B2瓜分,按照轮流填充的机制,B0和B2基本也是均衡的。

算法实现

M为查询表的大小。对与每一个节点ipermutation[i]为优先填充表,permutation[i]的取值是数组[0, M-1]的一个随机顺序排列,permutation是一个二维数组。

下面介绍论文给出的高效生成permutation[i]的方法:
首先使用两种哈希函数来哈希节点生成两个数字,offset skip. 论文中是计算节点名称的哈希值,为了简单我就直接计算了节点的索引值,哈希函数我用的是算法导论里提到的乘法散列法,代码如下:

func Hash1(k int) int {
    s := uint64(2654435769)
    p := uint32(14)
    tmp := (s * uint64(k)) % (1 << 32)
    return int(tmp / (1 << (32 - p)))
}

func Hash2(k int) int {
    s := uint64(1654435769)
    p := uint32(14)
    tmp := (s * uint64(k)) % (1 << 32)
    return int(tmp / (1 << (32 - p)))
}

第二个哈希函数我只是修改了一个参数值,哈希算法是一样的。offset skip计算方式如下:

offset←h1(name[i]) mod M
skip←h2(name[i]) mod (M−1)+1

从而得到permutation[i]中每一个值的计算方式:
permutation[ i ][ j ]←(offset+ j×skip) mod M 0<=j<= M-1
这里要注意的是M必须为质数,这样才能尽可能保证skip与M互斥。寻找合适的质数M我使用了简单的筛选算法:

func isPrime(n int) bool {
    if n < 2 {
        return false
    }
    end := int(math.Sqrt(float64(n)))
    for i := 2; i <= end; i++ {
        if n%i == 0 {
            return false
        }
    }
    return true
}

func findPrime(n int) int {
    //始终有大于n的质数
    for {
        if isPrime(n) {
            return n
        }
        n++
    }
}

上面介绍了一些辅助函数,下面介绍算法的具体实现流程:

type MaglevHash struct {
    m, n        int
    permutation [][]int
    entry       []int
    nodeState   []bool
}

func NewMaglevHash(n int) *MaglevHash {
    m := findPrime(5 * n)
    permutation := make([][]int, n)
    entry := make([]int, m)
    nodeState := make([]bool, n)
    for idx, _ := range nodeState {
        nodeState[idx] = true
    }

    return &MaglevHash{
        m:           m,
        n:           n,
        permutation: permutation,
        entry:       entry,
        nodeState:   nodeState,
    }
}

定义一个结构MaglevHash和结构体生成函数,golang的标准实现。其中permutation为一个N*M的二维数组,entry为长度N的查询表,nodeState为长度N的记录节点时候的下线的表。

接下来是生成permutation的函数,计算节点时实际上传入的是节点索引值加一,避免传入0,影响哈希值的计算:

func (mh *MaglevHash) Permutate() {
    for idx, _ := range mh.permutation {
        mh.permutation[idx] = make([]int, mh.m)
    }
    for i := 0; i < mh.n; i++ {
        offset := Hash1(i+1) % mh.m
        skip := Hash2(i+1)%(mh.m-1) + 1
        for j := 0; j < mh.m; j++ {
            mh.permutation[i][j] = (offset + j*skip) % mh.m
        }
    }
}

生成好节点优先填充表之后,就可以根据该表填充查询表:

func (mh *MaglevHash) Populate() {
    for idx, _ := range mh.entry {
        mh.entry[idx] = -1
    }
    next := make([]int, mh.n)
    n := 0
    for {
        for i := 0; i < mh.n; i++ {
            if !mh.nodeState[i] {
                continue
            }
            c := mh.permutation[i][next[i]]
            for mh.entry[c] >= 0 {
                next[i]++
                c = mh.permutation[i][next[i]]
            }
            mh.entry[c] = i
            next[i]++
            n++
            if n == mh.m {
                return
            }
        }
    }
}

在填充查询表时,会检查节点是否下线,若节点下线,则会忽略该节点。

func (mh *MaglevHash) DownNode(idx int) error {
    if idx > mh.n-1 {
        return errors.New("invalid idx")
    }
    mh.nodeState[idx] = false
    return nil
}

节点下线时,需要调用该函数,然后再调用Populate()重新填充查询表。

至此,Maglev hashing 一个简单的实现就算完成了,后续希望使用生产环境的哈希函数来替换本文用到哈希函数,并考虑在nginx上实现该一致性哈希算法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • --- layout: post title: "如果有人问你关系型数据库的原理,叫他看这篇文章(转)" date...
    蓝坠星阅读 782评论 0 3
  • Map 是一种很常见的数据结构,用于存储一些无序的键值对。在主流的编程语言中,默认就自带它的实现。C、C++ 中的...
    一缕殇流化隐半边冰霜阅读 9,261评论 23 67
  • 今天看到一位朋友写的mysql笔记总结,觉得写的很详细很用心,这里转载一下,供大家参考下,也希望大家能关注他原文地...
    信仰与初衷阅读 4,727评论 0 30
  • 前言 其实读完斯坦福的这本《互联网大规模数据挖掘》,让我感觉到,什么是人工智能?人工智能就是更高层次的数据挖掘。机...
    我偏笑_NSNirvana阅读 12,557评论 1 23
  • 感赏 今天再次听了锦明老师的讲座,猛然发现,我已经好久没有练习了。下定决心,再次开始练习。重新认识我的女儿。 女儿...
    妈妈随笔阅读 146评论 2 2