Tam's Blog

<- Quay về trang chủ

gRPC Load balancing (2) - Client architecture

Tiếp nối chuỗi bài viết về cân bằng tải gRPC, hôm nay mình sẽ tìm hiểu về các thành phần ở gRPC client, đặc biệt là những thành phần liên quan đến cân bằng tải.

Core component

Resolver

Resolver về cơ bản là một service discovery, khi client muốn gửi request, nó phải biết được địa chỉ của server. Hiện tại gRPC cung cấp một số cơ chế có sẵn để sử dụng:

Bên cạnh những cơ chế resolver mặc định, gRPC còn cung cấp API để người dùng có thể tự hiện thực, quá trình đăng kí và sử dụng như sau:

grpc-resolver

Balancer

Balancer hiện thực client-side load balancing cho gRPC client, ở phần trước, mình có mô tả về Client connection, gRPC sử dụng virtual connection này để quản lý các connection thực sự đến servers, thông qua đó để quản lý việc phân giải địa chỉ, khởi tạo kết nối, cập nhật trạng thái và cân bằng tải.

Giống như resolver, bên cạnh cung cấp các thuật toán cân bằng tải phổ biến, gRPC cũng cho phép người đăng kí các thuật toán tuỳ chỉnh, quá trình đăng kí và sử dụng giống với resolver.

grpc-resolver

Ví dụ

Mình sẽ hiện thực một custom resovler bằng Go để làm rõ hơn phần lý thuyết ở phần trước.

Phần code định nghĩa builder và đăng kí tới gRPC.

func init() {
	resolver.Register(&StaticBuilder{})
}

type StaticBuilder struct{}

func (sb *StaticBuilder) Build(target resolver.Target, cc resolver.ClientConn,
	opts resolver.BuildOptions) (resolver.Resolver, error) {
	endpoints := strings.Split(target.Endpoint(), ",")

	r := &StaticResolver{
		endpoints: endpoints,
		cc:        cc,
	}
	r.ResolveNow(resolver.ResolveNowOptions{})
	return r, nil
}

func (sb *StaticBuilder) Scheme() string {
	return "static"
}

Phần code định nghĩa static resolver.

type StaticResolver struct {
	endpoints []string
	cc        resolver.ClientConn
	sync.Mutex
}

func (r *StaticResolver) ResolveNow(opts resolver.ResolveNowOptions) {
	r.Lock()
	r.doResolve()
	r.Unlock()
}

func (r *StaticResolver) Close() {
}

func (r *StaticResolver) doResolve() {
	var addrs []resolver.Address
	for i, addr := range r.endpoints {
		addrs = append(addrs, resolver.Address{
			Addr:       addr,
			ServerName: fmt.Sprintf("instance-%d", i+1),
		})
	}

	newState := resolver.State{
		Addresses: addrs,
	}

	_ = r.cc.UpdateState(newState)
}

Khởi tạo client sử dụng static scheme.


const (
	target = "static:///127.0.0.1:50051,127.0.0.1:50052,127.0.0.1:50053"
)

func main() {
    conn, err := grpc.NewClient(target, 
                    grpc.WithTransportCredentials(insecure.NewCredentials()),
                    grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`))
}

Kết quả chạy chương trình:

grpc-static-resolver-result.png

Tổng kết

Thông qua bài viết này, chúng ta đã tìm hiểu về kiến trúc của gRPC client, biết được những thành phần và cách chúng hoạt động và tương tác với nhau:

điều này sẽ giúp chúng ta sử dụng gRPC dễ dàng hơn, đặt nền tảng để có thể triển khai các mô hình phức tạp hơn ở các bài sau.

Mã nguồn

Bạn có thể tham khảo và tuỳ chỉnh mã nguồn ở repository grpc-loadbalancing

Tham khảo