什麼是 RPC
RPC(Remote Procedure Call)—遠端過程調用,它是一種通過網絡從遠端計算機程式上請求服務,而不需要了解底層網絡技術的協定。也就是說兩台伺服器A,B,一個應用部署在A伺服器上,想要調用B伺服器上應用提供的方法,由于不在一個記憶體空間,不能直接調用,需要通過網絡來表達調用的語義和傳達調用的資料。
簡單來說,RPC 就是遠端方法調用,遠端方法調用和本地方法調用是相對的兩個概念,本地方法調用指的是程序内部的方法調用,而遠端方法調用指的是兩個程序内的方法互相調用。實作遠端方法調用,基本的就是通過網絡,通過傳輸資料來進行調用。
RPC 可以基于 HTTP 或者TCP 來傳輸資料:
- 1.RPC over Http:基于Http協定來傳輸資料。
- 2.PRC over Tcp:基于Tcp協定(socket)來傳輸資料。
對于所傳輸的資料,可以交由RPC的雙方來協商定義,但基本都會包括:
- 1.調用的是哪個類或接口。
- 2.調用的是哪個方法,方法名和方法參數類型(考慮方法重載)。
- 3.調用方法的入參。
是以,我們其實可以看到 RPC 的自定義性是很高的,各個公司内部都可以實作自己的一套 RPC 架構,而 Dubbo 就是阿裡所開源出來的一套 RPC 架構。
RPC 工作原理
RPC的設計由Client,Client stub,Network ,Server stub,Server構成。其中Client就是用來調用服務的,Cient stub是用來把調用的方法和參數序列化的(因為要在網絡中傳輸,必須要把對象轉變成位元組),Network用來傳輸這些資訊到Server stub, Server stub用來把這些資訊反序列化的,Server就是服務的提供者,最終調用的就是Server提供的方法。
- 1.Client像調用本地服務似的調用遠端服務。
- 2.Client stub接收到調用後,将方法、參數序列化。
- 3.用戶端通過網絡(socket,http等)将消息發送到服務端。
- 4.Server stub 收到消息後進行解碼(将消息對象反序列化)。
- 5.Server stub 根據解碼結果調用本地的服務。
- 6.本地服務執行(對于服務端來說是本地執行)并将結果傳回給Server stub。
- 7.Server stub将傳回結果打包成消息(将結果消息對象序列化)。
- 8.服務端通過sockets将消息發送到用戶端。
- 9.Client stub接收到結果消息,并進行解碼(将結果消息反序列化)。
- 10.用戶端得到最終結果。
目前,官網上是這麼介紹的:Apache Dubbo 是一款高性能、輕量級的開源 Java 服務架構。在幾個月前,官網的介紹是:Apache Dubbo 是一款高性能、輕量級的開源 Java RPC架構。
為什麼會将 RPC 改為服務?
Dubbo 一開始的定位就是 RPC 架構,專注于兩個服務之間的調用。但随着微服務的盛行,除開服務調用之外,Dubbo 也在逐漸的涉獵服務治理、服務監控、服務網關等等,是以現在的 Dubbo 目标已經不止是 RPC 架構了,而是想成為和Spring Cloud 類似的一個服務架構。
上面所說的 Dubbo 指的是 Dubbo 架構,另外 Dubbo 還有 Dubbo 協定的含義,Dubbo 架構提供了許許多多的協定實作,例如:dubbo(預設,基于 Netty),rmi, webservice,http,redis 等。
Provider需要完成以下内容:
- 1.提供服務的接口。
- 2.提供服務實作類。
- 3.将服務注冊到注冊中心(根據接口可以擷取到服務實作類和服務位址)。
- 4.暴露服務:HTTP協定(基于Tomcat),Dubbo協定(基于Netty)。
Consumer調用服務的時需要完成以下内容:
- 1.去注冊中心擷取服務資訊。
- 2.調用方法時需要提供以下資訊,我們把這四個必要的東西建構成一個對象(Invocation對象):
-
- 1.接口名
- 2.方法名
- 3.方法參數類型清單
- 4.方法值參數值清單
github 位址:
https://github.com/cr7258/dubbo-lab/tree/master/dubbo-simulateHelloService 服務
添加一個 HelloService 服務的接口,Consumer 在依賴中隻需要引用 HelloService 接口:
package com.chengzw.provider.api;
/**
* 服務的接口
* @author 程治玮
* @since 2021/3/30 10:10 下午
*/
public interface HelloService {
public String sayHello(String name);
}
Provider 需要實作該接口:
package com.chengzw.provider.impl;
import com.chengzw.provider.api.HelloService;
/**
* 服務實作類
* @author 程治玮
* @since 2021/3/30 10:11 下午
*/
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
}
RPC 遠端調用子產品
HTTP 和 Dubbo 兩個遠端調用協定都實作了 Protocol接口:
package com.chengzw.framework;
/**
* HTTP和Dubbo協定都實作該接口
* 政策模式
* @author 程治玮
* @since 2021/3/31 11:17 下午
*/
public interface Protocol {
void start(URL url);
String send(URL url,Invocation invocation);
}
Provider 和 Consumer 可以通過配置檔案來指定使用哪個協定來完成遠端調用(主要就是 Invocation 對象的序列号反序列化和方法的處理),而不需要将調用哪個協定寫死在代碼中。
package com.chengzw.framework;
import com.chengzw.framework.protocol.dubbo.DubboProtocol;
import com.chengzw.framework.protocol.http.HttpProtocol;
import org.springframework.beans.factory.annotation.Value;
/**
* 讀取配置檔案決定服務端和用戶端使用http還是dubbo協定
* 工廠模式,解決協定切換的問題
* @author 程治玮
* @since 2021/3/31 11:23 下午
*/
public class ProtocolFactory {
@Value("${protocol}")
private static String name;
public static Protocol getProtocol() {
if (name == null || name.equals("")) name = "http";
switch (name) {
case "http":
return new HttpProtocol();
case "dubbo":
return new DubboProtocol();
default:
break;
}
return new HttpProtocol();
}
}
服務注冊
使用 Zookeeper 來做服務的注冊中心,Provider 将服務(接口名)和 位址端口(URL)注冊到注冊中心中,Consumer 從注冊中心擷取服務的資訊:
package com.chengzw.framework.register;
import com.chengzw.framework.URL;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import java.util.HashMap;
import java.util.Map;
/**
* Zookeeper注冊中心寫入讀取服務端資訊
* @author 程治玮
* @since 2021/3/31 11:39 下午
*/
public class ZookeeperRegister {
static CuratorFramework client;
static Map<String, String> UrlCache = new HashMap<>();
static {
client = CuratorFrameworkFactory
.newClient("localhost:2181", new RetryNTimes(3, 1000));
client.start();
}
private static Map<String, String> REGISTER = new HashMap<>();
//Provider注冊服務
public static void regist(String interfaceName, String implClass, String url) {
try {
Stat stat = client.checkExists().forPath(String.format("/dubbo/service/%s", interfaceName));
if(stat != null){
client.delete().forPath(String.format("/dubbo/service/%s", interfaceName));
}
String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format("/dubbo/service/%s", interfaceName),(implClass + "::" + url).getBytes());
System.out.println("Provier服務注冊: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
//擷取Provider URL
public static URL getURL(String interfaceName) {
URL url = null;
String urlString = null;
//先查詢緩存
if (UrlCache.containsKey(interfaceName)) {
urlString = UrlCache.get(interfaceName);
} else {
try {
byte[] bytes = client.getData().forPath(String.format("/dubbo/service/%s", interfaceName));
urlString = new String(bytes);
} catch (Exception e) {
e.printStackTrace();
}
}
String host = urlString.split("::")[1].split(":")[0];
String port = urlString.split("::")[1].split(":")[1];
return new URL(host,Integer.parseInt(port));
}
//擷取Provider實作類
public static Class getImplClass(String interfaceName) throws Exception {
byte[] bytes = client.getData().forPath(String.format("/dubbo/service/%s", interfaceName));
String urlString = new String(bytes);
return Class.forName(urlString.split("::")[0]);
}
}