本章开始会进入GRPC子专题,先实现前面章节中提到的例子。然后就使用的知识点展开全面的描述。本章代码任务:1、实现一个简单的GRPC服务;2、实现GRPC拦截器。
本章的代码承接上一章的代码进行迭代。因模块间存在相互依赖关系,读者一定先按笔者讲述的顺序操作,否则最后程序可能由于依赖问题导致不能运行;
一、准备工作
因为本专题定位于准生产环境,所以我们在代码上也会按规范严格要求一下,本章涉及的准备工作就是在common模块优先定义一些辅助工具类。因代码比较多,详细可查看:
- 基于grpc从零开始搭建一个准生产分布式应用(8) - 01 - 附:GRPC公共库源码
- 基于grpc从零开始搭建一个准生产分布式应用(7) - 01 - 附:GRPC拦截器源码
二、API接口定义
修改【base-grpc-framework-api】模块,我们用proto方式来实现,首先要修改pom.xml文件引入必要的依赖,再定义一个.proto文件。proto的使用在下一章节会详细介绍,本章了解下必要的知识点即可。
2.1、pom.xml文件修改
这块没有需要注意的地方,用下列内容覆盖上一章中的源码即可。源码中的dependency全部用provided来修饰的原因是,在系统运行时会用到编译后的源码,这里的依赖只是用于辅助编译用。
<?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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-api</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<!--取得本地系统信息的插件-->
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${maven.kr.motd}</version>
</extension>
</extensions>
<plugins>
<!--grpc插件,用于IDEA集成使用-->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${maven.org.xolstice}</version>
<configuration>
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<protocArtifact>com.google.protobuf:protoc:${protobuf.java.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${io.grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal><!--对应插件的complie命令-->
<goal>compile-custom</goal><!--对应插件的complie-custom命令,需要本地配置插件-->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.2、systemlog.proto文件定义
文件路径:src/main/proto/sysrecord.proto,一个经验就是,定义入参时要用包装类型方便参数验证,如下面的的CreateSysRecordRequest定义。返回结果用简单类型防止空指针异常,如下面的ListSysRecordResponse定义。
syntax = "proto3";
package com.zd.baseframework.core.api.sysrecord;
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
option java_package = "com.zd.baseframework.core.api.systemlog";
option java_outer_classname = "SystemLogProto";
option java_multiple_files = true;
service ISystemLogService{
//创建
rpc CreateSystemLog (CreateSystemLogRequest) returns (SystemLogOperatorResponse);
//查询
rpc ListSystemLogByCondition (ListSystemLogRequest) returns (ListSystemLogResponse);
}
//请求参数
message CreateSystemLogRequest{
google.protobuf.StringValue biz_id = 1;
google.protobuf.Int64Value user_id = 2;
google.protobuf.StringValue code = 4;
google.protobuf.StringValue custom_code = 5;
}
message ListSystemLogRequest{
google.protobuf.StringValue biz_id = 1;
google.protobuf.Int64Value user_id = 2;
google.protobuf.Int32Value code = 3;
}
//返回结果
message SystemLogOperatorResponse{
optional int32 status = 1;
optional string message = 2;
}
message ListSystemLogResponse{
repeated SystemLogDto data = 1;
}
message SystemLogDto {
int64 id = 1;
string biz_id = 2;
int64 user_id = 3;
string track_uid = 4;
string code = 5;
string custom_code = 6;
int32 state = 7;
google.protobuf.Timestamp code_name = 8;
google.protobuf.Timestamp utime = 9;
}
2.3、proto文件编译
把.proto文件编译成java代码,在idea中的操作过程共分为三步:
- 在pom.xml中配置proto插件;
- 执行protobuf:complie生成接口;
- 执行protobuf:complile-custom生成实体类;
先确定已经按2.2节内容正确配置了protobuf-maven-plugin插件后,然后先后双击下图中红框的内容:
最后打开源码的target文件夹,会看到生成的Java源码文件,如下图所示,grpc-java文件夹中存储的是接口定义即proto文件中service标签中定义的内容,java文件中存储的是message标签定义的内容,至此api模块的所有修改就完成了:
三、GPRC实现-服务端
本章我们会传承上一章节的代码,需改动下图中标红的模块。服务端的实现稍复杂一些,同样也要改一些pom配置,本次我们会实现两个接口,同学们可以了解下grpc本地调用的实现方法,同样grpc详细的内容也会在后续章节详细展开。
- application模块:配置配置参数;
- core模块:实现一个grpc和一个controler服务,同时暴露两种协议的接口,熟悉grpc的本地调用;
3.1、pom.xml文件修改
修改【base-grpc-framework-core】模块,这里没有太多需要注意的内容,复制即可;下面源码中多了lombok和mapstruft的内容,暂时保留吧,后面也会用到了。
<?xml versinotallow="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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-core</artifactId>
<properties>
<lombok.mapstruct.binding.version>0.1.0</lombok.mapstruct.binding.version>
</properties>
<dependencies>
<!--grpc依赖-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<!--gprc依赖实现本地grpc调用-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
</dependency>
<!--项目依赖-->
<dependency>
<groupId>com.zd</groupId>
<artifactId>base-grpc-framework-common</artifactId>
<version>${project.parent.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.zd</groupId>
<artifactId>base-grpc-framework-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!--nacos相关-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
</dependency>
<!--===========以下全是工具类和工具框架=========================-->
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<!--hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 使用log4j2,性能更高 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!--处理lombok和mapstruft 加载顺序的问题 防止在生成的实体没有属性-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<!-- additional annotation processor required as of Lombok 1.18.16 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok.mapstruct.binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.2、Gprc服务实现
修改【base-grpc-framework-core】模块,类路径:com.zd.baseframework.core.core.sysrecord.api.impl.ISysRecordServiceApiImpl ,代码实现如下,这里我们只实现一个创建接口,本类创建后还需要在application.yml中进行一些配置,后面一起来讲;
下面源码唯一注意的是一定要标上@GrpcService注解,对于源码中没有找到的类全在common包中,可先按附录方式创建好必要的辅助类。
如果发现找不到API中的类,可点击右侧MAVEN窗口刷新工程。
@GrpcService
@Slf4j
public class SystemLogServiceApiImpl extends ISystemLogServiceGrpc.ISystemLogServiceImplBase {
@Override
public void createSystemLog(CreateSystemLogRequest request, StreamObserver<SystemLogOperatorResponse> responseObserver) {
String bizId = request.hasBizId()?request.getBizId().getValue():null;
Long userId = request.hasUserId()?request.getUserId().getValue():null;
String code = request.hasCode()?request.getCode().getValue():null;
String customCode = request.hasCustomCode()?request.getCustomCode().getValue():null;
String trackLog = LogGenerator.trackLog();
try{
//打印原始入参
log.info(trackLog
+ " grpcReqParam=" + JsonFormat.printer().omittingInsignificantWhitespace().print(request)
);
//入参验证
if(StrUtil.isEmpty(bizId)
|| null==userId
|| StrUtil.isEmpty(code) ){
throw new AppException("非法参数");
}
SysRecordOperatorResponse response = SysRecordOperatorResponse.newBuilder()
.setStatus(ResponseConst.SUCCESS)
.setMessage(ResponseConst.Msg.SUCCESS)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
responseObserver.onCompleted();
}catch (AppException e) {
log.error(e.getMessage());
responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException());
}
catch(Exception e){
log.error(e.getMessage());
responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
}
}
@Override
public void listSystemLogByCondition(ListSystemLogRequest request, StreamObserver<ListSystemLogResponse> responseObserver) {
String trackLog = LogGenerator.trackLog();
try{
//打印原始入参
log.info(trackLog
+ " grpcReqParam=" + JsonFormat.printer().omittingInsignificantWhitespace().print(request)
);
ListSystemLogResponse response = ListSystemLogResponse.newBuilder().build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}catch (AppException e) {
log.error(e.getMessage());
responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException());
}
catch(Exception e){
log.error(e.getMessage());
responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
}
}
}
3.3、Controller接口实现
修改【base-grpc-framework-core】模块,实现本地grpc调用,本类创建后还需要在application.yml中进行一些配置,后面一起来讲;类路径:com.zd.baseframework.core.controller.core.SystemLogController.java。代码如下:
注意@GrpcClient("inProcess")和@RestController注解不要忘记。
@Slf4j
@RestController
@RequestMapping("/systemlog")
public class SystemLogController {
@GrpcClient("inProcess")
private ISystemLogServiceGrpc.ISystemLogServiceBlockingStub iSysRecordServiceBlockingStub;
@GetMapping("/v1/create_systemlog")
public BaseResponse createSystemLog(
@RequestParam(value="biz_id") String bizId,
@RequestParam(value="user_id") Long userId,
@RequestParam(value="code") String code,
@RequestParam(value="custom_code", required=false) String customCode){
try{
//构建请求参数
CreateSystemLogRequest createSysRecordRequest = CreateSystemLogRequest.newBuilder()
.setBizId(StringValue.of(bizId))
.setUserId(Int64Value.of(userId))
.setCode(StringValue.of(code))
.setCustomCode(StringValue.of(customCode))
.build();
//正常此处需要判断response返回的state值
SystemLogOperatorResponse response = iSysRecordServiceBlockingStub.createSystemLog(createSysRecordRequest);
return BaseResponse.success(null);
}catch(StatusRuntimeException e){
log.error("ErrorCode : " + e.getStatus().getCode());
return BaseResponse.error(e.getMessage());
}catch(Exception e){
e.printStackTrace();
return BaseResponse.error(e.getMessage());
}
}
@PostMapping("/v1/list_systemlog")
public BaseResponse listSystemLog(@RequestBody SystemLogQueryRequest systemLogRequest){
try{
//构建查询参数
ListSystemLogResponse listSystemLogResponse = iSysRecordServiceBlockingStub.listSystemLogByCondition(null);
return BaseResponse.success(vos);
}catch(StatusRuntimeException e){
log.error("ErrorCode : " + e.getStatus().getCode());
return BaseResponse.error(e.getMessage());
}catch(Exception e){
e.printStackTrace();
return BaseResponse.error(e.getMessage());
}
}
}
3.4、服务端配置
修改【base-grpc-framework-application】模块,在这个专题中,与应用相关的配置全在base-grpc-framework-application模块的application.yml中配置,因为是本地运行,所以这里我们只修改application-dev.yml。修改后的源码如下:
# http配置
server:
compression:
enabled: true
mime-types: application/json,application/octet-stream
# spring配置
spring:
application:
name: GrpcFramework-Server-APP
# grpc Server配置
grpc:
server:
port: 9898 #发布远程访问地址
in-process-name: native #发布本地访问地址
client:
inProcess:
address: in-process:native #配置内部访问服务名称
上面配置注意grpc.server.in-process-name和grpc.client.inprocess.adderss一定要一样,而且要与controller中的@GrpcClient("inProcess")配置一样,其中grpc.client.inprocess.adderss后面内容in-process:native中的in-process:是固定的写法,不能更改。
3.5、GRPC服务端测试
启动BaseFrameworkApplication.java,启动时不要忘记配置环境变量为dev,配置方式可参考笔者的上一章文章https://blog.51cto.com/arch/5378945。
3.5.1、grpc测试
可先安装grpcurl工具,笔者使用的是mac系统,安装方式如下,其它系统可网上查询:
brew install grpcui #安装
grpcui -plaintext 127.0.0.1:9898 #运行
执行上面运行命令后,会自动打开浏览器,显示如下图所示,测试方式可自行操作下:
3.5.2、controller测试
在浏览器中输入 http://localhost:8080/swagger-ui.html ,下图中红框外的两个controller是笔者测试用的,可忽略。红框内的内容就是上面小节写的controller。展开后就可进行测试了:
四、GRPC服务端拦截器实现
这个拦截器主要是实现了日志打印功能,通用track的方式来跟踪每一次访问,详细设计思路可参考笔者写的另一篇文章的描述 https://blog.51cto.com/arch/5295170。
完整的类源码如下所下图所示,源码可以查看:基于grpc从零开始搭建一个准生产分布式应用(0) - 07 - 附:GRPC拦截器源码。
五、GRPC实现-客户端
修改【base-grpc-framework-client】模块。client端不是太重点,这里只是为了说明一下调用和配置方式,笔者的代码没有配置启动类,感兴趣的同学可按第3章的内容自行配置springboot来启动。也可跳过此章,了解下内容即可。完整代码如下图所示:
5.1、修改pom.xml文件
<?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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-client</artifactId>
<properties>
<lombok.mapstruct.binding.version>0.1.0</lombok.mapstruct.binding.version>
</properties>
<dependencies>
<!--grpc依赖-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<!--项目依赖-->
<dependency>
<groupId>com.zd</groupId>
<artifactId>base-grpc-framework-common</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.zd</groupId>
<artifactId>base-grpc-framework-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!--===========以下全是工具类和工具框架=========================-->
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<!--hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 使用log4j2,性能更高 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
</project>
5.2、Controller实现
同3.3节的实现一样,只是@GrpcClient("local-grpc-server")中的内容不一样,可参考5.3节的配置。
@Slf4j
@RestController
@RequestMapping("/systemlog")
public class SystemLogController {
@GrpcClient("local-grpc-server")
private ISystemLogServiceGrpc.ISystemLogServiceBlockingStub iSysRecordServiceBlockingStub;
@GetMapping("/v1/create_systemlog")
public BaseResponse listWorkFlowByStatus(
@RequestParam(value="biz_id") String bizId,
@RequestParam(value="user_id") Long userId,
@RequestParam(value="code") String code,
@RequestParam(value="custom_code", required=false) String customCode){
try{
//构建查询参数
CreateSystemLogRequest createSysRecordRequest = CreateSystemLogRequest.newBuilder()
.setBizId(StringValue.of(bizId))
.setUserId(Int64Value.of(userId))
.setCode(StringValue.of(code))
.setCustomCode(StringValue.of(customCode))
.build();
SystemLogOperatorResponse response = iSysRecordServiceBlockingStub.createSystemLog(createSysRecordRequest);
return BaseResponse.success(null);
}catch(StatusRuntimeException e){
log.error("ErrorCode : " + e.getStatus().getCode());
return BaseResponse.error(e.getMessage());
}catch(Exception e){
e.printStackTrace();
return BaseResponse.error(e.getMessage());
}
}
}
5.3、客户端配置
在base-grpc-framework-client】新建一个yml文件。
server:
port: 8089
spring:
application:
name: GrpcFramework-Client-APP
grpc:
client:
local-grpc-server:
address: 'static://127.0.0.1:9898'
enableKeepAlive: true
keepAliveWithoutCalls: true
下一章节会先讲述proto的内容。