gRPC基於Golang和Java的簡單實現

來源:互聯網
上載者:User

原文串連: 一文瞭解RPC以及gRPC基於Golang和Java的簡單實現

一:什麼是RPC

  • 簡介:RPC:Remote Procedure Call,遠端程序呼叫。簡單來說就是兩個進程之間的資料互動。正常服務端的介面服務是提供給使用者端(在Web開發中就是瀏覽器)或者自身調用的,也就是本地程序呼叫。和本地程序呼叫相對的就是:假如兩個服務端不在一個進程內怎麼進行資料互動?使用RPC。尤其是現在微服務的大量實踐,服務與服務之間的調用不可避免,RPC更顯得尤為重要。

  • 原理:電腦的世界中不管使用哪種技術,核心都是對資料的操作。RPC不過是將資料的操作垮了一個維度而已。解決的問題本質上只是資料在不同進程間的傳輸。說的再多一些,就要瞭解網路模型的知識,七層也好,四層五層也罷。這個不是本文的重點。我們所說的RPC一般是指在傳輸層使用TCP協議進行的資料互動,也有很多基於HTTP的成熟架構。

    盜用網路上一張圖片說明:

    gRPC流程

    描述了一個RPC的完整調用流程:

    1:client向client stub發起方法調用請求。

    2:client stub接收到請求後,將方法名,請求參數等資訊進行編碼序列化。

    3:client stub通過配置的ip和連接埠使用socket通過網路向遠程伺服器server發起請求。

    4:遠程伺服器server接收到請求,解碼還原序列化請求資訊。

    5:server將請求資訊交給server stub,server stub找到對應的本地真實方法實現。

    6:本地方法處理調用請求並將返回的資料交給server stub。

    7:server stub 將資料編碼序列化交給作業系統核心,使用socket將資料返回。

    8:client端socket接收到遠程伺服器的返回資訊。

    9:client stub將資訊進行解碼還原序列化。

    10:client收到遠程伺服器返回的資訊。

    中有一個stub(存根)的概念。stub負責接收本地方法調用,並將它們委託給各自的具體實現對象。server端stub又被稱為skeleton(骨架)。可以理解為代理類。而實際上基於Java的RPC架構stub基本上也都是使用動態代理。我們所說的client端和server端在RPC中一般也都是相對的概念。

    而所謂的RPC架構也就是封裝了上述流程中2-9的過程,讓開發人員調用遠程方法就像調用本地方法一樣。

二:常用RPC架構選型

  • Duboo:

    阿里開源的基於TCP的RPC架構,基本上是國內生產環境應用最廣的開發架構了。使用zookeeper做服務的註冊與發現,使用Netty做網路通訊。遺憾的是不能跨語言,目前只支援Java。

  • Thrift:

    Facebook開源的跨語言的RPC架構,通過IDL來定義RPC的介面和資料類型,使用thrift編譯器產生不同語言的實現。據說是目前效能最好的RPC架構,只是暫沒使用過。

  • gRPC:

    這個是我們今天要聊的重點。gRPC是Google的開源產品,是跨語言的通用型RPC架構,使用Go語言編寫。 Java語言的應用同樣使用了Netty做網路通訊,Go採用了Goroutine做網路通訊。序列化方式採用了Google自己開源的Protobuf。請求的調用和返回使用HTTP2的Stream。

  • SpringCloud:

    SpringCloud並不能算一個RPC架構,它是Spring家族中一個微服務治理的解決方案,是一系列架構的集合。但在這個方案中,微服務之間的通訊使用基於HTTP的Restful API,使用Eureka或Consul做服務註冊與發現,使用聲明式用戶端Feign做服務的遠程調用。這一系列的功能整合起來構成了一套完整的遠程服務調用。

如何選擇:

如果公司項目使用Java並不牽扯到跨語言,且規模並沒有大到難以治理,我推薦Dubbo。如果項目規模大,服務調用錯綜複雜,我推薦SpringCloud。

如果牽扯到跨語言,我推薦gRPC,這也是目前我司的選擇。即使Thrift效能是gRPC的2倍,但沒辦法,它有個好爹,現在我們的開發環境考慮最多的還是生態。

三:gRPC的原理

一個RPC架構必須有兩個基礎的組成部分:資料的序列化和進程資料通訊的互動方式。

對於序列化gRPC採用了自家公司開源的Protobuf。什麼是Protobuf?先看一句網路上 大部分的解釋:

Google Protocol Buffer(簡稱 Protobuf)是一種輕便高效的結構化資料存放區格式,平台無關、語言無關、可擴充,可用於通訊協議和資料存放區等領域。

上句有幾個關鍵點:它是一種資料存放區格式,跨語言,跨平台,用於通訊協議和資料存放區。

這麼看和我們熟悉的JSON類似,但其實著重點有些本質的區別。JSON主要是用於資料的傳輸,因為它輕量級,可讀性好,解析簡單。Protobuf主要是用於跨語言的IDL,它除了和JSON、XML一樣能定義結構體之外,還可以使用自描述格式定於出介面的特性,並可以使用針對不同語言的protocol編譯器產生不同語言的stub類。所以天然的適用於跨語言的RPC架構中。

而關於進程間的通訊,無疑是Socket。Java方面gRPC同樣使用了成熟的開源架構Netty。使用Netty Channel作為資料通道。傳輸協議使用了HTTP2。

通過以上的分析,我們可以將一個完整的gRPC流程總結為以下幾步:

  • 通過.proto檔案定義傳輸的介面和訊息體。

  • 通過protocol編譯器產生server端和client端的stub程式。

  • 將請求封裝成HTTP2的Stream。

  • 通過Channel作為資料通訊通道使用Socket進行資料轉送。

四:代碼的簡單實現

概念永遠都是枯燥的,只有實戰才能真正理解問題。下面我們使用代碼基於以上的步驟來實現一個簡單gRPC。為了體現gRPC跨語言的特性,這次我們使用兩種語言:Go實現server端,Java作為client端來實現。

1:安裝Protocol Buffers,定義.proto檔案

登入Google的 github下載對應Protocol Buffers版本。

安裝完成後當我們執行protoc命令如果返回如下資訊說明安裝成功。

protoc

下面我們定義一個simple.proto檔案,這也是後續我們實現gRPC的基礎

syntax = "proto3"; //定義了我們使用的Protocol Buffers版本。 //表明我們定義了一個命名為Simple的服務(介面),內部有一個遠程rpc方法,名字為SayHello。 //我們只要在server端實現這個介面,在實作類別中書寫我們的業務代碼。在client端調用這個介面。 service Simple{    rpc SayHello(HelloRequest) returns (HelloReplay){} } //請求的結構體 message HelloRequest{     string name = 1; } //返回的結構體 message HelloReplay{     string message = 1; }

通過上面的注釋可以看出此檔案是一個簡單的RPC遠程方法描述。

2:使用Golang實現sever端

根據官方文檔使用如下命令安裝針對Go的gRPC:

$ go get -u google.golang.org/grpc

但是由於我們有偉大的長城,一般這條命令都不會下載成功。但Google的檔案一般都會在github存有一份鏡像。我們可以使用如下命令:

$ go get -u github.com/grpc/grpc-go

隨後將下載的檔案夾重新命名為go,並放入一個建立的google.golang.org的檔案夾中。♀️

當我們安裝完gRPC並定義好了遠程介面調用的具體資訊後,我們要使用protocol編譯器產生我們的stub程式。

我們安裝的Protocol Buffers是用來編譯我們的.proto檔案的,但是編譯後的檔案是不能被Java、C、Go等這些語言使用。Google針對不同的語言有不同的編譯器。本次我們使用Golang語言,所以要安裝針對Golang的編譯器,根據官方提供的命令執行:

$ go get -u github.com/golang/protobuf/protoc-gen-go

但有可能我們會下載不成功,因為這個會依賴很多Golang的類庫,這些類庫和上面安裝gRPC一樣,鑒於牆的原因,還要執行一系列繁瑣的改檔案夾的步驟。但這個不是我們的重點,就不細說了。

安裝成功之後我們就可以建立Go的project了。

本次我們建立一個grpc-server的項目,然後將前面寫的simple.proto放入項目proto的package中。

隨後在項目的目錄下使用命令列執行如下命令:

protoc -I grpc-server/ proto/simple.proto --go_out=plugins=grpc:simple

這樣就將simple.proto編譯成了Go語言對應的stub程式了。

隨後我們就可以寫我們server端的代碼了:main.go。

package mainimport (    "context"    "grpc-server/proto"    "fmt"    "net"    "log"    "google.golang.org/grpc"    "google.golang.org/grpc/reflection")const(    port = ":50051")type server struct{}func (s *server) SayHello(ctx context.Context,req *simple.HelloRequest) (*simple.HelloReplay, error){    fmt.Println(req.Name)    return &simple.HelloReplay{Message:"hello =======> " + req.Name},nil}func main(){    lis,err := net.Listen("tcp",port)    if err != nil {        log.Fatal("fail to listen")    }    s := grpc.NewServer()    simple.RegisterSimpleServer(s,&server{})    reflection.Register(s)    if err:= s.Serve(lis);err != nil{        log.Fatal("fail to server")    }}

以上的代碼都是模板代碼,main函數是socket使用Go的標準實現。作為開發人員我們只關注遠程服務提供的具體介面實現即可。

最終我們的項目目錄是這樣的:

go-server

就這樣一個使用Go語言實現的最簡單server端就完成了。

3:使用Java實現client端

相對來說Java實現就簡單一些,首先我們可以使用熟悉的Maven外掛程式進行stub代碼的產生。

建立一個grpc-client的父項目,兩個子項目:client和lib。lib用於stub程式的代碼產生。

lib項目編輯pom.xml,添加gRPC針對Java的外掛程式編譯器:

<?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">    <modelVersion>4.0.0</modelVersion>    <groupId>org.js</groupId>    <artifactId>grpc-client</artifactId>    <version>0.0.1-SNAPSHOT</version>    <packaging>pom</packaging>    <modules>        <module>client</module>    </modules>    <name>grpc-client</name>    <description>Demo project for Spring Boot</description>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>        <java.version>1.8</java.version>        <grpc.version>1.13.1</grpc.version>        <springboot.version>2.0.4.RELEASE</springboot.version>    </properties>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter</artifactId>                <version>${springboot.version}</version>            </dependency>            <dependency>                <groupId>io.grpc</groupId>                <artifactId>grpc-netty</artifactId>                <version>${grpc.version}</version>            </dependency>            <dependency>                <groupId>io.grpc</groupId>                <artifactId>grpc-protobuf</artifactId>                <version>${grpc.version}</version>            </dependency>            <dependency>                <groupId>io.grpc</groupId>                <artifactId>grpc-stub</artifactId>                <version>${grpc.version}</version>            </dependency>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter-web</artifactId>                <version>${springboot.version}</version>            </dependency>        </dependencies>    </dependencyManagement>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

將定義好的simple.proto檔案拷貝項目proto的package下。隨後右鍵:Run Maven——compile。

maven

產生完成後將target中的兩個檔案拷貝到client項目目錄中。

target

之後就是編寫我們的業務代碼進行gRPC的遠程調用了。本次我們寫一個簡單的web程式類比遠端調用。

定義一個class:SimpleClient:

package org.js.client.grpc;import io.grpc.ManagedChannel;import io.grpc.ManagedChannelBuilder;import java.util.concurrent.TimeUnit;/** * @author JiaShun * @date 2018/8/11 12:11 */public class SimpleClient {    private final ManagedChannel channel;    private final SimpleGrpc.SimpleBlockingStub blockingStub;    public SimpleClient(String host, int port){        this(ManagedChannelBuilder.forAddress(host, port).usePlaintext());    }    private SimpleClient(ManagedChannelBuilder<?> channelBuilder){        channel = channelBuilder.build();        blockingStub = SimpleGrpc.newBlockingStub(channel);    }    public void shutdown()throws InterruptedException{        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);    }    public String sayHello(String name){        SimpleOuterClass.HelloRequest req = SimpleOuterClass.HelloRequest.newBuilder().setName(name).build();        SimpleOuterClass.HelloReplay replay = blockingStub.sayHello(req);        return replay.getMessage();    }}

基本都是模板代碼。下面再編寫一個簡單的web請求:

controller代碼:

package org.js.client.controller;import org.js.client.service.IHelloService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;/** * @author JiaShun * @date 2018/8/10 22:20 */@RestControllerpublic class HelloController {    @Autowired    private IHelloService helloService;    @GetMapping("/{name}")    public String sayHello(@PathVariable String name){        return helloService.sayHello(name);    }}

service實作類別:

package org.js.client.service;import org.js.client.grpc.SimpleClient;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;/** * @author JiaShun * @date 2018/8/10 22:22 */@Servicepublic class HelloServiceImpl implements IHelloService{    private Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);    @Value("${gRPC.host}")    private String host;    @Value("${gRPC.port}")    private int port;    @Override    public String sayHello(String name) {        SimpleClient client = new SimpleClient(host,port);        String replay = client.sayHello(name);        try {            client.shutdown();        } catch (InterruptedException e) {            logger.error("channel關閉異常:err={}",e.getMessage());        }        return replay;    }}

就這麼簡單。

隨後我們測試一下:

分別啟動Go server端,Java client端。

gRPC-start

訪問:http://localhost:8080/jiashun

gRPC-test

可以發現server端列印出了client端的請求,client端也收到了server端的返回。

完整代碼:

server:https://github.com/jia-shun/grpc-server

client:https://github.com/jia-shun/grpc-client

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.