package balancer import ( "errors" "fmt" "google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer/base" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/resolver" "strconv" ) const ConsistentHash = "consistent_hash_x" var ConsistentHashKey = "consistent-hash" func InitConsistentHashBuilder() { balancer.Register(newConsistentHashBuilder()) } // newConsistentHashBuilder creates a new ConsistentHash balancer builder. func newConsistentHashBuilder() balancer.Builder { return base.NewBalancerBuilder( ConsistentHash, &consistentHashPickerBuilder{}, base.Config{HealthCheck: true}, ) } type consistentHashPickerBuilder struct{} func (b *consistentHashPickerBuilder) Build(buildInfo base.PickerBuildInfo) balancer.Picker { grpclog.Infof("consistentHashPicker: newPicker called with buildInfo: %v", buildInfo) if len(buildInfo.ReadySCs) == 0 { return base.NewErrPicker(balancer.ErrNoSubConnAvailable) } picker := &consistentHashPicker{ subConns: make(map[string]balancer.SubConn), hash: NewKetama(DefaultReplicas, nil), } for sc, conInfo := range buildInfo.ReadySCs { weight := GetWeight(conInfo.Address) for i := 0; i < weight; i++ { node := wrapAddr(conInfo.Address.Addr, i) picker.hash.Add(node) picker.subConns[node] = sc } } return picker } type consistentHashPicker struct { subConns map[string]balancer.SubConn hash *Ketama } func (p *consistentHashPicker) Pick(info balancer.PickInfo) (ret balancer.PickResult, err error) { key, ok := info.Ctx.Value(ConsistentHashKey).(string) if !ok || key == "" { //key = strconv.Itoa(rand.Intn(65536)) //grpclog.Warning("empty consistent hash key") panic(errors.New("empty consistent hash key")) } targetAddr, ok := p.hash.Get(key) if ok { ret.SubConn = p.subConns[targetAddr] } return } func wrapAddr(addr string, idx int) string { return fmt.Sprintf("%s-%d", addr, idx) } func GetWeight(addr resolver.Address) (weight int) { weight = DefaultWeight if addr.Attributes == nil { return } val := addr.Attributes.Value(WeightKey) switch val.(type) { case int: weight = val.(int) case string: w, err := strconv.Atoi(val.(string)) if err != nil { grpclog.Errorf("instance weight format error: %v\n", val) return } weight = w default: grpclog.Errorf("instance weight value type not string: %v\n", val) } return }