GRPC : Java / Scala에서 처리량이 많은 클라이언트 만들기


9

메시지를 매우 빠른 속도로 전송하는 서비스가 있습니다.

현재 akka-tcp에서 제공하며 분당 3.5M 메시지를 생성합니다. 나는 grpc를 시도하기로 결정했다. 불행히도 처리량은 훨씬 작습니다. 분당 ~ 55,000 개의 메시지가 훨씬 적습니다.

최적화 방법을 추천 해 주시겠습니까?

내 설정

하드웨어 : 32 코어, 24Gb 힙.

grpc 버전 : 1.25.0

메시지 형식 및 엔드 포인트

메시지는 기본적으로 이진 얼룩입니다. 클라이언트는 100K-1M 이상의 메시지를 동일한 요청 (비동기 적으로)으로 스트리밍하고 서버가 아무 것도 응답하지 않으며 클라이언트가 no-op 관찰자를 사용합니다.

service MyService {
    rpc send (stream MyMessage) returns (stream DummyResponse);
}

message MyMessage {
    int64 someField = 1;
    bytes payload = 2;  //not huge
}

message DummyResponse {
}

문제점 : Akka 구현에 비해 메시지 전송률이 낮습니다. CPU 사용률이 낮으므로 grpc 호출이 실제로는 그렇지 않더라도 실제로 내부적으로 차단되고 있다고 생각합니다. onNext()실제로 전화하면 즉시 반환되지 않지만 테이블에 GC도 있습니다.

이 문제를 완화하기 위해 더 많은 발신자를 만들려고했지만 크게 개선되지 않았습니다.

내 연구 결과 Grpc는 직렬화 할 때 실제로 각 메시지에 8KB 바이트 버퍼를 할당합니다. 스택 추적을 참조하십시오.

java.lang.Thread.State : com.google.common.io.ByteStreams.createBuffer (ByteStreams.java:58) com.google.common.io.ByteStreams.copy (ByteStreams.java : 차단됨 (객체 모니터) 105) io.grpc.internal.MessageFramer.writeToOutputStream (MessageFramer.java:274) io.grpc.internal.MessageFramer.writeKnownLengthUncompressed (MessageFramer.java:230) io.grpc.internal.MessageFramer.writeUncompressed (MessageFramer.java) : 168) io.grpc.internal.MessageFramer.writePayload (MessageFramer.java:141) io.grpc.internal.AbstractStream.writeMessage (AbstractStream.java:53) io.grpc.internal.ForwardingClientStream.writeMessage (ForwardingClientStream. io.grpc.internal의 io.grpc.internal.DelayedStream.writeMessage (DelayedStream.java:252)의 java : 37)io.grpc.internal.ClientCallImpl.sendMessage (ClientCallImpl.java:457)의 io.grpc.ForwardingClientCall.sendMessage (ForwardingClientCall.java:37)의 io.grpc.ForwardingClientCall.java:37의 ClientCallImpl.sendMessageInternal (ClientCallImpl.java:473) (io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext (ClientCalls.java:346)의 (ForwardingClientCall.java:37)

처리량이 많은 grpc 클라이언트를 구축하는 모범 사례에 대한 도움을 주시면 감사하겠습니다.


Protobuf를 사용하고 있습니까? 이 코드 경로는 MethodDescriptor.Marshaller.stream ()에 의해 반환 된 InputStream이 Drainable을 구현하지 않는 경우에만 사용해야합니다. Protobuf Marshaller는 Drainable을 지원합니다. Protobuf를 사용하는 경우 ClientInterceptor가 MethodDescriptor를 변경하고 있습니까?
Eric Anderson

@EricAnderson 감사합니다. gradle (com.google.protobuf : protoc : 3.10.1, io.grpc : protoc-gen-grpc-java : 1.25.0) 및 표준 프로토 타입을 사용해 보았습니다 scalapb. 아마도이 스택 트레이스는 스칼라 프에서 생성 된 코드 일 것입니다. scalapb와 관련된 모든 것을 제거했지만 많은 wrt 성능에 도움이되지 않았습니다.
simpadjo

@EricAnderson 나는 내 문제를 해결했다. grpc 개발자로 핑 (Ping) 내 대답이 이해가 되나요?
simpadjo

답변:


4

ManagedChannel대상 당 여러 인스턴스 를 만들어 문제를 해결했습니다 . 기사에도 불구하고 ManagedChannel캔이 충분한 연결 자체를 생성하여 하나의 인스턴스로 충분하다고 주장합니다. 제 경우에는 사실이 아닙니다.

성능은 akka-tcp 구현과 동일합니다.


1
LB 정책이 내장 된 ManagedChannel은 백엔드 당 둘 이상의 연결을 사용하지 않습니다. 따라서 백엔드가 적은 처리량이 많은 경우 모든 백엔드에 대한 연결을 포화시킬 수 있습니다. 이러한 경우 여러 채널을 사용하면 성능이 향상 될 수 있습니다.
에릭 앤더슨

@EricAnderson 감사합니다. 심지어 하나의 백엔드 노드에 여러 채널을 산란 제 경우에는 도움이되었습니다
simpadjo

백엔드 수가 적고 대역폭이 높을수록 여러 채널이 필요할 가능성이 높습니다. 따라서 "단일 백엔드"는 더 많은 채널이 도움이 될 가능성이 높습니다.
에릭 앤더슨

0

흥미로운 질문입니다. 컴퓨터 네트워크 패키지는 프로토콜 스택을 사용하여 인코딩 되며 이러한 프로토콜은 이전 프로토콜의 사양 위에 구축됩니다. 따라서 프로토콜의 성능 (처리량)은 기본 프로토콜 위에 추가 인코딩 / 디코딩 단계를 추가하므로 프로토콜을 빌드하는 데 사용 된 프로토콜의 성능에 의해 제한됩니다.

예를 들어 , 응용 프로그램 계층 의 프로토콜 인 또는 gRPC위에 구축되어 있으므로 성능은 성능에 의해 제한됩니다 . 이제 자체의 상단에 빌드입니다 에있다, 전송 계층 , 또는 우리가 있음을 추론 할 수 있도록, 처리량이 없습니다 에서 제공 상응하는 코드보다 큰 층.HTTP 1.1/2L7HTTPHTTPTCPL4gRPCTCP

다시 말해, 서버가 원시 TCP패키지 를 처리 할 수 ​​있다면 새로운 계층의 복잡성 ( gRPC)을 추가하면 성능이 어떻게 향상 될까요?


바로 그 이유로 스트리밍 방식을 사용합니다. http 연결을 설정하기 위해 한 번 지불하고이를 사용하여 ~ 300M 메시지를 보냅니다. 오버 헤드가 상대적으로 적을 것으로 예상되는 후드 아래에서 웹 소켓을 사용합니다.
simpadjo

들어 gRPC당신도 연결을 설정 한 번씩 지불,하지만 당신은 protobuf을 구문 분석하는 추가 부담을 추가했습니다. 어쨌든 너무 많은 정보없이 추측하기는 어렵지만, 일반적으로 파이프 라인에 추가 인코딩 / 디코딩 단계를 추가하기 때문에 gRPC구현이 동등한 웹 소켓보다 느릴 것입니다.
Batato

Akka는 약간의 오버 헤드도 추가합니다. 어쨌든 x5 속도 저하가 너무 많이 보입니다.
simpadjo

github.com/REASY/akka-http-vs-akka-grpc 와 같은 흥미로운 사실을 발견 할 수 있다고 생각합니다.이 경우 병목 현상은 protobuf (de 직렬화-가비지 수집기에 대한 더 많은 호출을 트리거합니다.
Batato

고마워, 이미 내 문제를 해결에도 불구하고 흥미로운
simpadjo

0

나는 Akka TCP가 얼마나 잘 수행했는지에 깊은 인상을 받았습니다 : D

우리의 경험은 약간 달랐습니다. Akka Cluster를 사용하여 훨씬 작은 인스턴스를 작업했습니다. Akka remoting의 경우 Artery를 사용하여 Akka TCP에서 UDP로 변경했으며 훨씬 높은 속도 + 더 낮고 안정적인 응답 시간을 달성했습니다. 동맥에는 콜드 스타트에서 CPU 소비와 응답 시간 간의 균형을 맞추는 데 도움이되는 구성도 있습니다.

내 제안은 UDP 기반 프레임 워크를 사용하여 전송 안정성 (예 : 동맥 UDP)을 처리하고 전체 육체 gRPC를 사용하는 대신 Protobuf를 사용하여 직렬화하는 것입니다. HTTP / 2 전송 채널은 실제로 처리량이 많은 저 응답 시간 목적이 아닙니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.