Golang 的开源库GroupCache
传统hash的负载均衡
扩缩容 从hash(username) % 3
⇒hash(username) % 4
负载均衡策略变化,任何用户都可能导致hash失效, 缓存集体失效可能导致后端直接承受过请求
一致性 Hash 的原理
有序 Hash 环的方式选择目标缓存 Server 环中的每个节点对应于一台缓存 Server;每个节点也包含一个整数值。各节点按照该整数值从小到大依次排列
对于指定用户来说,我们依然首先出计算用户名的 hash 值。接着,在 Hash 环中按照值大小顺序,从小到大依次寻找,找到 第一个大于等于该 hash 值的节点,将其作为目标缓存 Server。
比如:新增一个节点 New-Node
, 假设该节点的值为 11。那么新的有序 Hash 环如图
hash 值范围在 Node-B
和 NewNode
之间(即(7, 11])的数据缓存失效。这部分数据原本分配到节点 Node-C
(值为 13),现在都需要迁移到新节点 NewNode
上。
而原本分配到 Node-A
、Node-B
两个节点上的缓存数据,不会受到任何影响。之前值范围在 NewNode
和 Node-B
之间(即(11, 13])的数据,被分配到了 Node-C
上面。新节点出现后,这部分数据依然属于 Node-C
,也不会受到任何影响。
这里有个小问题是,因为有序 Hash 环需要其中每个节点有持有一个整数值,那这个整数值如何得到呢?一般做法是,我们可以利用该节点的特有信息计算其 Hash 值得到, 例如 hash(ip:port)
数据倾斜与虚拟节点
假设整个集群就两个缓存节点: Node-A
和 Node-B
。则 Node-B
中将存放 Hash 值范围在 (Node-A, Node-B]
之间的数据。而 Node-A
将承担两部分的数据: hash < Node-A
和 hash > Node-B,
很容易出现分配给某个节点的数据远大于其他节点**“数据倾斜”
**
引入虚拟节点的概念,或者说是副本节点。每个真实的缓存 Server 在 Hash 环上都对应多个虚拟节点
我们其实依然只有三个缓存 Server。但是每个 Server 都有一个副本,例如 V-Node-A
和 Node-A
都对应同一个缓存 Server。
import (
"fmt"
"github.com/golang/groupcache/consistenthash"
)
func main() {
// 构造一个 consistenthash 对象,每个节点在 Hash 环上都一共有三个虚拟节点。
hash := consistenthash.New(3, nil)
// 添加节点
hash.Add(
"127.0.0.1:8080",
"127.0.0.1:8081",
"127.0.0.1:8082",
)
// 根据 key 获取其对应的节点
node := hash.Get("cyhone.com")
fmt.Println(node)
}