前言

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

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

自定义负载均衡策略

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

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

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

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

  1. type V2Picker interface {
  2. Pick(info PickInfo) (PickResult, error)
  3. }

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

Pick方法:子连接选择

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

  1. // attributeKey is the type used as the key to store AddrInfo in the Attributes
  2. // field of resolver.Address.
  3. type attributeKey struct{}
  4. // AddrInfo will be stored inside Address metadata in order to use weighted balancer.
  5. type AddrInfo struct {
  6. Weight int
  7. }
  8. // SetAddrInfo returns a copy of addr in which the Attributes field is updated
  9. // with addrInfo.
  10. func SetAddrInfo(addr resolver.Address, addrInfo AddrInfo) resolver.Address {
  11. addr.Attributes = attributes.New()
  12. addr.Attributes = addr.Attributes.WithValues(attributeKey{}, addrInfo)
  13. return addr
  14. }
  15. // GetAddrInfo returns the AddrInfo stored in the Attributes fields of addr.
  16. func GetAddrInfo(addr resolver.Address) AddrInfo {
  17. v := addr.Attributes.Value(attributeKey{})
  18. ai, _ := v.(AddrInfo)
  19. return ai
  20. }

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

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

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

  1. func (*rrPickerBuilder) Build(info base.PickerBuildInfo) balancer.V2Picker {
  2. grpclog.Infof("weightPicker: newPicker called with info: %v", info)
  3. if len(info.ReadySCs) == 0 {
  4. return base.NewErrPickerV2(balancer.ErrNoSubConnAvailable)
  5. }
  6. var scs []balancer.SubConn
  7. for subConn, addr := range info.ReadySCs {
  8. node := GetAddrInfo(addr.Address)
  9. if node.Weight <= 0 {
  10. node.Weight = minWeight
  11. } else if node.Weight > 5 {
  12. node.Weight = maxWeight
  13. }
  14. for i := 0; i < node.Weight; i++ {
  15. scs = append(scs, subConn)
  16. }
  17. }
  18. return &rrPicker{
  19. subConns: scs,
  20. }
  21. }

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

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

  1. type rrPicker struct {
  2. subConns []balancer.SubConn
  3. mu sync.Mutex
  4. }
  5. func (p *rrPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
  6. p.mu.Lock()
  7. index := rand.Intn(len(p.subConns))
  8. sc := p.subConns[index]
  9. p.mu.Unlock()
  10. return balancer.PickResult{SubConn: sc}, nil
  11. }

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

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

完整代码weight.go

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

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

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

  1. func main() {
  2. r := etcdv3.NewServiceDiscovery(EtcdEndpoints)
  3. resolver.Register(r)
  4. // 连接服务器
  5. conn, err := grpc.Dial(
  6. fmt.Sprintf("%s:///%s", r.Scheme(), SerName),
  7. grpc.WithBalancerName("weight"),
  8. grpc.WithInsecure(),
  9. )
  10. if err != nil {
  11. log.Fatalf("net.Connect err: %v", err)
  12. }
  13. 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

gRPC负载均衡(自定义负载均衡策略)的更多相关文章

  1. 【Ribbon篇四】自定义负载均衡策略(4)

    官方文档特别指出:自定义的负载均衡配置类不能放在 @componentScan 所扫描的当前包下及其子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特殊化定制 ...

  2. grpc服务发现与负载均衡

    前言 在后台服务开发中,高可用性是构建中核心且重要的一环.服务发现(Service discovery)和负载均衡(Load Balance)一直都是我关注的话题.今天来谈一下我在实际中是如何理解及落 ...

  3. Spring-Cloud-Ribbon学习笔记(二):自定义负载均衡规则

    Ribbon自定义负载均衡策略有两种方式,一是JavaConfig,一是通过配置文件(yml或properties文件). 需求 假设我有包含A和B服务在内的多个微服务,它们均注册在一个Eureka上 ...

  4. SpringBoot-dubbo自定义负载均衡实现简单灰度

    本文介绍如何利用dubbo自定义负载实现简单灰度(用户纬度,部分用户访问一个服务,其余访问剩余服务). 其实在这之前,对dubbo了解的也不是很多,只是简单的使用过,跑了几个demo而已,但是得知接下 ...

  5. SpringCloud全家桶学习之客户端负载均衡及自定义负载均衡算法----Ribbon(三)

    一.Ribbon是什么? Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端  负载均衡的工具(这里区别于nginx的负载均衡).简单来说,Ribbon是Netf ...

  6. Ribbon源码分析(一)-- RestTemplate 以及自定义负载均衡算法

    如果只是想看ribbon的自定义负载均衡配置,请查看: https://www.cnblogs.com/yangxiaohui227/p/13186004.html 注意: 1.RestTemplat ...

  7. Nginx负载均衡的5种策略(转载)

    Nginx的upstream目前支持的5种方式的分配 轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. upstream backserver { s ...

  8. spring-cloud: eureka之:ribbon负载均衡自定义配置(二)

    spring-cloud: eureka之:ribbon负载均衡自定义配置(二) 有默认配置的话基本上就是轮询接口,现在我们改用自定义配置,同时支持:轮询,随机接口读取 准备工作: 1.eureka服 ...

  9. 分布式系统的负载均衡以及ngnix负载均衡的五种策略

    一般而言,有以下几种常见的负载均衡策略: 一.轮询. 特点:给每个请求标记一个序号,然后将请求依次派发到服务器节点中,适用于集群中各个节点提供服务能力等同且无状态的场景. 缺点:该策略将节点视为等同, ...

随机推荐

  1. cocos2d 导演,场景

    导演(Director) Cocos2d-x 使用导演的概念,这个导演和电影制作过程中的导演一样!导演控制电影制作流程,指导团队完成各项任务.在使用 Cocos2d-x 开发游戏的过程中,你可以认为自 ...

  2. 虚拟化KVM之概述(一)

    云计算基本概述 云计算是一种按使用量付费的模式,这种模式提供可用的.便捷的.按需的网络访问,进入可配置的计算资源共享池(资源包括网络,服务器,存储,应用程序,服务),这些资源能够被快速提供,只需投入很 ...

  3. 原生JS中获取位置的方案总结

    获取鼠标当前位置 clientY.clientX: 鼠标当前位置 相对于 浏览器可视区域顶部.浏览器可视区域左部 的位置: pageY.pageX: 鼠标当前位置 相对于 文档顶部.文档左部的位置: ...

  4. IIS6服务器的请求流程(图文&源码)

    1.IIS 7开发与管理完全参考手册  http://book.51cto.com/art/200908/146040.htm 2.Web服务IIS 6   https://technet.micro ...

  5. 【ElasticSearch学习】之一图读懂文档索引全过程

    ES索引过程详解: 1.客户端发送索引请求. 客户端向ES节点发送索引请求,以RestClient客户端发起请求为例: ES提供了Java High Level REST Client,用户可以通过R ...

  6. MySQL分页查询的性能优化

    MySQL limit分页查询的性能优化 Mysql的分页查询十分简单,但是当数据量大的时候一般的分页就吃不消了. 传统分页查询:SELECT c1,c2,cn… FROM table LIMIT n ...

  7. Docker docker-compose 配置lnmp开发环境

    1.安装docker yum -y install dockersystemctl start dockersystemctl enable docker 安装docker-compose https ...

  8. Redis 6.0 新特性-多线程连环13问!

    Redis 6.0 来了 在全国一片祥和IT民工欢度五一节假日的时候,Redis 6.0不声不响地于5 月 2 日正式发布了,吓得我赶紧从床上爬起来,学无止境!学无止境! 对于6.0版本,Redis之 ...

  9. 【Hadoop离线基础总结】Hue与Hive集成

    目录 1.更改hue的配置hue.ini 2.启动hive的metastore以及hiveserver2服务 3.启动hue进程,查看Hive是否与Hue集成成功 1.更改hue的配置hue.ini ...

  10. android实现计时器

    新建布局文件activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearL ...