grpc 负载均衡 ( DNS负载均衡java客户端负载均衡nginx反向代理负载均衡,k8s集群环境负载均衡 ) 学习总结

大纲

grpc基于http2协议实现,所以可以几种负载均衡的方式

grpc DNS负载均衡

DNS负载均衡的原理使用 DNS轮询机制一个域名配置多个IP地址,每次发起连接请求前,客户端主机请求域名解析服务获取对应域名的IP。域名解析服务轮询输出域名对应的IP实现负载均衡。

注意:由于grpc基于http2协议(长连接) 所以当创建连接时候就已经选择好了后端的服务。所以测试时候需要启动多个grpc客户端程序验证

本次测试使用K8S 首选DNS服务器CoreDNS

在这里插入图片描述

使用CoreDNS实现自定义的DNS服务

使用CoreDNS来实现一个自定义域名解析服务

关于CoreDNS的安装使用可以参考《k8s kubernetes 核心组件 CoreDNS 域名解析服务 学习总结》

step1 创建区域配置文件

例如我们创建一个 db.mygrpc.com 区域配置文件内容如下

@  IN SOA grpc.mygrpc.com. liuyijiang3430.qq.com. (
                               1000   ; serial number
                               1h      ; refresh interval
                               10m     ; retry interval
                               3w      ; expiry period
                               1h      ; negative TTL
)

grpc    IN      A       192.168.0.211
        IN      A       192.168.0.210

在这里插入图片描述

step2 创建Corefile配置文件

创建Corefile配置文件加入我们自定义的区域配置mygrpc.com

mygrpc.com {
  file db.mygrpc.com
  loadbalance round_robin
}


. {
  forward . 114.114.114.114
  cache 30
  errors
  log
}

在这里插入图片描述

使用./coredns 启动CoreDNS 注意Corefile配置文件coredns在同一个文件夹

step3 修改主机域名解析服务

修改主机的DNS域名服务器地址,以下为win11为例子
在这里插入图片描述

执行nslookup命令 查询grpc.mygrpc.com这个域名两次执行nslookup命令之间稍微等待以下,可以刷新缓存

在这里插入图片描述

执行ping 命令可以看到域名对应ip 动态切换

在这里插入图片描述

到此 已经实现了DNS域名的负载均衡

测试grpc DNS负载均衡客户程序

实验前已经在192.168.0.210 192.168.0.211上启动my-grpcdemoserver项目(此项目一个grpc服务端程序

客户代码简单 (代码见my-grpcbasedemo/Client.java

创建channel
在这里插入图片描述

main方法循环创建channel 访问接口

在这里插入图片描述

注意需要关闭JVM的DNS缓存
java.security.Security.setProperty("networkaddress.cache.ttl", "0");

在这里插入图片描述

使用DNS负载均衡需要注意

grpc 客户端负载均衡

客户端负载均衡的核心是: 客户端能发现所有的后端服务,并且自己实现负载均衡算法

客户端负载均衡需要自行实现服务发现部分,负载均衡算法grpc已经提供了两个常用的round_robin  pick_first

例子使用grpcjava 实现,项目使用maven构建 pom.xml文件引入

<dependency>
	<groupId>io.grpc</groupId>
	<artifactId>grpc-all</artifactId>
	<version>1.26.0</version>
</dependency>

grpcjava 实现客户端负载均衡流程

简单版的代码实现

所有的复杂方式例如基于ectd zookeeper等都是基于以下最简单的代码实现的

实例代码:SimpleNameResolver.java

//自定义一个简单名字解析器   
public class SimpleNameResolver extends NameResolver {

	/**
	 * 必须配置一个服务权限
	 */
	@Override
	public String getServiceAuthority() {
		return "authority:none";
	}

	@Override
	public void shutdown() {
		
	}
	
	/**
	 *  创建等效地址组
	 *  这些地址就是会被负载均衡轮询地址
	 *  数据可以zookeeper nacos etcd coreDNS中获 这里写死两个地址127.0.0.1:55331 127.0.0.1:55332
	 */
	private List<EquivalentAddressGroup> initAddressGroups() {
		/**
		 * 创建等效地址组
		 */
		List<EquivalentAddressGroup> addressGroups = new ArrayList<>();
		/**
		 * 两个GRPC 服务端程序地址
		 */
		SocketAddress address1 = new InetSocketAddress("127.0.0.1",55331);
		SocketAddress address2 = new InetSocketAddress("127.0.0.1",55332);
		
		/**
		 * 创建等效地址组
		 */
		EquivalentAddressGroup group1 = new EquivalentAddressGroup(address1);
		EquivalentAddressGroup group2 = new EquivalentAddressGroup(address2);
		addressGroups.add(group1);
		addressGroups.add(group2);
		return addressGroups;
	}
	
	/**
	 * 核心重写start(Listener2 listener) 方法 手动listener设置多个等效地址
	 * listener.onResult(rr);
	 * 
	 * 以下代码是可以正常执行的
	 */
	@Override
    public void start(Listener2 listener) {
		System.out.println("================ start(Listener2 listener) ====================");
		ConfigOrError configOrError = ConfigOrError.fromError(Status.NOT_FOUND);
		
		ResolutionResult rr = ResolutionResult.newBuilder()
                .setAddresses(initAddressGroups()) //传入效地址组 即会被负载轮询的地址
                .setAttributes(Attributes.EMPTY)
                .setServiceConfig(configOrError)
                .build();
		/**
		 * listener设置多个等效地址
		 * 
		 * 这样grpc客户端就可以使用这些存在的GRPC服务端程序地址
		 * 
		 * 在创建ManagedChannel channel指定负载均衡策略为轮询 就可以实现最简单的客户端负载均衡
		 * ManagedChannelBuilder.defaultLoadBalancingPolicy("round_robin")
		 */
		listener.onResult(rr);
    }
	
}

SimpleNameResolver的作用就是获取所有的后端服务,这里还可以自行实现从zookeeper nacos etcd coreDNS中获取后端服务

实例代码:SimpleNameResolverProvider.java

//NameResolver提供者
public class SimpleNameResolverProvider  extends NameResolverProvider {

	/**
	 * 定义此NameResolverProvider 的DefaultScheme
	 */
	private static final String SCHEME = "simple";
	
	/**
	 * 重写newNameResolver方法
	 * 此方法可以根据URI 即ManagedChannelBuilder.forTarget方法出入的参数
	 * 
	 * 解析得到相关数据,做初始化功能例如创建zookeepr nacos etcd等客户端
	 * 
	 */
	@Override
    public NameResolver newNameResolver(URI targetUri, Attributes params) {
        if (!SCHEME.equals(targetUri.getScheme())) {
            return null;
        }
        return new SimpleNameResolver();
    }

	
	@Override
	protected boolean isAvailable() {
		return true;
	}

	@Override
	protected int priority() {
		return 1;
	}

	@Override
	public String getDefaultScheme() {
		return SCHEME;
	}

SimpleNameResolverProvider 的作用就是提供NameResolver

创建Channel代码如下

//远程连接管理器,管理连接的生命周期
private ManagedChannel channel;
private SearchServiceGrpc.SearchServiceBlockingStub blockingStub;

public ClientLoadbalancer() {
	/**
	 * 测试简单负载均衡
	 * .forTarget("simple:///")
	 */
	NameResolverRegistry.getDefaultRegistry().register(new SimpleNameResolverProvider());
	/**
	 * traget
	 * 通道不会直接调用解析程序匹配的 URI。 而是创建一个匹配解析程序
	 */
	channel = ManagedChannelBuilder
			 // 设置连接的目标地址
			 .forTarget("simple:///")
	    	 /**
	    	  *  设置轮询策略
	    	  */
	      .defaultLoadBalancingPolicy("round_robin")
	      .usePlaintext()
      .build();
	
	    //初始化远程服务Stub
    blockingStub = SearchServiceGrpc.newBlockingStub(channel);
}

运行程序可以看到,客户端请求轮询到两个后端grpc服务上了

在这里插入图片描述

使用客户端负载均衡需要注意

反向代理负载均衡

个人觉得反向代理负载均衡是最适合grpc的一种负载均衡的方式

因为grpc使用的是http2协议,任何支持http2协议的反向代理都可以实现负载均衡。并且不需要客户端有额外的代码开发工作,客户端专注业务功能实现

可以使用nginx ,Envoy等实现grpc负载均衡

非k8s集群环境 grpc nginx负载均衡

安装nginx

nginx从1.13.10开始支持grpc 同时nginx安装时需要指定 http2模块 (–withhttp_v2_module

nginx 安装 注意需要开启http2模块withhttp_v2_module

ubuntu 安装nginx
apt-get install openssl libssl-dev
apt-get install libpcre3-dev

./configure --prefix=/ops/openresty/openresty  --with-http_v2_module

nginx 配置

upstream grpc-server{
    server 192.168.0.210:55333;
    server 192.168.0.211:55333;
}

server {
   # 注意使用http2表示server支持http2
   listen 885 http2;
   charset utf-8;
   # 注意域名 
   server_name localhost;

   # 注意使用grpc_xx
   location / {
         grpc_pass         grpc://grpc-server;
         grpc_set_header   Host             $host;
         grpc_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
         grpc_set_header   X-Real-IP $remote_addr;
   }

}

客户端代码,使用ManagedChannelBuilder.forAddress 连接到nginx

  //nginx服务ip
	host= "192.168.0.160";  
	port = 885;
	
channel = ManagedChannelBuilder.forAddress(host, port)
        .usePlaintext()
        .build();

//初始化远程服务Stub
blockingStub = SearchServiceGrpc.newBlockingStub(channel)

运行测试客户端

在这里插入图片描述

k8s集群环境下处理方式

其实k8s集群环境和普通的主机环境部署基本思路是一致的。k8s只是对主机的一种虚拟化而已。由于grpc 使用http2协议实现,所以创建连接后会一直复用连接,k8s的service域名轮询访问就没有意义了

k8s集群环境下实现负载均衡还是可以使用

  • 客户端负载均衡
  • 反向代理负载均衡(反向代理需要部署在k8s集群内)

以下使用 nginx反向代理做grpc负载均衡测试

本次测试grpc客户端 服务端都部署在k8s集群内

Step1 安装nginx

关于k8s nginx的搭建参考《k8s 部署nginx 实现集群统一配置,自动更新nginx.conf配置文件 总结》

注意事项

k8s自定义Pod域名可以参考《k8s-Pod域名学习总结》

nginx部署yaml

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: grpc-nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grcp-nginx
  template:
    metadata:
      labels:
        app: grcp-nginx
    spec:
      imagePullSecrets:
        - name: myaliyunsecret
      #注意使用hostname + subdomain 实现对nginx在集群内部的域名访问
      hostname: nginx-host
      subdomain: nginx-inner-domain
      containers:
          # 使用基于nginx:1.23.3 为基础镜像制作的可热更新的nginx
        - image: registry.cn-hangzhou.aliyuncs.com/jimliu/nginx-auto-reload:latest
          name: grpc-nginx-containers
          volumeMounts:
            - mountPath: "/etc/nginx/conf.d/"
              name: config-volume
      volumes:
        - name: config-volume
          # 使用configmap实现统一配置nginx
          configMap:
            name: grcp-nginx-config

---

apiVersion: v1
kind: Service
metadata:  
  # # 注意name为 pod中 subdomain名称
  name: nginx-inner-domain  
spec:
  selector:  
    app: grcp-nginx
  clusterIP: None  #注意  clusterIP 为None            

nginx configmap yaml

# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: grcp-nginx-config
data:
  nginx.conf: |
    
      # upstream使用 gpc服务端的域名访问
      upstream grpc-server{
        server grpc-server-host.grpc-server-inner-domain.default.svc.cluster.local:55333;
      }
    
      # 集群内部使用
      server {
         listen 1885 http2;
         charset utf-8;
         # 指定server_name 为nginx的域名
         server_name nginx-host.nginx-inner-domain.default.svc.cluster.local;
         
         location / {
           grpc_pass         grpc://grpc-server;
           grpc_set_header   Host             $host;
           grpc_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
           grpc_set_header   X-Real-IP $remote_addr;
         }
      
      }

在这里插入图片描述

Step2 部署grpc服务端程序

注意事项

grpc服务端程序 deploy.yaml如下

apiVersion: apps/v1
kind: Deployment
metadata:
   name: grpc-server-deploy
spec:
    replicas: 3 #部署三个grpc 服务端
    selector:
       matchLabels: 
          app: grpc-server
    template:
       metadata:
           labels:
              app: grpc-server
       spec:     
            imagePullSecrets:
               - name: myaliyunsecret
            # hostname + subdomain定义Pod的域名
            hostname: grpc-server-host
            subdomain: grpc-server-inner-domain
            containers:
                - name: grpc-server-containers
                  image: registry.cn-hangzhou.aliyuncs.com/jimliu/grpc-server:latest
                  imagePullPolicy: Always
                  ports: 
                      - containerPort: 55333 
                        protocol: TCP 
                        name: http2 
  
 
---
# 内部访问
apiVersion: v1
kind: Service
metadata:  
  name: grpc-server-inner-domain  # 注意name为 pod中 subdomain 的名称
spec:
  selector:  
    app: grpc-server
  clusterIP: None  #注意  clusterIP 为None

部署服务端

在这里插入图片描述

Step3 部署grpc客户端程序

grpc客户端程序也是一个springboot项目,创建channel的代码如下

在这里插入图片描述

application.properties

在这里插入图片描述

打包好项目后,创建镜像,并推送阿里私库

在这里插入图片描述

client端的部署yaml 如下

apiVersion: apps/v1
kind: Deployment
metadata:
   name: grpc-client-deploy
spec:
    replicas: 1
    selector:
       matchLabels: 
          app: grpc-client
    template:
       metadata:
           labels:
              app: grpc-client
       spec:     
            imagePullSecrets:
               - name: myaliyunsecret
            # hostname + subdomain 自定义Pod的域名
            hostname: grpc-client-host
            subdomain: grpc-client-inner-domain
            containers:
                - name: grpc-server-containers
                  image: registry.cn-hangzhou.aliyuncs.com/jimliu/grpc-client:latest
                  imagePullPolicy: Always
                  ports: 
                      - containerPort: 5522 
                        protocol: TCP 
                        name: http2 
  
 
---
# 外部访问的接口 
apiVersion: v1
kind: Service
metadata:  
  name: grpc-client-service  
spec:
  ports:
    - protocol: TCP
      port: 15522
      targetPort: 5522
      nodePort: 15522  #暴露一个集群外访问的端口
      name: http
  selector:  
    app: grpc-client
  type: NodePort

部署grpc 客户端项目

在这里插入图片描述

Setp4 测试结果

浏览器访问接口

在这里插入图片描述

在这里插入图片描述

[外链图片转存失败,源站可能防盗链机制,建议图片保存下来直接上传(img-AkHlbC0X-1679823441159)(5.3-5.png)]

kubectl logs -f 查看三个服务端日志

在这里插入图片描述

原文地址:https://blog.csdn.net/liuyij3430448/article/details/129781935

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_33312.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注