본문 바로가기
기타

gRPC 더욱 효율적으로 Load Balancing하기 / Client side load balancing

by Limm_jk 2022. 3. 12.

Client Side LB in gRPC

지난 몇 년 동안 마이크로 서비스의 성장과 함께 gRPC 는 이러한 소규모 서비스 간의 상호 통신에 대한 많은 인기를 얻었다. 인기를 얻은 이유 중 하나는 속도이다. HTTP / 1에서 구현된 REST Client는 요청을 전송할 때마다 연결을 생성하고, 응답이 오면 해당 연결을 끊는다. 이처럼 요청마다 connection을 생성하기 때문에 소모되는 비용이 많다. 하지만, gRPC는 HTTP 2.0 기반의 HTTP Streaming을 이용하여 통신한다. HTTP 2.0은 한번 연결된 HTTP 연결을 통신이 끝났을 때 끊지 않고, 장기간 유지하며 재사용할 수 있다. gRPC 또한 이를 이용하여 보통 하나의 연결을 맺어두고, 이 연결을 재사용하는 식으로 이용한다. 이 연결(TCP Connection)을 Channel이라는 개념으로 사용한다.

HTTP/2: Smarter at scale | Cloud Native Computing Foundation

 

HTTP/2: Smarter at scale | Cloud Native Computing Foundation

This guest post was written by Jean de Klerk, Developer Program Engineer, Google Much of the web today runs on HTTP/1.1. The spec for HTTP/1.1 was published in June of 1999, just shy of 20 years ago.

www.cncf.io

 

위와 같이 커넥션을 재사용하는 것은 많은 장점을 가지고 있으나, 단점도 존재한다. 그 단점 중 하나가 오늘 다룰 예정인 로드 밸런싱에서 크게 발생한다.

 

Channel을 생성한 클라이언트에서 하나의 서버가 버티기 어려울 정도의 대량의 요청을 보내기 시작했다고 가정하자. 이 시점에 우리는 스케일 아웃을 통하여 이 문제를 해결하곤 한다. 하지만 gRPC에서는 한번 생성된 Channel을 재사용하려는 경향이 있기 때문에 여러 인스턴스를 준비하더라도 요청은 모두 하나의 인스턴스로 이동하게 될 것이다.

grpc client load balancing 구현하기 (grpc 번외편)

 

grpc client load balancing 구현하기 (grpc 번외편)

안녕하세요, 오늘은 grpc 번외편으로 grpc load balancing에 대한 이야기를 해볼까 합니다. 최근에 회사에서 이 이슈때문에 많은 삽질을 해서 .. 헤매는 분들께 도움이 될만한 부분이 있을 것 같습니다.

corgipan.tistory.com

 

이런 문제를 해결하기 위하여 client side load balancing이 존재한다. client에서 알고리즘을 구현하고, 연결을 유지하며 load balancing을 하는 방식인데 gRPC에서 지원해주는 방식은 세가지로 나뉜다.

Client Side LB

Pick First LB

세팅 시, 따로 설정을 해주지 않는다면 기본적으로 Pick First LB가 적용된다.

동작

  1. Name Resolver에서 address 목록을 가져온다.
  2. 연결 가능한 address를 찾을 때까지 순서대로 한 번에 하나씩 연결 시도한다.
  3. address 중 하나에 연결할 수 있다면 Channel의 상태를 READY로 설정한다.
  4. 전송되는 모든 RPC가 해당 address로 전송된다.
  5. 이후 해당 address의 연결이 끊어진다면 Channel의 상태를 IDLE 상태로 변경한다.
  6. IDLE 상태에서 요청이 발생하면 go to 2.
  7. 연결할 수 있는 address가 없다면 back off에 설정된 만큼의 재시도를 거친다.

Round Robin LB

// in example
roundrobinConn, err := grpc.Dial(
	fmt.Sprintf("%s:///%s",exampleScheme, exampleServiceName),
	~~grpc.WithBalancerName("round_robin"),~~ // deprecated
	grpc.WithInsecure(),
)

// recommended
roundrobinConn, err := grpc.Dial(
	fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName),
	grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`), // This sets the initial balancing policy.
	grpc.WithTransportCredentials(insecure.NewCredentials()),
)

Round-Robin LB는 name resolver에서 address 목록을 가져오고 각 address에 대하여 subchannel을 생성한다. 위에서 언급했던 클라이언트가 연결 가능한 모든 서버와 연결을 하는 부분이다.

subchannel의 연결이 끊어질 때마다 적절한 back off로 다시 연결을 시도한다.

channel의 상태는 subchannel의 상태를 통하여 설정된다

READY → CONNECTING → IDLE 의 순위로 각 상태의 subchannel이 하나라도 있다면, 가장 높은 순위의 상태로 channel의 상태가 정해진다.

channel을 통하여 RPC가 전송이 될 떄, READY 상태인 subchannel을 순회하며 사용한다.

Look aside (xDS)

이 방법은 서버 인스턴스 간 트래픽을 분산하기 위하여 외부의 load balancing smart를 위한 LB server가 필요하다.

외부의 LB 서버에서 독립적인 로드 밸런싱 로직과 서버 인스턴스의 정보를 알고 있어 논리적이고 성능이 좋은 load balancing이 가능하다. 또한, client는 위의 round robin과 같이 연결을 유지하고 있어서 재사용 가능한 연결의 성능적 장점 또한 챙길 수 있다.

하지만, LB 서버가 필요하다는 단점이 있다. 즉, LB만를 위한 서버가 필요하며 이 서비스를 관리해야 한다. 유지 보수, 운영, 모니터링, 경고, 새로운 point of failure 등등 신경 써야 할 부분이 많이 늘어난다.

이전에는 grpc에서 제공하는 grpclb라는 라이브러리를 사용하여 Look aside loadbalancing을 수행했다고 한다. 하지만, 다양한 지원 플랫폼의 이유로 인하여 xDS 프로토콜을 사용하는 라이브러리를 통하여 수행하는 것이 권장되도록 바뀌었다. - grpclb는 deprecated

reference

Test

설정

  • 클라이언트에서 1초마다 요청이 발생한다.
  • xds 서버는 10초마다 target server를 UpstreamPort를 순회하며 교체한다.

Pick First LB

  1. 첫 동작 시, 먼저 선언된 서버인 server 1부터 동작함.
  2. 4번 메세지가 간 시점에서 server 1을 종료함.
  3. server 2로 연결되어서 지속 동작
  4. 9번 메세지 이후 server 2도 종료.
  5. err에 의하여 종료

클라이언트가 연결을 처음 요청했을 시점부터 server 2만 살아 있었다면, 바로 server 2로 연결된다.

 

처음에 server 1로 연결되었다가 server 1이 죽으면 server 2로 연결된다. (Response 6)

이후 server 1이 다시 살아나고, server 2가 죽으면 다시 server 1로 연결된다. (Response 16)

Round-Robin LB

클라이언트가 n번 요청을 보낼 때, server 1, server 2 subchannel을 만들고 하나씩 보내는 것을 확인할 수 있다.

 

case 1 : channel 연결 시, server 2만 살아있는 상태
case 2 : Response 9 이후로 server 1이 죽을 때

위의 예시처럼 처음부터 하나의 서버만 살아있거나, 하나의 서버가 도중에 죽는다면, 남아있는 READY 상태의 서버로 동작한다.

 

이후 server 1이 살아난다면 다시 subchannel을 만들고 하나씩 보낸다.

Look aside LB

상단의 설정과 같이 xDS 서버는 10초 이후에 보낼 서버를 server 2로 변경한다.

그로 인하여 Response 10부터 server 2에서 오는 것을 확인할 수 있다.

 

별 다른 로직을 구현해두지 않아서 server 1이 죽으면 위의 다른 LB 정책들과 다르게 바로 죽는다.

이를 위하여 xDS에서 지원하는 round_robin 로직을 추가적으로 사용하거나, 직접 원하는 방향으로 개발하여 사용할 수 있다.

 

중간에 xds서버가 죽으면 죽기 이전에 알던 서버로 계속 연결이 된다..?

 

결론

성능이 중요하고, 리소스가 되고, 구체적인 제어를 원하고 가능하다면 Look aside (xDS) 사용하는 것이 권장됨.

  • xds로 넘어오면서 service mesh에도 강점을 얻음

아니면 connection 재사용과 로드밸런싱 효율성 및 클라이언트 제어 가능성을 고려해서

round robin vs proxy

 

위 테스트에 사용한 코드

https://github.com/Limm-jk/grpc_client_lb_example

 

GitHub - Limm-jk/grpc_client_lb_example: reference : https://github.com/salrashid123/grpc_xds

reference : https://github.com/salrashid123/grpc_xds - GitHub - Limm-jk/grpc_client_lb_example: reference : https://github.com/salrashid123/grpc_xds

github.com

 

Reference

HTTP/2: Smarter at scale | Cloud Native Computing Foundation

엔드 투 엔드 HTTP/2 지원을 통해 gRPC 워크로드를 뒷받침하는 Application Load Balancer

gRPC Load Balancing may not be that easy

gRPC Channel

https://github.com/evanj/grpclb_experiment

xDS

grpc-go/examples/features/xds at master · grpc/grpc-go

https://github.com/salrashid123/grpc_xds

https://github.com/salrashid123/grpc_xds

grpclb

grpc-proto/load_balancer.proto at master · grpc/grpc-proto

xDS GLB

proposal/A27-xds-global-load-balancing.md at master · grpc/proposal

grpc/connection-backoff.md at master · grpc/grpc

grpc/grpc_xds_features.md at master · grpc/grpc

gRPC Client-Side Load Balancing in Go

gRPC Load Balancing

Why load balancing gRPC is tricky?

댓글