• gRPC负载均衡(自定义负载均衡策略)


    前言

    上篇文章介绍了如何实现gRPC负载均衡,但目前官方只提供了pick_firstround_robin两种负载均衡策略,轮询法round_robin不能满足因服务器配置不同而承担不同负载量,这篇文章将介绍如何实现自定义负载均衡策略--加权随机法

    加权随机法可以根据服务器的处理能力而分配不同的权重,从而实现处理能力高的服务器可承担更多的请求,处理能力低的服务器少承担请求。

    自定义负载均衡策略

    gRPC提供了V2PickerBuilderV2Picker接口让我们实现自己的负载均衡策略。

    type V2PickerBuilder interface {
    	Build(info PickerBuildInfo) balancer.V2Picker
    }
    

    V2PickerBuilder接口:创建V2版本的子连接选择器。

    Build方法:返回一个V2选择器,将用于gRPC选择子连接。

    type V2Picker interface {
    	Pick(info PickInfo) (PickResult, error)
    }
    

    V2Picker 接口:用于gRPC选择子连接去发送请求。
    Pick方法:子连接选择

    问题来了,我们需要把服务器地址的权重添加进去,但是地址resolver.Address并没有提供权重的属性。官方给的答复是:把权重存储到地址的元数据metadata中。

    // attributeKey is the type used as the key to store AddrInfo in the Attributes
    // field of resolver.Address.
    type attributeKey struct{}
    
    // AddrInfo will be stored inside Address metadata in order to use weighted balancer.
    type AddrInfo struct {
    	Weight int
    }
    
    // SetAddrInfo returns a copy of addr in which the Attributes field is updated
    // with addrInfo.
    func SetAddrInfo(addr resolver.Address, addrInfo AddrInfo) resolver.Address {
    	addr.Attributes = attributes.New()
    	addr.Attributes = addr.Attributes.WithValues(attributeKey{}, addrInfo)
    	return addr
    }
    
    // GetAddrInfo returns the AddrInfo stored in the Attributes fields of addr.
    func GetAddrInfo(addr resolver.Address) AddrInfo {
    	v := addr.Attributes.Value(attributeKey{})
    	ai, _ := v.(AddrInfo)
    	return ai
    }
    

    定义AddrInfo结构体并添加权重Weight属性,Set方法把Weight存储到resolver.Address中,Get方法从resolver.Address获取Weight

    解决权重存储问题后,接下来我们实现加权随机法负载均衡策略。

    首先实现V2PickerBuilder接口,返回子连接选择器。

    func (*rrPickerBuilder) Build(info base.PickerBuildInfo) balancer.V2Picker {
    	grpclog.Infof("weightPicker: newPicker called with info: %v", info)
    	if len(info.ReadySCs) == 0 {
    		return base.NewErrPickerV2(balancer.ErrNoSubConnAvailable)
    	}
    	var scs []balancer.SubConn
    	for subConn, addr := range info.ReadySCs {
    		node := GetAddrInfo(addr.Address)
    		if node.Weight <= 0 {
    			node.Weight = minWeight
    		} else if node.Weight > 5 {
    			node.Weight = maxWeight
    		}
    		for i := 0; i < node.Weight; i++ {
    			scs = append(scs, subConn)
    		}
    	}
    	return &rrPicker{
    		subConns: scs,
    	}
    }
    

    加权随机法中,我使用空间换时间的方式,把权重转成地址个数(例如addr1的权重是3,那么添加3个子连接到切片中;addr2权重为1,则添加1个子连接;选择子连接时候,按子连接切片长度生成随机数,以随机数作为下标就是选中的子连接),避免重复计算权重。考虑到内存占用,权重定义从15权重。

    接下来实现子连接的选择,获取随机数,选择子连接

    type rrPicker struct {
    	subConns []balancer.SubConn
    	mu sync.Mutex
    }
    
    func (p *rrPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
    	p.mu.Lock()
    	index := rand.Intn(len(p.subConns))
    	sc := p.subConns[index]
    	p.mu.Unlock()
    	return balancer.PickResult{SubConn: sc}, nil
    }
    

    关键代码完成后,我们把加权随机法负载均衡策略命名为weight,并注册到gRPC的负载均衡策略中。

    // Name is the name of weight balancer.
    const Name = "weight"
    // NewBuilder creates a new weight balancer builder.
    func newBuilder() balancer.Builder {
    	return base.NewBalancerBuilderV2(Name, &rrPickerBuilder{}, base.Config{HealthCheck: false})
    }
    
    func init() {
    	balancer.Register(newBuilder())
    }
    

    完整代码weight.go

    最后,我们只需要在服务端注册服务时候附带权重,然后客户端在服务发现时把权重Setresolver.Address中,最后客户端把负载论衡策略改成weight就完成了。

    //SetServiceList 设置服务地址
    func (s *ServiceDiscovery) SetServiceList(key, val string) {
    	s.lock.Lock()
    	defer s.lock.Unlock()
    	//获取服务地址
    	addr := resolver.Address{Addr: strings.TrimPrefix(key, s.prefix)}
    	//获取服务地址权重
    	nodeWeight, err := strconv.Atoi(val)
    	if err != nil {
    		//非数字字符默认权重为1
    		nodeWeight = 1
    	}
    	//把服务地址权重存储到resolver.Address的元数据中
    	addr = weight.SetAddrInfo(addr, weight.AddrInfo{Weight: nodeWeight})
    	s.serverList[key] = addr
    	s.cc.UpdateState(resolver.State{Addresses: s.getServices()})
    	log.Println("put key :", key, "wieght:", val)
    }
    

    客户端使用weight负载均衡策略

    func main() {
    	r := etcdv3.NewServiceDiscovery(EtcdEndpoints)
    	resolver.Register(r)
    	// 连接服务器
    	conn, err := grpc.Dial(
    		fmt.Sprintf("%s:///%s", r.Scheme(), SerName),
    		grpc.WithBalancerName("weight"),
    		grpc.WithInsecure(),
    	)
    	if err != nil {
    		log.Fatalf("net.Connect err: %v", err)
    	}
    	defer conn.Close()
    

    运行效果:

    运行服务1,权重为1

    运行服务2,权重为4

    运行客户端

    查看前50次请求在服务1服务器2的负载情况。服务1分配了9次请求,服务2分配了41次请求,接近权重比值。

    断开服务2,所有请求流向服务1

    以权重为4,重启服务2,请求以加权随机法流向两个服务器

    总结

    本篇文章以加权随机法为例,介绍了如何实现gRPC自定义负载均衡策略,以满足我们的需求。

    源码地址:https://github.com/Bingjian-Zhu/etcd-example

  • 相关阅读:
    HDU 1124 Factorial(简单数论)
    29.QT主窗口加widget
    28.开始画面和异形窗口
    27.Qt时钟
    26.QT颜色与布局
    25.QT进度条
    146.正则表达式
    24.qint64转QString 以及获取文件属性
    23.QFile遍历
    22.监视文件
  • 原文地址:https://www.cnblogs.com/FireworksEasyCool/p/12924701.html
Copyright © 2020-2023  润新知