在前面的文章之中咱們介紹了基於Kubernetes及Istio如何一步一步把Service Mesh微服務架構玩起來!在該文章中,咱們演示了一個很是貼近實戰的案例,這裏回顧下該案例的結構,以下圖所示:java
該案例所演示的就是咱們平常使用微服務架構開發時,服務間最廣泛的通訊場景。在Spring Cloud微服務體系中,服務間能夠經過Fegin+Ribbon組合的方式,實現服務間負載均衡方式的Http接口調用;但在Service Mesh架構中,服務發現及負載均衡等治理邏輯已經由SideCar代理,若是還但願延續Spring Cloud場景下服務間接口調用的代碼體驗,通常能夠經過改寫Feign組件,去掉其中關於服務治理的邏輯,只保留簡單的接口聲明式調用邏輯來實現。spring
上述案例中「micro-api->micro-order」之間的服務通訊調用,就是基於該方式實現的(可參考以前的文章)。但在微服務架構中除了採用Http協議通訊外,對於某些對性能有着更高要求的系統來講,採用通訊效率更高的RPC協議每每是更合適的選擇!apache
在基於Spring Cloud框架的微服務體系中,服務之間也能夠經過RPC協議通訊,但因爲服務治理的須要,也須要一套相似於Fegin+Ribbon組合的SDK支持。例如gRPC框架就有針對Spring Boot框架的「grpc-client-spring-boot-starter」依賴支持!該項目是一個 gRPC 的 Spring Boot 模塊,能夠在 Spring Boot 中內嵌一個 gRPC Server 對外提供服務,並支持 Spring Cloud 的服務發現、註冊、鏈路跟蹤等等。編程
那麼在Service Mesh微服務體系下,服務間基於gRPC框架的通訊應該怎麼實現呢?接下來,我將以案例中"micro-order->micro-pay"之間的服務調用爲例,演示在Service Mesh微服務架構下實現服務間的gRPC通訊調用,並將案例中Http+gRPC服務間通訊的完整場景串起來! json
在演示Service Mesh微服務架構下的gRPC通訊場景以前,咱們先簡單介紹下RPC協議及gRPC框架的基本知識。 api
RPC(Remote Procedure Call),又稱遠程過程調用,是一種經過掩藏底層網絡通訊複雜性,從而屏蔽遠程和本地調用區別的通訊方式。相比於Http協議,RPC協議屬於一種自定義的TCP協議,從而在實現時避免了一些Http協議信息的臃腫問題,實現了更高效率的通訊。 服務器
在主流實現RPC協議的框架中,比較著名的有Dubbo、Thrift及gRPC等。由於目前主流的容器發佈平臺Kubernetes,以及Service Mesh開源平臺Istio都是經過gRPC協議來實現內部組件之間的交互,因此在Service Mesh微服務架構中,服務間通訊採用gRPC協議,從某種角度上說會更具備原生優點。何況在此以前,gRPC框架已經在分佈式、多語言服務場景中獲得了大量應用,所以能夠預測在Service Mesh微服務架構場景下,基於gRPC框架的微服務通訊方式會逐步成爲主流。網絡
gRPC是Google發佈的基於HTTP/2.0傳輸層協議承載的高性能開源軟件框架,提供了支持多種編程語言的、對網絡設備進行配置和納管的方法。因爲是開源框架,通訊的雙方能夠進行二次開發,因此客戶端和服務器端之間的通訊會更加專一於業務層面的內容,減小了對由gRPC框架實現的底層通訊的關注。架構
接下來的內容就具體演示在Service Mesh微服務架構下,實現微服務「micro-order->micro-pay」的gRPC通訊調用!app
首先從gRPC服務端的角度,在微服務micro-pay項目中集成gRPC-Java,並實現一個gRPC服務端程序。具體以下:
一、構建Spring Boot基本工程(micro-pay/micro-pay-client)
使用Spring Boot框架構建基本的Maven工程,爲了工程代碼的複用,這裏單獨抽象一個micro-pay-client工程,並定義micro-pay微服務gRPC服務接口的protobuf文件(*/proto/paycore.proto),代碼以下:
syntax = "proto3"; package com.wudimanong.pay.client; option java_multiple_files = true; option java_package = "com.wudimanong.micro.pay.proto"; service PayService { //定義支付rpc方法 rpc doPay (PayRequest) returns (PayResponse); } message PayRequest { string orderId = 1; int32 amount=2; } message PayResponse { int32 status = 1; }
如上所示,建立了一個基於protobuf協議的支付接口定義文件,其中定義了支付服務PayService及其中的doPay支付rpc方法,並定義了其請求和返回參數對象,具體的語法遵循「proto3」協議。
爲了可以正常編譯和生成protobuf文件所定義服務接口的代碼,須要在項目pom.xml文件中引入jar包依賴及Maven編譯插件配置,代碼以下:
<?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"> .... <dependencies> .... <!--gRPC通訊類庫(截止目前的最新版本)--> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-all</artifactId> <version>1.36.1</version> </dependency> </dependencies> <build> <!--引入gRpc框架proto文件編譯生產插件--> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.2</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.12.0:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.36.0:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
這是單獨關於gRPC接口proto文件定義的工程,定義後編譯工程,maven就會根據前面定義的paycore.proto文件生成gRPC服務端/客戶端相關代碼。
完成後,繼續構建micro-pay微服務的spring boot工程代碼,並在其pom.xml文件中引入上述gRPC協議文件定義的依賴,例如:
<!--引入支付服務gRPC ProtoBuf定義依賴--> <dependency> <groupId>com.wudimanong</groupId> <artifactId>micro-pay-client</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
在micro-pay-client工程中所引入的gRPC相關的依賴及插件配置會自動繼承至micro-pay工程!
二、編寫gRPC支付服務代碼
在micro-pay代碼工程中建立一個PayCoreProvider接口代碼,用於表示支付gRPC服務的入口(相似於Controller),其代碼以下:
package com.wudimanong.micro.pay.provider; import com.wudimanong.micro.pay.proto.PayRequest; import com.wudimanong.micro.pay.proto.PayResponse; import com.wudimanong.micro.pay.proto.PayServiceGrpc; import io.grpc.stub.StreamObserver; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component public class PayCoreProvider extends PayServiceGrpc.PayServiceImplBase { /** * 實現ProtoBuf中定義的服務方法 * * @param request * @param responseStreamObserver */ @Override public void doPay(PayRequest request, StreamObserver<PayResponse> responseStreamObserver) { //邏輯處理(簡單模擬打印日誌) log.info("處理gRPC支付處理請求,orderId->{};payAmount{}", request.getOrderId(), request.getAmount()); //構建返回對象(構建處理狀態) PayResponse response = PayResponse.newBuilder().setStatus(2).build(); //設置數據響應 responseStreamObserver.onNext(response); responseStreamObserver.onCompleted(); } }
上述代碼所引入的一些依賴代碼如PayServiceGrpc等,就是前面定義paycore.proto文件所生成的樁文件代碼!因爲只是簡單測試,這裏僅僅打印了下日誌就返回了,若是涉及複雜業務仍是能夠按照MVC分層架構思想進行代碼拆分!
三、編寫gRPC與Spring Boot框架集成配置代碼
在Spring Cloud微服務中集成gRPC能夠經過前面提到的「grpc-client-spring-boot-starter」來實現,但目前尚未現成的支持Service Mesh架構下的集成SDK,因此這裏經過手工配置定義的方式實現集成。先建立一個配置類,代碼以下:
package com.wudimanong.micro.pay.config; import com.wudimanong.micro.pay.provider.PayCoreProvider; import io.grpc.Server; import io.grpc.ServerBuilder; import java.io.IOException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Slf4j @Component public class GrpcServerConfiguration { @Autowired PayCoreProvider service; /** * 注入配置文件中的端口信息 */ @Value("${grpc.server-port}") private int port; private Server server; public void start() throws IOException { // 構建服務端 log.info("Starting gRPC on port {}.", port); server = ServerBuilder.forPort(port).addService(service).build().start(); log.info("gRPC server started, listening on {}.", port); // 添加服務端關閉的邏輯 Runtime.getRuntime().addShutdownHook(new Thread(() -> { log.info("Shutting down gRPC server."); GrpcServerConfiguration.this.stop(); log.info("gRPC server shut down successfully."); })); } private void stop() { if (server != null) { // 關閉服務端 server.shutdown(); } } public void block() throws InterruptedException { if (server != null) { // 服務端啓動後直到應用關閉都處於阻塞狀態,方便接收請求 server.awaitTermination(); } } }
如上所示,在該配置代碼中,經過gRPC-Java依賴所提供的Server對象構建了gRPC服務端啓動、中止、阻塞的方法,並在啓動時將前面定義的服務端類經過「.addService()」方法進行了加入(可考慮封裝更優雅的方式)!
爲了讓該配置類與Spring Boot集成,再定義一個集成類,代碼以下:
package com.wudimanong.micro.pay.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class GrpcCommandLineRunner implements CommandLineRunner { @Autowired GrpcServerConfiguration configuration; @Override public void run(String... args) throws Exception { configuration.start(); configuration.block(); } }
上述代碼會在Spring Boot應用啓動時自動加載,其中的邏輯就是啓動gRPC服務,並阻塞等待鏈接!
接下來在配置文件中定義服務所開啓的gRPC端口,配置以下:
spring: application: name: micro-pay server: port: 9092 #定義gRPC服務開放的端口 grpc: server-port: 18888
該配置所定義的參數在前面的服務配置類中引用,表示gRPC服務開啓的端口,這裏定義的是18888!
到這裏gRPC服務端工程代碼就構建完成了,從總體上看就是Spring Boot+gRPC的集成與整合,這其中沒有引入Spring Boot定製的gRPC集成SDK,目的在於避免其中所涉及的客戶端服務治理邏輯(與前面Http調用不直接引入Open Feign同樣)。
接下來咱們改造micro-order微服務,使其成爲調用micro-pay微服務的gRPC客戶端程序!
一、引入gRPC客戶端依賴包
引入前面定義micro-pay gRPC服務時構建的micro-pay-client protobuf工程依賴,代碼以下:
<!--引入支付服務gRPC ProtoBuf定義依賴--> <dependency> <groupId>com.wudimanong</groupId> <artifactId>micro-pay-client</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
二、業務邏輯中實現gRPC服務調用
接下來在micro-order邏輯中調用gRPC支付服務,代碼示例以下:
@Slf4j @Service public class OrderServiceImpl implements OrderService { /** * 引入gRPC客戶端配置依賴 */ @Autowired GrpcClientConfiguration gRpcClent; @Override public CreateOrderBO create(CreateOrderDTO createOrderDTO) { log.info("如今開始處理下單請求....."); //生成訂單號 String orderId = String.valueOf(new Random(100).nextInt(100000) + System.currentTimeMillis()); //構建支付請求(gRPC調用) PayRequest payRequest = PayRequest.newBuilder().setOrderId(orderId).setAmount(createOrderDTO.getAmount()) .build(); //使用stub發送請求到服務端 PayResponse payResponse = gRpcClent.getStub().doPay(payRequest); log.info("pay gRpc response->" + payResponse.toString()); return CreateOrderBO.builder().orderId(orderId).status(payResponse.getStatus()).build(); } }
如上所示,該業務邏輯在接收micro-api經過Http調用的請求後,會在邏輯實現過程當中經過gRPC協議訪問支付服務,其中涉及的接口定義代碼,由protobuf文件所定義!
三、gRPC客戶端配置
上述邏輯是經過定義「GrpcClientConfiguration」gRPC客戶端配置類來實現gRPC服務調用的,該配置類代碼以下:
@Slf4j @Component public class GrpcClientConfiguration { /** * 支付gRPC Server的地址 */ @Value("${server-host}") private String host; /** * 支付gRPC Server的端口 */ @Value("${server-port}") private int port; private ManagedChannel channel; /** * 支付服務stub對象 */ private PayServiceGrpc.PayServiceBlockingStub stub; public void start() { //開啓channel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); //經過channel獲取到服務端的stub stub = PayServiceGrpc.newBlockingStub(channel); log.info("gRPC client started, server address: {}:{}", host, port); } public void shutdown() throws InterruptedException { //調用shutdown方法後等待1秒關閉channel channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); log.info("gRPC client shut down successfully."); } public PayServiceGrpc.PayServiceBlockingStub getStub() { return this.stub; } }
如上所示配置代碼,經過依服務配置文件指定的gRPC服務端地址+端口,實現對gRPC客戶端的配置,其中主要包括啓動和中止方法,並在啓動的過程當中初始化gRPC服務客戶端的樁代碼的實例(可考慮更優雅地實現)。
在該配置類中所依賴的gRPC服務端地址+端口配置,依賴於服務配置文件的定義,代碼以下:
spring: application: name: micro-order server: port: 9091 #支付微服務Grpc服務地址、端口配置 server-host: ${grpc_server_host} server-port: ${grpc_server_port}
若是是本地測試能夠直接指定grpc_server_host及端口的值,但在Service Mesh微服務架構中,直接在應用的配置文件中指定其餘微服務的地址及端口可能並非很靈活,這個配置信息將在發佈Kubernetes集羣時,經過Kubernetes發佈文件注入!
爲了讓gRPC客戶端配置與Spring Boot集成,這裏也須要定義一個Spring Boot加載類,代碼以下:
@Component @Slf4j public class GrpcClientCommandLineRunner implements CommandLineRunner { @Autowired GrpcClientConfiguration configuration; @Override public void run(String... args) throws Exception { //開啓gRPC客戶端 configuration.start(); //添加客戶端關閉的邏輯 Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { configuration.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); } })); } }
該代碼將在Spring Boot應用自動時自動加載!到這裏micro-order gRPC客戶端配置就完成了!
前面基於「micro-order->micro-pay」微服務間的gRPC調用場景,分別將兩個微服務改形成了gRPC服務端/客戶端。但此時從代碼上是很難看出來它們兩者之間應該怎麼實現調用!而這也偏偏就印證了Service Mesh架構的優點,服務的發現、及負載均衡調用之類的服務治理邏輯,已經徹底不用微服務本身管了!
在Istio中,它們是基於Kubernetes的Service發現機制+Istio-proxy(SideCar代理)來實現的。而具體的操做就是經過微服務Kubernetes服務發佈文件的定義,接下來分別定義micro-order及micro-pay的Kubernetes發佈文件。
先看下做爲gRPC服務端的micro-pay的發佈文件(micro-pay.yaml),代碼以下:
apiVersion: v1 kind: Service metadata: name: micro-pay labels: app: micro-pay service: micro-pay spec: type: ClusterIP ports: - name: http #容器暴露端口 port: 19092 #目標應用端口 targetPort: 9092 #設置gRPC端口 - name: grpc port: 18888 targetPort: 18888 selector: app: micro-pay --- apiVersion: apps/v1 kind: Deployment metadata: name: micro-pay-v1 labels: app: micro-pay version: v1 spec: replicas: 2 selector: matchLabels: app: micro-pay version: v1 template: metadata: labels: app: micro-pay version: v1 spec: containers: - name: micro-pay image: 10.211.55.2:8080/micro-service/micro-pay:1.0-SNAPSHOT imagePullPolicy: Always tty: true ports: - name: http protocol: TCP containerPort: 19092 #指定服務gRPC端口 - name: grpc protocol: TCP containerPort: 18888
如上所示k8s發佈文件,主要是定義了Service服務訪問資源及Deployment容器編排資源,這兩種資源都是Kubernetes的資源類型,在容器編排資源和服務資源中分別定義了gRPC的訪問端口,經過這種設置,後續gRPC客戶端經過Service資源訪問服務時,就可以進行端口映射了!
而其餘配置則是基本的Kubernetes發佈部署邏輯,其中涉及的鏡像,須要在發佈以前,經過構建的方式對項目進行Docker鏡像打包並上傳私有鏡像倉庫(若是有疑問,能夠參考本號以前的文章)。
接下來繼續看看做爲gRPC客戶端的micro-order微服務的k8s發佈文件(micro-order.yaml),代碼以下:
apiVersion: v1 kind: Service metadata: name: micro-order labels: app: micro-order service: micro-order spec: type: ClusterIP ports: - name: http #此處設置80端口的緣由在於改造的Mock FeignClient代碼默認是基於80端口進行服務調用 port: 80 targetPort: 9091 selector: app: micro-order --- apiVersion: apps/v1 kind: Deployment metadata: name: micro-order-v1 labels: app: micro-order version: v1 spec: replicas: 2 selector: matchLabels: app: micro-order version: v1 template: metadata: labels: app: micro-order version: v1 spec: containers: - name: micro-order image: 10.211.55.2:8080/micro-service/micro-order:1.0-SNAPSHOT imagePullPolicy: Always tty: true ports: - name: http protocol: TCP containerPort: 19091 #環境參數設置(設置微服務返回gRPC服務端的地址+端口) env: - name: GRPC_SERVER_HOST value: micro-pay - name: GRPC_SERVER_PORT value: "18888"
在該發佈文件中,須要說明的主要就是經過容器env環境參數的設置,指定了以前gRPC客戶端服務配置中所依賴的參數變量「GRPC_SERVER_HOST及GRPC_SERVER_PORT」,其中服務地址就是micro-pay微服務在Kubernetes中Service資源定義的名稱,端口則是gRPC服務端所開啓的端口。
這樣在gRPC客戶端在Kubernetes集羣中根據Service名稱發起微服務調用時,Kubernetes集羣自身的服務發現邏輯就能自動將請求映射到相應的Pod資源了!這其實就是Service Mesh微服務架構服務發現的基本邏輯!
接下來將微服務進行發佈,這裏假設你已經部署了一套Kubernetes集羣並安裝了基於Istio的Service Mesh微服務架構環境,最終的部署效果以下所示:
root@kubernetes:/opt/istio/istio-1.8.4# kubectl get pods NAME READY STATUS RESTARTS AGE micro-api-6455654996-9lsxr 2/2 Running 2 43m micro-order-v1-744d469d84-rnqq8 2/2 Running 0 6m28s micro-order-v1-744d469d84-vsn5m 2/2 Running 0 6m28s micro-pay-v1-7fd5dd4768-txq9d 2/2 Running 0 43s micro-pay-v1-7fd5dd4768-wqw6b 2/2 Running 0 43s
如上所示,能夠看到案例所涉及的微服務都被部署了,而且對應的SideCar代理(istio-proxy)也被正常啓動了!爲了演示負載均衡效果,這裏micro-order及micro-pay都分別被部署了兩個副本!
若是環境都沒啥問題,此時能夠經過調用Istio Gateway來訪問micro-api服務,而後micro-api服務會經過Http的方式訪問micro-order服務,以後micro-order服務經過gRPC協議調用micro-pay服務。
經過curl命令訪問Istio Gateway網關服務,效果以下:
curl -H "Content-Type:application/json" -H "Data_Type:msg" -X POST --data '{"businessId": "202012102", "amount": 100, "channel": 2}' http://10.211.55.12:30844/api/order/create
若是正常返回響應結果,則說明上述調用鏈路走通了!此時分別經過觀察服務的業務日誌和istio-proxy代理日誌來加以觀測!
其中micro-pay兩個實例(PodA~PodB)業務日誌信息:
//支付微服務接口訪問日誌(POD-A) root@kubernetes:~# kubectl logs micro-pay-v1-7fd5dd4768-txq9d micro-pay .... 2021-04-01 14:46:15.818 INFO 1 --- [ main] c.w.m.p.config.GrpcServerConfiguration : Starting gRPC on port 18888. 2021-04-01 14:46:18.859 INFO 1 --- [ main] c.w.m.p.config.GrpcServerConfiguration : gRPC server started, listening on 18888. 2021-04-01 15:07:36.709 INFO 1 --- [ault-executor-0] c.w.micro.pay.provider.PayCoreProvider : 處理gRPC支付處理請求,orderId->1617289656289;payAmount100 //支付微服務接口訪問日誌(POD-B) root@kubernetes:~# kubectl logs micro-pay-v1-7fd5dd4768-wqw6b micro-pay ... 2021-04-01 15:34:59.673 INFO 1 --- [ main] c.w.m.p.config.GrpcServerConfiguration : Starting gRPC on port 18888. 2021-04-01 15:35:06.175 INFO 1 --- [ main] c.w.m.p.config.GrpcServerConfiguration : gRPC server started, listening on 18888. 2021-04-01 15:40:22.019 INFO 1 --- [ault-executor-0] c.w.micro.pay.provider.PayCoreProvider : 處理gRPC支付處理請求,orderId->1617291624127;payAmount100 2021-04-01 15:44:31.630 INFO 1 --- [ault-executor-2] c.w.micro.pay.provider.PayCoreProvider : 處理gRPC支付處理請求,orderId->1617291867537;payAmount100
能夠看到,屢次訪問接口,基於gRPC的微服務調用也實現了負載均衡調用!接下來分別看下這兩個微服務的istio-proxy(SideCar代理)的日誌,具體以下:
--istio-proxy代理日誌(POD-A) root@kubernetes:~# kubectl logs micro-pay-v1-7fd5dd4768-txq9d istio-proxy ... 2021-04-01T15:34:48.009972Z info Envoy proxy is ready [2021-04-01T15:40:26.240Z] "POST /com.wudimanong.pay.client.PayService/doPay HTTP/2" 200 - "-" 22 7 498 477 "-" "grpc-java-netty/1.36.1" "8eb318e5-ac09-922d-9ca7-603a5c14bdd5" "micro-pay:18888" "127.0.0.1:18888" inbound|18888|| 127.0.0.1:57506 10.32.0.10:18888 10.32.0.12:36844 outbound_.18888_._.micro-pay.default.svc.cluster.local default 2021-04-01T15:45:18.377555Z info xdsproxy disconnected ... [2021-04-01T15:45:34.885Z] "POST /com.wudimanong.pay.client.PayService/doPay HTTP/2" 200 - "-" 22 7 1200 171 "-" "grpc-java-netty/1.36.1" "c08d540e-db46-9228-b381-0808ac08377e" "micro-pay:18888" "127.0.0.1:18888" inbound|18888|| 127.0.0.1:33218 10.32.0.10:18888 10.32.0.2:42646 outbound_.18888_._.micro-pay.default.svc.cluster.local default ... 2021-04-01T15:52:49.825955Z info xdsproxy connecting to upstream XDS server: istiod.istio-system.svc:15012
如上所示,能夠看到istio-proxy代理日誌中顯示了經過post方式轉發gRPC服務的狀況,並且能夠看出gRRPC是採用Http/2實現的!
本文經過實戰案例,演示了在Service Mesh微服務架構下,服務間經過gRPC協議實現通訊調用的場景!
歡迎你們關注個人公衆號【風平浪靜如碼】,海量Java相關文章,學習資料都會在裏面更新,整理的資料也會放在裏面。
以爲寫的還不錯的就點個贊,加個關注唄!點關注,不迷路,持續更新!!!