GRPC: tạo ứng dụng khách thông lượng cao trong Java / Scala


9

Tôi có một dịch vụ chuyển tin nhắn với tốc độ khá cao.

Hiện tại nó được phục vụ bởi akka-tcp và nó tạo ra 3,5 triệu tin nhắn mỗi phút. Tôi quyết định thử grpc. Thật không may, nó dẫn đến thông lượng nhỏ hơn nhiều: ~ 500 nghìn tin nhắn mỗi phút thậm chí còn ít hơn.

Bạn có thể vui lòng giới thiệu làm thế nào để tối ưu hóa nó?

Thiết lập của tôi

Phần cứng : 32 lõi, heap 24Gb.

phiên bản grpc: 1.25.0

Định dạng tin nhắn và điểm cuối

Thông điệp về cơ bản là một blob nhị phân. Máy khách truyền 100K - 1M và nhiều tin nhắn hơn vào cùng một yêu cầu (không đồng bộ), máy chủ không phản hồi với bất cứ điều gì, khách hàng sử dụng trình quan sát không hoạt động

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

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

message DummyResponse {
}

Vấn đề: Tỷ lệ tin nhắn thấp so với thực hiện akka. Tôi quan sát việc sử dụng CPU thấp nên tôi nghi ngờ rằng cuộc gọi grpc thực sự đang chặn bên trong mặc dù nó nói khác đi. Gọi onNext()thực sự không trở lại ngay lập tức nhưng cũng có GC trên bàn.

Tôi đã cố gắng sinh ra nhiều người gửi hơn để giảm thiểu vấn đề này nhưng không được cải thiện nhiều.

Phát hiện của tôi Grpc thực sự phân bổ bộ đệm byte 8KB cho mỗi thông báo khi tuần tự hóa nó. Xem stacktrace:

java.lang.Thread.State: BLOCKED (trên màn hình đối tượng) tại com.google.common.io.ByteStreams.createBuffer (ByteStreams.java:58) tại com.google.common.io.ByteStreams.copy (ByteStreams.java: 105) tại io.grpc.iternal.MessageFramer.writeToOutputStream (MessageFramer.java:274) tại io.grpc.iternal.MessageFramer.writeKnownL wavelUncompression (MessageFramer.java:2). : 168) tại io.grpc.iternal.MessageFramer.writePayload (MessageFramer.java:141) tại io.grpc.i INTERNal.AbaugeStream.writeMessage (AbstractStream.java:53) tại io.grpc.i INTERNal java: 37) tại io.grpc.iternal.DelayedStream.writeMessage (DelayedStream.java:252) tại io.grpc.iternal.ClientCallImpl.sendMessageI Internalal (ClientCallImpl.java:473) tại io.grpc.i INTERNal.ClientCallImpl.sendMessage (ClientCallImpl.java: iat (ForwardingClientCall.java:37) tại io.grpc.stub.ClientCalls $ CallToStreamObserverAd Module.onNext (ClientCalls.java:346)

Bất kỳ trợ giúp với thực tiễn tốt nhất về xây dựng khách hàng grpc thông lượng cao đánh giá cao.


Bạn đang sử dụng Protobuf? Đường dẫn mã này chỉ nên được thực hiện nếu InputStream được trả về bởi MethodDescriptor.Marshaller.stream () không triển khai Drainable. Protobuf Marshaller không hỗ trợ Drainable. Nếu bạn đang sử dụng Protobuf, có thể ClientInterceptor đang thay đổi Phương thức mô tả không?
Eric Anderson

@EricAnderson cảm ơn bạn đã phản hồi. Tôi đã thử protobuf tiêu chuẩn với gradle (com.google.protobuf: protoc: 3.10.1, io.grpc: protoc-gen-grpc-java: 1.25.0) và cả scalapb. Có lẽ stacktrace này thực sự là từ mã được tạo bằng scalapb. Tôi đã loại bỏ mọi thứ liên quan đến scalapb nhưng nó không giúp ích nhiều cho hiệu suất wrt.
simpadjo

@EricAnderson Tôi đã giải quyết vấn đề của mình. Ping bạn là một nhà phát triển của grpc. Liệu câu trả lời của tôi có ý nghĩa?
simpadjo

Câu trả lời:


4

Tôi đã giải quyết vấn đề bằng cách tạo một số ManagedChanneltrường hợp cho mỗi điểm đến. Mặc dù các bài báo nói rằng một ManagedChannelkết nối có thể sinh ra đủ các kết nối nên một trường hợp là đủ, điều đó không đúng trong trường hợp của tôi.

Hiệu suất tương đương với việc thực hiện akka-tcp.


1
ManagedChannel (với các chính sách LB tích hợp) không sử dụng nhiều hơn một kết nối cho mỗi phụ trợ. Vì vậy, nếu bạn có thông lượng cao với một vài phụ trợ, có thể bão hòa các kết nối đến tất cả các phụ trợ. Sử dụng nhiều kênh có thể tăng hiệu suất trong những trường hợp đó.
Eric Anderson

@EricAnderson cảm ơn. Trong trường hợp của tôi, sinh ra một số kênh thậm chí đến một nút phụ trợ duy nhất đã giúp
simpadjo

Càng ít phụ trợ và băng thông càng cao, bạn càng cần nhiều kênh. Vì vậy, "phụ trợ đơn" sẽ làm cho nhiều kênh có thể hữu ích hơn.
Eric Anderson

0

Câu hỏi thú vị. Các gói mạng máy tính được mã hóa bằng cách sử dụng một chồng các giao thức và các giao thức như vậy được xây dựng dựa trên các thông số kỹ thuật của giao thức trước đó. Do đó, hiệu suất (thông lượng) của một giao thức bị giới hạn bởi hiệu suất của giao thức được sử dụng để xây dựng nó, vì bạn đang thêm các bước mã hóa / giải mã bổ sung lên trên giao thức cơ bản.

Ví dụ, gRPCđược xây dựng trên cùng HTTP 1.1/2, là một giao thức trên lớp Ứng dụng hoặc L7, và do đó hiệu suất của nó bị ràng buộc bởi hiệu suất của HTTP. Bây giờ HTTPchính là xây dựng trên đầu trang của TCP, mà là ở lớp Giao thông vận tải , hoặc L4, vì vậy chúng ta có thể suy luận rằng gRPCthông không thể lớn hơn một mã số tương đương phục vụ trong TCPlớp.

Nói cách khác: nếu máy chủ của bạn có thể xử lý TCPcác gói thô , việc thêm các lớp phức tạp mới ( gRPC) sẽ cải thiện hiệu suất như thế nào?


Vì lý do chính xác đó, tôi sử dụng phương pháp phát trực tuyến: Tôi trả tiền một lần để thiết lập kết nối http và gửi ~ 300M tin nhắn bằng cách sử dụng nó. Nó sử dụng websockets dưới mui xe mà tôi mong đợi có chi phí tương đối thấp.
simpadjo

Đối với gRPCbạn cũng phải trả một lần cho việc thiết lập kết nối, nhưng bạn đã thêm gánh nặng phân tích protobuf. Dù sao, thật khó để đoán mà không có quá nhiều thông tin, nhưng tôi cá rằng, nói chung, vì bạn đang thêm các bước mã hóa / giải mã bổ sung trong đường ống của mình, việc gRPCtriển khai sẽ chậm hơn so với ổ cắm web tương đương.
Batato

Akka thêm một số chi phí là tốt. Dù sao x5 chậm lại trông quá nhiều.
simpadjo

Tôi nghĩ bạn có thể thấy điều này thú vị: github.com/REASY/akka-http-vs-akka-grpc , trong trường hợp của anh ấy (và tôi nghĩ điều này mở rộng cho bạn), nút cổ chai có thể là do sử dụng bộ nhớ cao trong protobuf (de ) tuần tự hóa, từ đó kích hoạt nhiều cuộc gọi đến bộ thu gom rác.
Batato

cảm ơn, thật thú vị mặc dù tôi đã giải quyết được vấn đề của mình
simpadjo

0

Tôi khá ấn tượng với Akka TCP đã hoạt động tốt như thế nào ở đây: D

Kinh nghiệm của chúng tôi hơi khác nhau. Chúng tôi đã làm việc trên các trường hợp nhỏ hơn nhiều bằng cách sử dụng Akka Cluster. Đối với điều khiển từ xa Akka, chúng tôi đã thay đổi từ Akka TCP sang UDP bằng cách sử dụng Động mạch và đạt được tốc độ cao hơn nhiều + thời gian phản hồi ổn định và thấp hơn nhiều. Thậm chí còn có một cấu hình trong Động mạch giúp cân bằng giữa mức tiêu thụ CPU và thời gian phản hồi từ khi bắt đầu lạnh.

Đề nghị của tôi là sử dụng một số khung dựa trên UDP cũng đảm bảo độ tin cậy truyền cho bạn (ví dụ: UDP động mạch) và chỉ tuần tự hóa bằng Protobuf, thay vì sử dụng gRPC toàn phần. Kênh truyền HTTP / 2 không thực sự cho mục đích thời gian đáp ứng thấp thông lượng cao.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.