GRPC 程序在 Kubernetes 中的负载均衡

news2024/11/15 11:08:28

本文的背景使用的是 kratos 框架。

背景

众所周知 grpc 底层使用 http2 协议,而 http2 是一个长链接多路复用的。在正常情况下客服端与服务端一对一不会需要负载均衡手段;但是当服务上云之后为了保障服务的可用性所以我们服务端一般是多副本,这种情况下客户端通过服务端的 service 名称与服务端建立连接而 service 会将流量轮训到后端的某一个 pod 这样就会造成客户端只会与某一个 pod 建立连接并且所有的请求流量都会到一个 pod 上面,其他任意数量的 pod 都是摆设。

解决方法

在讲述解决办法之前先知道以下一些概念

  • Headless Service
  • GRPC Name Resolver

Headless Service

首先 headless svc 是 k8s 一种服务,但它不为自己分配 IP 地址。相反,会返回与 svc 关联的 pod 的 ip,允许客户端直连到单个 pod(客户端和 pod 之间直接通信)。

和正常/普通的 svc 的却别就是普通的 svc 会分配一个 vip 地址,然后流量会通过这个 vip 轮训到后端的一个 pod;headless svc 不会分配任何 ip 地址并且返回 headless svc 会返回 pods ip。
在这里插入图片描述

GRPC Name Resolver

grpc 一些基础概念

  • Resolver:解析器,用于从注册中心实时获取当前服务端的列表,同步发送给 Balancer
  • Balancer:平衡器,一是接收从 Resolver 发送的服务端列表,建立并维护(长)连接状态;二是每次当 client 发起 rpc 调用时,按照算法从连接池中选择一个连接进行 rpc 调用
  • Register:注册,用于服务端初始化将自己信息上报到注册中心,主要信息有 ip、port

本文没有注册中心,使用 k8s api

简单的来说 Name Resolver(名称解析器)可以看作是一个 map[service-name][]backend-ip。它接收一个服务名称,并返回后端的 IP 列表。gRPC 中根据目标字符串中的 scheme 选择名称解析器。

解决方法的本质就是通过 headless svc + Name Resolver 自定一个解析器。

使用方法

通过 kuberesolver 程序实现;kuberesolver 是一个使用 k8s api 的 GRPC Name Resolver。

  • 创建 headless svc
  • 改造代码
apiVersion: v1
kind: Service
metadata:
  name: headless
  namespace: oasis-dev
spec:
  ports:
  - name: http
    port: 8000
    protocol: TCP
    targetPort: 8000
  - name: grpc
    port: 9000
    protocol: TCP
    targetPort: 9000
  selector:
    app: oasis-dev-prizesinfrastructure
  type: ClusterIP

改造源码

import "github.com/sercand/kuberesolver/v4"
kuberesolver.RegisterInCluster()
ep := fmt.Sprintf("kubernetes:///%s.%s:%d", service, namespace, port)
conn, err := transgrpc.DialInsecure(
	context.Background(),
	transgrpc.WithEndpoint(ep),
	transgrpc.WithTimeout(60*time.Second),
	transgrpc.WithMiddleware(
		recovery.Recovery(),
		tracing.Client(),
	),
)

使用缺点

  • 使用 kuberesolver 只能在集群内运行
  • 使用 kuberesolver 部署需要一个 service account

原理

上文讲到 kuberesolver 内部自定义了一个名称解析器;其中 kubeBuilder 和 kResolver 结构体分别实现了 grpc 的 Builder 接口和 Resolver 接口。

注册解析器

// RegisterInCluster registers the kuberesolver builder to grpc with kubernetes schema
// kubernetesSchema = kubernetes
func RegisterInCluster() {
	RegisterInClusterWithSchema(kubernetesSchema)
}

// RegisterInClusterWithSchema registers the kuberesolver builder to the grpc with custom schema
func RegisterInClusterWithSchema(schema string) {
	resolver.Register(NewBuilder(nil, schema))
}

// NewBuilder creates a kubeBuilder which is used by grpc resolver.
func NewBuilder(client K8sClient, schema string) resolver.Builder {
	return &kubeBuilder{
		k8sClient: client,
		schema:    schema,
	}
}

grpc.DialInsecure 或者 grpc.Dial 函数中会先根据 kubernetes 这个 scheme 找到我们通过 RegisterInCluster 函数注册的 kubeBuilder,然后调用它的 Build() 方法构建我们自定义的 kResolver。kResolver 的 watch 方法一直以协程的方式监听我们指定的 headless svc 对应的 endpoints(如果 pod 上面或者销毁会被程序立即感知到)。

func (b *kubeBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	if b.k8sClient == nil {
		if cl, err := NewInClusterK8sClient(); err == nil {
			b.k8sClient = cl
		} else {
			return nil, err
		}
	}
	ti, err := parseResolverTarget(target)
	if err != nil {
		return nil, err
	}
	if ti.serviceNamespace == "" {
		ti.serviceNamespace = getCurrentNamespaceOrDefault()
	}
	ctx, cancel := context.WithCancel(context.Background())
	r := &kResolver{
		target:    ti,
		ctx:       ctx,
		cancel:    cancel,
		cc:        cc,
		rn:        make(chan struct{}, 1),
		k8sClient: b.k8sClient,
		t:         time.NewTimer(defaultFreq),
		freq:      defaultFreq,

		endpoints: endpointsForTarget.WithLabelValues(ti.String()),
		addresses: addressesForTarget.WithLabelValues(ti.String()),
	}
	go until(func() {
		r.wg.Add(1)
		err := r.watch()
		if err != nil && err != io.EOF {
			grpclog.Errorf("kuberesolver: watching ended with error='%v', will reconnect again", err)
		}
	}, time.Second, ctx.Done())
	return r, nil
}

watch 中一共有三个地方会触发 grpc address 的更新分别如下

  • k.t.C:定时器触发 30 min 一次
  • k.rn:ResolveNow 触发
  • up, hasMore := <-sw.ResultChan():streamWatcher 有新消息(事件)通知时,调用 k.handle(up.Object) 处理事件,即监听的资源发生了改变(创建/销毁/更新)
func (k *kResolver) watch() error {
	defer k.wg.Done()
	// watch endpoints lists existing endpoints at start
	sw, err := watchEndpoints(k.ctx, k.k8sClient, k.target.serviceNamespace, k.target.serviceName)
	if err != nil {
		return err
	}
	for {
		select {
		case <-k.ctx.Done():
			return nil
		case <-k.t.C:
			k.resolve()
		case <-k.rn:
			k.resolve()
		case up, hasMore := <-sw.ResultChan():
			if hasMore {
				k.handle(up.Object)
			} else {
				return nil
			}
		}
	}
}
func (k *kResolver) ResolveNow(resolver.ResolveNowOptions) {
	select {
	case k.rn <- struct{}{}:
	default:
	}
}

以上三种途径最后都会通过 k.handle() 处理。

func (k *kResolver) resolve() {
	e, err := getEndpoints(k.k8sClient, k.target.serviceNamespace, k.target.serviceName)
	if err == nil {
		k.handle(e)
	} else {
		grpclog.Errorf("kuberesolver: lookup endpoints failed: %v", err)
	}
	// Next lookup should happen after an interval defined by k.freq.
	k.t.Reset(k.freq)
}

k.makeAddresses 会循环 endpoints 的 subsets 返回一个 []resolver.Address 用于解析器调用 NewAddress 来通知 ClientConn 一个新的解析地址列表(即服务列表更新通知接口)

func (k *kResolver) handle(e Endpoints) {
	result, _ := k.makeAddresses(e)
	//	k.cc.NewServiceConfig(sc)
	if len(result) > 0 {
		k.cc.NewAddress(result)
	}

	k.endpoints.Set(float64(len(e.Subsets)))
	k.addresses.Set(float64(len(result)))
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/510426.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用chatgpt实现 java导出excel复杂表。

记录一次使用chatgpt解决实际问题的&#xff0c;需求是在页面添加一个订单导出excel的功能&#xff0c;订单编号、订单明细&#xff0c;相同订单编号合并单元格&#xff0c;模板如下 表头表尾不用说&#xff0c; 主要是表格内容部分&#xff0c;左边是订单编号&#xff0c;右边…

clickhouse linux 离线安装

1. 下载离线安装包&#xff0c;下四个包&#xff0c;版本号要一致, (在此下的是20.8.3.18-1.el7.x86_64版) clickhouse-server, clickhouse-client, clickhouse-common-static, clickhouse-server-common Altinity/clickhouse - Packages packagecloudBrowse pa…

人工智能基础部分15-自然语言处理中的数据处理上采样、下采样、负采样是什么?

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分15-自然语言处理中的数据处理上采样、下采样、负采样是什么&#xff1f;在自然语言处理中&#xff0c;上采样、下采样、负采样都是用于处理数据不平衡问题的技术&#xff0c;目的是为了优化模型的训…

《记录》chariles配置

Charles配置 1、下载&#xff1a;官网下载-傻瓜式安装 2、windows配置解析pc端的https包 http包解析是配好的&#xff0c;需要自行配置https 2.1、下载证书 1、如图点击 之后下一步点完成就行。 2.2、代理设置 这里勾选其实勾不勾都行&#xff0c;默认不改也行。我是看了…

DC域控服务器与辅助DC域控服务器之间的数据同步以及创建域组织机构和域用户

本篇主要是处理DC域控服务器与辅助DC域控服务器之间的数据同步关系&#xff0c;DC域控服务器与辅助DC域控服务器的创建可以参考上篇文章 验证DC域控服务器与辅助DC域控服务器之间的数据同步关系&#xff0c;分别在辅助DC域控服务器DC上面新建一个用户&#xff0c;看看再对应的…

一名【合格】前端工程师的自检清单

1.JavaScript规定了几种语言类型&#xff1f; 基本数据类型&#xff1a;number、string、boolean、null、undefined、symbol(es6) 对象引用类型&#xff1a;Array、Function、Object、RegExp、Error、Date 2.JavaScript对象的底层数据结构是什么&#xff1f; JavaScript 对象…

甄云科技对话格瑞德,探讨高复杂度采购事业的数“智”解决之道

在由甄云科技主办的客户高层访谈节目“甄知访谈”中&#xff0c;本期我们走进山东格瑞德集团&#xff0c;一起来分享格瑞德的采购数字化转型之路。由甄云科技总裁姚一鸣对话格瑞德集团有限公司供应链总经理徐涵先生。 山东格瑞德集团成立于 1993 年&#xff0c;是一家围绕人工…

[pgrx开发postgresql数据库扩展]7.返回序列的函数编写(2)表序列

前文再续&#xff0c;书接上一回。 上一节我们简单说了利用SetOfIterator返回一个srf&#xff08;Set Returning Functions&#xff09;&#xff0c;但是很多情况下&#xff0c;一个单值序列并不能很好的满足我们的需求&#xff0c;所以今天我们来说另外一个作用更广泛的srf&a…

您有一条群邀请消息:施耐德电气“绿色智能制造创赢计划”第四季正式启动!

4月26日&#xff0c;由工业和信息化部国际经济技术合作中心与施耐德电气共同主办的“2023绿色智能制造创赢计划”第四季正式启动 。 这一计划为拥有**技术专长与发展潜力的中小企业提供联合共创平台&#xff0c;帮助企业加速突破工业场景中的关键痛点&#xff0c;孵化和落地更…

GDB调试-从安装到使用

1、GDB简介 gdb 工具是 GNU 项目调试器&#xff0c;基于命令行。和其他的调试器一样&#xff0c;我们可以使用 gdb 来一行行的运行程序、单步执行、跳入/跳出函数、设置断点、查看变量等等&#xff0c;它是 UNIX/LINUX 操作系统下强大的程序调试工具。对于一般的Linux桌面系统(…

Nginx:worker_processes、worker_connections设置

转自&#xff1a;Nginx&#xff1a;worker_processes、worker_connections设置_worker_connections设置多少_it_zhenxiaobai的博客-CSDN博客 worker_processes与worker_connections 设置好合适大小&#xff0c;可以提升 nginx 处理性能&#xff0c;非常重要。 原作者的话&…

【PSO-LSTM】基于PSO优化LSTM网络的电力负荷预测(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

eSIM证书要求-涉及规范SGP.22-SGP.26-2

subjectPublicKeyInfo 证书链中所有证书的subjectPublicKeyInfo中的OID都是一样的 CRL Distribution Point 证书吊销列表分发点 (CRL Distribution Point &#xff0c;简称 CDP) 是含在数字证书中的一个可以共各种应用软件自动下载的最新的 CRL 的位置信息。一个 CDP 通常出现…

egg.js + mysql + windows 踩坑全纪录

资料&#xff1a; egg.js文档&#xff08;https://www.eggjs.org/zh-CN/intro/quickstart&#xff09; 背景&#xff1a;前面的都很简单&#xff0c;按照官方文档配置即可&#xff0c;全部调通以后&#xff0c;开始接触数据库mysql 因为米有后台开发背景&#xff0c;所以需要从…

从IDC数据库安全报告,看OceanBase安全能力

欢迎访问 OceanBase 官网获取更多信息&#xff1a;https://www.oceanbase.com/ 作为数据的承载工具&#xff0c;数据库自身安全能力对于数据安全至关重要。数据库软件诞生至今&#xff0c;经过了几十年的发展和演进&#xff0c;已经成为 IT 系统中不可或缺的关键技术。但是随着…

MySQL原理(三):索引

前言 上一篇介绍了 MySQL 的逻辑架构和执行过程&#xff0c;这一篇将介绍索引相关的内容。 索引是用额外的数据结构&#xff0c;来实现快速检索目标数据的。就像字典当中的目录一样&#xff0c;用额外的空间来存储部分内容&#xff0c;从而加快检索速度。 MySQL 的逻辑架构分…

Python——PyQt5在PyCharm的配置与应用(保姆级教程)

目录 一、安装pycharm与python版本 二、升级pip与换源&#xff0c;安装PyQt5、PyQt5-tool 三、添加环境变量 四、在pycharm的外部工具里添加3个工具 4.1、添加三个插件&#xff08;重点&#xff09; 五、如何使用QtDesigner 六、如何使用pyuic5 一、安装pycharm与python版…

C语言——扫雷小游戏(递归展开版)

哈喽&#xff0c;大家好&#xff0c;上次我们已经学习了三子棋小游戏&#xff0c;今天我们来学习扫雷小游戏了。 目录 1.游戏介绍 2.函数部分 2.1菜单 2.2game()函数 2.3mian()函数 2.4初始化棋盘 2.5打印棋盘 2.6布置雷 2.7排查雷 2.8统计雷 2.9递归,展开一片区域 …

眼球追踪、HDR、VST,从代码挖掘Valve下一代VR头显

擅长爆料、挖掘线索的Brad Lynch&#xff0c;此前发布了Quest Pro等设备的线索文章引发关注。​近期&#xff0c;又公布一系列与“Valve Deckard”VR头显相关消息&#xff0c;比如支持眼球追踪、HDR、VST透视、Wi-Fi网络等等。在SteamVR 1.26.1测试版更新、Steam用户端、Gamesc…

lazada、shopee转化率低怎么办?做好这几点,让你的店铺转化率提升

如若lazada, shopee如果转化率低&#xff0c;商家需要做好以下几个方面&#xff0c;通过以下几点来提高。毕竟只有流量没有转化率&#xff0c;店铺管理不好。 1、产品类别的选择 一个好的类别本身就是一个很好的排水渠道&#xff0c;可以给我们带来大量的流量&#xff0c;高流…