再谈一致性哈希

529 阅读2分钟

再谈一致性哈希

最近在好未来的面试中, 面试官问到了一致性哈希的问题, 但是自己当时脑子直接抽掉了, 根本没有回答出来, 也可能导致自己直接失去工作机会, 所以在此重新整理下一致性哈希的问题, 希望之后再遇到类似的场景能够尽可能的回答出来.

适用场景

一致性哈希算法就很好地解决了分布式系统在扩容或者缩容时,发生过多的数据迁移的问题。

一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值

我们可以把一致哈希算法是对 2^32 进行取模运算的结果值组织成一个圆环,就像钟表一样,钟表的圆可以理解成由 60 个点组成的圆,而此处我们把这个圆想象成由 2^32 个点组成的圆,这个圆环被称为哈希环,如下图:

技术要点

  1. 原始一致性哈希

    形成哈希环, 从该点向后查找, 查找到的第一个节点即为应该分配到的节点
  2. 增加虚拟节点的一致性哈希

    优点:
    节点数量多了后,节点在哈希环上的分布就相对均匀了
    当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高

示例代码

package conshash

import (
  "sort"
  "strconv"
)/

const BasicVirtualSlots = 10

type HashFunc func(s string) int
type HashRing struct {
  hash      HashFunc
  slotsMap  map[int]string
  slotsList []int
}

type ConsHash interface {
  AddNode(s string)
  GetNode(s string)
  RemoveNode(s string)
}

func NewHashRing(hashFunc HashFunc) *HashRing {
  return &HashRing{
    hash:      hashFunc,
    slotsMap:  make(map[int]string),
    slotsList: make([]int, 0),
  }
}

func (h *HashRing) AddNode(s string) {
  for i := 0; i < BasicVirtualSlots; i++ {
    hashK := h.hash(s + "*9182374$" + strconv.Itoa(i))
    h.slotsList = append(h.slotsList, hashK)
    h.slotsMap[hashK] = s
  }
  sort.Ints(h.slotsList)
}

func (h *HashRing) GetNode(s string) string {
  if len(h.slotsList) == 0 {
    return ""
  }
  hashK := h.hash(s)
  idx := sort.Search(
    len(h.slotsList), func(i int) bool {
      // print("get: i -> ")
      // println(i)
      return h.slotsList[i] >= hashK

    },
  )
  idx = idx % len(h.slotsList)
  return h.slotsMap[h.slotsList[idx]]
}

func (h *HashRing) RemoveNode(s string) {
  for i := 0; i < BasicVirtualSlots; i++ {
    hashK := h.hash(s + ";" + strconv.Itoa(i))
    delete(h.slotsMap, hashK)
  }
}

参考文档

www.xiaolincoding.com/os/8_networ…