“沒有最好的技術,隻有最合适的技術。”我想這句話也同樣适用于微服務領域,沒有最好的服務架構,隻有最适合自己的服務改造。在 Dubbo 的未來規劃中,除了保持自身技術上的領先性,關注性能、大流量、大規模叢集領域的挑戰外,圍繞 Dubbo 核心來發展生态,将 Dubbo 打造成一個服務化改造的整體方案也是重點之一。從本期開始,我們将推出“服務化改造”系列文章,通過在一些外圍系統和服務化基礎元件上的開發實踐,分享Dubbo 生态下的服務化改造收獲和總結。
» 8月26日,Aliware Open Source 将首次在成都舉辦 Apache Dubbo 的meetup活動,Dubbo 、Sentinel和 Nacos 的小哥哥和小姐姐都會在現場進行技術分享,歡迎成都的朋友報名參加我們的活動喔,搜尋“阿裡巴巴中間件”公衆号,背景發送“成都meetup”,擷取報名連結。
一、改造背景
在現代的分布式應用中,往往會出現節點和節點之間的協調問題,其中就包括了:選主,叢集管理,分布式鎖,分布式配置管理,統一命名服務,狀态同步等訴求。Apache ZooKeeper,正如它的名字所暗示的那樣,動物園管理者,就是為了解決這些訴求的一個分布式協調服務架構。
為了保證系統的高可用,ZooKeeper本身也可以部署成叢集模式,稱之為ZooKeeper Ensemble。ZooKeeper叢集中始終確定其中的一台為leader的角色,并通過ZAB(Zookeeper Atomic Broadcast Protocol)[1] 協定確定所有節點上的資訊的一緻。用戶端可以通路叢集中的任何一台進行讀寫操作,而不用擔心資料出現不一緻的現象。

O'Reilly的ebook-Zookeeper-Distributed Process Coordination
ZooKeeper中的資料存儲方式與傳統的UNIX檔案系統相似,節點按照樹狀結構來組織,其中,節點被稱之為znodes(ZooKeeper資料節點)

二、基本用法
可以通過直接下載下傳的方式[2] 安裝并運作ZooKeeper,在Mac上也可以通過Homebrew [3] brew install zookeeper 來安裝,考慮到通用性,本文采用docker的方式來運作ZooKeeper。如果沒有安裝docker,請先準備好docker環境 [4]。
1、啟動 ZooKeeper
執行指令将 ZooKeeper,運作在docker容器中。
docker run --rm --name zookeeper -p 2181:2181 zookeeper
2、進入 Zookeeper 容器
docker exec -it zookeeper bash
在bin 目錄下有啟動 ZooKeeper 的指令 zkServer 以及管理控制台 zkCli
bash-4.4# ls -l bintotal 36
-rwxr-xr-x 1 zookeepe zookeepe 232 Mar 27 04:32
README.txt
-rwxr-xr-x 1 zookeepe zookeepe 1937 Mar 27 04:32
zkCleanup.sh
-rwxr-xr-x 1 zookeepe zookeepe 1056 Mar 27 04:32
zkCli.cmd
-rwxr-xr-x 1 zookeepe zookeepe 1534 Mar 27 04:32
zkCli.sh
-rwxr-xr-x 1 zookeepe zookeepe 1759 Mar 27 04:32
zkEnv.cmd
-rwxr-xr-x 1 zookeepe zookeepe 2696 Mar 27 04:32
zkEnv.sh
-rwxr-xr-x 1 zookeepe zookeepe 1089 Mar 27 04:32
zkServer.cmd
-rwxr-xr-x 1 zookeepe zookeepe 6773 Mar 27 04:32
zkServer.sh``
3、通過zkCli進入Zookeeper管理界面
由于是通過Docker啟動,ZooKeeper 程序已經啟動,并通過2181端口對外提供服務。
bash-4.4# psPID USER TIME COMMAND
1 zookeepe 0:02 /usr/lib/jvm/java-1.8-openjdk/jre/bin/java -Dzookeeper.log.dir=. -Dzookeeper.root
32 root 0:00 bash
42 root 0:00 ps
是以可以直接通過zkCli來通路 ZooKeeper 的控制台來進行管理。
bash-4.4# bin/zkCli.sh -server 127.0.0.1:2181Connecting to 127.0.0.1:2181...
WATCHER::
WatchedEvent state:SyncConnected type:None path:null[zk: 127.0.0.1:2181(CONNECTED) 0] helpZooKeeper -server host:port cmd args stat path [watch]
set path data [version]
ls path [watch]
delquota [-n|-b] path
ls2 path [watch]
setAcl path acl
setquota -n|-b val path history
redo cmdno
printwatches on|off
delete path [version]
sync path
listquota path
rmr path
get path [watch]
create [-s] [-e] path data acl
addauth scheme auth
quit
getAcl path
close
connect host:port
4.zkCli上的一些基本操作
建立 /hello-zone 節點:
[zk: 127.0.0.1:2181(CONNECTED) 19] create /hello-zone 'world'Created /hello-zone
列出 / 下的子節點,确認 hello-zone 被建立:
[zk: 127.0.0.1:2181(CONNECTED) 19] create /hello-zone 'world'Created /hello-zone
列出 /hello-zone的子節點,确認為空:
[zk: 127.0.0.1:2181(CONNECTED) 21] ls /hello-zone[]
擷取存儲在 /hello-zone節點上的資料:
[zk: 127.0.0.1:2181(CONNECTED) 22] get /hello-zone
world
三、在 Dubbo 中使用ZooKeeper
Dubbo使用 ZooKeeper 用于服務的注冊發現和配置管理,在ZooKeeper中資料的組織由下圖所示:
首先,所有Dubbo相關的資料都組織在 /duboo 的根節點下。
二級目錄是服務名,如 com.foo.BarService。
三級目錄有兩個子節點,分别 providers 和 consumers,表示該服務的提供者和消費者。
四級目錄記錄了與該服務相關的每一個應用執行個體的URL資訊,在 providers 下的表示該服務的所有提供者,而在 consumers 下的表示該服務的所有消費者。舉例說明,com.foo.BarService 的服務提供者在啟動時将自己的URL資訊注冊到 /dubbo/com.foo.BarService/providers 下;同樣的,服務消費者将自己的資訊注冊到相應的 consumers 下,同時,服務消費者會訂閱其所對應的 providers 節點,以便能夠感覺到服務提供方位址清單的變化。
四、準備示例代碼
本文代碼可以在以下
連結中找到。
1、接口定義
一個定義簡單的 GreetingService 接口,隻有裡面一個簡單的方法 sayHello 向調用者問好。
public interface GreetingService {
String sayHello(String name);}
2、服務端:服務實作
實作 GreetingService接口,并通過 @Service 來标注其為Dubbo的一個服務。
@Servicepublic class AnnotatedGreetingService implements GreetingService {
public String sayHello(String name) {
return "hello, " + name;
}}
3、服務端:組裝
定義 ProviderConfiguration 來組裝Dubbo服務。
@Configuration@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.impl")@PropertySource("classpath:/spring/dubbo-provider.properties")static class ProviderConfiguration {}
dubbo-provider.properties是在Spring應用中外置配置的方式,内容如下:
dubbo.application.name=demo-provider
dubbo.registry.address=zookeeper://$DOCKER_HOST:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
由于ZooKeeper運作在Docker容器中,需要注意的是:
- 本文假定Dubbo應用運作在主控端上,也就是Docker容器外,需要将ZooKeeper的位址替換成環境變量${DOCKER_HOST}所指定的IP位址,相關資訊請查閱Docker官方文檔;
- 如果Dubbo應用也是Docker化的應用,隻需要用ZooKeeper的容器名,在本文中容器名是ZooKeeper;
- 當然,如果不用容器方式啟動ZooKeeper,隻需要簡單的将這裡的$ DOCKER_HOST換成localhost即可。
4、服務端:啟動服務
在 main 方法中通過啟動一個Spring Context來對外提供Dubbo服務。
public class ProviderBootstrap {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}}
服務啟動端的的 main 方法,将會看到下面的輸出,代表服務端啟動成功,并在注冊中心(ZooKeeperRegistry)注冊上了 GreetingService 這個服務:
[03/08/18 10:50:33:033 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Register: dubbo://192.168.99.1:20880/com.alibaba.dubbo.samples.api.GreetingService?anyhost=true&application=demo-provider&dubbo=2.6.2&generic=false&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=12938&side=provider×tamp=1533264631849, dubbo version: 2.6.2, current host: 192.168.99.1
通過ZooKeeper管理終端觀察服務提供方的注冊資訊:
$ docker exec -it zookeeper bash
bash-4.4# bin/zkCli.sh -server localhost:218
Connecting to localhost:2181
...
Welcome to ZooKeeper!
JLine support is enabled
...
[zk: localhost:2181(CONNECTED) 0] ls
[dubbo%3A%2F%2F192.168.99.1%3A20880%2Fcom.alibaba.dubbo.samples.api.GreetingService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.samples.api.GreetingService%26methods%3DsayHello%26pid%3D12938%26side%3Dprovider%26timestamp%3D1533264631849]
可以看到剛剛啟動的Dubbo的服務在providers 節點下注冊了自己的URL位址:dubbo://192.168.99.1:20880 /com.alibaba.dubbo.samples.api.GreetingService?anyhost = true&application = demo-provider&dubbo =2.6 0.2&通用=假接口=com.alibaba.dubbo.samples.api.GreetingService&方法= sayHello的&PID = 12938&側=提供商時間戳=1533264631849
5、用戶端:引用服務
通過 @Reference 來在用戶端聲明服務的引用,運作時将會通過該引用發起全程調用,而服務的目标位址将會從ZooKeeper的provider 節點下查詢。
@Component("annotatedConsumer")public class GreetingServiceConsumer {
@Reference
private GreetingService greetingService;
public String doSayHello(String name) {
return greetingService.sayHello(name);
}}
6、用戶端:組裝
定義ConsumerConfiguration來組裝Dubbo服務。
@Configuration@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.action")@PropertySource("classpath:/spring/dubbo-consumer.properties")@ComponentScan(value = {"com.alibaba.dubbo.samples.action"})static class ConsumerConfiguration {}
dubbo-consumer.properties是在Spring應用中外置配置的方式,内容如下:
dubbo.application.name=demo-consumer
dubbo.registry.address=zookeeper://$DOCKER_HOST:2181
dubbo.consumer.timeout=3000
與服務端:組裝相同,需要根據自己的運作環境來修改dubbo.registry.address中定義的$ DOCKER_HOST。請參閱步驟3的說明部分。
7、用戶端:發起遠端調用
運作 main 向已經啟動的服務提供方發起一次遠端調用。Dubbo會先向ZooKeeper訂閱服務位址,然後從傳回的位址清單中選取一個,向對端發起調用:
public class ConsumerBootstrap {
public static void main(String[] args) {public class ConsumerBootstrap {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
GreetingServiceConsumer greetingServiceConsumer = context.getBean(GreetingServiceConsumer.class);
String hello = greetingServiceConsumer.doSayHello("zookeeper");
System.out.println("result: " + hello);
System.in.read();
}
}
運作結果如下:
[03/08/18 01:42:31:031 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Register: consumer://192.168.99.1/com.alibaba.dubbo.samples.api.GreetingService?application=demo-consumer&category=consumers&check=false&default.timeout=3000&dubbo=2.6.2&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=82406&side=consumer×tamp=1533274951195, dubbo version: 2.6.2, current host: 192.168.99.1 #1[03/08/18 01:42:31:031 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Subscribe: consumer://192.168.99.1/com.alibaba.dubbo.samples.api.GreetingService?application=demo-consumer&category=providers,configurators,routers&default.timeout=3000&dubbo=2.6.2&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=82406&side=consumer×tamp=1533274951195, dubbo version: 2.6.2, current host: 192.168.99.1 #2...
result: hello, zookeeper
說明:
- 注冊:消費者://192.168.99.1/...& category= consumers&:消費者向ZooKeeper注冊自己的資訊,并放在 consumers 節點下
- 訂閱:消費者://192.168.99.1/...& 類别=提供商,配置器,路由器:消費者同時向動物園管理者訂閱了providers、configurators、routers 節點,其中 configurations 與多寶配置相關,routers 與路由規則相關,值得注意的英文 providers 節點的訂閱,當有新的服務提供方加入後,由于訂閱的關系,新的位址清單會推送給訂閱方,服務的消費者也是以動态感覺到了位址清單的變化。
$ docker exec -it zookeeper bash
bash-4.4# bin/zkCli.sh -server localhost:218
Connecting to localhost:2181
...
Welcome to ZooKeeper!
JLine support is enabled
...
[zk: localhost:2181(CONNECTED) 4] ls /dubbo/com.alibaba.dubbo.samples.api.GreetingService/consumers[consumer%3A%2F%2F192.168.99.1%2Fcom.alibaba.dubbo.samples.api.GreetingService%3Fapplication%3Ddemo-consumer%26category%3Dconsumers%26check%3Dfalse%26default.timeout%3D3000%26dubbo%3D2.6.2%26interface%3Dcom.alibaba.dubbo.samples.api.GreetingService%26methods%3DsayHello%26pid%3D82406%26side%3Dconsumer%26timestamp%3D1533274951195]
可以看到Dubbo的服務消費者在 consumers 節點下注冊了自己的URL位址:
consumer://192.168.99.1/com.alibaba.dubbo.samples.api.GreetingService?application=demo-consumer&category=providers,configurators,routers&default.timeout=3000&dubbo=2.6.2&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=82406&side=consumer×tamp=1533274951195
五、總結
本文側重介紹了如何在Dubbo應用中使用Zookeeper做為注冊中心,當然,本文也提到了Zookeeper在Dubbo的應用場景下還承擔了配置中心和服務治理的職責。本文中的Zookeeper是單節點,Standalone的模式,在生産環境中為了高可用的訴求,往往會元件Zookeeper叢集,也就是Zookeeper ensemble模式。
通過本文的學習,讀者可以掌握到:
- ZooKeeper的基本概念和基本用法
- ZooKeeper在Dubbo應用中的作用
- 通過實戰了解ZooKeeper與Dubbo的互動
- Dubbo在ZooKeeper中服務注冊,消費資訊的存儲方式
當然,自從阿裡巴巴開源 Nacos 後,Dubbo生态中又多了一項動态服務發現的選項,Nacos + Dubbo的組合正進一步釋放 Dubbo 在雲原生及ServiceMesh時代中,在大規模微服務治理、流量治理、服務內建與服務共享等服務平台能力建設上的威力。-
參考連結:
[1]
https://www.ixiacom.com/company/blog/apache-zab-zookeeper-atomic-broadcast-protocol[2]
https://www.apache.org/dyn/closer.cgi/zookeeper/[3] https: //brew.sh
[4]
https://www.docker.com/community-edition