gRPC结合nacos学习
1.1 基本概念
1.1.1 gRPC核心设计思路
对于远程调用的的设计思路,一般都是以下四个方面:
- 网络通信:gRPC 自己封装网络通信的部分,并提供多种语言的封装(C、Java[Netty]、GO).
- 协议:gRPC 使用 HTTP2 传输数据(二进制数据内容),支持双工(双向流)连接的多路复用.
- 序列化:基于 Protobuf 的序列化方式,时间效率和空间效率都是 JSON 的 3~5 倍.
- 代理的创建:让调用者像调用本地方法一样,去调用远端的服务方法.
1.1.2 为什么使用gRPC
- 高效:基于 HTTP2 协议,传输二进制数据内容,又基于 Protobuf 实现序列化,高效的进行进程间通信.
- 支持多种语言:原生支持 C、GO、Java 实现。C语言版本上可扩展 C++、C#、NodeJS、Python、Ruby、PHP.
- 跨平台:支持多平台运行 linux、Android、IOS、MacOS、Windows.
1.2 HTTP2.0 协议
- 二进制通信:HTTP2.0 协议是一个二进制协议,效率高于 HTTP1.x,但是可读性较差.
- 实现双工通信:服务器可对客户端进行消息推送.
- 实现了多路复用机制:一个连接可以请求多个数据.
具体的通信过程,首先 HTTP2.0 协议以下三个重要概念:
- 数据流(Stream):客户端和服务器之间传输数据的通道(一个连接中可以有多个数据流).
- 消息(message):一个消息中就包含了多个帧.
- 帧(frame):就是一些具体的请求头,请求体信息.
1.3、Protocol Buffers(Protobuf)
1.3.1、protobuf 定义
protobuf 是一种与编程语言无关,与具体平台无关(任意操作系统)的序列化工具,自定义了中间语言(IDL),使得数据在 client 和 server 中进行 RPC 传输.
api的开发
- .proto文件 书写protobuf的IDL
- [了解]protoc命令 把proto文件中的IDL 转换成编程语言
protoc --java_out=/xxx/xxx /xxx/xxx/xx.proto
syntax = "proto3";
option java_multiple_files = false;
option java_package = "com.grpc.hello";
option java_outer_classname = "HelloProto";
service HelloService {
rpc hello(HelloRequest) returns(HelloResponse) {};
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string result = 1;
}
pom.xml
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.52.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.52.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.52.1</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.52.1:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
service服务端开发
-
实现业务接口 添加具体的功能
-
创建服务端 (Netty)
/*
1.接受client提交的参数 request.getParameter()
2.业务处理 service——dao 调用对应的业务功能
3.提供返回值
*/
@Override
public void hello(com.grpc.hello.HelloProto.HelloRequest request,
io.grpc.stub.StreamObserver<com.grpc.hello.HelloProto.HelloResponse> responseObserver){
//1.接受client的请求参数
String name = request.getName();
//2.业务处理
System.out.println("name parameter"+name);
//3.封装响应
//3.1 创建相应对象的构造者
HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
//3.2填充数据
builder.setResult("hello method invoke ok");
//3.3封装响应
HelloProto.HelloResponse helloResponse = builder.build();
//把数据传回
responseObserver.onNext(helloResponse);
//标记响应结束
responseObserver.onCompleted();
}
2.创建服务端 (Netty)
public class GrpcServer1 {
public static void main(String[] args) throws IOException, InterruptedException {
//1.绑定接口
ServerBuilder serverBuilder = ServerBuilder.forPort(9000);
//2.发布服务
serverBuilder.addService(new HelloServiceImpl());
// serverBuilder.addService(new UserServiceImpl());
//3.创建服务对象
Server server = serverBuilder.build();
server.start();
server.awaitTermination();
}
}
client客户端
- client通过代理对象完成远端对象的调用
//1 创建通信的管道
//2 获得代理对象 stub
public class GrpcClient1 {
public static void main(String[] args) {
//1.创建通信的管道
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
//2 获得代理对象 stub
HelloServiceGrpc.HelloServiceBlockingStub helloService = HelloServiceGrpc.newBlockingStub(managedChannel);
//3.完成RPC调用
//3.1准备参数
HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
builder.setName("world");
HelloProto.HelloRequest request = builder.build();
//3.2 进行功能rpc调用,获取相应内容
HelloProto.HelloResponse helloResponse = helloService.hello(request);
String result = helloResponse.getResult();
System.out.println("result:"+result);
}
}
注意事项
服务端 处理返回值时
responseObserver.onNext(helloResponse1); //通过这个方法 把响应的消息 回传client
responseObserver.onCompleted(); //通知client 整个服务结束。底层返回标记
// client就会监听标记 【grpc做的】
requestObserver.onNext(helloRequest1);
requestObserver.onCompleted();
gRpc的四种通信方式
1. 简单rpc 一元rpc (Unary RPC)
2. 服务端流式RPC (Server Streaming RPC)
3. 客户端流式RPC (Client Streaming RPC)
4. 双向流RPC (Bi-directional Stream RPC)
-
简单RPC(一元RPC)
当client发起调用后,提交数据,并且等待 服务端响应。
service HelloService{ rpc hello(HelloRequest) returns (HelloResponse){} rpc hello1(HelloRequest1) returns (HelloResponse1){} }
-
服务端流式RPC
一个请求对象,服务端可以回传多个结果对象。
返回加“stream”关键字
service HelloService{
rpc hello(HelloRequest) returns (stream HelloResponse){} //服务端流式rpc
rpc hello1(HelloRequest1) returns (HelloResponse1){} //一元rpc
}
服务端
public void c2ss(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
//1 接受client的请求参数
String name = request.getName();
//2 做业务处理
System.out.println("name = " + name);
//3 根据业务处理的结果,提供响应
for (int i = 0; i < 9; i++) {
HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
builder.setResult("处理的结果 " + i);
HelloProto.HelloResponse helloResponse = builder.build();
responseObserver.onNext(helloResponse);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
responseObserver.onCompleted();
}
客户端
public class GprcClient3 {
public static void main(String[] args) {
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
try {
HelloServiceGrpc.HelloServiceBlockingStub helloService = HelloServiceGrpc.newBlockingStub(managedChannel);
HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
builder.setName("world");
HelloProto.HelloRequest helloRequest = builder.build();
这里要拿到服务端返回的所有信息 如果没拿到所有信息将会阻塞在这里 不可取!
Iterator<HelloProto.HelloResponse> helloResponseIterator = helloService.c2ss(helloRequest);
while (helloResponseIterator.hasNext()) {
HelloProto.HelloResponse helloResponse = helloResponseIterator.next();
System.out.println("helloResponse.getResult() = " + helloResponse.getResult());
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
managedChannel.shutdown();
}
}
}
监听 异步方式 处理服务端流式RPC的开发
1. api
2. 服务端
3. 客户端
public class GrpcClient4 {
public static void main(String[] args) {
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
try {
HelloServiceGrpc.HelloServiceStub helloService = HelloServiceGrpc.newStub(managedChannel);
HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
builder.setName("xiaohei");
HelloProto.HelloRequest helloRequest = builder.build();
helloService.c2ss(helloRequest, new StreamObserver<HelloProto.HelloResponse>() {
@Override
public void onNext(HelloProto.HelloResponse value) {
//服务端 响应了 一个消息后,需要立即处理的话。把代码写在这个方法中。
System.out.println("服务端每一次响应的信息 " + value.getResult());
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
//需要把服务端 响应的所有数据 拿到后,在进行业务处理。
System.out.println("服务端响应结束 后续可以根据需要 在这里统一处理服务端响应的所有内容");
}
});
managedChannel.awaitTermination(12, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
} finally {
managedChannel.shutdown();
}
}
}
整合springboot
开发服务
1. 搭建SpringBoot的开发环境
2. 引入与Grpc相关的内容
<dependency>
<groupId>com.suns</groupId>
<artifactId>rpc-grpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
开发服务
// 重复 多次
@GrpcService
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
String name = request.getName();
System.out.println("name is " + name);
responseObserver.onNext(HelloProto.HelloResponse.newBuilder().setResult("this is result").build());
responseObserver.onCompleted();
}
}
// application.yml
# 核心配置的 就是gRPC服务的端口号
spring:
application:
name: boot-server
main:
web-application-type: none
grpc:
server:
port: 9000
客户端
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
测试
1. yml
grpc:
client:
grpc-server:
address: 'static://127.0.0.1:9000'
negotiation-type: plaintext
2. 注入stub
@GrpcClient("grpc-server")
private HelloServiceGrpc.HelloServiceBlockingStub stub;
nacos:
目标
在 Nacos 服务器上注册多个相同的服务提供者实例。然后,服务消费者可以通过 Nacos 客户端来获取可用的服务实例列表,并选择其中一个进行请求。这样可以将请求分散到不同的服务实例上,从而实现负载均衡。
ter
2.14.0.RELEASE
测试
```java
1. yml
grpc:
client:
grpc-server:
address: 'static://127.0.0.1:9000'
negotiation-type: plaintext
2. 注入stub
@GrpcClient("grpc-server")
private HelloServiceGrpc.HelloServiceBlockingStub stub;
nacos:
目标
在 Nacos 服务器上注册多个相同的服务提供者实例。然后,服务消费者可以通过 Nacos 客户端来获取可用的服务实例列表,并选择其中一个进行请求。这样可以将请求分散到不同的服务实例上,从而实现负载均衡。
server:
application.yml
server:
port: 8080
spring:
application:
name: boot-server
cloud:
nacos:
discovery:
server-addr: 192.168.221.139:8848
enabled: true
ip: 127.0.0.1
grpc:
server:
port: 9001
grpc-boot-server: 服务端,提供服务
@GrpcService
@Slf4j
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
private final DiscoveryClient discoveryClient;
public HelloServiceImpl(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
@Override
public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
String name = request.getName();
System.out.println("name is " + name);
responseObserver.onNext(HelloProto.HelloResponse.newBuilder().setResult("this is result").build());
responseObserver.onCompleted();
}
}
grpc-boot-client:客户端 发起服务调用
server:
port: 8081
spring:
application:
name: grpc-boot-client
cloud:
nacos:
discovery:
server-addr: 192.168.221.139:8848
enabled: true
metadata:
gRPC:
server-addr: 192.168.221.139:8848
grpc:
client:
boot-server:
negotiation-type: plaintext
application:启动类
@SpringBootApplication
@EnableDiscoveryClient
public class GrpcBootClientApplication {
public static void main(String[] args) {
SpringApplication.run(GrpcBootClientApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
TestController:
@RestController
public class TestController {
@GrpcClient("boot-server")
private HelloServiceGrpc.HelloServiceBlockingStub stub;
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient nacosDiscoveryClient;
@Autowired
private NacosServiceManager nacosServiceManager;
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@GetMapping("/getInstance")
public Map<String, String> getServiceIpPort(String instanceName) {
Map<String, String> serviceMap = new HashMap<String, String>();
NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());
Instance instance = null;
try {
instance = namingService.selectOneHealthyInstance(instanceName, nacosDiscoveryProperties.getGroup());
} catch (NacosException e) {
e.printStackTrace();
}
serviceMap.put("ip", instance.getIp());
serviceMap.put("instanceId", instance.getInstanceId());
return serviceMap;
}
@Autowired
FactoryService factoryService;
@RequestMapping("/test")
public String test(String name) {
// factoryService.getChannel();
List<ServiceInstance> serviceInstances = nacosDiscoveryClient.getInstances("boot-server");
if (ObjectUtils.isEmpty(serviceInstances)) {
return "no server found";
}
ServiceInstance instance = serviceInstances.get(1); //或者使用负载均衡选择一个实例
// ServiceInstance instance = loadBalancerClient.choose("boot-server");
String host = instance.getHost();
int port = Integer.valueOf(instance.getMetadata().get("gRPC_port"));
// 创建GRPC通道和stub
// final Channel channel = ((InProcessOrAlternativeChannelFactory) factoryService.getChannel()).createChannel("boot-server");
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(managedChannel);
// 调用GRPC服务
HelloProto.HelloResponse helloResponse = stub.hello(HelloProto.HelloRequest.newBuilder().setName(name).build());
// final HelloProto.HelloResponse helloResponse = GrpcUtil.hello();
// 调用GRPC服务
System.out.println(helloResponse.getResult());
return helloResponse.getResult();
}
//不通过nacos的调用
@RequestMapping("/test2")
public String test2(String name) {
System.out.println("name=" + name);
HelloProto.HelloResponse helloResponse = stub.hello(HelloProto.HelloRequest.newBuilder().setName(name).build());
System.out.println(helloResponse.getResult());
return helloResponse.getResult();
}
}
启动两个服务
查看nacos
直接访问TestController中的接口就可以调用成功啦