数据结构之实现一致性哈希算法

学习极客时间《数据结构与算法之美》哈希算法的应用章节,了解到分布式系统中一致性哈希算法的重要性,以及哈希算法的广泛应用,下面是部分实际应用:

  • 安全加密
  • 唯一标识
  • 数据校验
  • 散列函数
  • 负载均衡
  • 数据分片
  • 分布式存储

一致性哈希算法较好的解决了一般的取模哈希算法无法解决的增减模数量导致需要全部重新计算哈希值的问题,可以防止增减节点规模导致的缓存大规模失效问题,对于解决大规模的缓存失效问题很有效,可以尽可能缩小缓存失效的数据范围。

关于一致性哈希的详情可以看下面这篇博文:

白话解析:一致性哈希算法 consistent hashing

下面是 go 语言实现示例,供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package main

import (
"fmt"
"hash/crc32"
"sort"
"strconv"
)

// UInt32Slice 虚拟环
type UInt32Slice []uint32

func (s UInt32Slice) Len() int {
return len(s)
}

func (s UInt32Slice) Less(i, j int) bool {
return s[i] < s[j]
}

func (s UInt32Slice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

// HashCode 哈希函数类型
type HashCode func(data []byte) uint32

// Map 一致性哈希结构类型
type Map struct {
hashCode HashCode
replica int // 复制因子,控制虚拟节点数量
virtualNode UInt32Slice // 虚拟节点列表,便于查找数据要映射到哪个虚拟节点
readNode map[uint32]string // 虚拟节点和真实节点之间的对应关系
}

// New 初始化一致性哈希
func New(replica int, fn HashCode) *Map {
m := &Map{
replica: replica,
hashCode: fn,
readNode: make(map[uint32]string),
}
// 默认使用CRC32算法
if m.hashCode == nil {
m.hashCode = crc32.ChecksumIEEE
}
return m
}

// IsEmpty 哈希环中是否还有节点
func (m *Map) IsEmpty() bool {
return len(m.virtualNode) == 0
}

// Add 添加缓存节点,参数为节点key,比如使用IP
func (m *Map) Add(virtualNode ...string) {
for _, key := range virtualNode {
// 根据配置好的复制因子,创建新增节点的虚拟节点并保存好映射关系
for i := 0; i < m.replica; i++ {
hash := m.hashCode([]byte(strconv.Itoa(i) + key))
m.virtualNode = append(m.virtualNode, hash)
m.readNode[hash] = key
}
}
// 对所有虚拟节点的哈希值进行排序,方便之后进行二分查找
sort.Sort(m.virtualNode)
}

// Get 给定的 key 获取最靠近它的那个虚拟节点 key
func (m *Map) Get(key string) string {
if m.IsEmpty() {
return ""
}

hashCode := m.hashCode([]byte(key))

// 二分查找第一个大于 hashCode 的值
// 关于二分查找,可以查看 https://blog.pingvim.com/数据结构之二分查找IP对应地区/
virtualNodeIndex := sort.Search(len(m.virtualNode), func(i int) bool { return m.virtualNode[i] >= hashCode })

// 如果查找结果大于虚拟节点组的最大索引,表示此时该对象哈希值位于最后一个虚拟节点之后,那么放入第一个节点中
// 这里就是一致性哈希环的作用,保证每个哈希值都有可放置的位置
if virtualNodeIndex == len(m.virtualNode) {
virtualNodeIndex = 0
}
return m.readNode[m.virtualNode[virtualNodeIndex]]
}

// Remove 移除节点
func (m *Map) Remove(virtualNode ...string) {
for _, key := range virtualNode {
for k, v := range m.readNode {
if v == key {
delete(m.readNode, k)
}
}
}
var temp UInt32Slice
for k := range m.readNode {
temp = append(temp, k)
}
m.virtualNode = temp
sort.Sort(m.virtualNode)
}

func main() {
hash := New(2, nil)
hash.Add("a", "b")
fmt.Println(hash.Get("asdasda"))
hash.Remove("b")
fmt.Println(hash.Get("asdasda"))
}
◀        
        ▶