[Updated at 18:00, May 05, 2019] Recently, gRPC seems to be more popular than REST for inter-node communication on distributed systems, so I tried it. In addition, gRPC is provided as a library in java, and in Spring, a starter for boot is provided from LogNet. It seems that there is, so use it on Spring Boot. Also, I'm assuming that it will be used on a distributed system if it is to be put to practical use in the future, so I decided to incorporate it into the Spring Cloud project and run it.
Surprisingly, there were some addictive points before I just moved them, so this time I will share those points and describe what I confirmed to the point where the project works normally.
I may mention what is implemented in Spring Cloud separately, but I tried gRPC communication in the following project for load balancing by Eureka and Ribbon.
When the user sends an HTTP request to the above eureka-lb-demo
, API to eureka-lb-demo
→ eureka-client-demo
via gRPC communication. In the form of going to call. Since the project name is given according to the structure of Eureka, it will be the opposite, but as gRPC
eureka-client-demo
eureka-lb-demo
Is the relationship.
eureka-demo
, config-server-demo
Has nothing to do with this theme. The only part related to the theme is the white part.
It's not the essence of this time, but I'll briefly explain it so that you don't lose track of what you're doing when you look at the code snippet.
This time, we implemented a service that allows you to obtain equipment information by passing the school name as a parameter. When the request is received by eureka-lb-demo
, it internally queries the API of eureka-client-demo
called `` `getEquipmentInfo. Therefore, the interface definition this time is named
EquipmentService```.
Before moving on to implementation, I will briefly explain the premise of gRPC.
gRPC defines a file called .proto
and compiles it using a compiler called protoc, and the source of the API part is automatically generated. Same feeling as SOAP. After creating this file, create a classpath called src / main / proto
and build it.
In maven, if you define a plugin, you can add an automatically generated source on the build path just by building as usual.
The reason for the name ** proto ** is that it uses a technology called Protocol Buffers, but the explanation is omitted because it goes against the purpose of this time.
The proto file was created like this. What I want to do is return the object as a list type if the parameter school name exists in the DB. The specification is that only the message is returned if it does not exist. Therefore, in EquipmentResponse, the message is defined as the first field, and the list corresponding to java objects is defined as the second field.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.ek.eureka_client_demo.equipment";
package equipment_proto;
service EquipmentService {
rpc getEquipment (EquipmentRequest) returns (EquipmentResponse);
}
message EquipmentRequest{
string schoolName = 1;
}
message EquipmentResponse{
string message = 1;
repeated EquipmentInfo equipmentInfo = 2;
}
message EquipmentInfo{
string catagory = 1;
string name = 2;
}
The getEquipment defined in the EquipmentService is the API for acquiring equipment information.
See below for the detailed grammar of proto. Protocol Buffers Language Guide (proto3)
eureka-client-demo
)<!-- gRPC server -->
<dependency>
<groupId>io.github.lognet</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
</dependency>
Next, since it is necessary to use the above-mentioned protocol, add the following under the ```
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
//Abbreviation
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.18.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
First, an error occurred here. On my MacOS, I got an error saying that $ {os.detected.classifier}` `` could not be identified, so I specified the property and surpassed it. It seems that profile settings can be used with
settings.xml```, but I specify the properties on the same pom file. Please note that it seems that you can go or not depending on the OS because you can go in the state of variables in windows.
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RC2</spring-cloud.version>
<os.detected.classifier>osx-x86_64</os.detected.classifier>
</properties>
Next, if you build in this state, the source code for gRPC will be automatically generated.
Implement a Controller that accepts requests from eureka-lb-demo
(client side) using the automatically generated code.
The created Controller is as follows.
package com.example.ek.eureka_client_demo.equipment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.CollectionUtils;
import org.lognet.springboot.grpc.GRpcService;
import org.springframework.stereotype.Controller;
import com.example.ek.eureka_client_demo.equipment.EquipmentServiceGrpc.EquipmentServiceImplBase;
import io.grpc.stub.StreamObserver;
@GRpcService // (1)
@Controller
public class EquipmentController extends EquipmentServiceImplBase { // (2)
private static Map<String, List<Equipment>> schoolEquipmentDB; // (3)
@Override
public void getEquipment(EquipmentRequest req, StreamObserver<EquipmentResponse> resObserber) { // (4)
String schoolNm = req.getSchoolName(); // (5)
List<Equipment> list = schoolEquipmentDB.get(schoolNm);
EquipmentResponse equipmentResponsse = null;
if (CollectionUtils.isEmpty(list)) {
equipmentResponsse = EquipmentResponse.newBuilder()
.setMessage(
"There is no equipment in this scool " + schoolNm + System.getProperty("line.separator"))
.build(); // (6)
resObserber.onNext(equipmentResponsse); // (8)
}
//When equipment information can be obtained
else {
EquipmentResponse.Builder responseBuilder = EquipmentResponse.newBuilder();
for (Equipment eq : list) {
EquipmentInfo eqInfo = EquipmentInfo.newBuilder()
.setCatagory(eq.getCategory())
.setName(eq.getName())
.build(); // (7)
responseBuilder.addEquipmentInfo(eqInfo);
}
EquipmentResponse res = responseBuilder.setMessage("").build();
resObserber.onNext(res); //(8)
}
resObserber.onCompleted(); // (9)
}
// (3)
static {
schoolEquipmentDB = new HashMap<String, List<Equipment>>();
List<Equipment> lst = new ArrayList<Equipment>();
Equipment eqp = new Equipment("chair", "High Grade Chair I");
lst.add(eqp);
eqp = new Equipment("desk", "High Grade Desk I");
lst.add(eqp);
schoolEquipmentDB.put("abcschool", lst);
lst = new ArrayList<Equipment>();
eqp = new Equipment("chair", "Low Grade Chair I");
lst.add(eqp);
eqp = new Equipment("desk", "Low Grade Desk I");
lst.add(eqp);
schoolEquipmentDB.put("xyzschool", lst);
//Check inside the DB
System.out.println("School Equipment information are as below.");
for (Entry<String, List<Equipment>> entry : schoolEquipmentDB.entrySet()) {
System.out.println("School: " + entry.getKey());
for (Equipment e : entry.getValue()) {
System.out.println(" Equipment:" + e.getCategory() + "," + e.getName());
}
}
}
No. | Description |
---|---|
(1) | Add this annotation when using gRPC. |
(2) | Automatically generatedEquipmentServiceImplBase Inherit. There are some automatically generated files,.Service name specified by ptoro+ ImplBase Is. |
(3) | Since it was troublesome to build a DB, a DB mock that just puts values in variables created with static. |
(4) | me too.Method name specified by proto Override. |
(5) | .proto As defined in, in the requestschoolName Since we defined the parameter, get from the request object and get the value from the DB mock. Below this is the source for returning a response. |
(6) | The response also sets the value of the automatically generated bean with the builder pattern. Here, only a message is set when equipment information cannot be obtained. |
(7) | This is a branch when a value is taken, not a message, but an object of list elementsEquipmentInfo (This is also an auto-generated source)Set the value to. Like the response object, this also creates an object with the builder pattern. |
(8) | It is an argument of the methodStreamObserver ofonNext Send a value to the client side by setting a response object in the method argument. |
(9) | Finally to end the communicationonComplete Call the method. |
Also, property settings for gRPC
application.yml
## grpc settings
grpc:
port: 6565
Then, try building and booting with `` `$ mvn clean install spring-boot: run```. I got the following error.
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call the method com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;CLjava/lang/Object;)
V but it does not exist. Its class, com.google.common.base.Preconditions, is available from the following locations:
jar:file:/Users/***/.m2/repository/com/google/guava/guava/16.0/guava-16.0.jar!/com/google/common/base/Preconditions.class
When I looked it up, there was a Git issue that the guava library was old and should be updated, so when I looked up the source of guava with `` `$ mvn dependency: tree```
[INFO] | +- com.netflix.eureka:eureka-client:jar:1.9.8:compile
[INFO] | | +- org.codehaus.jettison:jettison:jar:1.3.7:runtime
[INFO] | | | \- stax:stax-api:jar:1.0.1:runtime
[INFO] | | +- com.netflix.netflix-commons:netflix-eventbus:jar:0.3.0:runtime
[INFO] | | | +- com.netflix.netflix-commons:netflix-infix:jar:0.3.0:runtime
[INFO] | | | | +- commons-jxpath:commons-jxpath:jar:1.3:runtime
[INFO] | | | | +- joda-time:joda-time:jar:2.10.1:runtime
[INFO] | | | | \- org.antlr:antlr-runtime:jar:3.4:runtime
[INFO] | | | | +- org.antlr:stringtemplate:jar:3.2.1:runtime
[INFO] | | | | \- antlr:antlr:jar:2.7.7:runtime
[INFO] | | | \- org.apache.commons:commons-math:jar:2.2:runtime
[INFO] | | +- com.netflix.archaius:archaius-core:jar:0.7.6:compile
[INFO] | | | \- com.google.guava:guava:jar:16.0:compile
It seems that the jar of eureka-client and the boot-starter of grpc do not match. Exlude the jar of eureka-client as follows and rebuild & execute.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
In addition, since Spring Cloud type projects basically include guava jar, it is necessary to match the version properly when using gRPC. I also added config server and hystrix to dependency, and different versions of guava were included in each, so I also added exclude settings there.
The build result was successful.
Started EurekaClientDemoApplication in 15.476 seconds (JVM running for 56.095)
o.l.springboot.grpc.GRpcServerRunner : gRPC Server started, listening on port 6565.
eureka-lb-demo
)Like the Server side, pom excludes guava and just adds the boot-starter of gRPC, so I will omit it. Also, the `` `.proto``` file is exactly the same as the one on the Server side, and the build → source code is automatically generated, which is not unique to the Client side. Since the content unique to the Client side is only the calling method (java code), only the Controller is described.
@RestController
public class SchoolController {
@Autowired
EurekaClient client; // (1)
//abridgement
@GetMapping(value = "/getEquipmentInfo/{schoolname}")
public String getEquipments(@PathVariable String schoolname) {
System.out.println("Getting School details for " + schoolname);
InstanceInfo instance = client.getNextServerFromEureka("studentService", false);
ManagedChannel channel = ManagedChannelBuilder.forAddress(instance.getIPAddr(), 6565)
.usePlaintext().build(); // (2)
EquipmentServiceBlockingStub stb = EquipmentServiceGrpc.newBlockingStub(channel);
EquipmentRequest req = EquipmentRequest.newBuilder().setSchoolName(schoolname).build();
EquipmentResponse res = stb.getEquipment(req); // (3)
if (StringUtils.hasLength(res.getMessage())) {
return res.getMessage();
}
StringBuilder sb = new StringBuilder();
for (EquipmentInfo eq : res.getEquipmentInfoList()) {
sb.append(editResponseForEquipment(eq));
sb.append(System.getProperty("line.separator"));
}
return sb.toString();
}
private String editResponseForEquipment(EquipmentInfo eq) {
return "Category: " + eq.getCatagory() + ", " + "EquipmentName: " + eq.getName() + ";";
}
No. | Description |
---|---|
(1) | eureka-client-demo To get information on the sideEurekaClient DI. |
(2) | To use gRPCMannagedChannel To use. For channel generationManagedChannelBuilder.forAddress Toeureka-client-demo Pass the IP and port of. I eureka-client-I have hard-coded the port because I have a REST port for communication with demo, but if I only use gRPCinstance.getPort Is okay. |
(3) | This is an automatically generated stub class like the way this library is writtenEquipmentServiceBlockingStub Communicate using. After setting the parameters in the request, the reststb.getEquipment The request is set in the argument of the method and the response is received. |
On the other hand, if you make an HTTP request, you can get the result safely as follows.
$ curl http://localhost:8086/getEquipmentInfo/abcschool
Category: chair, EquipmentName: High Grade Chair I;
Category: desk, EquipmentName: High Grade Desk I;
When a school name that does not exist in the DB is specified
$ curl http://localhost:8086/getEquipmentInfo/defschool
There is no equipment in this school defschool
Therefore, it can be seen that the list type of the object and the message of String can communicate as intended.
The source is as follows, although various other contents are mixed. Source code
In addition, all property setting values not related to gRPC are aggregated in `config-server-demo```, and each project has
`src / main / resource / bootstrap.yml``` in config-server. When browsing because it is supposed to go to the setting value for the project (config) Please be careful.
Recommended Posts