SpringBoot整合grpc
springboot-grpc整合文档
grpc简介
gRPC (gRPC Remote Procedure Calls) 是 Google 发起的一个开源远程过程调用系统,该系统基于 HTTP/2 协议传输。(摘自知乎:https://zhuanlan.zhihu.com/p/389328756)
功能点
- 跨平台
- 序列化
- 流式数据传输
操作环境
- 系统:ubuntu 18.04
- 架构:linux-x86_64
- 环境:JDK8 maven idea
- 仓库:阿里云maven
环境准备
Protobuf
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准。他们用于 RPC 系统和持续数据存储系统。提供一个具有高效的协议数据交换格式工具库(类似Json)。
但相比于Json,Protobuf有更高的转化效率,时间效率和空间效率都是JSON的3-5倍。
可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 、OC、Swift等语言的 API。总只一句话就是很好,支持多平台且与语言无关。(https://www.jianshu.com/p/a24c88c0526a)
简单理解:protobuf是将后缀名为.proto的文件编译成各类语言API的编译工具
安装Protobuf
执行命令:
sudo apt install protobuf-compiler
查看版本
protoc --version
libprotoc 3.0.0
创建项目
项目结构
- grpc为maven父项目。
- grpc-lib为maven子项目,用于生成由protobuf编译的接口API。
- grpc-server为服务端SpringBoot子项目,负责实现接口功能。
- grcp-client为客户端Springboot子项目,负责调用服务端进行返回。
父项目构建
- 选择一个空的maven项目
- 删除src,使之成为一个空的父项目,这里面项目名为grpc-test,后续会使用grpc代替
- pom依赖
<?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>
<groupId>com.nces</groupId>
<artifactId>grpc</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<!-- 声明模块,可写不写,创建子模块的时候会自动引入,没有引入再写-->
<modules>
<module>grpc-lib</module>
<module>grpc-client</module>
<module>grpc-server</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<!-- grpc版本-->
<grpc-version>1.43.1</grpc-version>
<!-- service和client要使用的lib版本-->
<lib-version>1.0-SNAPSHOT</lib-version>
<!-- netty版本-->
<netty-version>4.1.65.Final</netty-version>
<!-- Springboot版本-->
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<!-- Springboot-grpc版本,用于server服务注解使用-->
<grpc-spring-boot-starter.version>3.0.0</grpc-spring-boot-starter.version>
<!-- maven构建工具版本-->
<maven-plugin-version>3.8.1</maven-plugin-version>
<!-- lombok-->
<lombok-version>1.18.16</lombok-version>
</properties>
<!-- 使用dependencyManagement声明得到依赖子模块不需要再进行版本指定,直接通过父模块指定即可,以此实现依赖的统一管理,防止出现依赖冲突-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.nces</groupId>
<artifactId>grpc-lib</artifactId>
<version>${lib-version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc-version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc-version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc-version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty-version}</version>
</dependency>
<dependency>
<groupId>io.github.lognet</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>${grpc-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子模块构建-lib接口API
模块结构
模块结构
├── pom.xml
└── src
└── main
├── java
│ └── GetClassifier.java 获取架构工具类,后续使用会详述
└── proto
└── HelloWorld.proto 用于生成接口proto文件
创建步骤
- 创建一个maven子项目
创建成功后子项目会引入父项目的坐标,同时,父项目会自动引入模块。如果没有,可以手动写进去
子项目
父项目
2.引入pom依赖
<?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">
<parent>
<artifactId>grpc</artifactId>
<groupId>com.nces</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>grpc-lib</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<!-- 架构信息,后续会将怎么获取-->
<os.detected.classifier>linux-x86_64</os.detected.classifier>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!--配置protobuf插件 可参阅https://github.com/grpc/grpc-java-->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.19.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.43.1:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-plugin-version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 获取架构信息
写一个类,长期放在java文件夹下,方便其他人获取架构信息时直接调用
/grpc/grpc-lib/src/main/java
/**
* @author Xi Peng
* @Description
* @createTime 2021年12月29日 17:33:00
*/
public class GetClassifier {
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
System.out.println(System.getProperty("os.arch"));
}
}
执行一下,得到结果
Linux
amd64
打开网站
查看架构关系
翻到readme中Generated properties部分
根据os.name查看osname
根据os.arch获取arch
组装得到linux-x86_64
将linux-x86_64配置到pom文件下properties下的<os.detected.classifier>里。
- 编写接口文件到src/main/proto中(protoBuf3语法规范)
/**
* 编译工具版本
*/
syntax = "proto3";
/**
* 指定生成实体
*/
option java_multiple_files = true;
/**
* 指定生成接口
*/
option java_generic_services = true;
/**
* 声明包
*/
package com.grpc.grpcserver.server;
/**
*声明实体
*/
message Person {
string first_name = 1;
string last_name = 2;
}
message Greeting {
string message = 1;
}
/**
* 声明接口
*/
service HelloWorldService {
rpc sayHello (Person) returns (Greeting);
}
- 右侧打开maven插件进行编译
- 编译结果
- 发布
将编译好的接口和实体进行打包发布
打开idea自带的控制台,会自动将目录指定到模块下
切换到lib模块
打包发布到本地maven仓库
cd grpc-lib/
mvn clean package install
查看是否打包成功
查看maven仓库坐标是否有该文件
子模块构建-server服务端
模块结构
server服务模块主要用于开放实现接口功能,开放端口给客户端使用
项目结构如图
创建步骤
- 创建一个经典的springboot模块,引入依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nces</groupId>
<artifactId>grpc-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpc-server</name>
<description>grpc-server</description>
<!-- 指定父模块,同时在父模块引入该模块-->
<parent>
<artifactId>grpc</artifactId>
<groupId>com.nces</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- Spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- lib-->
<dependency>
<groupId>com.nces</groupId>
<artifactId>grpc-lib</artifactId>
</dependency>
<!-- grpc-->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
</dependency>
<dependency>
<groupId>io.github.lognet</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
</dependency>
<!-- other-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-plugin-version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.nces.grpcserver.GrpcServerApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- application.properties配置服务端口
spring.application.name=grpc-server
grpc.port=9099
- service实现
其中,@GRpcService声明是一个GRpc服务实现,使用它进行bean注入,就不能添加@Service一类的注解,否则会发生重复注入的情况
package com.nces.grpcserver.server;
import com.grpc.grpcserver.server.Greeting;
import com.grpc.grpcserver.server.HelloWorldServiceGrpc;
import com.grpc.grpcserver.server.Person;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import org.lognet.springboot.grpc.GRpcService;
/**
* @author Xi Peng
* @Description Grpc服务端
* @createTime 2021年12月29日 17:04:00
*/
@GRpcService
@Slf4j
public class HelloServer extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {
@Override
public void sayHello(Person request, StreamObserver<Greeting> responseObserver) {
log.info("request:{}",request);
responseObserver.onNext(Greeting.newBuilder().setMessage("当前时间为"+System.currentTimeMillis()).build());
responseObserver.onCompleted();
}
}
- 启动服务端
子模块构建-客户端
模块结构
客户端整合了spring-web,所以使用了传统的三层
创建步骤
- 创建一个空的springboot项目,引入依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nces</groupId>
<artifactId>grpc-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpc-client</name>
<description>grpc-client</description>
<parent>
<artifactId>grpc</artifactId>
<groupId>com.nces</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.nces</groupId>
<artifactId>grpc-lib</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-plugin-version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.nces.grpcclient.GrpcClientApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- client客户端实现
package com.nces.grpcclient.client;
import com.grpc.grpcserver.server.Greeting;
import com.grpc.grpcserver.server.HelloWorldServiceGrpc;
import com.grpc.grpcserver.server.Person;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author Xi Peng
* @Description
* @createTime 2021年12月29日 19:01:00
*/
@Component
@Slf4j
public class HelloWorldClient {
/**
* 阻塞stub
*/
private HelloWorldServiceGrpc.HelloWorldServiceBlockingStub serviceBl;
@PostConstruct
public void init(){
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9099).usePlaintext().build();
serviceBl = HelloWorldServiceGrpc.newBlockingStub(managedChannel);
}
public String sayHello(String firstName,String lastName){
Person person = Person.newBuilder().setFirstName(firstName).setLastName(lastName).build();
Greeting greeting = serviceBl.sayHello(person);
return greeting.getMessage();
}
}
- service层
package com.nces.grpcclient.service.impl;
import com.nces.grpcclient.client.HelloWorldClient;
import com.nces.grpcclient.service.HelloWorldService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author Xi Peng
* @Description
* @createTime 2021年12月29日 19:22:00
*/
@Service
public class HelloWorldServiceImpl implements HelloWorldService {
@Resource
private HelloWorldClient client;
@Override
public String helloWorld(String beginName, String endName) {
return client.sayHello(beginName, endName);
}
}
- service接口
package com.nces.grpcclient.service;
public interface HelloWorldService {
String helloWorld(String beginName,String endName);
}
- controller
package com.nces.grpcclient.controller;
import com.nces.grpcclient.service.HelloWorldService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author Xi Peng
* @Description
* @createTime 2021年12月29日 19:24:00
*/
@RestController
@RequestMapping(path = "/hello")
public class HelloWorldController {
@Resource
private HelloWorldService helloWorldService;
@GetMapping("/world")
public String helloWorld(String beginName,String endName){
return helloWorldService.helloWorld(beginName,endName);
}
}
- 启动客户端
测试
URL:http://localhost:8080/hello/world?beginName=a&endName=b
server控制台
接口返回
grpc流
普通接口
- 大文件传输时数据包过大,数据传输时间过长,传输完成后客户端才能使用
- 只能一对一进行通信,无法实现轮训消息传输、
接口设计
/**
* 编译工具版本
*/
syntax = "proto3";
/**
* 指定生成实体
*/
option java_multiple_files = true;
/**
* 指定生成接口
*/
option java_generic_services = true;
/**
* 声明包
*/
package com.ncse.api.worker.service;
service TestGRpc{
/**
普通流
*/
rpc test(Request) returns (Response);
/**
服务端流
*/
rpc testStreamResponse(Request) returns (stream Response);
/**
客户端流
*/
rpc testStreamRequest(stream Request) returns (Response);
/**
双向流
*/
rpc testStream(stream Request) returns (stream Response);
}
message Request{
string message = 1;
}
message Response{
string message = 1;
}
客户端连接
//阻塞式流,支持服务端流和普通流
private TestGRpcGrpc.TestGRpcBlockingStub testBlocking;
//非阻塞式流,只支持普通流
private TestGRpcGrpc.TestGRpcFutureStub testFuture;
//流,支持普通流,服务端流,客户端流和双端流
private TestGRpcGrpc.TestGRpcStub testGRpcStub;
@PostConstruct
public void init() {
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 6565).usePlaintext().build();
testBlocking = TestGRpcGrpc.newBlockingStub(managedChannel);
testGRpcStub = TestGRpcGrpc.newStub(managedChannel);
testFuture = TestGRpcGrpc.newFutureStub(managedChannel);
}
grpc普通流
服务端实现
public void test(Request request, StreamObserver<Response> responseObserver) {
log.info("request:{}", request);
responseObserver.onNext(Response.newBuilder().setMessage(request.toString()).build());
responseObserver.onCompleted();
}
客户端实现
public void print() {
Iterator<Response> test = testBlocking.test(Request.newBuilder().setMessage("一个参数一个返回值" + new Date().toString()).build());
while (test.hasNext()) {
System.out.println(test.next());
}
}
grpc服务端流
服务端实现
@Override
public void testStreamResponse(Request request, StreamObserver<Response> responseObserver) {
log.info("request:{}", request);
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
responseObserver.onNext(Response.newBuilder().setMessage("服务端流" + request + ":" + i).build());
}
responseObserver.onCompleted();
}
客户端实现
public void print1m() {
Iterator<Response> test = testBlocking.testStreamResponse(Request.newBuilder().build());
while (test.hasNext()) {
System.out.println(test.next());
}
}
grpc客户端流
服务端实现
@Override
public StreamObserver<Request> testStreamRequest(StreamObserver<Response> responseObserver) {
StreamObserver<Request> observer = new StreamObserver<Request>() {
@Override
public void onNext(Request value) {
log.info("客户端流:request:{}", value);
}
@Override
public void onError(Throwable t) {
log.error(t.getMessage());
}
@Override
public void onCompleted() {
log.info("close");
}
};
observer.onNext(Request.newBuilder().setMessage("客户端流").build());
return observer;
}
客户端实现
public void printm1() {
StreamObserver<Request> request = testGRpcStub.testStreamRequest(new StreamObserver<Response>() {
@Override
public void onNext(Response value) {
log.info("response:{}", value);
}
@Override
public void onError(Throwable t) {
log.error(t.getMessage());
}
@Override
public void onCompleted() {
log.info("close");
}
});
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
request.onNext(Request.newBuilder().setMessage("客户端流:"+i).build());
}
request.onCompleted();
}
grpc双端流
服务端实现
@Override
public StreamObserver<Request> testStream(StreamObserver<Response> responseObserver) {
return new StreamObserver<Request>() {
@Override
public void onNext(Request value) {
for (int i = 0; i < 10; i++) {
log.info(value.getMessage());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
responseObserver.onNext(Response.newBuilder().setMessage("双端流服务端消息").build());
}
}
@Override
public void onError(Throwable t) {
log.error(t.getMessage());
}
@Override
public void onCompleted() {
log.info("close");
responseObserver.onCompleted();
}
};
}
客户端实现
public void printmm(){
StreamObserver<Request> stream = testGRpcStub.testStream(new StreamObserver<Response>() {
@Override
public void onNext(Response value) {
log.info(value.getMessage());
}
@Override
public void onError(Throwable t) {
log.error(t.getMessage());
}
@Override
public void onCompleted() {
log.info("close");
}
});
for (int i = 0; i < 10; i++) {
stream.onNext(Request.newBuilder().setMessage("双端流客户端消息").build());
}
stream.onCompleted();
}
附录
更多推荐