一、什麼是Doubbo
1.1、doubbo的引入
如何實現遠程調用(系統之間的相互調用)
- WebService:基於soa協議,效率不高;
- doubbo:使用rpc協議遠程調用,直接使用socket通信,傳輸效率高,並且可以統計出系統之間的調用關係,調用次數;
doubbo缺點:
由於doubbo是java開發的,所以只能是兩個java系統之間互相調用時才能使用doubbo作爲服務治理的中間件,即doubbo不能跨語言;
1.2、什麼是doubbo
doubo是阿里的開源項目;分佈式架構下,系統之間的調用非常混亂,doubbo就是用來治理服務的一個工具;即doubo就是資源調度和治理中心的管理工具;
1.3、doubbo架構
我們解釋一下這個架構圖:
- Provider:暴露服務的服務提供者;
- Consumer:調用遠程服務的服務消費者;
- Container:服務運行容器,一般使用Spring作爲容器;
- Registry:服務註冊與發現的註冊中心,官方推薦使用zookeeper,當然也可以是redis;
- Monitor:統計服務的調用次數和調用時間的監控中心,Monitor是可以沒有的;
調用關係說明:
- 服務容器負責啓動加載,運行服務提供者;
- 服務提供者在啓動時,想註冊中心註冊自己的服務;
- 註冊中心返回服務提供者提供的地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者;
- 服務消費者,從提供者地址列表裏,基於軟負載均衡算法,選一臺提供者進行調用。如果調用失敗,再選另一臺進行調用;
- 服務消費者與提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心;
二、doubbo的使用
Doubbo採用全Spring配置的方式,透明化接入應用,對應用沒有任何的API侵入,只需要用spring加載doubbo的配置即可,Doubbo基於Spring的Schema擴展進行加載;
單一工程的spring配置:
遠程服務:
在本地服務的基礎上,只需要做簡單的配置,即可完成遠程化;
將上面的xml文件配置財拆分,將服務定義部分放在提供方,將服務引用部分放在服務消費放,並在服務提供方配置增加暴露服務的配置dubbo:service,在服務消費放增加引用服務的配置doubbo:reference;
三、註冊中心
3.1、Zookeeper介紹
註冊中心負責服務地址的註冊於查找,相當於目錄服務,服務提供者與消費者只在啓動時與註冊中心交互,註冊中心不轉發請求,壓力較小;
zookeeper是Apache Hadoop下的一個子項目,是一個樹形的目錄服務,支持變更推送,適合做Doubbo的註冊中心;
Zookeeper主要是兩個功能:管理(存儲、讀取)數據和提供監聽(監聽哪個服務器掛了);zookeeper內部本身就是一個集羣(配置文件裏沒有主(leader)從(follower),但是自己內部機制會選出主與從),且內部的機器是奇數臺,只要有半數以上的機器存活,zookeeper就能提供服務;zookeeper配置文件只需要配置一個id和服務器即可;
3.2、linux下的Zookeeper集羣安裝
3.2.1、在linux下安裝jdk
安裝好jdk後,記得關閉防火牆;如果對linux不熟悉的同學,則具體操作參考:linux安裝jdk以及關閉防火牆
這裏配置了三臺linux來搭建zookeeper集羣,每臺機器裏的/etc/hosts文件都要配置其他機器地址與主機名對應,這樣做是爲了讓集羣裏的每個zookeeper找到其他zookeeper
# 集羣裏的每臺機器,/etc/hosts文件都要添加這個配置
192.168.75.129 maltose01
192.168.75.130 maltose02
192.168.75.131 maltose03
3.2.2、安裝zookeeper
在Apache官網下載zookeeper
zookeeper官網
打開官網後點擊下邊的鏈接
選擇要下載的版本
點擊下載即可
linux下安裝Zookeeper
上傳zookeeper壓縮包到linux下(我這裏上傳到了/usr/local目錄下),並解壓;解壓後進入conf目錄裏
,將zoo_sample.cfg名字改爲zoo.cfg
cp zoo_sample.cfg zoo.cfg
然後對這個文件進行編輯
vim zoo.cfg
zoo.cfg配置內容如下:
# 心跳週期默認2000毫秒;
tickTime=2000
# 數據目錄,需要修改爲自己的目錄;這裏改爲/root/zkdada;
dataDir=/tmp/zookeeper
# 客戶端訪問zookeeper的端口;
clientPort=2181
# 添加如下內容,配置集羣
# server.1=192.168.75.131:2888:3888(ip地址也可以寫成主機名,2888是leader與followe之間通信的端口,3888是投票時通信程序的端口);配置三個如下:
server.1=maltose01:2888:3888
server.2=maltose02:2888:3888
server.3=maltose03:2888:3888
注意,上邊的安裝過程,在三臺機器裏的操作都是一樣的;爲了方便,使用如下命令可以將第一臺機器上的配置直接傳到其他兩臺機器上去
# 將當前機器的zookeeper文件夾傳到其他機器的/usr/local目錄下
scp -r zookeeper/ [email protected]:/usr/local;
scp -r zookeeper/ [email protected]:/usr/local;
啓動zookeeper:
bin/zkServer.sh start
查看哪臺機器時leader,哪臺是folwer:
bin/zkServer.sh status
使用zookeeper自己的客戶端連接zookeeper服務端:
# 進入zookeeper安裝目錄的bin目錄執行命令
./zkCli.sh
可以看到該客戶端連接的是哪個zookeeper服務器(此時執行help命令可以看到創建節點,修改節點,獲取節點值等等的語法);
./zkCli.sh命令連接的都是本機器上的zookeeper,如果想要連上其他服務器上的zookeeper,可以執行如下命令:
connect maltose02:2181;
3.3、Zookeeper的詳細介紹
3.3.1、zookeeper節點
- 層次化的目錄結構,命名符合常規文件系統規範(見下圖)
- 每個節點在zookeeper中叫做znode,並且其有一個唯一的路徑標識(ls / 查看根節點)
- 節點Znode可以包含數據和子節點(但是EPHEMERAL類型的節點不能有子節點,後邊會詳細講解)
- 客戶端應用可以在節點上設置監視器(後續詳細講解)
3.3.2、znode節點類型
Znode有兩種類型
- 短暫型(ephemeral)(客戶端斷開連接後,該節點自己刪除)
建立短暫節點:
# 不加-e的話創建的就是持久節點
create -e /app-empheral 888
# 查看該節點信息
get /app-empheral
使用quit命令退出該客戶端後,再連上,發現找不到該節點了,這就是短暫型的意思;
在新建的節點下再新建節點且賦值123:
create /app-empheral/test01 123;
通過get來獲取數據:
get / app-empheral /test01
- 持久型(persistent)(客戶端斷開連接後,該節點不自己刪除,需要人爲刪除)
3.3.3、Znode四種形式的目錄節點
znode默認是persistent,上邊-e前加-s的話就是帶序號的節點
- PERSISTENT 持久的
- PERSISTENT_SEQUENTIAL(持久序列/test0000000019 )持久帶序號
- EPHEMERAL 暫時的
- EPHEMERAL_SEQUENTIAL 暫時帶序號的
eg:
create /test 888;
# 該文件會帶上序號;(-s前或後可以加-e)
create -s /test/aa 111
同一個目錄裏創建文件的話,文件的序號會依次增加,此時在其他目錄裏創建文件的話,序號又從0開始;
# 修改節點值:
set /test/aa 222;
# 當其他服務器的節點值發生改變時,使用如下命令會有提示
get /test/aa watch
刪除節點:
# 遞歸全部刪除
rmr /zk
# 只刪除一個節點,如果有子節點是刪除不了的
delete /zk
注意事項:
- 創建znode時設置順序標識,znode名稱後會附加一個值,順序號是一個單調遞增的計數器,由父節點維護
- 在分佈式系統中,順序號可以被用於爲所有的事件進行全局排序,這樣客戶端可以通過順序號推斷事件的順序;
- 臨時節點下創建永久節點會報錯;臨時節點不會有子節點;
3.3.4、zookeeper的java客戶端API
使用java實現增刪改查:
public class SimpleDemo {
// 會話超時時間,設置爲與系統默認時間一致
private static final int SESSION_TIMEOUT = 2000;
//連不上服務器端其中一個的話,會繼續連接及羣裏的下一個
private static final String connectString="maltose01:2181,maltose02:2181,maltose03:2181";
// 創建 ZooKeeper 實例
ZooKeeper zk;
// 創建 Watcher 實例(監聽器)
Watcher wh = new Watcher() {
public void process(org.apache.zookeeper.WatchedEvent event)
{
//收到事件通知後的回調函數(自己的事件處理邏輯)
System.out.println(event.toString());
}
};
// 初始化 ZooKeeper 實例
private void createZKInstance() throws IOException
{
//參數:1獲得連接 2會話超時 3監聽器
zk = new ZooKeeper(connectString , SimpleDemo.SESSION_TIMEOUT, this.wh);
}
private void ZKOperations() throws IOException, InterruptedException, KeeperException
{
System.out.println("/n1. 創建 ZooKeeper 節點 (znode : zoo2, 數據: myData2 ,權限: OPEN_ACL_UNSAFE ,節點類型(前邊學的四種類型): Persistent");
zk.create("/zoo2", "myData2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("/n2. 查看是否創建成功: ");
System.out.println(new String(zk.getData("/zoo2", false, null)));
System.out.println("/n3. 修改節點數據 ");
zk.setData("/zoo2", "shenlan211314".getBytes(), -1);
System.out.println("/n4. 查看是否修改成功: ");
System.out.println(new String(zk.getData("/zoo2", false, null)));
System.out.println("/n5. 刪除節點 ");
zk.delete("/zoo2", -1);
System.out.println("/n6. 查看節點是否被刪除: ");
System.out.println(" 節點狀態: [" + zk.exists("/zoo2", false) + "]");
}
//下邊的方法,當子節點改變時,會執行
Public void getChildren() throws Exception{
List<String> children=zk.getChildren(“/”,true);
For(String child:children){
System.out.print(child);
}
Thread.sleep(Long.Max_VALUE);
}
private void ZKClose() throws InterruptedException
{
zk.close();
}
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
SimpleDemo dm = new SimpleDemo();
dm.createZKInstance();
dm.ZKOperations();
dm.ZKClose();
}
}
3.3.5、實現分佈式應用及客戶端動態更新主節點狀態
- 某分佈式系統中,主節點可以有多臺,可以動態上下線
- 任意一臺客戶端都能實時感知到主節點服務器的上下線
上邊案例創建的是臨時節點,因爲服務器掛了的話,會發生一個事件去通知客戶端;
此時,客戶端的實現:
public class DistributedClient {
private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
private static final int sessionTimeout = 2000;
private static final String parentNode = "/servers";
// 注意:加volatile的意義何在?暫時理解爲多線程裏static修飾的共享數據
private volatile List<String> serverList;
private ZooKeeper zk = null;
/**
* 創建到zk的客戶端連接
*
* @throws Exception
*/
public void getConnect() throws Exception {
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知後的回調函數(應該是我們自己的事件處理邏輯)
try {
//服務器數據更改後,重新更新服務器列表,並且註冊了監聽
getServerList();
} catch (Exception e) {
}
}
});
}
/**
* 獲取服務器信息列表
*
* @throws Exception
*/
public void getServerList() throws Exception {
// 獲取服務器子節點信息,並且對父節點進行監聽(true)
List<String> children = zk.getChildren(parentNode, true);
// 先創建一個局部的list來存服務器信息
List<String> servers = new ArrayList<String>();
for (String child : children) {
// child只是子節點的節點名,參數二表示不監聽子節點(子節點改變時不通知客戶端),
byte[] data = zk.getData(parentNode + "/" + child, false, null);
servers.add(new String(data));
}
// 把servers賦值給成員變量serverList,已提供給各業務線程使用
serverList = servers;
//打印服務器列表
System.out.println(serverList);
}
/**
* 業務功能
*
* @throws InterruptedException
*/
public void handleBussiness() throws InterruptedException {
System.out.println("client start working.....");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 獲取zk連接
DistributedClient client = new DistributedClient();
client.getConnect();
// 獲取servers的子節點信息(並監聽),從中獲取服務器信息列表
client.getServerList();
// 業務線程啓動
client.handleBussiness();
}
}
服務端的實現:
package cn.maltose.zkdist;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
public class DistributedServer {
private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
private static final int sessionTimeout = 2000;
private static final String parentNode = "/servers";
private ZooKeeper zk = null;
/**
* 創建到zk的客戶端連接
*
* @throws Exception
*/
public void getConnect() throws Exception {
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知後的回調函數(應該是我們自己的事件處理邏輯)
System.out.println(event.getType() + "---" + event.getPath());
try {
zk.getChildren("/", true);
} catch (Exception e) {
}
}
});
}
/**
* 向zk集羣註冊服務器信息
*
* @param hostname
* @throws Exception
*/
public void registerServer(String hostname) throws Exception {
String create = zk.create(parentNode + "/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname + "is online.." + create);
}
/**
* 業務功能
*
* @throws InterruptedException
*/
public void handleBussiness(String hostname) throws InterruptedException {
System.out.println(hostname + "start working.....");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 獲取zk連接
DistributedServer server = new DistributedServer();
server.getConnect();
// 利用zk連接註冊服務器信息
server.registerServer(args[0]);
// 啓動業務功能
server.handleBussiness(args[0]);
}
}