高效能Java RPC框架Dubbo與zookeeper的使用
一. 什麼是RPC
1. RPC 協議(Remote Procedure Call Protocol)
遠端過程呼叫協議,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC 協議假定某些傳輸協議的存在,如 TCP 或 UDP,為通訊程式之間攜帶資訊資料。在 OSI 網路通訊模型中,RPC 跨越了傳輸層和應用層。RPC 使得開發包括網路分散式程式在內的應用程式更加容易。 RPC 採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。首先,客戶機呼叫程序傳送一個有程序引數的呼叫資訊到服務程序,然後等待應答資訊。在伺服器端,程序保持睡眠狀態直到呼叫資訊到達為止。當一個呼叫資訊到達,伺服器獲得程序引數,計算結果,傳送答覆資訊,然後等待下一個呼叫資訊,最後,客戶端呼叫程序接收答覆資訊,獲得程序結果,然後呼叫執行繼續進行。
2. RPC 框架
在單機時代一臺電腦執行多個程序,程序之間無法通訊,顯然這會浪費很多資源,因此後來出現 IPC(Inter-process communication: 單機中執行的程序之間的相互通訊 ),這樣就能允許程序之間進行通訊,比如在一臺計算機中的 A 程序寫了一個吃飯的方法,那在以前如果在 B 程序中也要有一個吃飯的方法,必須要在 B 程序中進行建立,但有了 RPC 後 B 只需要呼叫 A 程序的程式即可完成,再到後來網路時代的出現, 大家電腦都連起來,這時可不可以呼叫其他電腦上的程序呢,當然可以,這樣 RPC 框架就出現了。 嚴格意義上來講:Unix的生態系統中 RPC 可以在同一臺電腦上不同程序進行,也可以在不同電腦上進行;而在windows 裡面同一臺電腦上不同程序間的通訊還可以採用 LPC(本地訪問)。綜上:RPC 或 LPC是上層建築,IPC 是底層基礎。 RPC 框架有很多:比如 Thrift、dubbo、grpc 等。
3. RPC 與 HTTP 、TCP 、UDP 、Socket 的區別
TCP/UDP: 都是傳輸協議,主要區別是 tcp 協議連線需要 3 次握手,斷開需要四次揮手,是通過流來傳輸的,就是確定連線後,一直髮送資訊,傳完後斷開。udp 不需要進行連線,直接把資訊封裝成多個報文,直接傳送。所以 udp 的速度更快寫,但是不保證資料的完整性。
HTTP: 超文字傳輸協議是一種應用層協議,建立在 TCP 協議之上
Socket: 是在應用程式層面上對 TCP/IP 協議的封裝和應用。其實是一個呼叫介面,方便程式設計師使用 TCP/IP 協議棧而已。程式設計師通過 socket 來使用 tcp/ip 協議。但是 socket 並不是一定要使用 tcp/ip 協議,Socket 程式設計介面在設計的時候,就希望也能適應其他的網路協議。
小結:
4. RPC 的執行流程
首先, 要解決通訊的問題,主要是通過在客戶端和伺服器之間建立 TCP 連線,遠端過程呼叫的所有交換的資料都在這個連線裡傳輸。連線可以是按需連線,呼叫結束後就斷掉,也可以是長連線,多個遠端過程呼叫共享同一個連線。 第二, 要解決定址的問題,也就是說,A 伺服器上的應用怎麼告訴底層的 RPC 框架,如何連線到 B 伺服器(如主機或 IP 地址)以及特定的埠,方法的名稱名稱是什麼,這樣才能完成呼叫。比如基於Web服務協議棧的RPC,就要提供一個endpoint URI,或者是從UDDI(一種目錄服務,通過該目錄服務進行服務註冊與搜尋)服務上查詢。如果是 RMI 呼叫的話,還需要一個 RMI Registry 來註冊服務的地址。 第三, 當 A 伺服器上的應用發起遠端過程呼叫時,方法的引數需要通過底層的網路協議如 TCP 傳遞到 B 伺服器,由於網路協議是基於二進位制的,記憶體中的引數的值要序列化成二進位制的形式,也就是序列化(Serialize)或編組(marshal),通過定址和傳輸將序列化的二進位制傳送給 B 伺服器。 第四, B 伺服器收到請求後,需要對引數進行反序列化(序列化的逆操作),恢復為記憶體中的表達方式,然後找到對應的方法(定址的一部分)進行本地呼叫,然後得到返回值。 第五, 返回值還要傳送回伺服器 A 上的應用,也要經過序列化的方式傳送,伺服器 A 接到後,再反序列化,恢復為記憶體中的表達方式,交給 A 伺服器上的應用 JAVAEE 裡面的 stub 是為遮蔽客戶呼叫遠端主機上的物件,必須提供某種方式來模擬本地物件,這種本地物件稱為存根(stub), 存根負責接收本地方法呼叫,並將它們委派給各自的具體實現物件 Skeleton:伺服器的骨架
5. RPC 基於RMI的簡單實現
5-1 首先建一個多模組的maven專案, 結構如下:
5-2 rpc_rmi_api模組實現, 就是提供呼叫的介面, 結構圖如下:
User類:(必須實現序列化介面)
public class User implements Serializable {
private static final long serialVersionUID = 6290816946970912418L;
private Integer id;
private String userName;
private String userPwd;
public User(Integer id, String userName, String userPwd) {
this.id = id;
this.userName = userName;
this.userPwd = userPwd;
}
public User(){
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", userPwd='" + userPwd + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
}
UserService介面:(相關規定看註釋)
public interface UserService extends Remote { //規定繼承Remote父類
public User queryUserById(Integer userId) throws RemoteException; //規定丟擲異常
}
5-3 rpc_rmi_provider的模組實現,結構圖如下:(主要提供服務)
UserServiceImp類實現: (要繼承UnicastRemoteObject 類,規定的)
public class UserServiceImp extends UnicastRemoteObject implements UserService {
private Map<Integer, User> users = new HashMap<Integer, User>();
public UserServiceImp() throws RemoteException{
users.put(1,new User(1,"admin","123456"));
users.put(2,new User(2,"shsxt","123456"));
}
@Override
public User queryUserById(Integer userId) throws RemoteException {
return users.get(userId);
}
}
Publisher類的實現:(釋出服務端)
public class Publisher {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
//指定服務埠
LocateRegistry.createRegistry(8989);
//釋出服務
Naming.bind("rmi://192.168.1.158:8989/userService", new UserServiceImp());
System.out.println("成功釋出服務!!!");
}
}
5-4 rpc_rmi_consumer結構圖如下:(客戶端的呼叫)
App 類的實現:(呼叫服務端)
public class App {
public static void main( String[] args ) throws RemoteException, NotBoundException, MalformedURLException {
UserService userService = (UserService) Naming.lookup("rmi://192.168.1.158:8989/userService");
System.out.println(userService.queryUserById(2));
}
}
結果:
二. Dubbo框架
1. 框架基本概念
A high performance Java RPC framework ----一個高效的遠端呼叫框架 Dubbo 是由阿里巴巴開源的一個高效能、基於 Java 開源的遠端呼叫框架。正如在許多RPC 系統中一樣,dubbo 是基於定義服務的概念,指定可以通過引數和返回型別遠端呼叫的方法。在伺服器端,伺服器實現這個介面,並執行一個 dubbo 伺服器來處理客戶端呼叫。在客戶端,客戶機有一個存根,它提供與伺服器相同的方法。 dubbo的架構圖: Dubbo 提供三個核心功能:基於介面的遠端呼叫、容錯和負載均衡,以及服務的自動注 冊與發現。Dubbo 框架廣泛的在阿里巴巴內部使用,以及京東、噹噹、去哪兒、考拉等都在 使用。
2. Dubbo快速入門
這裡也是從實戰出發,搭建一個簡單的遠端呼叫小例項, 首先也是要建立一個maven的多模組專案, 結果圖如下: 下面來一個模組一個模組的列出來:
2-1 dubbo_api 模組的結構圖如下:(也是提供方法呼叫介面)
大家不用像我建的那麼深,我的錯誤; 裡面的User與UserService與上面的rmi一樣;
2-2 dubbo_provider的結構圖如下:
UserServiceImp類的實現:(與上面不一樣的是不用繼承那個鬼類啦!)
@Service
public class UserServiceImp implements UserService {
private Map<Integer, User> users = new HashMap<>();
public UserServiceImp(){
users.put(1,new User(1,"admin","123456"));
}
@Override
public User queryUserById(Integer userId) {
return users.get(userId);
}
}
Publisher類的實現,與上面完全不同:
public class Publisher {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
System.out.println("服務釋出成功!!!");
System.in.read(); //按任意鍵退出
}
}
配置檔案provider.xml的實現
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.shsxt.provider"/>
<!--
dubbo 環境配置
-->
<!--應用名稱-->
<dubbo:application name="dubbo_provider"></dubbo:application>
<!--註冊中心配置:廣播地址-->
<dubbo:registry address="multicast://224.5.6.7:1234"></dubbo:registry>
<!--指定公佈的服務使用的協議與埠號-->
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
<!--配置公佈的服務-->
<dubbo:service interface="com.shsxt.api.service.UserService" ref="userServiceImp"></dubbo:service>
</beans>
2-3 dubbo_consumer 模組買的實現, 結構圖如下:
UserController類的實現
@Controller
public class UserController {
@Resource
private UserService userService;
public User queryUserById(Integer userId){
return userService.queryUserById(userId);
}
}
App類的實現
public class App {
public static void main( String[] args ) {
ApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
UserController userController = (UserController) context.getBean("userController");
System.out.println(userController.queryUserById(1));
}
}
配置檔案consumer.xml的程式碼(注意與provider.xml比較一下區別)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.shsxt.consumer"/>
<!--
dubbo 環境配置
-->
<!--配置應用名稱-->
<dubbo:application name="dubbo_consumer"></dubbo:application>
<!--指定註冊中心-->
<dubbo:registry address="multicast://224.5.6.7:1234"></dubbo:registry>
<!--配置訂閱的服務列表-->
<dubbo:reference id="userService" interface="com.shsxt.api.service.UserService"></dubbo:reference>
</beans>
2-4 最後在主專案的pom.xml中加入下面依賴:(主專案中有單元測試,其他三個子模組的單元測試可以刪除掉)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--引入dubbo依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.6</version>
</dependency>
</dependencies>
三. 幾種重要配置的介紹
1. dubbo:application
標籤 目的 介紹
dubbo:service 服務出口 用於匯出服務,定義服務元資料,使用多個協議匯出服務,向多個登錄檔註冊服務
dubbo:reference 服務參考 用於建立遠端代理,訂閱多個登錄檔
dubbo:protocol 協議配置 在供應商方面配置服務協議,消費者方面如下。
dubbo:application 應用程式配置 適用於提供者和消費者。
dubbo:module 模組配置 可選的。
dubbo:registry 註冊中心 登錄檔資訊:地址,協議等
dubbo:monitor 監控中心 監控資訊:地址,地址等。可選。
dubbo:provider 提供商的預設配置 ServiceConfigs的預設配置。可選的。
dubbo:consumer 消費者的預設配置 ReferenceConfigs的預設配置。可選的。
dubbo:method 方法級別配置 ServiceConfig和ReferenceConfig的方法級別配置。
dubbo:argument 引數配置 用於指定方法引數配置。
2. dubbo:registry
註冊中⼼配置。對應的配置類: com.alibaba.dubbo.config.RegistryConfig。同時如果有多個不 同的註冊 中⼼, 可以聲 明多個 dubbo:registry 標籤 ,並在 dubbo:service 或dubbo:reference 的 registry 屬性指定使⽤的註冊⼼。
3. dubbo:protocol
服務提供者協議配置。對應的配置類:com.alibaba.dubbo.config.ProtocolConfig,同時, 如果需要⽀持多協議, 可以宣告多個 dubbo:protocol 標籤, 並在 dubbo:service 中通過protocol 屬性指定使⽤的協議
4. dubbo:service
服務提供者暴露服務配置。對應的配置類:com.alibaba.dubbo.config.ServiceConfig
5. dubbo:reference
服務消費者引⽤服務配置。對應的配置類: com.alibaba.dubbo.config.ReferenceConfig
四. zookeeper安裝與註冊中心配置
上面為zookeeper的主要工作結構圖
流程說明:
- 服務提供者啟動時: 向/dubbo/com.foo.BarService/providers 目錄下寫入自己的 URL 地址
- 服務消費者啟動時: 訂閱 /dubbo/com.foo.BarService/providers 目錄下的提供者 URL 地址。並向 /dubbo/com.foo.BarService/consumers 目錄下寫入自己的URL 地址
- 監控中心啟動時: 訂閱 /dubbo/com.foo.BarService 目錄下的所有提供者和消費者 URL 地址。
支援以下功能:
- 當提供者出現斷電等異常停機時,註冊中心能自動刪除提供者資訊
- 當註冊中心重啟時,能自動恢復註冊資料,以及訂閱請求
- 當會話過期時,能自動恢復註冊資料,以及訂閱請求
- 當設定 <dubbo:registry check=“false” /> 時,記錄失敗註冊和訂閱請求,後臺定時重試
- 可 通 過 <dubbo:registry username=“admin” password=“1234” /> 設 置zookeeper 登入資訊
- 可通過 <dubbo:registry group=“dubbo” /> 設定 zookeeper 的根節點,不設定將使用無根樹
- 支援 * 號萬用字元 <dubbo:reference group="" version="" /> ,可訂閱服務的所有分組和所有版本的提供者
1. zookeeper的下載及安裝(linux環境)
為啥是linux呢? 簡單+不卡啊!
1-1 下載
在你認為合適的位置建一個資料夾: mkdir + 資料夾名
進入該目錄進行下載操作: wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz
1-2 解壓
tar -zvxf + 你要解壓的壓縮包
解壓完成後的目錄結構
1-3 啟動前的熱身運動
如果你直接去bin目錄啟動zookeeper伺服器的話會報錯, 錯誤為:不能在conf中找到zoo.cfg這個配置檔案, 因為一開始conf裡面只有一個zoo_sample_cfg , 所以我們先在conf目錄下賦值一份, 名為zoo.cfg
cp zoo_sample_cfg zoo.cfg
用vim命令開啟zoo.cfg檔案, vim zoo.cfg 內容如下:
每個配置對應的解釋詳細也能看懂,就不介紹了,可以不用改就可以跑去bin目錄下啟動啦!
1-4 正式啟動(bin目錄下)
./zkServer.sh start (注意是要加start的) 啟動服務端
./zkCli.sh 為啟動客戶端
2. 對應的java配置
2-1 在 provider和consumer 中增加 zookeeper 客戶端 jar 包依賴:
<!--zookeeper配置-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.3.3</version>
</dependency>
<!--zkclient客戶端-->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
上面使用了zkclient客戶端連線,除了這個還可以使用curator客戶端連線, 相應的依賴為:
<dependency>
<groupId>com.netflix.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>1.1.10</version>
</dependency>
2-2 Zookeeper 單機配置
就是改對應的provider與consumer模組的xml配置即可
<dubbo:registry address="zookeeper://10.20.153.10:2181" />
<dubbo:registry protocol="zookeeper" address="10.20.153.10:2181" />
2-3 Zookeeper 叢集配置
不同的ip用逗號隔開
<dubbo:registry address="zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181" />
<dubbo:registry protocol="zookeeper" address="10.20.153.10:2181,10.20.153.11:2181,10.20.153.12:2181" />
2-4 同一 Zookeeper分成多組註冊中心
<dubbo:registry id="chinaRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="china" />
<dubbo:registry id="intlRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="intl" />
我們上面的為單機版的就行,然後啟動java裡面的服務端與客戶程式的程式碼就行了, 因為zookeeper管理dubbo為樹形結構,使用可以檢視註冊中心的服務情況, 如下用ls 命令檢視就行, 檢視根節點為:
ls /dubbo (記得前面加/) 想檢視哪一級的進哪一級檢視就行