gRPC负载均衡(自定义负载均衡策略)
前言
上篇文章介绍了如何实现gRPC负载均衡,但目前官方只提供了pick_first和round_robin两种负载均衡策略,轮询法round_robin不能满足因服务器配置不同而承担不同负载量,这篇文章将介绍如何实现自定义负载均衡策略--加权随机法。
加权随机法可以根据服务器的处理能力而分配不同的权重,从而实现处理能力高的服务器可承担更多的请求,处理能力低的服务器少承担请求。
自定义负载均衡策略
gRPC提供了V2PickerBuilder和V2Picker接口让我们实现自己的负载均衡策略。
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个子连接;选择子连接时候,按子连接切片长度生成随机数,以随机数作为下标就是选中的子连接),避免重复计算权重。考虑到内存占用,权重定义从1到5权重。
接下来实现子连接的选择,获取随机数,选择子连接
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
最后,我们只需要在服务端注册服务时候附带权重,然后客户端在服务发现时把权重Set到resolver.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
gRPC负载均衡(自定义负载均衡策略)的更多相关文章
- 【Ribbon篇四】自定义负载均衡策略(4)
		
官方文档特别指出:自定义的负载均衡配置类不能放在 @componentScan 所扫描的当前包下及其子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特殊化定制 ...
 - grpc服务发现与负载均衡
		
前言 在后台服务开发中,高可用性是构建中核心且重要的一环.服务发现(Service discovery)和负载均衡(Load Balance)一直都是我关注的话题.今天来谈一下我在实际中是如何理解及落 ...
 - Spring-Cloud-Ribbon学习笔记(二):自定义负载均衡规则
		
Ribbon自定义负载均衡策略有两种方式,一是JavaConfig,一是通过配置文件(yml或properties文件). 需求 假设我有包含A和B服务在内的多个微服务,它们均注册在一个Eureka上 ...
 - SpringBoot-dubbo自定义负载均衡实现简单灰度
		
本文介绍如何利用dubbo自定义负载实现简单灰度(用户纬度,部分用户访问一个服务,其余访问剩余服务). 其实在这之前,对dubbo了解的也不是很多,只是简单的使用过,跑了几个demo而已,但是得知接下 ...
 - SpringCloud全家桶学习之客户端负载均衡及自定义负载均衡算法----Ribbon(三)
		
一.Ribbon是什么? Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具(这里区别于nginx的负载均衡).简单来说,Ribbon是Netf ...
 - Ribbon源码分析(一)-- RestTemplate 以及自定义负载均衡算法
		
如果只是想看ribbon的自定义负载均衡配置,请查看: https://www.cnblogs.com/yangxiaohui227/p/13186004.html 注意: 1.RestTemplat ...
 - Nginx负载均衡的5种策略(转载)
		
Nginx的upstream目前支持的5种方式的分配 轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. upstream backserver { s ...
 - spring-cloud: eureka之:ribbon负载均衡自定义配置(二)
		
spring-cloud: eureka之:ribbon负载均衡自定义配置(二) 有默认配置的话基本上就是轮询接口,现在我们改用自定义配置,同时支持:轮询,随机接口读取 准备工作: 1.eureka服 ...
 - 分布式系统的负载均衡以及ngnix负载均衡的五种策略
		
一般而言,有以下几种常见的负载均衡策略: 一.轮询. 特点:给每个请求标记一个序号,然后将请求依次派发到服务器节点中,适用于集群中各个节点提供服务能力等同且无状态的场景. 缺点:该策略将节点视为等同, ...
 
随机推荐
- 来自BAT大厂前端工程师的自白-怎么才能学好前端
			
如果说理解学好web前端是先能找到一份工作,那么你应该这样做: 1.制定好一下系统的web前端学习规划,每天定量,学完什么知识点就掌握,能自己应用,而不是能看懂,写不出来东西. 2.不要自己一个人闷头 ...
 - Zabbix数据库表分区
			
zabbix的监控主机数量将近300,且运行了一年时间了,最近zabbix server服务监控历史数据等服务不断自身告警.查询性能也变得很低 关于历史数据的两个参数,在zabbix server的配 ...
 - 在IBM Cloud中运行Fabric
			
文章目录 打包智能合约 创建IBM Cloud services 创建fabric网络 创建org和相应的节点 创建order org和相应节点 创建和加入channel 导入智能合约 上篇文章我们讲 ...
 - 天大福利!世界第一科技出版公司 Springer 免费开放 400 多本电子书!
			
前几天,世界著名的科技期刊/图书出版公司施普林格(Springer)宣布:免费向公众开放 400 多本正版的电子书!! Springer 即施普林格出版社,于1842 年在德国柏林创立,20 世纪60 ...
 - 【java基础】01 计算机基础知识
			
一.计算机基础知识 1. 计算机 1. 什么是计算机? 计算机在生活中的应用举例 计算机(Computer)全称:电子计算机,俗称电脑.是一种能够按照程序运行,自动.高速处理海量数据的现代化智能电子设 ...
 - zabbix3.x.x升级教程
			
1:停掉正在运行的zabbix服务,确保没有新数据写入数据库. /etc/init.d/zabbix_server stop 2:备份原zabbix的数据库数据,以及相关文件. mysqldump - ...
 - C# 基础知识系列- 14 IO篇 文件的操作 (3)
			
本篇继续前两篇内容,跟大家介绍一下Path类以及FileSystemInfo这个类的主要方法和属性. 上文提到,在<C# 基础知识系列-IO篇>之文件相关的内容完结之后,会带领大家开发一个 ...
 - 补题Codeforces 1102E. Monotonic Renumeration
			
这个题还是不太懂,下面附上的是大佬的题解(https://zhanghuimeng.github.io/post/codeforces-1102e-monotonic-renumeration/) E ...
 - javaweb系统调优方案
			
1. java代码优化 java代码优化6大原则 : https://blog.csdn.net/bunny1024/article/details/72803708 java代码优化: https: ...
 - JavaWeb开发规范
			
以下的建议将帮助你更有效地使用本文所描述的 Java 编程标准: ******************************************************* 当你写代码时就应该遵守 ...