在网关系统的实现中,需要提供对session一致性的支持,而网关系统通过hash一致性来支持session一致性。
一致性哈希实现的难点在于一是需要让key尽量分别均匀,二是需要考虑到目标节点动态变化的情况。
一 哈希环实现
参考rpc框架中kitc 客户端哈希一致性的实现:
负责哈希一致性的负载类实现
type KeyFunc func(ctx context.Context, req interface{}) (string, error)
type ConsistBalancer struct {
GetKey KeyFunc
backup LoadBalancer
// 为了使key尽量均匀分布于节点上,引入虚拟节点
replicas int
instanceInfo map[string][]*discovery.Instance
sortedHash []uint64
hash2Ins map[uint64]*discovery.Instance
lock lock.RWMutex
}
Picker 选择器实现:
func (cb *ConsistBalancer) NewPicker(ctx context.Context, req interface{}, key string, instances []*discovery.Instance) Picker {
...
hashKey, err := cb.GetKey(ctx, req)
if err != nil {...}
intList := cb.search(hash(hashKey))
if len(intList) == 0 {...}
return &consistBalancePicker{intList}
}
func (cb *ConsistBalancer) search(hashCode uint64) []*discovery.Instance {
results := make([]*discovery.Instance, 0, 3)
dup := make(map[string]struct{}, 0, 3)
cb.Lock.RLock()
defer cb.Lock.RULock()
index := sort.Search(len(cb.sortedHash), func(x int) bool {
return cb.soredHash[x] > hashCode
})
if index == len(cb.sortedHash) { index = 0 }
for i = 0; i<len(sortedHash); i++ {
ins := cb.hash2Ins[cb.sortedHash[index]]
// instanceKey 根据ins信息生成string
key = instanceKey(ins)
if _, ok := dup[key]; ok {
index = (index + 1) % len(cb.sortedHash)
continue
}
dup[key] = struct{}{}
results = append(results, ins)
index = (index + 1) % len(cb.sortedHash)
if len(results) == 3 {
break
}
}
return results
}
type consistBalancePicker struct {
insList []*discovery.Instance
}
func (cbp *ConsistBalancePicker) Pick() (*discovery.Instance, bool){
if len(cbp.insList) == 0 { ... }
ret := cbp.insList[0]
cbp.insList = cbp.insList[1:]
return ret, true
}
节点信息更新实现:
func (cb *consistBalancer) Rebalance(key string, insList []*discovery.Instance) {
var oldInsList []*discovery.Instance
if !cb.isExitKey(key) {
goto DoRebalance
}
cb.Lock.RLock()
oldInsList = cb.instanceInfo[key]
cb.Lock.RUnlock()
if len(insList) == len(oldInsList) {
newMap, oldMap := make(map[string]struct{}, len(insList), make(map[string]struct{}, len(insList)
for _, ins := range insList {
newMap[instanceKey(ins)] = struct{}{}
}
for _, ins := range oldList {
oldMap[instanceKey(ins)] = struct{}{}
}
if len(newMap) == len(oldMap) {
for k := range newMap {
if _, ok := oldMap[key]; !ok {
goto DoRebalance
}
}
return
}
}
DoRebalane :
cb.Rebalance(key, insList)
}
func (cb *ConsistBalancer) Rebalance(key string, insList []*discovery.Instance) {
cb.Lock.Lock()
cb.instanceInfo[key] = insList
totalLen = 0
for _, instances := range cb.instanceInfo {
totalLen += len(instances)
}
sortedHash := make([]uint64, 0, totalLen*cb.replicas)
hash2Ins := make(map[uint64]*discovery.Instance,totalLen*cb.replicas)
for _, insList := range cb.instanceInfo {
for _, ins := range insList {
for i:= 0;i<=cb.replicas;i++ {
key := fmt.Sprintf("%v:%v",instanceKey(ins), i)
hashcode := hash(key)
sortedHash = append(sortedHash, hashcode)
hash2Ins[hashcode] = ins
}
}
}
sort.Slice(sortedHash, func(i, j int) bool {
return sortedHash[i] < sortedHash[j]
}
cb.hash2Ins = hash2Ins
cb.sortedHash = sortedHash
cb.Lock.UnLock()
}
func instanceKey(ins *discovery.Instance) string {
return ins.Host + ":" + ins.Port
}
func hash(key string) uint64 {
return xxhash.Sum64String(key)
}
二 谷歌哈希一致性算法
具体可以参考:
谷歌哈希一致性算法
实现方式:
var DefaultHasher hash.Hash64 = crc64.New(crc64.MakeTable(crc64.ECMA))
func Hash(key uint64, buckets int32) int32 {
var b, j int64
if bucket <= 1 {
buckets = 1
}
for j< int64(buckets) {
b = j
key = key * 2862933555777941757 + 1
j = int64(float(b+1)*float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
// key为string时调用的方法
func HashString (key string, buckets int32) int32 {
return hashStringWithHasher(key, buckets, DefaultHasher)
}
func HashStringWithHasher(key string, buckets int32, hasher hash.Hash64) int32 {
hasher.Reset()
hasher.Write([]byte(key))
return Hash(hasher.Sum64(), buckets)
}