In the previous article, we introduced how to play with the Service Mesh microservice architecture step by step based on Kubernetes and Istio. In this article, we demonstrated a case that was very close to actual combat. Here we review the structure of the case, as shown in the figure below:

This example demonstrates the most common communication scenario between services when we use microservices architecture for daily development. In the Spring Cloud microservice system, Http interface invocation of load balancing between services can be realized through the combination of Fegin and Ribbon. However, in the Service Mesh architecture, the governance logic of Service discovery and load balancing has been proxy by SideCar. If you still want to continue the code experience of Service indirect interface invocation in the Spring Cloud scenario, you can generally rewrite the Feign component to remove the logic of Service governance. Keep only the simple interface declarative invocation logic to implement.

The service communication call between ** “micro-API -> micro-Order” ** in the above case is implemented in this way (see previous article). However, in addition to using Http protocol for communication in microservice architecture, for some systems with higher performance requirements, using RPC protocol with higher communication efficiency is often a more appropriate choice!

In the microservice system based on the Spring Cloud framework, services can also communicate with each other through RPC protocol. However, due to the needs of service governance, a set of SDK support similar to Fegin+Ribbon combination is also required. For example, the gRPC framework has the “GRPC-client-spring-boot-starter” dependency support for the Spring Boot framework! The project is a gRPC Spring Boot module, which can embed a gRPC Server in Spring Boot to provide services externally, and support Spring Cloud service discovery, registration, link tracking and so on.

Then how to realize the communication between services based on gRPC framework under Service Mesh microservice system? Next, I will use the Service invocation between **”micro-order->micro-pay”** as an example to demonstrate the implementation of gRPC communication invocation between services under the Service Mesh microservice architecture, and string together the complete scenario of Http+gRPC communication between services in the case!

GRPC overview

Before demonstrating the gRPC communication scenario under the Service Mesh microservice architecture, we first briefly introduce the basic knowledge of RPC protocol and gRPC framework.

Remote Procedure Call (RPC), also called Remote Procedure Call (RPC), hides the difference between Remote and local calls by hiding the complexity of the underlying network communication. Compared with Http, RPC is a customized TCP protocol, which avoids the bloated information of Http and achieves more efficient communication.

Among the main RPC implementation frameworks, Dubbo, Thrift and gRPC are well known. At present, the mainstream container publishing platform Kubernetes and the open source Service Mesh platform Istio use gRPC protocol to realize the interaction between internal components. Therefore, in the Service Mesh micro-service architecture, the communication between services adopts gRPC protocol. In some ways, it’s more native. Moreover, gRPC framework has been widely used in distributed and multi-language Service scenarios before this, so it can be predicted that the micro-service communication mode based on gRPC framework will gradually become the mainstream in the Service Mesh micro-service architecture scenario.

GRPC is a high-performance open source software framework released by Google based on HTTP/2.0 transport layer protocol. It provides a method for configuring and managing network devices that supports multiple programming languages. As it is an open source framework, both sides of the communication can carry out secondary development, so the communication between the client and server will be more focused on the content of the business level, reducing the attention to the underlying communication realized by the gRPC framework.

The following content specifically demonstrates the implementation of micro-service “micro-order->micro-pay” gRPC communication call under the Service Mesh micro-service architecture.

Building gRPC server program (Micro-pay)

First of all, from the perspective of gRPC server, grPC-Java is integrated in micro-service Micro-pay project, and a gRPC server program is implemented. Details are as follows:

1. Build Spring Boot basic engineering (micro-pay/micro-pay-client)

Spring Boot framework is used to build the basic Maven project. In order to reuse the project code, a micro-pay-client project is abstracted separately here. Protobuf file (*/proto/paycore.proto) for micro-pay micro-service gRPC service interface is defined as follows:

syntax = "proto3"; package com.wudimanong.pay.client; option java_multiple_files = true; option java_package = "com.wudimanong.micro.pay.proto"; Service PayService {// Define the payment RPC method RPC doPay (PayRequest) returns (PayResponse); } message PayRequest { string orderId = 1; int32 amount=2; } message PayResponse { int32 status = 1; }Copy the code

As shown above, a payment interface definition file based on the Protobuf protocol is created, which defines the payment service PayService and its doPay payment RPC method, as well as its request and return parameter objects, with the specific syntax following the “Proto3” protocol.

In order to compile and generate protobuf service interface code normally, you need to introduce jar package dependencies and Maven compilation plug-in configuration in the project pop.xml file, as follows:

<? The 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> .... <! --> <dependency> <groupId> IO. GRPC </groupId> <artifactId> <version>1.36.1</version> </dependency> </dependencies> <build> <! Maven </groupId> <extensions> <extensions> <groupId>kr.motd. Maven </groupId> <artifactId> oS-maven-plugin </artifactId> <version>1.6.2</version> </extension> </extensions> <plugins> <plugin> . < the groupId > org. Xolstice. Maven plugins < / groupId > < artifactId > protobuf - maven - plugin < / artifactId > < version > 0.6.1 < / version > The < configuration > < protocArtifact > com. Google. Protobuf: protoc: 3.12.0: exe: ${OS. Detected. Classifier} < / protocArtifact > <pluginId>grpc-java</pluginId> The < 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>Copy the code

Maven generates gRPC server/client code from the paycore.proto file defined above.

After completion, continue to build the Spring Boot engineering code of micro-Pay microservice, and introduce the dependencies defined by the above gRPC protocol file into its POM.xml file, for example:

<! <dependency> <groupId>com.wudimanong</groupId> <artifactId>micro-pay-client</artifactId> < version > 1.0 - the SNAPSHOT < / version > < / dependency >Copy the code

Grpc-related dependencies and plug-in configurations introduced in the Micro-pay-Client project are automatically inherited to the Micro-Pay project!

2. Write gRPC payment service code

Create a PayCoreProvider interface code in the Micro-Pay codeband that represents the entry to the payment gRPC service (similar to Controller). The code is as follows:

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 {/ * * * * * defined ProtoBuf service methods @param request * @param responseStreamObserver */ @Override public void doPay(PayRequest request, StreamObserver<PayResponse> responseStreamObserver) {log.info(" Processing gRPC payment processing request,orderId->{}; payAmount{}", request.getOrderId(), request.getAmount()); // Build return object (build processing status) PayResponse response = payResponse.newBuilder ().setStatus(2).build(); / / set data response responseStreamObserver. OnNext (response). responseStreamObserver.onCompleted(); }}Copy the code

Some of the dependencies introduced in the above code, such as PayServiceGrpc, are generated by the previous definition of paycore.proto file code! Because it is only a simple test, here just print the log and return, if it involves complex business or can be divided according to the MVC hierarchical architecture idea of code!

3. Write the integration configuration code of gRPC and Spring Boot framework

GRPC integration in Spring Cloud microservices can be achieved through the aforementioned “GRPC-client-spring-boot-starter”, but there is no ready-made integration SDK that supports Service Mesh architecture. So the integration is done by manually configuring the definition. Create a configuration class with the following code:

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 {// Starting gRPC on port {}.", port); server = ServerBuilder.forPort(port).addService(service).build().start(); log.info("gRPC server started, listening on {}.", port); 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) {// Close server.shutdown(); } } public void block() throws InterruptedException { if (server ! Server.awaittermination (); = null) {server.awaittermination (); }}}Copy the code

As shown above, in this configuration code, gRPC Server start, stop, and block methods are built using grPC-Java dependencies on the provided Server object, and the previously defined Server classes are added at startup via the ** “.addService() “** method (consider packaging more elegantly)!

To integrate this configuration class with Spring Boot, define another integration class as follows:

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(); }}Copy the code

The above code is automatically loaded when the Spring Boot application starts, and the logic is to start the gRPC service and block waiting for connections!

Then define the gRPC port enabled by the service in the configuration file as follows:

Spring: Application: name: micro-pay server: port: 9092 #Copy the code

The parameters defined by this configuration are referenced in the previous service configuration class and represent the port on which the gRPC service is enabled, in this case 18888!

The gRPC server engineering code is built here. On the whole, it is the integration and integration of Spring Boot+gRPC. There is no introduction of Spring Boot customized gRPC integration SDK. The goal is to avoid the client-side service governance logic involved (like the previous Http calls not directly introduced into Open Feign).

Building gRPC client program (Micro-Order)

Next we transform the Micro-Order microservice into a gRPC client that calls micro-pay microservices!

1. Import the gRPC client dependency package

Introduce the micro-pay-client Protobuf project dependencies built when the micro-pay gRPC service is defined, and the code is as follows:

<! <dependency> <groupId>com.wudimanong</groupId> <artifactId>micro-pay-client</artifactId> < version > 1.0 - the SNAPSHOT < / version > < / dependency >Copy the code

2. Implement gRPC service invocation in business logic

Next call gRPC payment service in micro-order logic as shown in the following code example:

@slf4j @service public class OrderServiceImpl implements OrderService {/** * implements The gRPC client configuration dependency */ @autoWired GrpcClientConfiguration gRpcClent; @override public CreateOrderBO create(CreateOrderDTO CreateOrderDTO) {log.info(" now start processing order.....") ); String orderId = String.Valueof (New Random(100).Nextint (100000) + System.CurrentTimemillis ()); // Construct payment request (gRPC call) PayRequest PayRequest = PayRequest.newBuilder().setOrderId(orderId).setAmount(createOrderDTO.getAmount()) .build(); PayResponse PayResponse = grpcClent.getStub ().dopay (payRequest); log.info("pay gRpc response->" + payResponse.toString()); return CreateOrderBO.builder().orderId(orderId).status(payResponse.getStatus()).build(); }}Copy the code

As shown above, the business logic will access the payment service through gRPC protocol during the logic implementation after receiving the request of micro-API through Http invocation, which involves the interface definition code defined by the Protobuf file!

3. Configure gRPC client

The above logic is implemented by defining the “GrpcClientConfiguration” gRPC client configuration class to implement the gRPC service invocation. The configuration class code is as follows:

@slf4j @Component Public class GrpcClientConfiguration {/** * pay gRPC Server address */ @value ("${server-host}") private String host; /** * @value ("${server-port}") private int port; private ManagedChannel channel; / * * * payment service stub object * / private PayServiceGrpc. PayServiceBlockingStub stub; Public void the start () {/ / open channel channel. = ManagedChannelBuilder forAddress (host, port). UsePlaintext (). The build (); / / through the channel access to the server stub stub. = PayServiceGrpc newBlockingStub (channel); log.info("gRPC client started, server address: {}:{}", host, port); } public void shutdown() throws InterruptedException {// Wait 1 second after calling the shutdown method to close the channel channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); log.info("gRPC client shut down successfully."); } public PayServiceGrpc.PayServiceBlockingStub getStub() { return this.stub; }}Copy the code

The configuration code shown above, through the gRPC server address + port specified by the service configuration file, to achieve the configuration of the gRPC client, which mainly includes the start and stop methods, and in the process of starting the initialization of the gRPC service client pile code instances (can consider more elegant implementation).

The gRPC server address + port configuration relied on in this configuration class depends on the definition of the service configuration file. The code is as follows:

Spring: Application: name: micro-order server: port: 9091 # ${grpc_server_host} server-port: ${grpc_server_port}Copy the code

In the Service Mesh microservice architecture, it is not flexible to specify the addresses and ports of other microservices directly in the application configuration file. This configuration information will be used when publishing the Kubernetes cluster. Publish file injection via Kubernetes!

To integrate gRPC client configuration with Spring Boot, we also need to define a Spring Boot loading class as follows:

@Component @Slf4j public class GrpcClientCommandLineRunner implements CommandLineRunner { @Autowired GrpcClientConfiguration configuration; @Override public void run(String... Args) throws Exception {// Start the gRPC client configuration.start(); Runtime.getruntime ().addShutdownHook(new Thread() -> {try {configuration.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); }})); }}Copy the code

This code will load automatically when Spring Boot is applied automatically! Here the micro-Order gRPC client configuration is complete!

Deploy the Service to the Service Mesh architecture environment

Based on the gRPC call scenario between ** “micro-order->micro-pay” ** microservices, the two microservices were transformed into gRPC server/client respectively. But at this point from the code is very difficult to see how they should be implemented between the call! This demonstrates the strength of the Service Mesh architecture. Service governance logic, such as Service discovery and load balancing invocation, is no longer the responsibility of microservices themselves.

In Istio, they are implemented based on Kubernetes Service discovery mechanism + IStio-proxy (SideCar proxy). The specific operation is through the micro-service Kubernetes service distribution file definition, then respectively define micro-order and micro-pay Kubernetes distribution file.

Take a look at the micro-pay distribution file (micro-pay.yaml) on the gRPC server. The code is as follows:

apiVersion: v1 kind: Service metadata: name: micro-pay labels: app: micro-pay service: micro-pay spec: type: ClusterIP Ports: -name: HTTP # Container exposed port port: 19092 # Target application port targetPort: 9092 # Set gRPC port - 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 # Name: gRPC protocol: TCP containerPort: 18888Copy the code

As shown above, the K8S distribution file mainly defines the Service access resource and the Deployment container scheduling resource, both of which are Kubernetes resource types. The access port of gRPC is defined in the container scheduling resource and Service resource respectively. When the gRPC client accesses the Service through the Service resource, port mapping can be performed.

The rest of the configuration is basic Kubernetes publish deployment logic, which involves images that need to be built into a Docker image package and uploaded to a private image repository prior to release (see the previous article in this issue if in doubt).

Next, take a look at the K8S distribution file (micro-order.yaml) as the MICRO-order microservice on the gRPC client. The code is as follows:

apiVersion: v1 kind: Service metadata: name: micro-order labels: app: micro-order service: micro-order spec: type: ClusterIP Ports: - name: HTTP # Mock FeignClient: Mock FeignClient: port 80 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: Env: - name: GRPC_SERVER_HOST value: micro-pay-name: GRPC_SERVER_PORT value: "18888"Copy the code

In this release, the main thing to note is that the container env environment parameter setting specifies the parameter variables ** “GRPC_SERVER_HOST and GRPC_SERVER_PORT” that were previously dependent on the gRPC client service configuration **, The Service address is the name of the Service resource defined in Kubernetes for micro-pay microservice, and the port is the port enabled by the gRPC server.

In this way, when the gRPC client invokes the microservice according to the Service name in the Kubernetes cluster, the Kubernetes cluster’s own Service discovery logic can automatically map the request to the corresponding Pod resource! This is the basic logic of Service discovery in the Service Mesh microservice architecture!

The next step is to publish the microservices. Assume that you have deployed a Kubernetes cluster and installed the IsTIo-based Service Mesh microservices environment. The final deployment results are as follows:

Root @ kubernetes: / opt/istio/istio - 1.8.4 # kubectl get the pods NAME READY STATUS RESTARTS AGE micro - API - 6455654996-9 LSXR 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 43sCopy the code

As shown above, you can see that the microservices involved in the case are deployed and the corresponding IStio-proxy is started normally! To demonstrate load balancing, there are two copies of both micro-order and micro-pay deployed!

Demonstration of a microservice multi-copy load balancing call

If the environment is fine, you can call Istio Gateway to access the Micro-API service, which then accesses the Micro-Order service via Http. Then the Micro-Order service invokes the micro-Pay service using the gRPC protocol.

Using the curl command to access the Istio Gateway service, the command output is as follows:

curl -H "Content-Type:application/json" -H "Data_Type:msg" -X POST --data '{"businessId": "202012102", "amount": 100, the "channel" : 2} 'http://10.211.55.12:30844/api/order/createCopy the code

If the response result is returned normally, it indicates that the invocation link is up. In this case, observe service logs and ISTIo-proxy proxy logs.

Service logs of the two micro-pay instances (PodA to PodB) :

// Payment Microservice Interface Access log (POD-A) root@kubernetes:~# kubectl logs micro-pay v1-7fd5DD4768-txq9d micro-pay.... The 2021-04-01 14:46:15. 818 INFO 1 - [the main] C.W.M.P.C onfig. GrpcServerConfiguration: Starting gRPC on port 18888. The 2021-04-01 14:46:18. 859 INFO 1 - [the main] C.W.M.P.C onfig. GrpcServerConfiguration: gRPC server started, Listening on 18888. The 2021-04-01 15:07:36. 709 INFO 1 - [ault executor - 0] C.W.M icro. Pay. The provider. PayCoreProvider: To process gRPC payment processing request,orderId->1617289656289; PayAmount100 // Payment microservice Interface Access Log (POD-B) root@kubernetes:~# kubectl logs micro-pay-v1-7fd5DD4768-wqw6b micro-pay... The 2021-04-01 15:34:59. 673 INFO 1 - [the main] C.W.M.P.C onfig. GrpcServerConfiguration: Starting gRPC on port 18888. The 2021-04-01 15:35:06. 175 INFO 1 - [the main] C.W.M.P.C onfig. GrpcServerConfiguration: gRPC server started, Listening on 18888. The 2021-04-01 15:40:22. 019 INFO 1 - [ault executor - 0] C.W.M icro. Pay. The provider. PayCoreProvider: To process gRPC payment processing request,orderId->1617291624127; PayAmount100 15:44:31 2021-04-01. 630 INFO 1 - [ault - executor - 2] C.W.M icro. Pay. The provider. PayCoreProvider: To process gRPC payment processing request,orderId->1617291867537; payAmount100Copy the code

As can be seen, multiple access interface, based on gRPC microservice call also realized load balancing call! Next, take a look at the istio-proxy(SideCar proxy) logs of the two microservices, as follows:

- istio - proxy agent log (POD) -a root @ kubernetes: ~ # kubectl logs micro - pay - v1-7 fd5dd4768 - 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/" 200-2-22 July 498, "477" - "" GRPC - Java - netty / 1.36.1" "Eight eb318e5 - ac09-922 - d - 9 a5c14bdd5 ca7-603" "micro - pay: 18888" "127.0.0.1:18888" the inbound | 18888 | | 127.0.0.1:57506 10.32.0.10:18888 10.32.0.12:36844 outbound_. 18888 _ _. Micro - pay. Default. SVC. Cluster. The local default 2021-04-01T15:45:18.377555z info XDSProxy disconnected... [the 2021-04-01 T15:45:34. 885 z] "POST/com. Wudimanong. Pay. Client. PayService doPay HTTP / 2" 200 - "- 22 July 1200" 171 "-" "GRPC - Java - netty / 1.36.1" "c08d540e - db46-9228 - ac08377e b381-0808" "micro - pay: 18888" "127.0.0.1:18888" the inbound | 18888 | | 10.32.0.2 127.0.0.1:33218 10.32.0.10:18888:42646 outbound_. 18888 _ _. Micro - pay. Default. SVC. Cluster. The local default... 2021-04-01T15:52:49.825955z info XDSproxy connecting to upstream XDS Server: IStiod.istio-system.svC :15012Copy the code

As shown above, isTIo-Proxy logs show that gRPC services are forwarded in POST mode, and gRRPC is implemented using Http/2.

Afterword.

In this paper, an actual case is used to demonstrate the communication call scenario between services through gRPC protocol under the Service Mesh micro-service architecture.

Welcome to pay attention to my public number [calm as code], massive Java related articles, learning materials will be updated in it, sorting out the data will be placed in it.

If you think it’s written well, click a “like” and add a follow! Point attention, do not get lost, continue to update!!