整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Spring Boot + gRPC 構建可擴展的微

          Spring Boot + gRPC 構建可擴展的微服務

          文是一份詳盡且深入的指南,旨在幫助讀者理解并實現將 gRPC 通過 Maven 集成到 SpringBoot 項目中的全過程。文章首先以高度概括的方式探討了 gRPC 的理論基礎,為讀者提供了對其核心概念的清晰認識。隨后,我們將轉向更為具體的實踐層面,展示 gRPC 在實際應用中的多種實現方式。

          雖然全文篇幅較長,但我們強烈建議您耐心閱讀,以確保您能夠全面掌握 gRPC 的技術細節和應用場景。通過本文的學習,您將不僅能夠理解 gRPC 的工作原理,還能在您的 SpringBoot 項目中成功集成和應用這一強大的通信協議,從而提升系統的性能和效率。

          什么是gRPC?

          gRPC 是最初由 Google 開發的開源遠程過程調用 (RPC) 框架。它是云原生計算基金會 (CNCF) 的一部分,旨在實現微服務架構中的服務之間高效、穩健的通信。以下是 gRPC 的一些關鍵功能和概念:

          1. 協議緩沖區(Protobuf):

          • gRPC 使用 Protocol Buffers 作為其接口定義語言 (IDL)。
          • Protobuf 是一種與語言無關、與平臺無關的可擴展機制,用于序列化結構化數據,類似于 XML 或 JSON,但更小、更快、更簡單。
          • .proto文件用于定義服務方法和消息格式。

          2.基于HTTP/2:

          • gRPC 利用 HTTP/2 作為其傳輸協議,它允許對 HTTP/1.x 進行許多改進,例如通過單個 TCP 連接復用多個請求、二進制幀和高效的錯誤處理。

          3、四種服務方式:

          • Unary RPC :客戶端發送單個請求并獲得單個響應。
          • Server Streaming RPC :客戶端發送請求并獲取響應流。
          • Client Streaming RPC :客戶端將一系列消息流式傳輸到服務器,然后服務器發回單個響應。
          • Bidirectional Streaming RPC :客戶端和服務器都獨立地向對方發送消息流。

          在這個實際的實現中,我們將看到一元實現它等于發送單個請求和響應。我們現在知道 gRPC 使用ProtocolBuffer來定義服務。首先讓我們從定義接口定義開始,然后讓我們看看項目結構和腳手架。下面的項目結構中的每個模塊都是一個 Maven 模塊。

          模塊 3(原型服務):

          proto-service模塊負責保存與 proto 文件相關的所有內容,并將它們編譯成 gRPC 相關的存根和接口。

          讓我們在src/main/proto文件夾中創建一個Product.proto文件并復制以下內容。

          /**
           * @author vaslabs(M K Pavan Kumar)
           * @medium (https://medium.com/@manthapavankumar11)
           */
          syntax="proto3";
          
          option java_multiple_files=true;
          
          package com.vaslabs.proto;
          
          message Product {
            int32 product_id=1;
            string name=2;
            string description=4;
            float price=3;
            int32 category_id=5;
          }
          
          message ProductList{
            repeated Product product=1;
          }
          
          message Category {
            int32 category_id=1;
            string name=2;
          }
          
          service ProductService {
          
            //unary - synchronous
            //request-response stype [not streaming]
            rpc getProductById(Product) returns(Product){}
            rpc getProductByCategoryId(Category) returns(ProductList){}
          }

          提供的 proto 文件采用 Protocol Buffers 版本 3 ( proto3 ) 的語法編寫,是與產品和類別相關的 gRPC 服務的結構化定義。以下是其組成部分的簡要說明:

          文件頭:

          • syntax="proto3"; :指定文件使用proto3語法,Protocol Buffers的最新版本。

          選項:

          • option java_multiple_files=true; :指示 Protocol Buffers 編譯器為每種消息類型生成單獨的 Java 文件,而不是單個文件。

          包裹聲明:

          • package com.vaslabs.proto; :定義包名稱,有助于防止不同項目之間的名稱沖突。

          消息定義:

          • message Product :定義Product消息,其中包含產品 ID、名稱、描述、價格和類別 ID 字段。每個字段都有一個唯一的標簽號(例如, product_id=1 )。
          • message ProductList :定義一個ProductList消息,該消息可以包含多個Product消息。 repeated表示它是Product對象的列表。
          • message Category :表示具有類別 ID 和名稱的類別。

          服務定義:

          • service ProductService :聲明一個名為ProductService服務。
          • rpc getProductById(Product) returns(Product){} :定義一元(單個請求-響應)方法getProductById ,該方法將Product消息作為輸入并返回Product消息。
          • rpc getProductByCategoryId(Category) returns(ProductList){} :另一個一元方法getProductByCategoryIdCategory消息作為輸入并返回ProductList消息。

          RPC 類型:

          • RPC 方法是一元的,這意味著它們遵循簡單的請求-響應模式,無需流式傳輸。

          讓我們使用 maven 插件運行“mvncompile”來編譯并獲取 gRPC 風格生成的源代碼以獲取源代碼。

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
              <parent>
                  <groupId>org.vaslabs</groupId>
                  <artifactId>springboot-grpc-demo</artifactId>
                  <version>0.0.1-SNAPSHOT</version>
              </parent>
          
              <artifactId>proto-service</artifactId>
          
              <properties>
                  <maven.compiler.source>21</maven.compiler.source>
                  <maven.compiler.target>21</maven.compiler.target>
                  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              </properties>
          
              <dependencies>
                  <dependency>
                      <groupId>io.grpc</groupId>
                      <artifactId>grpc-stub</artifactId>
                      <version>1.56.1</version>
                  </dependency>
                  <dependency>
                      <groupId>io.grpc</groupId>
                      <artifactId>grpc-protobuf</artifactId>
                      <version>1.54.2</version>
                  </dependency>
                  <dependency>
                      <groupId>javax.annotation</groupId>
                      <artifactId>javax.annotation-api</artifactId>
                      <version>1.3.2</version>
                  </dependency>
              </dependencies>
          
              <build>
                  <extensions>
                      <extension>
                          <groupId>kr.motd.maven</groupId>
                          <artifactId>os-maven-plugin</artifactId>
                          <version>1.6.1</version>
                      </extension>
                  </extensions>
                  <plugins>
                      <plugin>
                          <groupId>org.xolstice.maven.plugins</groupId>
                          <artifactId>protobuf-maven-plugin</artifactId>
                          <version>0.6.1</version>
                          <configuration>
                              <protocArtifact>
                                  com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}
                              </protocArtifact>
                              <pluginId>grpc-java</pluginId>
                              <pluginArtifact>
                                  io.grpc:protoc-gen-grpc-java:1.53.0:exe:${os.detected.classifier}
                              </pluginArtifact>
                          </configuration>
                          <executions>
                              <execution>
                                  <goals>
                                      <goal>compile</goal>
                                      <goal>compile-custom</goal>
                                  </goals>
                              </execution>
                          </executions>
                      </plugin>
                  </plugins>
              </build>
          </project>

          模塊 2(產品服務)

          這是保存原型服務的實現的服務,接收原型請求并將原型響應發送給調用它的客戶端。該服務由grpc-spring-boot-starter提供支持

          將以下內容復制到該模塊的 pom.xml 中。觀察 pom 文件,我們已將proto-service模塊添加為依賴項,因為我們將在此模塊中實現 proto-services。

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
              <parent>
                  <groupId>org.vaslabs</groupId>
                  <artifactId>springboot-grpc-demo</artifactId>
                  <version>0.0.1-SNAPSHOT</version>
              </parent>
          
              <artifactId>product-service</artifactId>
          
              <properties>
                  <maven.compiler.source>21</maven.compiler.source>
                  <maven.compiler.target>21</maven.compiler.target>
                  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              </properties>
          
              <dependencies>
                  <dependency>
                      <groupId>org.vaslabs</groupId>
                      <artifactId>proto-service</artifactId>
                      <version>0.0.1-SNAPSHOT</version>
                  </dependency>
                  <dependency>
                      <groupId>net.devh</groupId>
                      <artifactId>grpc-spring-boot-starter</artifactId>
                      <version>2.14.0.RELEASE</version>
                  </dependency>
              </dependencies>
          
              <build>
                  <plugins>
                      <plugin>
                          <groupId>org.springframework.boot</groupId>
                          <artifactId>spring-boot-maven-plugin</artifactId>
                      </plugin>
                  </plugins>
              </build>
          
          </project>

          讓我們在src/main/java中創建一個ProductServiceImpl.java類。觀察該類擴展了由protoc生成的基類ProductServiceGrpc.ProductServiceImplBase

          package org.vaslabs;
          
          import com.vaslabs.proto.Category;
          import com.vaslabs.proto.Product;
          import com.vaslabs.proto.ProductList;
          import com.vaslabs.proto.ProductServiceGrpc;
          import io.grpc.stub.StreamObserver;
          import net.devh.boot.grpc.server.service.GrpcService;
          
          import java.util.List;
          
          @GrpcService
          public class ProductServiceImpl extends ProductServiceGrpc.ProductServiceImplBase {
              @Override
              public void getProductById(Product request, StreamObserver<Product> responseObserver) {
                  InMemoryData.getProducts()
                          .stream()
                          .filter(product -> product.getProductId()==request.getProductId())
                          .findFirst()
                          .ifPresent(responseObserver::onNext);
                  responseObserver.onCompleted();
              }
          
              @Override
              public void getProductByCategoryId(Category request, StreamObserver<ProductList> responseObserver) {
                  List<Product> products=InMemoryData.getProducts()
                          .stream()
                          .filter(product -> product.getCategoryId()==request.getCategoryId())
                          .toList();
          
                  ProductList productList=ProductList.newBuilder().addAllProduct(products).build();
          
                  responseObserver.onNext(productList);
                  responseObserver.onCompleted();
              }
          }

          使用主類,我們可以啟動服務ProductServiceApplication.java ,如下所示。

          package org.vaslabs;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          
          @SpringBootApplication
          public class ProductServiceApplication {
              public static void main(String[] args) {
                  SpringApplication.run(ProductServiceApplication.class, args);
              }
          }

          如果要更改默認的 gRPC 服務端口,請在src/main/resources/application.yaml中保留以下內容。

          grpc:
            server:
              port: 9090

          模塊 1(產品-客戶端)

          該模塊是一個純Spring Boot Web客戶端,它將以Http形式偵聽Web請求,并使用proto請求與產品服務通信并接收proto響應,然后轉換為DTO并將其作為Http響應發送給調用客戶端。

          讓我們將以下內容復制到pom.xml,該模塊由grpc-client-spring-boot-starter提供支持,以發出啟用 grpc 的請求和響應。

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
              <parent>
                  <groupId>org.vaslabs</groupId>
                  <artifactId>springboot-grpc-demo</artifactId>
                  <version>0.0.1-SNAPSHOT</version>
              </parent>
          
              <artifactId>product-client</artifactId>
          
              <properties>
                  <maven.compiler.source>21</maven.compiler.source>
                  <maven.compiler.target>21</maven.compiler.target>
                  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              </properties>
          
              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.vaslabs</groupId>
                      <artifactId>proto-service</artifactId>
                      <version>0.0.1-SNAPSHOT</version>
                  </dependency>
                  <dependency>
                      <groupId>net.devh</groupId>
                      <artifactId>grpc-client-spring-boot-starter</artifactId>
                      <version>2.14.0.RELEASE</version>
                  </dependency>
                  <dependency>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                      <version>1.18.30</version>
                      <scope>provided</scope>
                  </dependency>
              </dependencies>
          
              <build>
                  <plugins>
                      <plugin>
                          <groupId>org.springframework.boot</groupId>
                          <artifactId>spring-boot-maven-plugin</artifactId>
                          <version>3.1.2</version>
                          <configuration>
                              <excludes>
                                  <exclude>
                                      <groupId>org.projectlombok</groupId>
                                      <artifactId>lombok</artifactId>
                                  </exclude>
                              </excludes>
                          </configuration>
                      </plugin>
                  </plugins>
              </build>
          </project>

          讓我們創建一個服務,它將使用生成的存根與 grpc 服務進行通信,如下所示。 src/main/java/org/vaslabs/service/ProductServiceGRPC.java

          package org.vaslabs.service;
          
          import com.vaslabs.proto.Category;
          import com.vaslabs.proto.Product;
          import com.vaslabs.proto.ProductList;
          import com.vaslabs.proto.ProductServiceGrpc;
          import net.devh.boot.grpc.client.inject.GrpcClient;
          import org.springframework.stereotype.Service;
          
          import java.util.List;
          
          import static org.vaslabs.service.helpers.DtoMappingHelper.mapProductListToProductDTO;
          import static org.vaslabs.service.helpers.DtoMappingHelper.mapProductToProductDTO;
          
          @Service
          public class ProductServiceRPC {
          
              @GrpcClient("grpc-product-service")
              ProductServiceGrpc.ProductServiceBlockingStub productServiceBlockingStub;
          
              public org.vaslabs.dto.Product getProductById(int id) {
                  Product product=Product.newBuilder().setProductId(id).build();
                  Product response=productServiceBlockingStub.getProductById(product);
                  return mapProductToProductDTO(response);
              }
          
              public List<org.vaslabs.dto.Product> getProductByCategoryId(int id) {
                  Category category=Category.newBuilder().setCategoryId(id).build();
                  ProductList response=productServiceBlockingStub.getProductByCategoryId(category);
                  return mapProductListToProductDTO(response);
              }
          }

          一旦收到原始響應,我們將使用以下輔助方法將其轉換為 DTO。 **/helpers/DtoMappingHelper.java

          package org.vaslabs.service.helpers;
          
          import com.vaslabs.proto.ProductList;
          import org.vaslabs.dto.Product;
          
          import java.util.ArrayList;
          import java.util.List;
          
          public class DtoMappingHelper {
          
              public static List<org.vaslabs.dto.Product> mapProductListToProductDTO(ProductList productList) {
                  List<Product> products=new ArrayList<>();
                  productList.getProductList().forEach(product -> {
                      Product product1=getProduct();
                      product1.setId(product.getProductId());
                      product1.setCategoryId(product.getCategoryId());
                      product1.setName(product.getName());
                      product1.setDescription(product.getDescription());
                      product1.setPrice(product.getPrice());
          
                      products.add(product1);
                  });
                  return products;
              }
          
              public static org.vaslabs.dto.Product mapProductToProductDTO(com.vaslabs.proto.Product product) {
                  Product product1=getProduct();
                  product1.setId(product.getProductId());
                  product1.setCategoryId(product.getCategoryId());
                  product1.setName(product.getName());
                  product1.setDescription(product.getDescription());
                  product1.setPrice(product.getPrice());
                  return product1;
              }
          
              private static Product getProduct(){
                  return new Product();
              }
          }

          **/dto/Product.java

          package org.vaslabs.dto;
          
          import lombok.Data;
          import lombok.Getter;
          import lombok.Setter;
          
          @Data
          @Setter
          @Getter
          public class Product {
              private int id;
              private int categoryId;
              private String name;
              private String description;
              private float price;
          }

          讓我們創建一個名為**/ProductController.java的控制器,如下所示。

          package org.vaslabs.controller;
          
          import org.springframework.http.MediaType;
          import org.springframework.http.ResponseEntity;
          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.PathVariable;
          import org.springframework.web.bind.annotation.RestController;
          import org.vaslabs.dto.Product;
          import org.vaslabs.service.ProductServiceRPC;
          
          import java.util.List;
          
          @RestController
          public class ProductController {
          
              private final ProductServiceRPC productServiceRPC;
          
              public ProductController(ProductServiceRPC productServiceRPC) {
                  this.productServiceRPC=productServiceRPC;
              }
          
              @GetMapping("/product/{id}")
              public ResponseEntity<Product> getProductById(@PathVariable String id){
                  return ResponseEntity.ok().body(productServiceRPC.getProductById(Integer.parseInt(id)));
              }
          
              @GetMapping(value="/product/category/{id}", produces=MediaType.APPLICATION_JSON_VALUE)
              public ResponseEntity<List<Product>> getProductByCategoryId(@PathVariable String id){
                  return ResponseEntity.ok().body(productServiceRPC.getProductByCategoryId(Integer.parseInt(id)));
              }
          }

          在此模塊的資源文件夾中,請將以下內容添加到application.yaml文件中

          grpc:
            client:
              grpc-product-service:
                address: static://localhost:9090
                negotiationType: plaintext

          為此,存根將創建一個名為grpc-product-service通道來與服務進行通信。

          執行:

          分別運行產品服務和產品客戶端,如果下面的所有內容應該是您應該看到的輸出。

          我使用 HTTPie 來測試服務。

          總結

          在微服務架構的背景下,采用 gRPC 和 Protobuf 進行服務間通信已被證明是提升效率、穩健性和跨語言互操作性的關鍵策略。gRPC 以其基于 HTTP/2 的通信機制和對多種編程語言的廣泛支持,極大地促進了高性能和低延遲的服務交互,這與微服務架構的分布式特性完美契合。

          Protobuf(Protocol Buffers)通過提供一種緊湊的二進制序列化格式,進一步強化了這一優勢,確保服務之間交換的數據不僅體積小,而且序列化和反序列化的速度極快。這種高效的序列化機制使得數據傳輸更為迅速,同時也減少了網絡帶寬的占用,對于構建現代、高效且有效的微服務生態系統至關重要。

          綜上所述,gRPC 與 Protobuf 的結合,不僅為微服務架構提供了一種強大的通信工具,還為構建高性能、低延遲的分布式系統奠定了堅實的基礎。這種組合無疑成為了推動微服務生態系統發展的強大動力。

          來源:https://spring4all.com/forum-post/7462.html

          隨著.net core3.0的正式發布,gRPC服務被集成到了VS2019。本文主要演示如何對gRPC的服務進行認證授權。

          分析

          目前.net core使用最廣的認證授權組件是基于OAuth2.0協議的IdentityServer4。而gRPC可以與ASP.NET Core Authentication一起使用來實現認證授權功能。本文將創建3個應用程序來完成gRPC的認證授權演示過程。

          步驟

          Ids4.Server

          1.創建一個.net core的webapi
          2.nuget引用最新的IdentityServer4的包
          <PackageReference Include="IdentityServer4" Version="3.0.1" />
          

          IdentityServer4相關配置,因為是演示所以很簡單,生產場景大家根據實際情況配置。

          namespace Ids4.Server
          {
              public class Config
              {
                  public static IEnumerable<IdentityResource> GetIdentityResources()
                  {
                      return new List<IdentityResource>
                      {
                          new IdentityResources.OpenId(),
                          new IdentityResources.Profile(),
                          new IdentityResources.Email(),
                      };
                  }
                  public static IEnumerable<ApiResource> GetApis()
                  {
                      return new List<ApiResource>
                      {
                          new ApiResource("api", "Demo API")
                          {
                              ApiSecrets={ new Secret("secret".Sha256()) }
                          }
                      };
                  }
                  public static IEnumerable<Client> GetClients()
                  {
                      return new List<Client>
                          {
                              new Client
                              {
                                  ClientId="client",
                                  ClientSecrets={ new Secret("secret".Sha256()) },
          
                                  AllowedGrantTypes=GrantTypes.ClientCredentials,
                                  AllowedScopes={ "api" },
                              },
                          };
                  }
              }
          }
          
          4. startup.cs 注入服務
          services.AddIdentityServer().AddInMemoryApiResources(Config.GetApis())
              .AddInMemoryIdentityResources(Config.GetIdentityResources())
              .AddInMemoryClients(Config.GetClients())
              .AddDeveloperSigningCredential(persistKey: false); 
          
          
          5. startup.cs 配置http請求管道
          app.UseIdentityServer();
          
          6. 啟動服務,使用PostMan進行調試,有返回結果表示服務創建成功
          POST /connect/token HTTP/1.1
          Host: localhost:5000
          Content-Type: application/x-www-form-urlencoded
          grant_type=client_credentials&client_id=client1&client_secret=secret
          

          {
              "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IlVyMmxuM2EwNGhWaGdDdWZTVTNtZVEiLCJ0eXAiOiJhdCtqd3QifQ.eyJuYmYiOjE1NzEzMDkwMTMsImV4cCI6MTU3MTMxMjYxMywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiYXBpIiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYXBpIl19.X4pg9_FbPbWZl814XC0NYWTslfhMG4aXWEyXLrXhIojPJaL7Qvq9ieDF4S7x0psRcClwbwCg81hTrG3j2Cmcl0nzj_Ic7UY8MfN0dvAuy_fJdUf76TX0oOpir3SxgC8gnfaKyEoWmmbIyvwicWbKp9PP-EeTxG6-oMYn6PO22cwRVHDD28ZdEAq2DEkATOh9XPavoi9vGZhPQ1nviKL1K6tcYUGXSQbhWI9ISEqnTHqMX1xA_gcDIAplGvquXmtXdgyTsRoGolEtzDAYVH4sGUb1SpYx2nc8bgl6Qw27fhe0Uy9MR70kQMcEkCTdXLivjYjkuI9_quUyJHzdi5KgnQ",
              "expires_in": 3600,
              "token_type": "Bearer",
              "scope": "api"
          }
          

          本篇不對IdentityServer4做更多的講解,大家可以參考官方文檔了解更多。

          Grpc.Server

          1. 使用vs2019創建gRPC服務端。

          2. 不用做任何更改,直接使用默認創建的gRPC服務

          Grpc.Client

          1. 創建一個控制臺程序
          2. 引入nuget安裝包
          <PackageReference Include="Google.Protobuf" Version="3.10.0" />
          <PackageReference Include="Grpc.Net.Client" Version="2.23.2" />
          <PackageReference Include="Grpc.Tools" Version="2.24.0">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
          </PackageReference>
          

          這3個核心包是客戶端必備的,其中grpc.tools幫我們把proto文件轉化成C#代碼。

          3. 創建Protos文件夾
          4. 復制Grpc.Server項目Protos文件夾下的greet.proto文件到本項目的Protos文件夾
          5. greet.proto文件右鍵設置gGRC Stub Classes為Client only。

          也可以直接使用在項目文件里面代碼設置如下:

          <ItemGroup>
            <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
          </ItemGroup>
          
          6. gRPC客戶端訪問服務端代碼
          var channel=GrpcChannel.ForAddress("https://localhost:5001");
          var client=new Greeter.GreeterClient(channel);
          var response=client.SayHello(new HelloRequest { Name="World" });
          Console.WriteLine(response.Message);
          

          啟動gRPC服務端,在啟動gRPC客戶端控制臺打印hello word表示成功。

          identityServer接入gRPC是非常容易,和傳統webapi差不多。

          改造Grpc.Server支持IdentityServer4

          1. 引入nuget包
          <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
          
          2. startup.cs 注入服務,和IdentityServer4一樣。
          services.AddGrpc(x=> x.EnableDetailedErrors=false);
          services.AddAuthorization();
          services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
              .AddIdentityServerAuthentication(options=>
              {
                  options.Authority="http://localhost:5000";
                  options.RequireHttpsMetadata=false;
              });
          
          3. startup.cs 配置http請求管道
          if (env.IsDevelopment())
          {
              app.UseDeveloperExceptionPage();
          }
          app.UseRouting();
          app.UseAuthentication();
          app.UseAuthorization();
          app.UseEndpoints(endpoints=>
          {
              endpoints.MapGrpcService<GreeterService>();
          
              endpoints.MapGet("/", async context=>
              {
                  await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
              });
          });
          
          4. 對需要授權的服務打標簽[Authorize],可以打在類上也可以打在方法上
          [Authorize]
          public class GreeterService : Greeter.GreeterBase
          {
          }
          

          這個時候我們啟動Grpc.Client訪問Grpc.Server服務

          發現報錯401。說明此服務需要攜帶令牌才能訪問。

          改造Grpc.Client攜帶令牌訪問

          //獲取token可以直接使用HttpClient來獲取,這里使用IdentityModel來獲取token
          var httpClient=new HttpClient();
          var disco=await httpClient.GetDiscoveryDocumentAsync("http://localhost:5000");
          if (!disco.IsError)
          {
              var token=await httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest()
              {
                  Address=disco.TokenEndpoint,
                  ClientId="client",
                  ClientSecret="secret"
              });
              var tokenValue="Bearer " + token.AccessToken;
              var metadata=new Metadata
              {
                  { "Authorization", tokenValue }
              };
              var callOptions=new CallOptions(metadata);
              var channel=GrpcChannel.ForAddress("https://localhost:5001");
              var client=new Greeter.GreeterClient(channel);
              var response=client.SayHello(new HelloRequest { Name="World" }, callOptions);
              Console.WriteLine(response.Message);
          }
          

          執行程序返回hello world表示成功。

          傳統調用webapi把token放到Header頭的Authorization屬性里面,grpc是放到Metadata里面,調用方法的時候傳入CallOptions。使用上大同小異。

          后記

          目前gRPC各個語言的支持都已經很完善,因為跨語言,性能更高的特性非常適合做內網的通信。筆者也將繼續對gRPC進行跟進,會嘗試將部分的內部服務改造成gRPC,關于gRPC的相關問題也可以留言大家一起討論。 源代碼地址:https://github.com/longxianghui/grpc_ientityserver


          原文地址:https://www.cnblogs.com/longxianghui/p/11719190.html

          在移動端平臺開發中,為了增加代碼復用,降低開發成本,通常會需要采用跨平臺的開發技術,花椒也不例外。本次新的單品開發,由于時間緊,人員有限,經過調研選型,最終確定了 flutter 方案(具體選型過程不在本文討論之內)。

          為了讓客戶端更專注業務實現,降低接口聯調測試成本,我們選用了 gRPC 方案。gRPC是一個高性能、通用的開源 RPC 框架,由 Google 開發并基于 HTTP/2 協議標準而設計,基于 ProtoBuf(Protocol Buffers)序列化協議開發,且支持當前主流開發語言。gRPC通過定義一個服務并指定一個可以遠程調用的帶有參數和返回類型的的方法,使客戶端可以直接調用不同機器上的服務應用的方法,就像是本地對象一樣。在服務端,服務實現這個接口并且運行 gRPC 服務處理客戶端調用。在客戶端,有一個stub提供和服務端相同的方法。

          gRPC

          特點

          • 基于標準化的 IDL(ProtoBuf)來生成服務器端和客戶端代碼,支持多種主流開發語言。同時可以更好的支持團隊與團隊之間的接口設計,開發,測試,協作等。
          • 基于 HTTP/2 設計,支持雙向流,多路復用,頭部壓縮。
          • 支持流式發送和響應,批量傳輸數據,提升性能。
          • ProtoBuf 序列化數據抓包、調試難度較大。我們使用服務端注入方式提供了用戶或設備過濾,請求及返回值日志捕獲,并開發對應后臺模擬抓包展示。
          • 相比 JSON, 對前端不夠友好。gRPC 生態 提供了 gateway 的方式為 gRPC 服務代理出 RESTful 接口。
          • ProtoBuf 提供了非常強的擴展性,可以為 protoc 開發定制插件,從而擴展 proto 文件的功能及描述性。

          gRPC-Web

          gRPC-Web 為前端瀏覽器提供了 Javascript 庫用來訪問 gRPC 服務,但是需要通過 Envoy 提供代理服務。相比 JSON 的方式對前端不夠友好,同時也增加了服務端的部署成本。因此在這次項目中前端未使用 gRPC 服務,而是由 gRPC-Gateway 提供代理的 RESTful 接口。

          gRPC-Gateway

          grpc-gateway 是 protoc 的一個插件,它能讀取 gRPC 的服務定義并生成反向代理服務器,將 RESTful 的 JSON 請求轉換為 gRPC 的方式。這樣無需太多工作即可實現一套基于 gRPC 服務的 RESTful 接口,方便前端使用調用接口,同時也方便開發過程中通過 Postman/Paw 之類的工具調試接口。

          gateway -> gRPC 映射方式:

          • HTTP 源 IP 添加到 gRPC 的 X-Forwarded-For 請求頭
          • HTTP 請求 Host 添加到 gRPC 的 X-Forwarded-Host 請求頭
          • HTTP 請求頭 Authorization 添加到 gRPC 的 authorization 請求頭
          • HTTP 請求頭帶 Grpc-Metadata- 前綴的映射到 gRPC 的 metadata (key 名不帶前綴)

          例如,gRPC 接口要求的通用的 metadata 參數(如 platform, device_id 等)在 HTTP RESTful 的傳遞方式如下:

          基礎庫

          dart

          為了便于客戶端調用,連接復用及通用參數傳遞,我們封裝了 dart 的基礎庫。

          BaseClient 維護了針對 HOST 緩存的連接池,同時也提供了接口需要傳遞的 metadata 信息。

          golang

          golang 后端服務需要同時支持 gRPC 和 gateway 兩種請求方式。為了簡化部署和上線依賴,gateway 和 gRPC 的功能放在了一起,并通過攔截器注入對應的功能,主要包括 gRPC 統計,訪問日志,接口鑒權,請求參數校驗,gateway JSON 編碼等。

          • 引用到的 package


          • 開發流程

          為了提高開發效率,方便維護及模塊復用,服務端按功能進行組件化開發。每個組件可以單獨運行一個服務,也可以和其它組件共同組成一個服務。每個組件都需要實現 Component 接口:

          對應組件開發完成后,需要開發對應的服務容器,步驟如下。

          • 初始化 base package
          1 base.Init(context.TODO(), cfg, &global.Callback{
          2 Authenticator: &auth.Callback{},
          3 LogCapture: &log.Capture{},
          4 })
          
          • 如需對外提供服務,需要提供端口及 TLS 證書


          base.DefaultServer.AddPublicServer(rpcPort, gatewayPort, setting.TLSConfig)

          • 組件注冊
          1 base.DefaultServer.RegisterComponent(&user.Component{})
          2 base.DefaultServer.RegisterComponent(&push.Component{})
          3 ...
          
          • 監聽服務


          base.DefaultServer.Serve()

          接口定義及實現

          proto 規范

          gRPC 基于標準化的 IDL(ProtoBuf)來生成服務器端和客戶端代碼,我們決定將所有的接口描述及文檔說明都放到 proto 文件中,便于查看及修改。對 proto 的接口描述及注釋的規范如下:

          代碼生成

          golang

           1 gengo:
           2 @protoc -Iproto \
           3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
           4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \
           5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \
           6 --go_out=plugins=grpc:go/pb \
           7 --grpc-gateway_out=logtostderr=true:go/pb \
           8 --validate_out="lang=go:go/pb" \
           9 --auth_out="lang=go:go/pb" \
          10 proto/*.proto
          
          • SDK 引入


          golang 使用 go mod 的方式直接引入 pb 生成的 .go 文件

          dart

          • SDK 引入


          修改 pubspec.yaml,執行 flutter packages get 或 flutter packages upgrade

           1 dependencies:
           2 flutter:
           3 sdk: flutter
           4
           5 protobuf: ^0.13.4
           6 grpc: ^1.0.1
           7 user:
           8 git:
           9 url: git@github.com:project/repo.git
          10 path: dart/user
          
          • 已知問題:
          1. dart 在對 protobuf 生成的類型做 json 編碼時,json 中的 key 是字段號而非名字,導致無法與其它語言交互。ISSUE (https://github.com/dart-lang/protobuf/issues/220)


          文檔生成

          gRPC gateway 提供了通過 proto 文件生成 swagger API 文檔,缺點是只支持 gateway 的 RESTful 接口,并且默認的展示方式有點不符合我們的常規文檔使用方式。

          我們基于 protoc 插件開發了 protoc-gen-markdown 工具,可以由 proto 文件生成 markdown 文檔,提供 gRPC 接口描述,以及 RESTful 接口描述及 JSON 示例,提供全文目錄,支持錨點導航等。生成方式如下:

          1gendoc:
          2 @protoc -Iproto \
          3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
          4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \
          5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \
          6 --markdown_out=":doc" \
          7 proto/*.proto
          

          文檔會在對應路徑生成接口列表 README.md,以及每個 protobuf 對應的接口文檔。

          調試

          傳統的 RESTful 接口在調試及問題排查時,可以通過抓包或者 MitM(中間人攻擊)的方式,配置也比較容易。而 gRPC 因為使用了 HTTP2 及 protobuf 二進制流,抓包及數據流反解難度相對較高,調試及問題排查時會比較復雜。為了解決這個問題,我們通過服務端注入的方式,配合查詢后臺過濾對應的請求日志,從而實現如下類似抓包的效果。



          后續計劃

          1. gRPC Streaming
          2. 框架層集成全鏈路 Trace 支持
          3. 迭代優化框架,提供對應腳手架,簡化新組件/服務創建及開發流程

          Go語言中文網,致力于每日分享編碼、開源等知識,歡迎關注我,會有意想不到的收獲!

          本文由花椒服務端團隊原創授權發布


          主站蜘蛛池模板: 国精产品999一区二区三区有限| 国产一区二区在线观看| 国产一区二区四区在线观看| 国精产品一区一区三区有限公司| 亚洲乱码一区二区三区国产精品| 久久综合亚洲色一区二区三区| 亚洲一区影音先锋色资源| 国产美女av在线一区| 国产精品视频一区二区三区不卡| 爆乳熟妇一区二区三区霸乳| 国产精品99无码一区二区| 99精品久久精品一区二区| 亚洲av无码不卡一区二区三区| 无码aⅴ精品一区二区三区浪潮| 一区二区在线视频| 亚洲av综合av一区二区三区 | 亚洲AV成人精品日韩一区18p | 亚洲bt加勒比一区二区| 日韩精品无码免费一区二区三区| 无码国产精品一区二区免费式芒果| 色欲精品国产一区二区三区AV| 国产免费私拍一区二区三区| 中文字幕精品亚洲无线码一区应用| 国产精品 一区 在线| 国产一区二区三区精品视频| 中文人妻无码一区二区三区 | 亚洲一区二区三区高清在线观看 | 一区二区三区四区无限乱码| 一区二区三区内射美女毛片| 国产伦精品一区二区三区免费下载 | 国产婷婷色一区二区三区深爱网 | 亚洲日韩精品国产一区二区三区| 精品一区狼人国产在线| 亚洲一区二区精品视频| 亚洲午夜精品一区二区| 亚洲欧美日韩中文字幕在线一区 | 99国产精品欧美一区二区三区| 91精品福利一区二区| 夜夜高潮夜夜爽夜夜爱爱一区| 人妻无码第一区二区三区| 精品一区二区三区自拍图片区|