微服务之负载均衡

LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载“均摊”到不同的机器上。 避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。 通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载。 在为高负载服务器分流的同时,还可以避免资源浪费,一举两得。

应用层的负载均衡器,例如nginx,可以将请求发散到后端多个服务,实现负载均衡。

实现方式

  • 集中式

    在客户端和服务端之间有一个独立的LB(通常是专门的硬件设备如FS,或者基于软件如LVS,HAproxy等实现)

    LB上有所有服务的地址映射表,通过这个映射表实现流量转发

  • 进程内(gRPC采用的方式就是这种)

    由客户端内的一个子进程管理,进程获取所有服务端信息,并且保持所有服务端的连接,通过某个LB策略访问某个服务端。

  • 独立进程

    Service Mesh的思想,使用一个本地的proxy 进程,通过与本地proxy进程通信,由proxy服务实现LB

负载均衡算法

负载均衡算法决定了后端的哪些健康服务器会被选中。几个常用的算法:

  • Round Robin(轮询):为第一个请求选择列表中的第一个服务器,然后按顺序向下移动列表直到结尾,然后循环。
  • Least Connections(最小连接):优先选择连接数最少的服务器,在普遍会话较长的情况下推荐使用。
  • Source:根据请求源的 IP 的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。

k8s环境上,k8s通过svcprobe实现服务发现,但是没有提供七层负载均衡能力,在pod扩容时,负载均衡能力就失效了。因此,需要gRPC自己实现负载均衡。

具体实现方案可以查看推荐文档:gRPC(Go)教程(十三)— Kubernetes 环境下的 gRPC 负载均衡

相比较之下,更加推荐使用专门提供负载能力的consul实现

gRPC使用负载均衡

官方文档:Load Balancing in gRPC

image-20220929165837352

使用consul配合gRPC

安装

1
go get github.com/mbobakov/grpc-consul-resolver

客户端实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"time"
"log"

_ "github.com/mbobakov/grpc-consul-resolver" // It's important

"google.golang.org/grpc"
)

func main() {
conn, err := grpc.Dial(
// 请求服务的名称
"consul://127.0.0.1:8500/mitaka-grpc?wait=14s",
grpc.WithInsecure(),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
...

client := minegrpc.NewGreeterClient(dial)

// 获取到的client在多goroutine中使用,可以起到lb的作用
reply, err := client.SayHello(ctx, &minegrpc.HelloRequest{Name: "mitaka"})
}

用法:

consul://[user:password@]127.0.0.127:8555/my-service?[healthy=]&[wait=]&[near=]&[insecure=]&[limit=]&[tag=]&[token=]

真实测试案例

启动两个gRPC服务,都注册到consul

image-20221009172559703

负载均衡的现象

1
2
3
4
5
6
7
8
➜  client go run main.go
reply: message:"i am service 1"
➜ client go run main.go
reply: message:"i am service"
➜ client go run main.go
reply: message:"i am service"
➜ client go run main.go
reply: message:"i am service 1"

关于长连接:

发送请求时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@n206 ~]# netstat -an | grep -E "36937|40941|8500"
tcp 0 0 10.0.0.1:37638 10.0.0.206:36937 TIME_WAIT
tcp 0 0 10.0.0.1:33788 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:34094 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:60304 10.0.0.161:40941 TIME_WAIT
tcp 0 0 127.0.0.1:43786 127.0.0.1:8500 FIN_WAIT2
tcp 0 0 10.0.0.1:38040 10.0.0.206:36937 TIME_WAIT
tcp 0 0 10.0.0.1:33284 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:32842 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:59526 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:35834 10.0.0.206:36937 TIME_WAIT
tcp 0 0 10.0.0.1:36790 10.0.0.206:36937 TIME_WAIT
tcp 0 0 10.0.0.1:34748 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:59916 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:40278 10.0.0.206:36937 TIME_WAIT
tcp 0 0 10.0.0.1:39350 10.0.0.206:36937 TIME_WAIT
tcp 0 0 10.0.0.1:40042 10.0.0.206:36937 TIME_WAIT
tcp 0 0 10.0.0.1:60674 10.0.0.161:40941 TIME_WAIT
tcp 0 0 10.0.0.1:38968 10.0.0.206:36937 TIME_WAIT
tcp6 0 0 :::8500 :::* LISTEN
tcp6 0 0 127.0.0.1:8500 127.0.0.1:43786 CLOSE_WAIT

客户端在本地,127.0.0.1:43786请求127.0.0.1:8500,也就是rpc客户端请求consul服务端而不是rpc客户端与rpc服务端直接通信。consul服务端与rpc服务端保持长连接,中间过程类似于一个代理的作用。

推荐阅读:

什么是负载均衡?

gRPC(Go)教程(十三)— Kubernetes 环境下的 gRPC 负载均衡