gRPC trong .NET

Giới thiệu

gRPC là một framework gọi thủ tục từ xa (RPC) hiệu suất cao, mã nguồn mở, dựa trên giao thức HTTP/2, hỗ trợ luồng hai chiều, nén header và các tính năng khác. Nó sử dụng mặc định Protocol Buffers (Protobuf) làm ngôn ngữ định nghĩa giao diện (IDL) và định dạng tuần tự hóa dữ liệu, phù hợp cho vi dịch vụ, giao tiếp thời gian thực và các trường hợp sử dụng khác.

Chúng ta có thể so sánh với dịch vụ HTTP thông thường để hiểu rõ hơn. Về bản chất, dịch vụ HTTP dựa trên giao thức HTTP, là một giao thức tầng ứng dụng, trong khi RPC dựa trên TCP/IP, là một giao thức tầng truyền. Giao thức tầng ứng dụng nằm trên tầng truyền, do đó RPC hiệu quả hơn, vì vậy RPC phù hợp hơn cho các cuộc gọi trong mạng nội bộ, không cần phải thực hiện 3 lần bắt tay như HTTP mỗi lần giao tiếp, giảm thiểu overhead mạng.

Kiến trúc RPC

Một kiến trúc RPC hoàn chỉnh bao gồm bốn thành phần cốt lõi: Client, Server, Client Stub và Server Stub, Stub có thể hiểu là "mô-đun trung gian". Hãy cùng tìm hiểu các thành phần này:

  • Client (khách hàng): bên gọi dịch vụ.
  • Server (máy chủ): nhà cung cấp dịch vụ thực sự.
  • Client Stub: lưu trữ thông tin địa chỉ của máy chủ, đóng gói tham số yêu cầu của client thành tin nhắn mạng, sau đó gửi từ xa đến máy chủ qua mạng.
  • Server Stub: nhận tin nhắn từ client, giải nén tin nhắn, và gọi phương thức cục bộ.

So sánh với HTTP và các trường hợp sử dụng

1. Giao thức và Định dạng Dữ liệu

Đặc tính gRPC Giao diện HTTP (REST)
**Giao thức** Dựa trên **HTTP/2** Dựa trên **HTTP/1.1** hoặc **HTTP/2**
**Định dạng dữ liệu** Sử dụng **Protocol Buffers (Protobuf)** Thường sử dụng **JSON** hoặc **XML**
**Phương thức truyền** Truyền nhị phân Truyền văn bản

2. Hiệu suất

Đặc tính gRPC Giao diện HTTP (REST)
**Hiệu suất tuần tự hóa** Protobuf là định dạng nhị phân, hiệu suất tuần tự hóa cao JSON/XML là định dạng văn bản, hiệu suất tuần tự hóa thấp hơn
**Hiệu suất truyền** Dựa trên HTTP/2, hỗ trợ đa luồng và nén header HTTP/1.1 không hỗ trợ đa luồng, hiệu suất thấp hơn
**Độ trễ** Độ trễ thấp, phù hợp cho giao tiếp thời gian thực Độ trễ cao hơn, phù hợp cho các kịch bản yêu cầu-đáp ứng đơn giản

3. Chế độ giao tiếp

Đặc tính gRPC Giao diện HTTP (REST)
**Chế độ giao tiếp** Hỗ trợ bốn chế độ: 1. RPC đơn 2. RPC luồng máy chủ 3. RPC luồng client 4. RPC luồng hai chiều Chỉ hỗ trợ chế độ yêu cầu-đáp ứng
**Giao tiếp thời gian thực** Hỗ trợ luồng hai chiều, phù hợp cho giao tiếp thời gian thực Không hỗ trợ luồng hai chiều, cần mô phỏng bằng polling

4. Phát triển và Sử dụng

Đặc tính gRPC Giao diện HTTP (REST)
**Định nghĩa giao diện** Sử dụng Protobuf để định nghĩa giao diện dịch vụ, kiểu mạnh Sử dụng tài liệu (như OpenAPI) để định nghĩa giao diện
**Tạo mã** Tự động tạo mã client và server Thường cần viết mã client thủ công
**Công cụ gỡ lỗi** Công cụ ít, gỡ lỗi phức tạp Công cụ phong phú (như Postman)
**Hỗ trợ đa ngôn ngữ** Hỗ trợ nhiều ngôn ngữ (C++, Java, Go, C#,...) Hỗ trợ nhiều ngôn ngữ, nhưng cần điều chỉnh thủ công

5. Trường hợp sử dụng

Đặc tính gRPC Giao diện HTTP (REST)
**Giao tiếp vi dịch vụ** Phù hợp cho giao tiếp vi dịch vụ hiệu suất cao, độ trễ thấp Phù hợp cho giao tiếp giữa các dịch vụ đơn giản
**Giao tiếp thời gian thực** Phù hợp cho các kịch bản giao tiếp thời gian thực (như chat, push) Không phù hợp cho giao tiếp thời gian thực
**Giao tiếp đa ngôn ngữ** Phù hợp cho giao tiếp giữa các hệ thống khác nhau Phù hợp cho giao tiếp front-end và back-end đơn giản
**Hỗ trợ trình duyệt** Cần gRPC-Web để tương thích Hỗ trợ gốc

Một ví dụ gRPCDemo đơn giản

Server

Tạo order.proto

syntax = "proto3";

option csharp_namespace = "GrpcService";

package order;

service Order{
    rpc CreateOrder(CreateRequest) returns (CreateResult);
    rpc QueryOrder(QueryRequest) returns (QueryResult);
}
//Tham số yêu cầu tạo đơn hàng
message CreateRequest {
string OrderNo = 1;
string OrderName=2;
double Price=3;
}

//Kết quả trả về khi tạo đơn hàng
message CreateResult {
bool IsSuccess = 1; // Thành công hay không
string Message = 2; // Thông điệp lỗi
}

//Tham số yêu cầu truy vấn đơn hàng
message QueryRequest{
int32 Id=1;
}
//Kết quả trả về khi truy vấn đơn hàng
message QueryResult{
int32 Id=1;
string OrderNo=2;
string OrderName=3;
double Price=4;
}

Tạo lớp dịch vụ

public class OrderService : Order.OrderBase
{
    private readonly ILogger<OrderService> _logger;
    public OrderService(ILogger<OrderService> logger)
    {
        _logger = logger;
    }
    /// <summary>
    /// Tạo đơn hàng
    /// </summary>
    /// <param name="request"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    public override Task<CreateResult> CreateOrder(CreateRequest request, ServerCallContext context)
    {
        //Lưu vào cơ sở dữ liệu todo

        return Task.FromResult(new CreateResult
        {
            IsSuccess = true,
            Message = "Tạo đơn hàng thành công"
        });
    }
    /// <summary>
    /// Truy vấn đơn hàng
    /// </summary>
    /// <param name="request"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    public override Task<QueryResult> QueryOrder(QueryRequest request, ServerCallContext context)
    {
        //Truy vấn cơ sở dữ liệu //todo

        return Task.FromResult(new QueryResult
        {
            Id = request.Id,
            OrderNo = DateTime.Now.ToString("yyyyMMddHHmmss"),
            OrderName = "Gói quà Tết",
            Price = 699
        });
    }
}

Thêm dịch vụ phụ thuộc và cấu hình pipeline

using GrpcService.Services;

var builder = WebApplication.CreateBuilder(args);

// Thêm dịch vụ vào container.
builder.Services.AddGrpc();

var app = builder.Build();

// Cấu hình pipeline HTTP.
app.MapGrpcService<GreeterService>();
app.MapGrpcService<OrderService>();
app.MapGet("/", () => "hello world");
app.Run();

Client

Tạo ứng dụng console

Thêm tham chiếu

Google.Protobuf
Grpc.Net.Client
Grpc.Tools

Sao chép file order.proto từ server, và thêm cấu hình vào file .csproj

<ItemGroup>
  <Protobuf Include="Protos\order.proto" GrpcServices="Client" />
</ItemGroup>

Sau khi biên dịch, mã sẽ được tạo tự động

using Grpc.Net.Client;
using GrpcService;

string url = "https://localhost:7097";
using (var channel = GrpcChannel.ForAddress(url))
{
    var client = new Order.OrderClient(channel);
    var reply = await client.CreateOrderAsync(new CreateRequest
    {
        OrderName = "Đơn hàng 1",
        OrderNo = "1",
        Price = 100
    });
    Console.WriteLine($"Kết quả gọi từ client gRPC: {reply.Message},{reply.IsSuccess}");
    Console.Read();
}

Gọi gRPC bằng cách sử dụng IOC

Thêm dependency injection

using GrpcClient.IOC;
using static GrpcService.Order;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<GrpcRequestTest>();
builder.Services.AddGrpcClient<OrderClient>(o =>
{
    o.Address = new Uri("https://localhost:7097");
}).ConfigureChannel(grpcOptions => { });

var app = builder.Build();
var grpcRequestTest = app.Services.GetRequiredService<GrpcRequestTest>();
grpcRequestTest.CreateOrder();
app.Run();
 public class GrpcRequestTest
 {
     private Order.OrderClient _orderClient;
     public GrpcRequestTest(Order.OrderClient orderClient)
     {
         _orderClient = orderClient;
     }
     public void CreateOrder()
     {
         var reply = _orderClient.CreateOrder(new CreateRequest()
         {
             OrderNo = DateTime.Now.ToString("yyyMMddHHmmss"),
             OrderName = "Tủ lạnh 2022",
             Price = 1688
         });
         Console.WriteLine($"Kết quả:{reply.IsSuccess},thông điệp:{reply.Message}");
         Console.ReadKey();
     }
 }

Thêm dịch vụ gRPC vào webapi

Server

Trong dự án, thường cần cung cấp cả dịch vụ webapi và dịch vụ gRPC.

Tạo một webapi, thêm thư mục Protos, lưu ý kiểm tra thuộc tính tạo mã của file.

Thêm OrderService.cs

Trong program, thêm dịch vụ và cấu hình pipeline.

Client

1、Sao chép file proto

2、Sửa file dự án để thêm cấu hình

<ItemGroup> <Protobuf Include="Protos\order.proto" GrpcServices="Client" /> </ItemGroup>

3、Cấu hình và gọi dịch vụ gRPC và webapi

using GrpcServiceWithWebApi.Client;
using static GrpcServiceWithWebApi.Client.Order;

var builder = WebApplication.CreateBuilder(args);

//Cấu hình một httpclient, địa chỉ yêu cầu là https://localhost:7097
builder.Services.AddHttpClient("GrpcServiceWithWebApi", c =>
{
    c.BaseAddress = new Uri("https://localhost:7095");
});
//Cấu hình một grpcclient, địa chỉ yêu cầu là https://localhost:7097
builder.Services.AddGrpcClient<OrderClient>(o =>
{
    o.Address = new Uri("https://localhost:7095");
}).ConfigureChannel(grpcOptions => { });
var app = builder.Build();

app.MapGet("/", () =>
{
    //Gọi dịch vụ gRPC
    var client = app.Services.GetRequiredService<OrderClient>();
    var reply = client.CreateOrder(new CreateRequest
    {
        OrderName = "Đơn hàng 1",
        OrderNo = "1",
        Price = 100
    });
    var repcResult = $"Kết quả gọi từ client gRPC: {reply.Message},{reply.IsSuccess}";
    //Gọi dịch vụ webapi
    var httpClient = app.Services.GetRequiredService<IHttpClientFactory>().CreateClient("GrpcServiceWithWebApi");
    var response = httpClient.GetAsync("/weatherforecast").Result;
    var webApiResult = response.Content.ReadAsStringAsync().Result;
    var webApiRel = $"{repcResult} <br/> {webApiResult}";
    return repcResult+webApiRel;
});

app.Run();

Địa chỉ mã nguồn: https://gitee.com/xiaoqingyao/g-rpcdemo.git

Tham khảo:

.NET Core(.NET6) sử dụng gRPC - Baozi wxl - Blog园 Sử dụng gRPC trong .NET Core

Giải thích trực quan về sự khác biệt giữa cuộc gọi RPC và cuộc gọi HTTP!

Thẻ: grpc .NET protobuf HTTP/2 RPC

Đăng vào ngày 11 tháng 6 lúc 18:30