1. 程式人生 > >rpc框架之gRPC 學習

rpc框架之gRPC 學習

grpc是google在github於2015年開源的一款RPC框架,雖然protobuf很早google就開源了,但是google一直沒推出正式的開源框架,導致github上基於protobuf的rpc五花八門,國內比較著名的有百度的sofa-pbrpc,但是遺憾的是soft-pbrpc沒有對應的java實現版本。rgpc還有一個獨立的官網:http://www.grpc.io/,目前已經支援的語言有 C, C++, Java, Go, Node.js, Python, Ruby, Objective-C, PHP 、 C#. grpc最大的特點是基於protobuf + http2 協議,http2協議雖然還未正式定稿,但從目前得知的內容來看,潛力巨大。下面是grpc基本的hello world的示例:

一、grpc-contract

還是按老套路,把服務涉及的物件定義、介面定義抽象出來,下面是專案結構圖:

\

pom.xml的內容如下:

\
  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <project xmlns="http://maven.apache.org/POM/4.0.0"
  3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>yjmyzz.grpc</groupId> 8 <artifactId>grpc-contract</artifactId> 9 <version>1.0</version> 10 11 12 <dependencies> 13 14 <dependency> 15 <
groupId>junit</groupId> 16 <artifactId>junit</artifactId> 17 <version>4.10</version> 18 </dependency> 19 20 <dependency> 21 <groupId>com.google.protobuf</groupId> 22 <artifactId>protobuf-java</artifactId> 23 <version>3.0.0-beta-1</version> 24 </dependency> 25 26 <dependency> 27 <groupId>io.grpc</groupId> 28 <artifactId>grpc-all</artifactId> 29 <version>0.8.0</version> 30 </dependency> 31 32 </dependencies> 33 34 <!--下面這個節點可選--> 35 <pluginRepositories> 36 <pluginRepository> 37 <releases> 38 <updatePolicy>never</updatePolicy> 39 </releases> 40 <snapshots> 41 <enabled>false</enabled> 42 </snapshots> 43 <id>central</id> 44 <name>Central Repository</name> 45 <url>https://repo.maven.apache.org/maven2</url> 46 </pluginRepository> 47 <pluginRepository> 48 <id>protoc-plugin</id> 49 <url>https://dl.bintray.com/sergei-ivanov/maven/</url> 50 </pluginRepository> 51 </pluginRepositories> 52 53 54 <build> 55 <extensions> 56 <extension> 57 <groupId>kr.motd.maven</groupId> 58 <artifactId>os-maven-plugin</artifactId> 59 <version>1.4.0.Final</version> 60 </extension> 61 </extensions> 62 <plugins> 63 <!--用於根據proto檔案生成java類的外掛--> 64 <plugin> 65 <groupId>com.google.protobuf.tools</groupId> 66 <artifactId>maven-protoc-plugin</artifactId> 67 <version>0.4.2</version> 68 <configuration> 69 <protocArtifact>com.google.protobuf:protoc:3.0.0-alpha-3.1:exe:${os.detected.classifier} 70 </protocArtifact> 71 <pluginId>grpc-java</pluginId> 72 <pluginArtifact>io.grpc:protoc-gen-grpc-java:0.8.0:exe:${os.detected.classifier}</pluginArtifact> 73 </configuration> 74 <executions> 75 <execution> 76 <goals> 77 <goal>compile</goal> 78 <goal>compile-custom</goal> 79 </goals> 80 </execution> 81 </executions> 82 </plugin> 83 84 <!--生成原始碼jar包的外掛(可選)--> 85 <plugin> 86 <artifactId>maven-source-plugin</artifactId> 87 <version>2.4</version> 88 <executions> 89 <execution> 90 <phase>package</phase> 91 <goals> 92 <goal>jar-no-fork</goal> 93 </goals> 94 </execution> 95 </executions> 96 </plugin> 97 98 </plugins> 99 </build> 100 </project>
View Code

demo_service_dto.proto內容如下:

\
syntax = "proto3";

package yjmyzz.grpc.study.dto;

option java_multiple_files = true;
option java_outer_classname = "DemoServiceDto";

message PingRequest {
     string in=1;
}

message PingResponse {
     string out=1;
}

message QueryParameter {
     int32 ageStart = 1;
     int32 ageEnd = 2;
}

message Person {
     int32 age = 1;
     string name = 2;
     bool sex=3;
     double salary=4;
     int32 childrenCount=5;
}

message PersonList{
     repeated Person items=1;
}
View Code

注:grpc要求protobuf必須使用3.0以上版本

demo_service.proto內容如下:

\
syntax = "proto3";

import "demo_service_dto.proto";

package yjmyzz.grpc.study.service;

option java_multiple_files = true;
option java_outer_classname = "DemoServiceDto";

service DemoService {
    rpc Ping (yjmyzz.grpc.study.dto.PingRequest) returns (yjmyzz.grpc.study.dto.PingResponse) {}

    rpc getPersonList (yjmyzz.grpc.study.dto.QueryParameter) returns (yjmyzz.grpc.study.dto.PersonList) {}
}
View Code

mvn install 後,會自動在target下生成相應的java class類

\

二、grpc-server

pom.xml檔案如下:

\
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>yjmyzz.grpc</groupId>
 8     <artifactId>grpc-server</artifactId>
 9     <version>1.0</version>
10 
11 
12     <dependencies>
13         <dependency>
14             <groupId>com.google.protobuf</groupId>
15             <artifactId>protobuf-java</artifactId>
16             <version>3.0.0-beta-1</version>
17         </dependency>
18 
19         <dependency>
20             <groupId>yjmyzz.grpc</groupId>
21             <artifactId>grpc-contract</artifactId>
22             <version>1.0</version>
23         </dependency>
24 
25         <dependency>
26             <groupId>io.grpc</groupId>
27             <artifactId>grpc-all</artifactId>
28             <version>0.8.0</version>
29         </dependency>
30 
31         <dependency>
32             <groupId>junit</groupId>
33             <artifactId>junit</artifactId>
34             <version>4.10</version>
35         </dependency>
36 
37     </dependencies>
38 
39 
40 </project>
View Code

先對服務介面提供實現:

package yjmyzz.grpc.study.service.impl;

import io.grpc.stub.StreamObserver;
import yjmyzz.grpc.study.dto.*;
import yjmyzz.grpc.study.service.DemoServiceGrpc;

import java.util.ArrayList;
import java.util.List;


public class DemoServiceImpl implements DemoServiceGrpc.DemoService {
    public void ping(PingRequest pingRequest, StreamObserver<PingResponse> streamObserver) {
        PingResponse reply = PingResponse.newBuilder().setOut("pong => " + pingRequest.getIn()).build();
        streamObserver.onValue(reply);
        streamObserver.onCompleted();
    }

    public void getPersonList(QueryParameter queryParameter, StreamObserver<PersonList> streamObserver) {
        //System.out.println(queryParameter.getAgeStart() + "-" + queryParameter.getAgeEnd());
        PersonList.Builder personListBuilder = PersonList.newBuilder();
        Person.Builder builder = Person.newBuilder();
        List<Person> list = new ArrayList<Person>();
        for (short i = 0; i < 10; i++) {
            list.add(builder.setAge(i).setChildrenCount(i).setName("test" + i).setSex(true).build());
        }
        personListBuilder.addAllItems(list);
        streamObserver.onValue(personListBuilder.build());
        streamObserver.onCompleted();
    }
}

和前面thrift、avro的helloworld一樣,這裡的實現只是意思一下,方便測試而已。

grpc的server端是基於Netty的(當然還有OKHttp的實現,詳情見github專案主頁),下面是server端的程式碼:

package yjmyzz.grpc.study.server;

import io.grpc.ServerImpl;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.netty.NettyServerBuilder;
import yjmyzz.grpc.study.service.DemoServiceGrpc;
import yjmyzz.grpc.study.service.impl.DemoServiceImpl;


public class DemoServiceServer {

    private int port = 50051;
    private ServerImpl server;

    private void start() throws Exception {
        server = NettyServerBuilder.forPort(port)
                .addService(DemoServiceGrpc.bindService(new DemoServiceImpl()))
                .build().start();

        server = InProcessServerBuilder.forName("testServer")
                .addService(DemoServiceGrpc.bindService(new DemoServiceImpl()))
                .build().start();

        System.out.println("Server started, listening on " + port);
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("*** shutting down gRPC server since JVM is shutting down");
                DemoServiceServer.this.stop();
                System.out.println("*** server shut down");
            }
        });
    }

    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }


    public static void main(String[] args) throws Exception {
        final DemoServiceServer server = new DemoServiceServer();
        server.start();
    }

}

三、grpc-client

pom.xml內容:

\
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>yjmyzz.grpc</groupId>
 8     <artifactId>grpc-client</artifactId>
 9     <version>1.0</version>
10 
11     <dependencies>
12 
13         <dependency>
14             <groupId>com.google.protobuf</groupId>
15             <artifactId>protobuf-java</artifactId>
16             <version>3.0.0-beta-1</version>
17         </dependency>
18 
19         <dependency>
20             <groupId>yjmyzz.grpc</groupId>
21             <artifactId>grpc-contract</artifactId>
22             <version>1.0</version>
23         </dependency>
24 
25         <dependency>
26             <groupId>io.grpc</groupId>
27             <artifactId>grpc-all</artifactId>
28             <version>0.8.0</version>
29         </dependency>
30 
31         <dependency>
32             <groupId>junit</groupId>
33             <artifactId>junit</artifactId>
34             <version>4.10</version>
35         </dependency>
36 
37     </dependencies>
38 
39 </project>
View Code

Client端程式碼:

package yjmyzz.grpc.study.client;

import io.grpc.ChannelImpl;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import yjmyzz.grpc.study.dto.PersonList;
import yjmyzz.grpc.study.dto.PingRequest;
import yjmyzz.grpc.study.dto.PingResponse;
import yjmyzz.grpc.study.dto.QueryParameter;
import yjmyzz.grpc.study.service.DemoServiceGrpc;

import java.util.concurrent.TimeUnit;

public class DemoServiceClient {

    private final ChannelImpl channel;
    private final DemoServiceGrpc.DemoServiceBlockingStub blockingStub;

    public DemoServiceClient(String host, int port) {
        channel =
                NettyChannelBuilder.forAddress(host, port).negotiationType(NegotiationType.PLAINTEXT)
                        .build();


        blockingStub = DemoServiceGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void ping(String name) {
        try {
            System.out.println("Will try to ping " + name + " ...");
            PingRequest request = PingRequest.newBuilder().setIn(name).build();
            PingResponse response = blockingStub.ping(request);
            System.out.println("ping: " + response.getOut());
        } catch (RuntimeException e) {
            System.out.println("RPC failed:" + e.getMessage());
            return;
        }
    }

    public void getPersonList(QueryParameter parameter) {
        try {
            //System.out.println("Will try to getPersonList " + parameter + " ...");
            PersonList response = blockingStub.getPersonList(parameter);
            //System.out.println("items count: " + response.getItemsCount());
//            for (Person p : response.getItemsList()) {
//                System.out.println(p);
//            }
        } catch (RuntimeException e) {
            System.out.println("RPC failed:" + e.getMessage());
            return;
        }
    }


    public static void main(String[] args) throws Exception {
        DemoServiceClient client = new DemoServiceClient("localhost", 50051);
        try {
            client.ping("a");

            int max = 100000;
            Long start = System.currentTimeMillis();

            for (int i = 0; i < max; i++) {
                client.getPersonList(getParameter());
            }
            Long end = System.currentTimeMillis();
            Long elapse = end - start;
            int perform = Double.valueOf(max / (elapse / 1000d)).intValue();

            System.out.print("rgpc " + max + " 次NettyServer呼叫,耗時:" + elapse + "毫秒,平均" + perform + "次/秒");
        } finally {
            client.shutdown();
        }
    }

    private static QueryParameter getParameter() {
        return QueryParameter.newBuilder().setAgeStart(5).setAgeEnd(50).build();
    }
}

在筆記本測試的結果:

Will try to ping a ...
ping: pong => a
rgpc 100000 次NettyServer呼叫,耗時:36409毫秒,平均2746次/秒

基本上在每秒3k次的數量級,相對thrift(1w+)、avro(5k+)來講,目前的差距還是很明顯的,但是新事物成長總是需要時間,再給google一段時間,相信以後會讓大家感到驚豔的。

在序列化方面,也做了一個跟之前thrift、avro類似的測試:

    @Test
    public void test() throws InvalidProtocolBufferException {

        QueryParameter queryParameter = QueryParameter.newBuilder().setAgeStart(1).setAgeEnd(5).build();
        byte[] bytes1 = queryParameter.toByteArray();
        System.out.println("Protobuf 3.0 二進位制序列後的byte陣列長度:" + bytes1.length);

        QueryParameter result = QueryParameter.parseFrom(bytes1);
        System.out.println(queryParameter.getAgeStart() + " - " + result.getAgeStart());

    }

輸出:

Protobuf 3.0 二進位制序列後的byte陣列長度:4
1 - 1

在2進位制序列化後的大小方面,protobuf 3大體跟thrift的TCompactProtocal(大小5)接近,比avro(大小2)略差。

文中示例原始碼下載:http://code.taobao.org/svn/grpc-demo/