RPC 框架(附 dubbo & grpc 簡例)




HTTP 和 RPC

在微服務體系結構中,獨立部署在各個機器或容器上的服務之間,如何進行有效的通信,是一個很重要的問題,現在常用的主要是 RESTful HTTP 和 RPC

HTTP 的優點

  • 通用性強,基本上所有框架,所有語言都支持 HTTP
  • 可讀性高,URL 對資源的定義,Action 對操作的定義,Payload 的定義都比較清晰易懂
  • 可以通過各種防火牆、網關

HTTP 的缺點

  • HTTP 協議的效率比較低(畢竟是網絡第 7 層協議)
  • HTTP 協議的有效信息占比小
  • 客戶度編寫調用 HTTP 請求的代碼並不易用

RPC(Remote Procedure Call,遠程過程調用)使客戶端向服務端發請求就像調用本地函數一樣

比如客戶端調用

String message = service.sayHi("dubbo");

服務端會有相應的函數被執行並返回結果給客戶端

    public String sayHi(String name) {
        return "hi, " + name;
    }

RPC 的優點

  • 使用 TCP 或 HTTP2.0 協議,通信效率高,有效信息占比大
  • 客戶端調用 RPC 請求就像調用本地函數一樣,代碼比較簡單,容易使用

RPC 的缺點

  • 可讀性沒有 HTTP 強
  • 缺少通用性,和 HTTP 是統一的標准不同,RPC 框架的實現各不相同,有的僅支持單語言,有的支持跨語言,有的支持多種序列化協議,有的僅有一種固定的序列化協議,有的支持管理中心、服務發現、負載均衡、熔斷降級等功能,有的不支持
  • 需要和特定的框架綁定使用,比較緊耦合
  • 不同的網關對 RPC 的支持可能會不夠(對 TCP、HTTP2.0 的支持)

可以看到,HTTP 和 RPC 各有千秋,通常暴露給外部用戶的都是 HTTP,而當系統內部的微服務特別多,微服務之間的通信量特別大的時候,為了提高性能簡化代碼可以使用 RPC,但是如果微服務沒有拆的很細,或是對性能要求不是很高,內部通信也可以使用 HTTP

RPC 技術

通常包含以下技術實現

  • 動態代理

客戶端實際上只定義了接口,具體的實現在服務端,所以需要有動態代理,當客戶端調用函數的時候,將信息傳遞到服務端,在服務端調用真正的實現,接受它的返回給客戶端

  • 序列化放序列化

客戶端需要將數據序列化,在服務端要做反序列化

  • 通信協議

需要高效的通信協議以提高性能

  • 異常處理

出現網絡故障、服務端錯誤等各種異常的時候要怎么處理

RPC 常用框架

  • dubbo

阿里巴巴開發的 RPC 框架,支持 Java,2012 年開源,2014 年停止維護,2017 年又重新維護,后來捐給了 Apache

dubbo 架構如下圖

https://dubbo.apache.org/zh/docs/v2.7/user/preface/architecture/

Provider 向 Registry 注冊服務,Consumer 通過 Registry 獲取 Provider 服務的信息

服務注冊中心,服務提供者,服務消費者三者之間均為長連接

Consumer 向 Provider (可以配多個 Provider 實例)發請求時,可以自動實現負載均衡等算法

監控中心負責統計各服務調用次數,調用時間等,統計先在內存匯總后每分鍾一次發送到監控中心服務器,並以報表展示

注冊中心和監控中心全部宕機,不影響已運行的提供者和消費者,消費者在本地緩存了提供者列表

注冊中心和監控中心都是可選的,服務消費者可以直連服務提供者

健壯性和伸縮性

底層協議可以是:dubbo、rest、http、hessian、redis、thrift、gRPC、memcached、rmi、webservice,默認是 dubbo(采用單一長連接和 NIO 異步通訊,適合於小數據量大並發的服務調用,不適合傳送大數據量的服務)
https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-registry/
https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/
https://dubbo.apache.org/zh/docs/v2.7/user/perf-test/

注冊中心可以是:dubbo, nacos, multicast, zookeeper, redis, consul, sofa, etcd,推薦使用 zookeeper
https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-registry/

2.7.5 引入了基於 AK/SK 機制的認證鑒權機制,並且引入了鑒權服務中心,主要原理是消費端在請求需要鑒權的服務時,會通過 SK、請求元數據、時間戳、參數等信息來生成對應的請求簽名,通過 Dubbo 的 Attahcment 機制攜帶到對端進行驗簽,驗簽通過才進行業務邏輯處理

dubbo 可以直接和 SpringBoot 集成

各種用法示例
https://dubbo.apache.org/zh/docs/v2.7/user/examples/

  • gRPC

Google 開發的 RPC 框架

支持多種語言,並且 Consumer 和 Provider 之間可以跨語言

使用 proto3 定義接口后可以生成不同語言的源文件

沒有注冊服務中心、負載均衡、熔斷降級等功能

默認使用 protocol buffers 作為序列化反序列化機制,protocol buffers 的壓縮速度快,壓縮率高

支持 SSL/TLS、OAuth 2.0 等認證授權協議

使用 HTTP2.0 作為通信協議,HTTP2.0 采用新的數據格式、新的 Header 壓縮算法、服務端推送、鏈接共享等機制,性能大幅度提升,但現在支持的組件可能不多,比如如果要通過 Nginx 它可能不認 HTTP2.0 只把它當做 TCP 消息對待

  • Motan

微博開發的 RPC 框架,用 Java 實現,可以和 SpringBoot 集成

https://github.com/weibocom/motan

無需多少額外代碼即可實現
支持 Consul、Zookeeper 作為服務發現中心
支持負載均衡
為高負載場景做了優化
支持同步調用和異步調用
支持 Java、Go、PHP 等多語言

  • rpcx

微博開發的,用 Go 語言開發,參考了阿里巴巴的 dubbo 和微博的 Motan

https://github.com/smallnest/rpcx
https://blog.rpcx.io/posts/why-did-i-develop-rpcx/
https://doc.rpcx.io/

性能很好,貌似比 dubbo、grpc、motan 都要好

支持原生 Go 函數,不需要定義 proto 文件
支持 TCP、HTTP、KCP、QUIC 等傳輸協議
支持 JSON、Protobuf、MessagePack 數據編碼協議
服務發現,支持 P2P、zookeeper、etcd、consul、mDNS 等
容錯(Fault tolerance):支持 Failover(切換)、Failfast(快速失敗)、Failtry(重試)
支持負載均衡
支持認證授權
支持心跳檢測

貌似還支持跨語言
rpcx uses a binary protocol and platform-independent, which means you can develop services in other languages such as Java, python, nodejs, and you can use other prorgramming languages to invoke services developed in Go.

  • Thrift

由 Facebook 開發的 RPC 框架,后來捐給了 Apache

https://github.com/apache/thrift

是一個輕量級的、跨語言的、點到點的 RPC 框架

定義好接口后,代碼生成器可以生成不同語言的代碼

從上圖可以看到,Thrift 支持很多種語言、傳輸協議、數據協議、應用模式

Thrift 不支持服務發現、負載均衡、熔斷降級等功能

dubbo 例子

定義有 3 個模塊的項目

    <groupId>com.example</groupId>
    <artifactId>dubbo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <modules>
        <module>dubbo-example-interface</module>
        <module>dubbo-example-consumer</module>
        <module>dubbo-example-provider</module>
    </modules>

interface 只定義了一個接口

package com.example.dubbo.interfaces;

public interface GreetingService {
    String sayHi(String name);
}

maven install 編譯安裝 interface

provider 實現了接口

    <parent>
        <groupId>com.example</groupId>
        <artifactId>dubbo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    
    <artifactId>dubbo-example-provider</artifactId>
    <packaging>jar</packaging>
    <description>Demo project for dubbo</description>

    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <version>2.7.8</version>
            <type>pom</type>
        </dependency>
        
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>dubbo-example-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
public class GreetingServiceImpl implements GreetingService {
    @Override
    public String sayHi(String name) {
        System.out.println("receive msg " + name);
        return "hi, " + name;
    }
}
public class Provider {
    private static String zookeeperHost = System.getProperty("zookeeper.address", "localhost");

    public static void main(String[] args) throws Exception {
        ServiceConfig<GreetingService> service = new ServiceConfig<>();
        service.setApplication(new ApplicationConfig("first-dubbo-provider"));
        service.setRegistry(new RegistryConfig("zookeeper://" + zookeeperHost + ":2181"));
        service.setInterface(GreetingService.class);
        service.setRef(new GreetingServiceImpl());
        service.export();

        System.out.println("dubbo service started");
        new CountDownLatch(1).await();
    }
}

consumer 調用接口

    <parent>
        <groupId>com.example</groupId>
        <artifactId>dubbo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    
    <artifactId>dubbo-example-consumer</artifactId>
    <packaging>jar</packaging>
    <description>Demo project for dubbo</description>

    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <version>2.7.8</version>
            <type>pom</type>
        </dependency>
        
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>dubbo-example-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
public class Consumer {
    private static String zookeeperHost = System.getProperty("zookeeper.address", "localhost");

    public static void main(String[] args) {
        ReferenceConfig<GreetingService> reference = new ReferenceConfig<>();
        reference.setApplication(new ApplicationConfig("first-dubbo-consumer"));
        reference.setRegistry(new RegistryConfig("zookeeper://" + zookeeperHost + ":2181"));
        reference.setInterface(GreetingService.class);
        GreetingService service = reference.get();
        String message = service.sayHi("dubbo");
        System.out.println(message);
    }
}

運行后看到 consumer 打出 hi, dubbo

gRPC 例子

定義有 4 個模塊的項目

    <modules>
        <module>grpc-example-proto</module>
        <module>grpc-example-interfaces</module>
        <module>grpc-example-client</module>
        <module>grpc-example-server</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-netty-shaded</artifactId>
                <version>1.35.0</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-protobuf</artifactId>
                <version>1.35.0</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-stub</artifactId>
                <version>1.35.0</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>
    </dependencyManagement>

proto 定義接口並用於生產代碼

    <parent>
        <groupId>com.example</groupId>
        <artifactId>grpc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    
    <artifactId>grpc-example-proto</artifactId>
    <packaging>jar</packaging>
    <description>Demo project for grpc</description>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
  
        <plugins>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.35.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

在 src/main/proto 下創建 GreetingService.proto 文件,定義接口

syntax = "proto3";


option java_multiple_files = true; 
option java_package = "com.example.grpc.interfaces"; 
option java_outer_classname = "GreetingServiceProto"; 
option objc_class_prefix = "HLW";


package GreetingService;


service GreetingService {
    rpc SayHi (GreetingRequest) returns (GreetingReply) {} 
}

message GreetingRequest { 
    string name = 1; 
}

message GreetingReply { 
    string message = 1; 
} 

然后執行 maven install 會生產源代碼
將 target/generated-sources/protobuf 下面的源文件都考到 interface 項目下面

interface 的 pom 要引用 grpc

    <parent>
        <groupId>com.example</groupId>
        <artifactId>grpc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    
    <artifactId>grpc-example-interfaces</artifactId>
    <packaging>jar</packaging>
    <description>Demo project for grpc</description>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

maven install 編譯安裝 interface

再編寫 server 實現接口

    <parent>
        <groupId>com.example</groupId>
        <artifactId>grpc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    
    <artifactId>grpc-example-server</artifactId>
    <packaging>jar</packaging>
    <description>Demo project for grpc</description>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>grpc-example-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase  {

    @Override 
    public void sayHi(GreetingRequest req, StreamObserver<GreetingReply> responseObserver){ 
        GreetingReply reply = GreetingReply.newBuilder().setMessage(("Hi " + req.getName())).build(); 
        responseObserver.onNext(reply); 
        responseObserver.onCompleted(); 
    } 
}
public class GrpcServer {

    private static final Logger logger = Logger.getLogger(GrpcServer.class.getName());

    private int port = 50051; 
    private Server server;

    private void start() throws IOException {
        server = ServerBuilder.forPort(port) 
                              .addService(new GreetingServiceImpl()) 
                              .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"); 
                GrpcServer.this.stop(); 
                System.err.println("*** server shut down"); 
            } 
        }); 
    }

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

    private void blockUntilShutdown() throws InterruptedException { 
        if (server != null){ 
            server.awaitTermination(); 
        } 
    }


    public static void main(String[] args) throws IOException, InterruptedException {
        final GrpcServer server = new GrpcServer(); 
        server.start(); 
        server.blockUntilShutdown(); 
    }
} 

再編寫 client 調用接口

    <parent>
        <groupId>com.example</groupId>
        <artifactId>grpc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    
    <artifactId>grpc-example-client</artifactId>
    <packaging>jar</packaging>
    <description>Demo project for grpc</description>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>grpc-example-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
public class GrpcClient {

    private final ManagedChannel channel; 
    private final GreetingServiceGrpc.GreetingServiceBlockingStub blockingStub; 
    private static final Logger logger = Logger.getLogger(GrpcClient.class.getName());

    public GrpcClient(String host, int port){ 
        channel = ManagedChannelBuilder.forAddress(host, port) 
                                       .usePlaintext() 
                                       .build();

        blockingStub = GreetingServiceGrpc.newBlockingStub(channel); 
    }

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

    public void greet(String name){ 
        GreetingRequest request = GreetingRequest.newBuilder().setName(name).build(); 
        GreetingReply response;
        
        try{ 
            response = blockingStub.sayHi(request); 
        } catch (StatusRuntimeException e) { 
            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); 
            return;
        }
        
        logger.info("Greeting: " + response.getMessage()); 
    }

    public static void main(String[] args) throws InterruptedException { 
        GrpcClient client = new GrpcClient("localhost", 50051); 
        
        try{ 
            String user = "GRPC"; 
            if (args.length > 0){
                user = args[0];
            }
            client.greet(user); 
        }finally { 
            client.shutdown(); 
        } 
    } 
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM