Gọi dịch vụ gRPC với .NET client
Thư viện .NET gRPC client có sẵn trong gói NuGet Grpc.Net.Client. Tài liệu này giải thích cách:
- Cấu hình gRPC client để gọi dịch vụ gRPC.
- Thực hiện các lời gọi gRPC đến các phương thức unary, server streaming, client streaming và bi-directional streaming.
Cấu hình gRPC client
gRPC client là các kiểu client cụ thể được tạo ra từ file .proto. gRPC client cụ thể có các phương thức dịch sang dịch vụ gRPC trong file .proto. Ví dụ, một dịch vụ có tên Greeter tạo ra kiểu GreeterClient với các phương thức để gọi dịch vụ.
gRPC client được tạo từ một channel (kênh). Bắt đầu bằng cách sử dụng GrpcChannel.ForAddress để tạo channel, sau đó sử dụng channel để tạo gRPC client:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);Channel đại diện cho một kết nối dài hạn đến dịch vụ gRPC. Khi channel được tạo, nó được cấu hình với các tùy chọn liên quan đến việc gọi dịch vụ. Ví dụ, HttpClient được sử dụng để thực hiện lời gọi, kích thước message gửi và nhận tối đa và logging có thể được chỉ định trên GrpcChannelOptions và sử dụng với GrpcChannel.ForAddress. Để xem danh sách đầy đủ các tùy chọn, xem client configuration options.
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var greeterClient = new Greet.GreeterClient(channel);
var counterClient = new Count.CounterClient(channel);
// Use clients to call gRPC servicesCấu hình TLS
gRPC client phải sử dụng cùng bảo mật cấp kết nối như dịch vụ được gọi. gRPC client Transport Layer Security (TLS - bảo mật tầng vận chuyển) được cấu hình khi gRPC channel được tạo. gRPC client gặp lỗi khi gọi dịch vụ và bảo mật cấp kết nối của channel và dịch vụ không khớp.
Để cấu hình gRPC channel sử dụng TLS, hãy đảm bảo địa chỉ server bắt đầu bằng https. Ví dụ, GrpcChannel.ForAddress("https://localhost:5001") sử dụng giao thức HTTPS. gRPC channel tự động thương lượng kết nối được bảo mật bởi TLS và sử dụng kết nối an toàn để thực hiện các lời gọi gRPC.
gRPC hỗ trợ xác thực chứng chỉ client qua TLS. Để biết thông tin về cách cấu hình chứng chỉ client với gRPC channel, xem Authentication and authorization in gRPC for ASP.NET Core.
Để gọi dịch vụ gRPC không bảo mật, hãy đảm bảo địa chỉ server bắt đầu bằng http. Ví dụ, GrpcChannel.ForAddress("http://localhost:5000") sử dụng giao thức HTTP. Trong .NET Core 3.1, cần cấu hình bổ sung để gọi dịch vụ gRPC không bảo mật với .NET client.
Hiệu suất của client
Hiệu suất và cách sử dụng channel và client:
- Việc tạo channel có thể là một thao tác tốn kém. Tái sử dụng channel cho các lời gọi gRPC mang lại lợi ích hiệu suất.
- Channel quản lý các kết nối đến server. Nếu kết nối bị đóng hoặc mất, channel tự động kết nối lại vào lần tiếp theo gRPC call được thực hiện.
- gRPC client được tạo với channel. gRPC client là các đối tượng nhẹ và không cần được cache (lưu trữ đệm) hoặc tái sử dụng.
- Nhiều gRPC client có thể được tạo từ một channel, bao gồm các kiểu client khác nhau.
- Channel và các client được tạo từ channel có thể được sử dụng an toàn bởi nhiều thread.
- Các client được tạo từ channel có thể thực hiện nhiều lời gọi đồng thời.
GrpcChannel.ForAddress không phải là tùy chọn duy nhất để tạo gRPC client. Nếu gọi dịch vụ gRPC từ ứng dụng ASP.NET Core, hãy xem xét gRPC client factory integration. gRPC tích hợp với HttpClientFactory cung cấp một giải pháp tập trung để tạo gRPC client.
Khi gọi các phương thức gRPC, ưu tiên sử dụng lập trình bất đồng bộ với async và await. Thực hiện các lời gọi gRPC theo cách blocking, chẳng hạn sử dụng Task.Result hoặc Task.Wait(), ngăn các task khác sử dụng thread. Điều này có thể dẫn đến thread pool starvation (kiệt sức thread pool) và hiệu suất kém. Nó có thể gây ra ứng dụng bị treo với deadlock (bế tắc).
Thực hiện các lời gọi gRPC
gRPC call được khởi tạo bằng cách gọi một phương thức trên client. gRPC client sẽ xử lý việc serialize message (mã hóa hóa message) và địa chỉ của lời gọi gRPC đến đúng dịch vụ.
gRPC có các kiểu phương thức khác nhau. Cách client được sử dụng để thực hiện lời gọi gRPC phụ thuộc vào kiểu phương thức được gọi. Các kiểu phương thức gRPC là:
- Unary (đơn nhất)
- Server streaming (luồng từ server)
- Client streaming (luồng từ client)
- Bi-directional streaming (luồng hai chiều)
Lời gọi Unary
Lời gọi unary bắt đầu bằng việc client gửi request message. Response message được trả về khi dịch vụ hoàn thành.
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello WorldMỗi phương thức dịch vụ unary trong file .proto sẽ tạo ra hai phương thức .NET trên kiểu gRPC client cụ thể để gọi phương thức: phương thức bất đồng bộ và phương thức blocking. Ví dụ, trên GreeterClient có hai cách gọi SayHello:
GreeterClient.SayHelloAsync- gọi dịch vụGreeter.SayHellobất đồng bộ. Có thể được await.GreeterClient.SayHello- gọi dịch vụGreeter.SayHellovà block cho đến khi hoàn thành. Đừng dùng trong code bất đồng bộ. Có thể gây ra vấn đề hiệu suất và độ tin cậy.
Lời gọi Server streaming
Lời gọi server streaming bắt đầu bằng việc client gửi request message. ResponseStream.MoveNext() đọc các message được stream từ dịch vụ. Lời gọi server streaming hoàn thành khi ResponseStream.MoveNext() trả về false.
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
while (await call.ResponseStream.MoveNext())
{
Console.WriteLine("Greeting: " + call.ResponseStream.Current.Message);
// "Greeting: Hello World" is written multiple times
}Khi sử dụng C# 8 hoặc mới hơn, cú pháp await foreach có thể được dùng để đọc message. Extension method IAsyncStreamReader<T>.ReadAllAsync() đọc tất cả message từ response stream:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
// "Greeting: Hello World" is written multiple times
}Kiểu được trả về từ việc bắt đầu lời gọi server streaming triển khai IDisposable. Luôn dispose (giải phóng) lời gọi streaming để đảm bảo nó được dừng lại và tất cả tài nguyên được dọn sạch.
Lời gọi Client streaming
Lời gọi client streaming bắt đầu mà không có client gửi message. Client có thể chọn gửi message bằng RequestStream.WriteAsync. Khi client đã gửi xong message, RequestStream.CompleteAsync() nên được gọi để thông báo cho dịch vụ. Lời gọi hoàn thành khi dịch vụ trả về response message.
var client = new Counter.CounterClient(channel);
using var call = client.AccumulateCount();
for (var i = 0; i < 3; i++)
{
await call.RequestStream.WriteAsync(new CounterRequest { Count = 1 });
}
await call.RequestStream.CompleteAsync();
var response = await call;
Console.WriteLine($"Count: {response.Count}");
// Count: 3Kiểu được trả về từ việc bắt đầu lời gọi client streaming triển khai IDisposable. Luôn dispose lời gọi streaming để đảm bảo nó được dừng lại và tất cả tài nguyên được dọn sạch.
Lời gọi Bi-directional streaming
Lời gọi bi-directional streaming bắt đầu mà không có client gửi message. Client có thể chọn gửi message bằng RequestStream.WriteAsync. Các message được stream từ dịch vụ có thể truy cập bằng ResponseStream.MoveNext() hoặc ResponseStream.ReadAllAsync(). Lời gọi bi-directional streaming hoàn thành khi ResponseStream không còn message.
var client = new Echo.EchoClient(channel);
using var call = client.Echo();
Console.WriteLine("Starting background task to receive messages");
var readTask = Task.Run(async () =>
{
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine(response.Message);
// Echo messages sent to the service
}
});
Console.WriteLine("Starting to send messages");
Console.WriteLine("Type a message to echo then press enter.");
while (true)
{
var result = Console.ReadLine();
if (string.IsNullOrEmpty(result))
{
break;
}
await call.RequestStream.WriteAsync(new EchoMessage { Message = result });
}
Console.WriteLine("Disconnecting");
await call.RequestStream.CompleteAsync();
await readTask;Để có hiệu suất tốt nhất và tránh lỗi không cần thiết trong client và dịch vụ, hãy cố gắng hoàn thành các lời gọi bi-directional streaming một cách duyên dáng (gracefully). Lời gọi bi-directional kết thúc gracefully khi server đã đọc xong request stream và client đã đọc xong response stream. Lời gọi mẫu trước là một ví dụ về lời gọi bi-directional kết thúc gracefully. Trong lời gọi, client:
- Bắt đầu lời gọi bi-directional streaming mới bằng cách gọi
EchoClient.Echo. - Tạo một background task để đọc message từ dịch vụ sử dụng
ResponseStream.ReadAllAsync(). - Gửi message đến server bằng
RequestStream.WriteAsync. - Thông báo cho server rằng đã hoàn thành gửi message bằng
RequestStream.CompleteAsync(). - Chờ cho đến khi background task đã đọc tất cả message đến.
Trong suốt lời gọi bi-directional streaming, client và dịch vụ có thể gửi message cho nhau bất cứ lúc nào. Logic client tốt nhất để tương tác với lời gọi bi-directional thay đổi tùy thuộc vào logic dịch vụ.
Kiểu được trả về từ việc bắt đầu lời gọi bi-directional streaming triển khai IDisposable. Luôn dispose lời gọi streaming để đảm bảo nó được dừng lại và tất cả tài nguyên được dọn sạch.
Truy cập gRPC headers (tiêu đề)
Lời gọi gRPC trả về response headers. HTTP response headers truyền metadata tên/giá trị về một lời gọi không liên quan đến message được trả về.
Headers có thể truy cập bằng ResponseHeadersAsync, trả về một collection metadata. Headers thường được trả về cùng với response message; do đó, bạn phải await chúng.
var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var headers = await call.ResponseHeadersAsync;
var myValue = headers.GetValue("my-trailer-name");
var response = await call.ResponseAsync;Cách sử dụng ResponseHeadersAsync:
- Phải await kết quả của
ResponseHeadersAsyncđể lấy collection headers. - Không nhất thiết phải được truy cập trước
ResponseAsync(hoặc response stream khi streaming). Nếu response đã được trả về, thìResponseHeadersAsynctrả về headers ngay lập tức. - Sẽ ném exception nếu có lỗi kết nối hoặc server và headers không được trả về cho lời gọi gRPC.
Truy cập gRPC trailers
Lời gọi gRPC có thể trả về response trailers. Trailers được dùng để cung cấp metadata tên/giá trị về một lời gọi. Trailers cung cấp chức năng tương tự HTTP headers, nhưng được nhận ở cuối lời gọi.
Trailers có thể truy cập bằng GetTrailers(), trả về một collection metadata. Trailers được trả về sau khi response hoàn thành. Do đó, bạn phải await tất cả response message trước khi truy cập trailers.
Lời gọi unary và client streaming phải await ResponseAsync trước khi gọi GetTrailers():
var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var response = await call.ResponseAsync;
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");Lời gọi server và bidirectional streaming phải hoàn thành await response stream trước khi gọi GetTrailers():
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
// "Greeting: Hello World" is written multiple times
}
var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");Trailers cũng có thể truy cập từ RpcException. Dịch vụ có thể trả về trailers cùng với trạng thái gRPC không phải OK. Trong tình huống này, trailers được lấy từ exception được ném bởi gRPC client:
var client = new Greet.GreeterClient(channel);
string myValue = null;
try
{
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var response = await call.ResponseAsync;
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
var trailers = call.GetTrailers();
myValue = trailers.GetValue("my-trailer-name");
}
catch (RpcException ex)
{
var trailers = ex.Trailers;
myValue = trailers.GetValue("my-trailer-name");
}Cấu hình deadline (thời hạn)
Cấu hình deadline (thời hạn) cho lời gọi gRPC được khuyến nghị vì nó cung cấp giới hạn trên về thời gian lời gọi có thể chạy. Nó ngăn các dịch vụ hoạt động sai chạy mãi mãi và làm cạn kiệt tài nguyên server. Deadlines là công cụ hữu ích để xây dựng ứng dụng đáng tin cậy.
Cấu hình CallOptions.Deadline để thiết lập deadline cho lời gọi gRPC:
var client = new Greet.GreeterClient(channel);
try
{
var response = await client.SayHelloAsync(
new HelloRequest { Name = "World" },
deadline: DateTime.UtcNow.AddSeconds(5));
// Greeting: Hello World
Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
Console.WriteLine("Greeting timeout.");
}Để biết thêm thông tin, xem Reliable gRPC services with deadlines and cancellation.