首页 > 技术文章 > RPC 框架(附 dubbo & grpc 简例)

moonlight-lin 2021-01-29 20:55 原文



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(); 
        } 
    } 
}


推荐阅读