RPC 框架的调用原理图如下所示:
3. 支持服务治理等服务化特性的分布式服务框架,其底层内核仍然是 RPC 框架, 例如阿里的 Dubbo。
随着微服务的发展,基于语言中立性原则构建微服务,逐渐成为一种主流模式,例如对于后端并发处理要求高的微服务,比较适合采用 Go 语言构建,而对于前端的 Web 界面,则更适合 Java 和 JavaScript。gRPC 是一个高性能、开源和通用的 RPC 框架,面向服务端和移动端,基于 HTTP/2 设计。
4. 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
本文的demo使用的项目构建工具是gradle,版本是4.6,配置如下:
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'com.google.protobuf'
repositories {
jcenter()
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3'
}
}
sourceSets {
main {
java{
srcDir 'gen/main/java'
srcDir 'gen/main/grpc'
}
proto {
srcDir 'src/main/proto'
}
}
}
jar {
from {
configurations.runtime.collect {
it.isDirectory() ? it : zipTree(it)
}
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes 'Main-Class': 'com.ylifegroup.protobuf.server.GRpcServer'
}
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.5.1-1"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.11.0'
}
}
generatedFilesBaseDir = "$projectDir/gen/"
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
dependencies {
//json
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.8'
//logback
compile 'ch.qos.logback:logback-classic:1.1.8'
//logstash-logback-encoder
compile 'net.logstash.logback:logstash-logback-encoder:4.8'
//grpc
compile 'io.grpc:grpc-netty:1.11.0'
compile 'io.grpc:grpc-protobuf:1.11.0'
compile 'io.grpc:grpc-stub:1.11.0'
//ssl
compile 'io.netty:netty-tcnative-boringssl-static:2.0.8.Final'
//netty
compile 'io.netty:netty-all:4.1.22.Final'
// The production code uses the SLF4J logging API at compile time
compile 'org.slf4j:slf4j-api:1.7.21'
testCompile 'junit:junit:4.12'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.fork = true
}
eclipse {
classpath {
defaultOutputDir = file('build/eclipse/bin')
}
}
clean {
delete protobuf.generatedFilesBaseDir
}
简单说明一下,apply plugin: 'com.google.protobuf' ,这个是生成protobuf的强大工具,能够把proto文件生成protobuf对应的基础文件跟rpc文件;sourceSets定义了protobuf生成的规范,srcDir 'src/main/proto'表示存放proto原始文件目录, srcDir 'gen/main/java'表示存放proto基础文件的目录,srcDir 'gen/main/grpc'表示基础文件对应的rpc调用目录。整体项目结构图如下:
其中,grpc的基本依赖如下(no android):
//grpc
compile 'io.grpc:grpc-netty:1.11.0'
compile 'io.grpc:grpc-protobuf:1.11.0'
compile 'io.grpc:grpc-stub:1.11.0'
按照之前概念说的,基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub,本demo 的 phonebook.proto 定义如下
syntax = "proto3";
option go_package = "user";
option java_package = "com.ylifegroup.protobuf";
enum PhoneType {
HOME = 0;
WORK = 1;
OTHER = 2;
}
message ProtobufUser {
int32 id = 1;
string name = 2;
message Phone{
PhoneType phoneType = 1;
string phoneNumber = 2;
}
repeated Phone phones = 3;
}
message AddPhoneToUserRequest{
int32 uid = 1;
PhoneType phoneType = 2;
string phoneNumber = 3;
}
message AddPhoneToUserResponse{
bool result = 1;
}
service PhoneService {
rpc addPhoneToUser(AddPhoneToUserRequest) returns (AddPhoneToUserResponse);
}
其中rpc的配置是:
service PhoneService {
rpc addPhoneToUser(AddPhoneToUserRequest) returns (AddPhoneToUserResponse);
}
其它部分都是基本数据的定义。
那么接下来怎么 通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub 呢?
运行gradle指令: gradle build
我们看到生成了grpc对应的PhoneServiceGrpc文件,以及其基础类Phonebook。
到此基础工作已经准备好了,接下来我们构建server服务,构建server服务之前我们需要重新实现PhoneServiceGrpc,
/**
* @describe PhoneService Imp
* @author zhikai.chen
* @date 2018年5月7日 下午3:56:54
*/
import com.ylifegroup.protobuf.PhoneServiceGrpc;
import com.ylifegroup.protobuf.Phonebook.AddPhoneToUserRequest;
import com.ylifegroup.protobuf.Phonebook.AddPhoneToUserResponse;
import io.grpc.stub.StreamObserver;
public class PhoneServiceImp extends PhoneServiceGrpc.PhoneServiceImplBase{
@Override
public void addPhoneToUser(AddPhoneToUserRequest request, StreamObserver responseObserver) {
// TODO Auto-generated method stub
AddPhoneToUserResponse response = null;
if(request.getPhoneNumber().length() == 11 ){
System.out.printf("uid = %s , phone type is %s, nubmer is %s\n", request.getUid(), request.getPhoneType(), request.getPhoneNumber());
response = AddPhoneToUserResponse.newBuilder().setResult(true).build();
}else{
System.out.printf("The phone nubmer %s is wrong!\n",request.getPhoneNumber());
response = AddPhoneToUserResponse.newBuilder().setResult(false).build();
}
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
为了项目的简单易懂,这里我们使用默认的Block配置
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.kenhome.protobuf.service.PhoneServiceImp;
import io.grpc.Server;
import io.grpc.ServerBuilder;
/**
* @describe GRpcServer demo
* @author zhikai.chen
* @date 2018年5月7日 下午3:55:10
*/
public class GRpcServerDefault {
private static final Logger logger = LoggerFactory.getLogger(GRpcServerDefault.class);
private Server server;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder
.forPort(port)
.addService(new PhoneServiceImp())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GRpcServerDefault.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* Await termination on the main thread since the grpc library uses daemon
* threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final GRpcServerDefault server = new GRpcServerDefault();
server.start();
server.blockUntilShutdown();
}
}
ServerBuilder 底层默认使用netty4.1的nio非阻塞模型。服务提供的内容通过addService添加 ,这里对应的是我们刚刚重写的PhoneServiceImp类。
最后我们来构建client类,一样的为了项目的易懂,这里也使用默认的Block配置:
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ylifegroup.protobuf.PhoneServiceGrpc;
import com.ylifegroup.protobuf.Phonebook.AddPhoneToUserRequest;
import com.ylifegroup.protobuf.Phonebook.AddPhoneToUserResponse;
import com.ylifegroup.protobuf.Phonebook.PhoneType;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
/**
* @describe GRpcClient Block demo
* @author zhikai.chen
* @date 2018年5月7日 下午4:00:58
*/
public class GRpcClientBlock {
private static final Logger logger = LoggerFactory.getLogger(GRpcClientBlock.class);
private final ManagedChannel channel;
private final PhoneServiceGrpc.PhoneServiceBlockingStub blockingStub;
/** Construct client connecting to gRPC server at {@code host:port}. */
public GRpcClientBlock(String host, int port) {
ManagedChannelBuilder> channelBuilder = ManagedChannelBuilder.forAddress(host, port).usePlaintext();
channel = channelBuilder.build();
blockingStub = PhoneServiceGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/** add phone to user. */
public void addPhoneToUser(int uid, PhoneType phoneType, String phoneNubmer) {
logger.info("Will try to add phone to user " + uid);
AddPhoneToUserRequest request = AddPhoneToUserRequest.newBuilder().setUid(uid).setPhoneType(phoneType)
.setPhoneNumber(phoneNubmer).build();
AddPhoneToUserResponse response;
try {
response = blockingStub.addPhoneToUser(request);
} catch (StatusRuntimeException e) {
logger.warn("RPC failed: {0} --> "+e.getLocalizedMessage(), e.getStatus());
return;
}
logger.info("Result: " + response.getResult());
}
public static void main(String[] args) throws Exception {
GRpcClientBlock client = new GRpcClientBlock("localhost", 50051);
try {
client.addPhoneToUser(1, PhoneType.WORK, "13888888888");
} finally {
client.shutdown();
}
}
}
ManagedChannel 是对 Transport 层 SocketChannel 的抽象,Transport 层负责协议消息的序列化和反序列化,以及协议消息的发送和读取。
ManagedChannel 将处理后的请求和响应传递给与之相关联的 ClientCall 进行上层处理,同时,ManagedChannel 提供了对 Channel 的生命周期管理(链路创建、空闲、关闭等)。
好了,本教程就到这里了,下一章我会为大家讲解grpc有哪些io通信模型,以及他们各个部分简单的使用。