目录
一、简介
二、下图是通信流程模型
三、准备
四、环境搭建
1、服务端server
1)pom文件
2)创建proto用于生成proto目标文件
3)服务端yml配置
4)创建GrpcServerService实现服务端接口
2、客户端client
1)pom文件
2)创建proto用于生成proto目标文件
3)客户端yml配置
4)创建GrpcClientService客户端类
5)创建SimpleGrpcController
五、启动/测试
1、服务端测试
2、客户端测试
六、问题
七、源码验证解析
客户端
服务端
之所以会说grpc是高性能框架,默认情况下,gRPC基于Netty进行服务端和客户端互通,使用Protocol Buffers进行传输,这是Google用于序列化结构化数据的成熟开源机制,基于proto3情况下它还是一个跨语言的RPC框架(目前支持Java、c++、Dart、Python、Objective-C、c#、lite-runtime (Android Java)、Ruby和JavaScript(来自协议缓冲区GitHub repo),以及来自golang/protobuf官方包的Go语言生成器)
如果对grpc不够了解可以参考Introduction to gRPC | gRPC。
1、PROTOC下载及安装
下载地址
2、准备两个springboot项目,作为服务端和客户端
3、下载Apifox软件,用于直连服务端(可选)
下载地址
1、服务端server
org.springframework.boot
spring-boot-starter
io.grpc
grpc-protobuf
${grpc.version}
io.grpc
grpc-stub
${grpc.version}
net.devh
grpc-server-spring-boot-starter
2.13.1.RELEASE
1.42.1
3.7.1
${project.artifactId}-${project.version}
kr.motd.maven
os-maven-plugin
1.6.2
org.xolstice.maven.plugins
protobuf-maven-plugin
0.6.1
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
grpc-java
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
compile
compile-custom
注意:proto文件需要创建在src/main/proto下,因为pom使用的是protoSourceRoot默认路径
创建Simple.proto
syntax = "proto3"; // 协议版本
// 选项配置
option java_multiple_files = true;
//生成位置
option java_package = "com.na.model.proto";
option java_outer_classname = "SimpleProto";
service Simple {
// 简单gRPC
rpc OneToOne (MyRequest) returns (MyResponse) {
}
}
message MyRequest {
string name = 1;
int32 value = 2;
}
message MyResponse {
string message = 1;
int64 result = 2;
}
生成方式有两种一种是通过命令,这里使用maven插件生成compile和compile-custom
编译后可在target下看到生成的相应的java类
这里只贴出关键代码,其他配置根据实际情况来
# gRPC有关的配置,这里只需要配置服务端口号默认9090
grpc:
server:
port: 19898
spring:
application:
name: nacos-grpc
package com.na.grpc.server;
import com.na.model.proto.MyRequest;
import com.na.model.proto.MyResponse;
import com.na.model.proto.SimpleGrpc;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;
/**
* GrpcServerService.java中有几处需要注意:
*
* 是使用@GrpcService注解,再继承SimpleImplBase,这样就可以借助grpc-server-spring-boot-starter库将oneToOne暴露为gRPC服务;
*
* SimpleImplBase是前文中根据maven compile编译 proto文件自动生成的java代码,
*
* oneToOne方法中处理完毕业务逻辑后,调用responseObserver.onNext方法填入返回内容;
*
* 调用responseObserver.onCompleted方法表示本次gRPC服务完成;
*/
@GrpcService
@Slf4j
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
@Override
public void oneToOne(MyRequest request, StreamObserver responseObserver) {
log.info("接收客户端数据{}", request);
MyResponse response = MyResponse.newBuilder().setMessage( request.getName()).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
2、客户端client
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
io.grpc
grpc-protobuf
${grpc.version}
io.grpc
grpc-stub
${grpc.version}
net.devh
grpc-client-spring-boot-starter
2.14.0.RELEASE
1.42.1
3.7.1
${project.artifactId}-${project.version}
kr.motd.maven
os-maven-plugin
1.6.2
org.xolstice.maven.plugins
protobuf-maven-plugin
0.6.1
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
grpc-java
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
compile
compile-custom
与服务端操作一样,这里省略。。。。。。
这里只贴出关键代码,其他配置根据实际情况来
server:
port: 11278
servlet:
context-path: /
# grpc配置
grpc:
# grpc clienT相关配置
client:
# 服务名(不同服务名可对应不同配置)
# nacos-grpc是服务端配置的名字,GrpcClient注解会用到
nacos-grpc:
# gRPC服务端地址
# address: 'dns://127.0.0.1:19898'
address: 'static://127.0.0.1:19898'
# 是否开启保持连接(长连接)
enableKeepAlive: true
# 保持连接时长(默认20s)
keepAliveTimeout: 20s
# 没有RPC调用时是否保持连接(默认false,可禁用避免额外消耗CPU)
keepAliveWithoutCalls: false
# 客户端负载均衡策略(round_robin(默认), pick_first)
defaultLoadBalancingPolicy: round_robin
# 通信类型
# plaintext | plaintext_upgrade | tls
# 明文通信且http/2 | 明文通信且升级http/1.1为http/2 | 使用TLS(ALPN/NPN)通信
negotiationType: plaintext
package com.na.grpc.client;
import com.na.model.proto.MyRequest;
import com.na.model.proto.MyResponse;
import com.na.model.proto.SimpleGrpc;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
/**
* GrpcClientService类有几处要注意的地方:
*
* 用@Service将GrpcClientService注册为spring的普通bean实例;
*
* 用@GrpcClient修饰SimpleBlockingStub,这样就可以通过grpc-client-spring-boot-starter库发起gRPC调用,被调用的服务端信息来自名为nacos-grpc服务端配置;
*
* SimpleBlockingStub来自前文中根据helloworld.proto生成的java代码;
*
* SimpleBlockingStub.oneToOne方法会远程调用nacos-grpc应用的gRPC服务;
*/
@Service
@Slf4j
public class GrpcClientService {
@GrpcClient("nacos-grpc")
private SimpleGrpc.SimpleBlockingStub simpleStub;
public String oneToOne(final String name) {
try {
final MyResponse response = this.simpleStub.oneToOne(MyRequest.newBuilder().setName(name).build());
return response.getMessage();
} catch (final StatusRuntimeException e) {
log.error("FAILED with " + e.getStatus().getCode().name() + ",and e:{}", e.getMessage());
return "FAILED with " + e.getStatus().getCode().name() + ",and e:" + e.getMessage();
}
}
}
定义了两种连接方法getOneToOne和testForAddress
package com.na.controller;
import com.na.base.BaseResponse;
import com.na.grpc.client.GrpcClientService;
import com.na.model.proto.MyRequest;
import com.na.model.proto.SimpleGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description 测试grpc接口
* @Author kele
* @Data 2023/9/6 15:35
*/
@RestController
@RequestMapping("grpc")
public class SimpleGrpcController {
@Autowired
private GrpcClientService service;
@GetMapping("getOneToOne")
public BaseResponse getOneToOne() {
return new BaseResponse(service.oneToOne("客户端kele连接"));
}
@GetMapping("testForAddress")
public BaseResponse testForAddress() {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 19898)
.usePlaintext()
.build();
MyRequest request = MyRequest.newBuilder().setName("kele的访问").build();
SimpleGrpc.SimpleBlockingStub stub = SimpleGrpc.newBlockingStub(channel);
return new BaseResponse(stub.oneToOne(request));
}
}
1)先启动服务端启动的netty端口为19898
2)使用Apifox连接服务端
创建grpc操作文档
可以看到,在Apifox中导入了Simple.proto帮我自动生成了客户端连接操作,自己只需要改一下19898端口和请求的参数
1)启动客户端web端口为11278
这里访问的是getOneToOne方法, 地址http://localhost:11278/grpc/getOneToOne
另外的testForAddress方法可以自己玩一下生产一般不会那样写
1、出现无法访问com.google.protobuf.GeneratedMessageV3 找不到com.google.protobuf.GeneratedMessageV,在pom中添加以下依赖。
com.google.protobuf
protobuf-java
${protobuf.version}
com.google.protobuf
protobuf-java-util
${protobuf.version}
2、出现:io.grpc.StatusRuntimeException: UNAVAILABLE
1、检查下IP是否能ping通,IP、端口 是否正确
2、Server是否打开
3、连接中如果有证书,证书是否有效
4、无证书的,是否写了明文连接,例如:
5、以上都没问题可以查看服务端是否添加了这个依赖,grpc-netty-shaded包中可能会存在core的冲突
io.grpc
grpc-netty-shaded
${grpc.version}
1、客户端序列化,可以查看MyRequest
2、在客户端调用SimpleGrpc.SimpleBlockingStub.oneToOne方法时,已经将name序列化传输了
3、再往下可以看到客户端ClientCalls中,采用的是异步的方式进行发送二进制数据,等结束执行后再进行判断是否执行结束,
waitAndDrain()即为等待,直到有一个Runnable,然后执行它和所有在它之后排队的Runnables。一次只能由一个线程调用。poll()方法用于从队列中取出并返回头部的元素,如果队列为空,则返回null。之后又将当前线程赋给了waiter,
用的是线程池,在服务端断点的情况下,后续请求会进入等待队列。
待更新。。。。。。