Java RPC原理及Dubbo的实践应用

5,396 阅读11分钟

[TOC]

一、RPC原理

RPC(Remote Procedure Call)即远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

1.框架原理

在RPC框架中主要有三个角色:Provider、Consumer和Registry。如下图所示:

RPC原理

节点角色说明:

  • Server: 暴露服务的服务提供方。

  • Client: 调用远程服务的服务消费方。

  • Registry: 服务注册与发现的注册中心。

2.RPC的调用该流程

RPC调用流程

1.调用客户端句柄;执行传送参数

2.调用本地系统内核发送网络消息

3.消息传送到远程主机

4.服务器句柄得到消息并取得参数

5.执行远程过程

6.执行的过程将结果返回服务器句柄

7.服务器句柄返回结果,调用远程系统内核

8.消息传回本地主机

9.客户句柄由内核接收消息

10.客户接收句柄返回的数据

3.服务注册与发现

服务提供者启动后主动向注册中心注册机器ip、port以及提供的服务列表;

服务消费者启动时向注册中心获取服务提供方地址列表,可实现软负载均衡和Failover;

2.使用到的技术要点

1、动态代理

生成 client stub和server stub需要用到 Java 动态代理技术 ,我们可以使用JDK原生的动态代理机制,可以使用一些开源字节码工具框架 如:CgLib、Javassist等。

2、序列化

为了能在网络上传输和接收 Java对象,我们需要对它进行 序列化和反序列化操作。

* 序列化:将Java对象转换成byte[]的过程,也就是编码的过程;

* 反序列化:将byte[]转换成Java对象的过程;

可以使用Java原生的序列化机制,但是效率非常低,推荐使用一些开源的、成熟的序列化技术,例如:protobuf、Thrift、hessian、Kryo、Msgpack

关于序列化工具性能比较可以参考:jvm-serializers

3、NIO

当前很多RPC框架都直接基于netty这一IO通信框架,比如阿里巴巴的HSF、dubbo,Hadoop Avro,推荐使用Netty 作为底层通信框架。

4、服务注册中心

可选技术:

  • Redis

  • Zookeeper

  • Consul

  • Etcd

5.RPC功能目标

PC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。 为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。 下面我们将具体细化 stub 结构的实现。

6. RPC调用分类

RPC 调用分以下两种:

1. 同步调用
客户方等待调用执行完成并返回结果。
2. 异步调用
客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。 若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。

7.RPC结构拆分

RPC结构拆分

RPC 服务方通过 RpcServer 去导出(export)远程接口方法,而客户方通过 RpcClient 去引入(import)远程接口方法。 客户方像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理 RpcProxy 。 代理封装调用信息并将调用转交给 RpcInvoker 去实际执行。 在客户端的 RpcInvoker 通过连接器 RpcConnector 去维持与服务端的通道 RpcChannel, 并使用 RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。

RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,同样使用 RpcProtocol 执行协议解码(decode)。 解码后的调用信息传递给 RpcProcessor 去控制处理调用过程,最后再委托调用给 RpcInvoker 去实际执行并返回调用结果。

8.Java 常用的RPC框架

Java中的RPC框架比较多,各有特色,广泛使用的有RMI、Hessian、Dubbo等。

8.开源的优秀RPC框架

二、Dubbo框架

1.框架介绍

1.1 基本介绍

Dubbo是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。 Dubbo[]是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。

其核心部分包含:

  • 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
  • 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
  • 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

1.2 背景

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进

当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。

当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。

接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?

1.3Dubbo架构

Dubbo结构

Dubbo架构

Dubbo

节点角色说明:

  • Provider: 暴露服务的服务提供方。
  • Consumer: 调用远程服务的服务消费方。
  • Registry: 服务注册与发现的注册中心。
  • Monitor: 统计服务的调用次调和调用时间的监控中心。
  • Container: 服务运行容器。 调用关系说明
0. 服务容器负责启动,加载,运行服务提供者。

1. 服务提供者在启动时,向注册中心注册自己提供的服务。

2. 服务消费者在启动时,向注册中心订阅自己所需的服务。

3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

三、项目实例

1.1 项目背景介绍

此项目以SpringBoot为基础,集成aliaba dubbo-spring-boot-stater ,不以apache最新孵化Dubbo项目为搭建环境,采用注入风格,前提需要准备zookeeper环境,确保服务能够注入到zookeeper节点。 完整项目源码GitHub 地址:Dubbo-SpringBoot

1.2 项目模块说明

项目模块分为 dubbo-api、springBoot-dubbo-consumer、springboot-dubbo-provider 三个模块、将接口抽取出来为dubbo-api,便于消费者使用,提供者实现。 项目结构如下图所示:

AoKy8g.png
详细结构说明如下:

.Dubbo
├── dubbo-api
│   ├── dubboapi.iml
│   ├── pom.xml
│   ├── src
│   │   ├── main
│   │   └── test
│   └── target
│       ├── classes
│       ├── generated-sources
│       └── maven-status
├── Dubbo.iml
├── pom.xml
├── springboot-dubbo-consumer
│   ├── HELP.md
│   ├── pom.xml
│   ├── springboot-dubbo-consumer.iml
│   ├── src
│   │   ├── main
│   │   └── test
│   └── target
│       ├── classes
│       ├── generated-sources
│       ├── generated-test-sources
│       ├── maven-archiver
│       ├── maven-status
│       ├── springboot-dubbo-consumer-0.0.1-SNAPSHOT.jar
│       ├── springboot-dubbo-consumer-0.0.1-SNAPSHOT.jar.original
│       ├── surefire-reports
│       └── test-classes
└── springboot-dubbo-provider
    ├── HELP.md
    ├── pom.xml
    ├── springboot-dubbo-provider.iml
    ├── src
    │   ├── main
    │   └── test
    └── target
        ├── classes
        ├── generated-sources
        ├── generated-test-sources
        ├── maven-archiver
        ├── maven-status
        ├── springboot-dubbo-provider-0.0.1-SNAPSHOT.jar
        ├── springboot-dubbo-provider-0.0.1-SNAPSHOT.jar.original
        ├── surefire-reports
        └── test-classes

1.3 项目搭建

  • Step1:

Idea创建一个空的maven工程,在创建一个maven模块dubbo-api,在dubbo-api 创建一个接口RemoteUserService,用于消费者调用,提供者实现。

package com.yongliang.dubbo.api;

/**
 * @version 1.0.0
 */
public interface RemoteUserService {
    String sayHello(String name);
}
  • Step 2:

创建springBoot模块 springboot-dubbo-provider, 配置pom.xml 信息如下:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yongliang.dubbo</groupId>
    <artifactId>springboot-dubbo-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-dubbo-provider</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>
        <!-- dubbo依赖 -->
        <dependency>
            <groupId>com.alibaba.spring.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.6</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>Dubbo</groupId>
            <artifactId>dubbo-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>25.1-jre</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

编辑application.properites 文件,加入dubbo配置参数

server.port=8082
server.servlet.context-path=/DubboProvider
## dubbo 
spring.dubbo.application.id=dubbo-provider
spring.dubbo.application.name=dubbo-provider
#spring.dubbo.provider.filter=dubboLogFilter
# zookeeper 注册信息
spring.dubbo.registry.address=zookeeper://10.18.33.158:2181
spring.dubbo.server=true
通信协议
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880

在启动入口增加dubbo自动注解

package com.yongliang.dubbo.provider;

import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableDubboConfiguration
@SpringBootApplication
public class SpringbootDubboProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDubboProviderApplication.class, args);
    }

}

在provider 模块实现接口RemoteUserService

package com.yongliang.dubbo.provider.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.yongliang.dubbo.api.RemoteUserService;
import org.springframework.stereotype.Component;

/**
 * @author zhangyongliang
 * @create 2019-04-08 18:44
 **/
@Service(interfaceClass = RemoteUserService.class,retries = 0,version = "1.0.0")
@Component
public class RemoteServiceimpl implements RemoteUserService {
    @Override
    public String sayHello(String name) {
        return "Hello"+name;
    }
}

retries标识,失败后重复调用次数,version代表接口实现版本,此版本号与消费者实际依赖保持一致。

  • Step3:

创建springboot-dubbo-consumer 消费者模块,pom.xml 配置信息如下:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yongliang.dubbo</groupId>
    <artifactId>springboot-dubbo-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-dubbo-consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.spring.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.6</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>Dubbo</groupId>
            <artifactId>dubbo-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

修改 application.properties 文件,增加相关dubbo配置信息

server.port=8081
server.servlet.context-path=/DubboConsumer
## dubbo
spring.dubbo.application.name=dubbo-consumer
spring.dubbo.application.id=dubbo-consumer
spring.dubbo.registry.address=zookeeper://10.18.33.158:2181
spring.dubbo.consumer.check=false
spring.dubbo.reference.check=false

在启动入口增加dubbo自动配置注解

package com.yongliang.dubbo.consumer;

import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableDubboConfiguration
@SpringBootApplication
public class SpringbootDubboConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDubboConsumerApplication.class, args);
    }

}

实现Rest控制类,实现方法调用:

package com.yongliang.dubbo.consumer.rest;

import com.alibaba.dubbo.config.annotation.Reference;
import com.yongliang.dubbo.api.RemoteUserService;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author zhangyongliang
 * @create 2019-04-08 18:48
 **/
@RestController
public class RemoteUserRest  {
    @Reference(version = "1.0.0")
    private RemoteUserService remoteUserService;

    @GetMapping("/dubboTest")
    public String dubboTest() {
        return remoteUserService.sayHello("dubbo");
    }
}

打开浏览器访问:localhost:8081/DubboConsumer/dubboTest

请求成功后返回:HelloDubbo则证明Dubbo集成成功。 此过程没有说明父 pom.xml 配置信息,读者自行参考源码组织。