Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Иван Патудин «gRPC и его реализация в .NET Core»

Иван Патудин «gRPC и его реализация в .NET Core»

gRPC — опенсорсный фреймворк для удалённого вызова процедур, который был доступен уже давно, но недавно Майкрософт интегрировал его в .NET Core, и он пришёл на смену отжившему своё WCF.

В этом докладе мы разберём какие у него могут быть юзкейсы и какие задачи он решает, сильные и слабые стороны, и посравниваем его с REST и с WCF. А также разберём, как он работает, какие интересные фичи предлагает, и как помогает в кроссплатформенной разработке.

Ceecdee9ee77b63d81100be62b7e1090?s=128

DotNetRu

July 23, 2020
Tweet

Transcript

  1. реализация в .NET Core Патудин Иван

  2. • Что такое gRPC • Сравнение с REST и WCF

    • Язык Protocol buffer • Особенности gRPC и работа с браузером • gRPC в .NET Core 3 • Пример сервиса на REST и gRPC и средства тестирования Краткое содержание 2
  3. • gRPC - Google Remote Procedure Calls • “Высокопроизводительный фреймворк

    для удаленного вызова процедур с открытым исходным кодом” • Contract-first, использует protobuf для определения интерфейса • Multiplatform • HTTP 2.0 Что такое gRPC 3
  4. Зачем это нужно 4 {REST}

  5. 5 {REST}

  6. gRPC REST 6 • Protocol Buffers. Автогенерация клиента и сервера

    • OpenAPI • HTTP/2; Streaming • Работа только в формате запрос-ответ • Фреймворк удаленного вызова процедур • Предназначен прежде всего для управления ресурсами • Ограниченные возможности по работе напрямую с браузером • Отличное взаимодействие с браузерами • Ограниченный набор инструментов тестирования • Большой набор средств, включая отладку из браузера
  7. 7

  8. gRPC WCF • Прекрасные кроссплатформенные возможности • Contract-first • Code-first;

    • Слабая возможность межплатформенного взаимодействия • Устаревающая технология • Теперь официально поддерживается; 8
  9. Benchmarks 9

  10. 10 public class SomeService : ISomeService { public IEnumerable<CompositeType> GetUserData(int

    min, int max) { var response = new CompositeType[max - min]; for (int i = min; i < max; i++) { response[i] = new CompositeType {IntValue = i, StringValue = i.ToString() }; } return response; } } var someService = new SomeService(); var host = new ServiceHost(someService); var binding = new NetTcpBinding(); host.AddServiceEndpoint( typeof(ISomeService), binding, "net.tcp://localhost:5000/serv" ); [ServiceContract] public interface ISomeService { [OperationContract] IEnumerable<CompositeType> GetUserData(int min, int max); }
  11. 11 [GlobalSetup] public void Setup() { _client = new SomeService.SomeServiceClient(

    new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:5000/serv") ); } Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated Wcf 211.5 us 1.625 us 1.440 us 0.7324 - - 3.52 KB [Benchmark] public int Wcf() { var response = _client.GetUserData(0, 42); var sum = 0; foreach (var item in response) { sum += item.IntValue; } return sum; }
  12. 12 syntax = "proto3"; package GrpcService; service SomeGrpcService { rpc

    GetUserData(GetDataRequest) returns (GetDataResponse) {} } message GetDataStreamRequest { int32 min = 1; int32 max = 2; } message GetDataResponse { repeated CompositeType value = 1; } message CompositeType { bool BoolValue = 1; string StringValue = 2; int32 IntValue = 3; }
  13. 13 public class SomeService : SomeService.SomeServiceBase { public override Task<GetDataStreamResponse>

    GetUserData(GetDataStreamRequest request, ServerCallContext context) { var response = new GetDataStreamResponse(); foreach (int i = min; i < max; i++) { response.Value.Add(new CompositeType {IntValue = i, StringValue = i.ToString() }); } return Task.FromResult(response); } } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<SomeService>(); }); }
  14. 14 [Benchmark] public async Task<int> Grpc() { var response =

    await _client.GetDataStreamAsync( new GetDataStreamRequest {Min = 0, Max = 42} ); var sum = 0; foreach (var item in response.Value) { sum += item.IntValue; } return sum; } Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated Grpc 190.1 us 1.984 us 1.856 us 1.4648 - - 1.14 KB
  15. gRPC vs WCF • gRPC выделяет меньше памяти • gRPC

    хоть и незначительно но быстрее (20us) 15 Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated WCF 211.5 us 1.625 us 1.440 us 0.7324 - - 3.52 KB gRPC 190.1 us 1.984 us 1.856 us 1.4648 - - 1.14 KB
  16. Protobuf 16

  17. Protocol buffers (protobuf) • Язык описания интерфейса сервиса • Формат

    сериализации используемый по умолчанию • Используя строгую типизацию полей и бинарный формат • Быстрая сериализации/десериализации gRPC использует contract-first подход для разработки API. 17
  18. syntax = "proto3"; import "models/realty.proto"; //something very important! option csharp_namespace

    = "DowntownRealty"; message RealtyRequest{ int64 id = 1; } message RealtyResponse{ RealtyAd realty = 1; } service DowntownRealty{ rpc GetRealtyById (RealtyRequest) returns (RealtyResponse); rpc GetRealtyList (RealtyListRequest) returns (RealtyListResponse); rpc GetUserById (stream UserRequest) returns (stream UserResponse); } Protocol buffers (protobuf) 18
  19. message RealtyAd{ int32 id = 1; RealtyType type =2; string

    topic = 3; string message = 4; string phone = 5; } enum RealtyType { OTHER = 0; COMMERCIAL = 1; LIVING = 2; } Protocol buffers (protobuf) 19
  20. Типы данных protobuf 20

  21. Protocol buffers (protobuf) 21 message User { string id =

    1; reserved 2, 3; int32 age = 4; } • reserved import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2; } c#: object • any
  22. Protocol buffers (protobuf) • oneof message SampleMessage { oneof test

    { string name = 1; SubMessage message = 2; } } 22 • maps message SampleMessage { map<string, Project> projects = 3; } c#: Dictionary<string, Project>
  23. 23 enum TestOneofCase { None = 0, Name = 1,

    Message = 2 } public TestOneofCase TestCase { get; } public void ClearTest(); public string Name { get; set; } public SubMessage Message { get; set; } switch (change.TestCase) { case TestOneofCase.None: return; case TestOneofCase.Name: FormatName(change.Name); break; case TestOneofCase.Message: FormatMessage(change.Message); break; default: throw new ArgumentException("Unknown instrument type"); }
  24. Nullable 24 import "google/protobuf/wrappers.proto message Person { ... google.protobuf.Int32Value age

    = 5; } message Int32Value { // The int32 value. int32 value = 1; }
  25. Особенности HTTP/2 30

  26. • Протокол HTTP/2 является бинарным • Может отправлять не запрошенные

    данные в рамках соединения • Решена проблема Head-of-line blocking • Мультиплексирование в HTTP/2 Особенности HTTP/2 31
  27. • Мультиплексирование в HTTP/2 Особенности HTTP/2 32

  28. Устройство 33

  29. Варианты взаимодействия Unary RPC rpc SayHello(HelloRequest) returns (HelloResponse); Client streaming

    RPC rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) Server streaming rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse) Bidirectional streaming RPC rpc BidiHello(stream HelloRequest) returns (stream HelloResponse) 34
  30. Подключение • Deadlines (Timeouts) • Metadata • Channels • RPC

    termination 35
  31. Interceptors 36

  32. Middleware Client Interceptor

  33. 38 public abstract class Interceptor { public delegate TResponse BlockingUnaryCallContinuation<TRequest,

    TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context) …… public delegate AsyncUnaryCall<TResponse> AsyncUnaryCallContinuation<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context) …… //…… …… // public virtual Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation) …… public virtual Task DuplexStreamingServerHandler<TRequest, TResponse>(ServerCallContext context, DuplexStreamingServerMethod<TRequest, TResponse> continuation) …… }
  34. Middleware Client Interceptor Service

  35. Exceptions 40

  36. 41 Exceptions class RpcException Ограниченный набор статусов: enum StatusCode Использование

    метаданных
  37. 42 //... ... if (!ValidateUser(user)) { var metadata = new

    Metadata { { "User", user.Identity.Name } }; throw new RpcException(new Status(StatusCode.PermissionDenied, "Permission denied"), metadata); } try { //... ... } catch (RpcException ex) when (ex.StatusCode == StatusCode.PermissionDenied) { var userEntry = ex.Trailers.FirstOrDefault(e => e.Key == "User"); } catch (RpcException) { // Handle any other error type ... }
  38. Взаимодействие из браузера 43

  39. 44 Service Service Взаимодействе из браузера Externals gRPC Mobile gRPC

    JavaScript Web app ?
  40. Взаимодействе из браузера 45 Service Service Proxy JavaScript Web app

    Backend Frontend HTTP gRPC gRPC
  41. gRPC-Web Client 46

  42. gRPC-Web Client • Является прокси между браузером и сервисами •

    Берет на себя задачи по сериализации/десериализации а а 47
  43. Google: gRPC-Web Client 48

  44. Google: gRPC-Web Client • Запускается вручную • Unary calls and

    Server-side streaming • Работает с Deadline • Production ready Roadmap: • Bidi Support • Интеграция с React, Vue и Angular • Web UI Support • Использование разных прокси-клиентов (кроме Envoy) 49
  45. Microsoft .NET: gRPC-Web Client public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

    { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<GreeterService>() }); } Install-Package Grpc.AspNetCore.Web app.UseGrpcWeb(); .EnableGrpcWeb(); 50
  46. • Install-Package Grpc.Net.Client.Web • Install-Package Grpc.Net.Client Microsoft .NET: gRPC-Web Client

    var handler = new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler()); var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpClient = new HttpClient(handler) }); var client = new TicketerService.TicketerClient(channel); var response = await client.GetTickets(); 51 Решение для Blazor WebAssembly:
  47. Microsoft .NET: gRPC-Web Client • Клиентский код из JS приложения

    будет аналогичен решению от Google • Быстро конфигурируется и запускается для Blazor WebAssembly • Unary calls and Server-side streaming • Может требовать настройки CORS 52
  48. Кроссплатформенность 53

  49. Особенности gRPC: Кроссплатформенность GO С/C++, PHP, C#, Objective-C 54

  50. + 55

  51. gRPC C-core и .NET Core gRPC • Вместо C-библиотеки для

    реализации http/2 использует нативную реализацию в .Net Core 3 • Вместо создания класса Server используется Kestrel • Больше не используется GrpcEnvironment • ILogger 56
  52. • Поддержка proto формата • REST и gRPC • Документация

    по миграции с grpc-c на .NET Core gRPC .NET Core gRPC: Visual Studio 57
  53. Безопасность 58

  54. Аутентификация • JWT Bearer Token • OAuth 2.0 • OpenID

    Connect • Azure Active Directory • IdentityServer • WS-Federation • Сертификаты 59 Call credentials Channel credentials
  55. Channel credentials 60 public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)

    .ConfigureWebHostDefaults(webBuilder => { }); var serverCert = ObtainServerCertificate(); webBuilder.UseStartup<Startup>() .ConfigureKestrel(kestrelServerOptions => { }); }); opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate; // Verify that client certificate was issued by same CA as server certificate opt.ClientCertificateValidation = (certificate, chain, errors) => certificate.Issuer == serverCert.Issuer;
  56. Channel credentials 61 var cert = ObtainServerCertificate(); var handler =

    new HttpClientHandler(); handler.ClientCertificates.Add(cert); var httpClient = new HttpClient(handler); var channel = GrpcChannel.ForAddress("https://localhost:5001/", new GrpcChannelOptions { HttpClient = httpClient }); var someServiceClient = new SomeService.SomeServiceClient(channel); var response = await someServiceClient.SayHelloAsync(new HelloRequest { Name = “Hi“ }));
  57. Call credentials: server 62 public class TicketerService : Ticketer.TicketerBase, ITicketerService

    { public override Task<BuyTicketsResponse> BuyTickets(BuyTicketsRequest request, ServerCallContext context) { var user = context.GetHttpContext().User; return Task.FromResult(new BuyTicketsResponse { Success = _ticketRepository.BuyTickets(user.Identity.Name, request.Count) }); }
  58. Call credentials: server 63 public void ConfigureServices(IServiceCollection services) { services.AddGrpc();

    var signingKey = ObtainSigningKeySomehow(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateLifetime = true, IssuerSigningKey = signingKey }; }); }
  59. Call credentials: client 64 public async Task BuyTicketsAsync(string name) {

    var headers = new Metadata { { "Authorization", $"Bearer {_userToken}" } }; var request = new GetRequest { Time = DateTime.Now.AddDays(1), Name = name }; var response = await _ticketsService.BuyTickets(request, headers); }
  60. Server reflection 65

  61. • Рантайм информация о сервисе • Используется gRPC CLI для

    получения данных о сервере • NuGet Grpc.Reflection • Не позволяет автоматически генерировать proto-file Server reflection 66
  62. Server reflection 67 public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<RealtyService>(); }); } services.AddGrpcReflection(); endpoints.MapGrpcReflectionService();
  63. Deadline & ContextPropagationToken 68

  64. ContextPropagationToken gate клиент Srvs 1 Srvs 2 Srvs 3 t2

    ? t1 ? t3 ? 500ms 69
  65. ContextPropagationToken клиент Srvs 1 Srvs 2 Srvs 3 500ms 500

    500 500 gate 70
  66. ContextPropagationToken клиент Srvs 1 Srvs 2 Srvs 3 500ms 500

    500 500 gate 200мс 400мс 200мс timeout => retry 71
  67. ContextPropagationToken • Grpc.Net.ClientFactory 72 services .AddGrpcClient<Greeter.GreeterClient>(o => { o.Address =

    new Uri("https://localhost:5001"); }) .EnableCallContextPropagation(); var channel = new Channel("localhost", ServerPort, ChannelCredentials.Insecure); var client = new DowntownRealtyClient(channel); client.GetRealtyList(new RealtyListRequest() { Type = RealtyType.House }, new CallOptions(deadline: DateTime.Now.AddSeconds(90)));
  68. Немного кода 74

  69. Summarize • отличная альтернатива WCF • облегченное межплатформенное взаимодействие •

    хорошая скорость и гибкое взаимодействие клиент-сервера • Не работает с IIS • инструменты для тестирования 75
  70. 76 https://github.com/grem0087/gRpcNext Патудин Иван