Java RPC原理及Dubbo的實踐應用

清風雲悅發表於2019-04-09

[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 配置資訊,讀者自行參考原始碼組織。

相關文章