本文將介紹的是我們自研的RPC框架Dapeng-soa(https://github.com/dapeng-soa/dapeng-soa),使用 etcd 作為新的注冊中心的一種方案
Dapeng注冊中心節(jié)點
基本根節(jié)點
/soa/runtime/services
/soa/config/services
/soa/config/routes
節(jié)點目錄圖
├── soa
│ ├── runtime
│ │ │ ├── services
│ │ │ │ └── com.today.service.goods.GoodsService
│ │ │ │ └── 192.168.10.121:9071
└── 192.168.10.121:9072
└── 192.168.10.121:9073
└── 192.168.10.121:9074
│ ├── config
│ │ │ ├── services
│ │ │ │ └── com.today.service.goods.GoodsService
│ │ │ │ └──
│ │ │ ├── routes
│ │ │ │ └── com.today.service.goods.GoodsService
- 1.除了帶有IP,端口信息的節(jié)點為臨時節(jié)點(相對于zk),其他均為永久性節(jié)點
- 2.zk類似與文件系統(tǒng)庵芭,父節(jié)點,子節(jié)點的模式闹蒜。
192.168.10.121:9071
在zk中為一個臨時子節(jié)點 - 3.
etcd
是以key-value
形式進(jìn)行存儲的,/soa/runtime/services/
和/soa/runtime/services/com.UserService
對應(yīng)etcd中的兩個key,他們之間沒有什么聯(lián)系抑淫,只有在查詢key時绷落,指定--prefix
時,會根據(jù)前綴進(jìn)行搜尋key
etcd租約節(jié)點 實現(xiàn)類似zookeeper的臨時節(jié)點方案
- 1.etcd使用租約的方式,對創(chuàng)建的
key
設(shè)置超時時間,當(dāng)超時時,該節(jié)點就會被刪除始苇。 - 2.我們可以為此節(jié)點續(xù)租,當(dāng)節(jié)點即將超時時砌烁,就進(jìn)行續(xù)租。這樣就可以達(dá)到類似于zookeeper臨時節(jié)點的作用。
- 3.當(dāng)客戶端由于什么原因掛掉以后,etcd上的節(jié)點由于沒有被繼續(xù)續(xù)租函喉,很快就會到期被刪除避归。
Java客戶端實現(xiàn)租約與續(xù)租的代碼邏輯
public Main(String registryAddress, String host) {
Client client = Client.builder().endpoints(registryAddress).build();
this.lease = client.getLeaseClient();
this.kv = client.getKVClient();
try {
this.leaseId = lease.grant(5).get().getID();
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
keepAlive();
try {
int port = 1000;
register("com.DemoService", host, port + 50);
logger.info("provider-agent provider register to etcd at port {}", port + 50);
} catch (Exception e) {
logger.error(e.getMessage,e);
}
}
/**
* 向 ETCD 中注冊服務(wù)
*/
public void register(String serviceName, String host, int port) throws Exception {
String strKey = MessageFormat.format("/{0}/{1}/{2}:{3}", rootPath, serviceName, host, String.valueOf(port));
ByteSequence key = ByteSequence.fromString(strKey);
String weight = "50";
ByteSequence val = ByteSequence.fromString(weight);
kv.put(key, val, PutOption.newBuilder().withLeaseId(leaseId).withPrevKV().build()).get();
kv.txn();
logger.info("Register a new service at:{},weight:{}", strKey, weight);
}
/**
* 發(fā)送心跳到ETCD,表明該host是活著的
*/
public void keepAlive() {
Executors.newSingleThreadExecutor().submit(
() -> {
try {
Lease.KeepAliveListener listener = lease.keepAlive(leaseId);
listener.listen();
logger.info("KeepAlive lease:" + leaseId + "; Hex format:" + Long.toHexString(leaseId));
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
);
}
- 1.通過
this.leaseId = lease.grant(5).get().getID();
注冊租約,然后再注冊服務(wù)到etcd上時,使用該租約. - 2.
keepAlive()
方法保持客戶端與etcd的聯(lián)系管呵,并對租約進(jìn)行續(xù)租梳毙,一旦將要超時時,馬上就行續(xù)租捐下。 - 3.當(dāng)客戶端掉線或者關(guān)閉后账锹,
keepAlive
將不會繼續(xù)續(xù)租,5s后坷襟,租約到期奸柬,節(jié)點就會被刪除.
etcd
有序節(jié)點與zookeepr
有序節(jié)點
- 1.zookeeper臨時節(jié)點會在節(jié)點最后生成一個序列串,在相同父節(jié)點下每次創(chuàng)建子節(jié)點時啤握,節(jié)點最后的序列串會有序遞增
- 2.
etcd
創(chuàng)建的每一個節(jié)點都會帶有如下幾個信息:
message KeyValue {
bytes key = 1;
int64 create_revision = 2;
int64 mod_revision = 3;
int64 version = 4;
bytes value = 5;
int64 lease = 6;
}
1.etcd服務(wù)段有一個機(jī)制鸟缕,他會對每一次請求創(chuàng)建的任何key提供create_revision和mod_revision的遞增,遞增時全局性的排抬,任何key的操作都會在全局的舉出上面進(jìn)行遞增。
2.那么在服務(wù)節(jié)點下授段,我們每一次創(chuàng)建一個key時,Create_Revision都會在之前最后創(chuàng)建的節(jié)點的基礎(chǔ)上增加1蹲蒲。
3.每一次修改一個key的內(nèi)容時,
mod_Revision
就會在全局計數(shù)器上增加一次。
結(jié)論侵贵,etcd的key支持天然的有序性届搁。可以根據(jù)這兩個屬性來判斷key創(chuàng)建的先后順序窍育。
etcd watch機(jī)制
- 1.
zookeeper
的watch
機(jī)制時一次性的卡睦,當(dāng)watch
的節(jié)點發(fā)生變更后,會通知客戶端漱抓,同時watch失效 - 2.
etcd
的watch
機(jī)制時一直生效的表锻,watch
一次,一直可以得到watch
的節(jié)點的變更信息乞娄。但是瞬逊,etcd的watch時阻塞模式的,watch某個節(jié)點后仪或,就會阻塞等待回應(yīng)确镊。
public static void etcdWatch(Watch watch, String key, Boolean usePrefix, WatchCallback callback) {
executorService.execute(() -> {
try {
Watch.Watcher watcher;
if (usePrefix) {
watcher = watch.watch(ByteSequence.fromString(key), WatchOption.newBuilder().withPrefix(ByteSequence.fromString(key)).build());
} else {
watcher = watch.watch(ByteSequence.fromString(key));
}
List<WatchEvent> events = watcher.listen().getEvents();
callback.callback(events);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
});
}
- 1.使用異步線程對key進(jìn)行watch,注意范删,我們?nèi)绻獁atch蕾域,一個服務(wù)節(jié)點下的多個實例的變更,需要在watch時指定前綴到旦,即watch當(dāng)前key為一個前綴旨巷,這樣后面的所有實例key的變化都能夠watch到廓块。
- 2.當(dāng)watch事件觸發(fā)后,回調(diào)
watchCallBack
方法進(jìn)行相應(yīng)的處理契沫,這個內(nèi)容需要我們自己實現(xiàn)带猴。
etcd 獲取節(jié)點和節(jié)點內(nèi)容
- 1.etcd為key-value形式存儲數(shù)據(jù),所以獲取key的數(shù)據(jù)非常簡單
private String getEtcdValue(String path, Boolean usePrefix) {
try {
KV kv = client.getKVClient();
ByteSequence seqKey = ByteSequence.fromString(path);
GetResponse response = kv.get(seqKey).get();
String key = response.getKvs().get(0).getKey().toStringUtf8();
String value = response.getKvs().get(0).getValue().toStringUtf8();
logger.info("Get data from etcdServer, key:{}, value:{}", key, value);
return value;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
Etcd Key-Value Api
鍵值A(chǔ)PI操作存儲在etcd中的鍵值對
對etcd
來說,鍵值對(key-value pair
)是最小可操作單元,每個鍵值對都有許多字段,以protobuf
格式定義懈万。
message KeyValue {
bytes key = 1;
int64 create_revision = 2;
int64 mod_revision = 3;
int64 version = 4;
bytes value = 5;
int64 lease = 6;
}
- key: 以字節(jié)為單位的鍵拴清。不允許使用空密鑰。
- value: 以字節(jié)為單位的值会通。
- version: 版本是密鑰的版本口予。刪除將版本重置為零,任何對密鑰的修改都會增加版本號
- Create_Revision: 修改鍵上的最后一個創(chuàng)建.
- Mod_Revision: 修改最后修改的密鑰.
- Lease(租約): 附在鑰匙上的租約的ID涕侈。如果租約是0沪停,則沒有租約附在鑰匙上。
除了鍵和值之外裳涛,etcd
還將附加的修訂元數(shù)據(jù)作為鍵消息的一部分木张。該修訂信息按創(chuàng)建和修改的時間對鍵進(jìn)行排序,這對于管理分布式同步的并發(fā)性非常有用端三。etcd客戶端的分布式共享鎖使用創(chuàng)建修改來等待鎖定所有權(quán)舷礼。類似地,修改修訂用于檢測軟件事務(wù)內(nèi)存讀集沖突并等待領(lǐng)導(dǎo)人選舉更新郊闯。
Etcd vs Zookeeper
相較之下妻献,Zookeeper有如下缺點。
- 1.復(fù)雜团赁。Zookeeper的部署維護(hù)復(fù)雜育拨,管理員需要掌握一系列的知識和技能;而Paxos強一致性算法也是素來以復(fù)雜難懂而聞名于世欢摄;另外熬丧,Zookeeper的使用也比較復(fù)雜,需要安裝客戶端剧浸,官方只提供了java和C兩種語言的接口锹引。
- 2.Java編寫。這里不是對Java有偏見唆香,而是Java本身就偏向于重型應(yīng)用嫌变,它會引入大量的依賴。而運維人員則普遍希望機(jī)器集群盡可能簡單躬它,維護(hù)起來也不易出錯腾啥。
- 3.發(fā)展緩慢。Apache基金會項目特有的“Apache Way”在開源界飽受爭議,其中一大原因就是由于基金會龐大的結(jié)構(gòu)以及松散的管理導(dǎo)致項目發(fā)展緩慢倘待。
而etcd作為一個后起之秀疮跑,其優(yōu)點也很明顯。
- 1.簡單凸舵。使用Go語言編寫部署簡單祖娘;使用HTTP作為接口使用簡單;使用Raft算法保證強一致性讓用戶易于理解啊奄。
- 2.數(shù)據(jù)持久化渐苏。etcd默認(rèn)數(shù)據(jù)一更新就進(jìn)行持久化。
- 3.安全菇夸。etcd支持SSL客戶端安全認(rèn)證琼富。
最后,etcd作為一個年輕的項目庄新,正在高速迭代和開發(fā)中鞠眉,這既是一個優(yōu)點,也是一個缺點择诈。優(yōu)點在于它的未來具有無限的可能性械蹋,缺點是版本的迭代導(dǎo)致其使用的可靠性無法保證,無法得到大項目長時間使用的檢驗吭从。然而朝蜘,目前CoreOS、Kubernetes和Cloudfoundry等知名項目均在生產(chǎn)環(huán)境中使用了etcd涩金,所以總的來說,etcd值得你去嘗試暇仲。
etcd 基本操作
docker-compose形式啟動etcd單節(jié)點服務(wù)
etcd:
container_name: etcd
# image: quay.io/coreos/etcd:v3.1
image: registry.cn-hangzhou.aliyuncs.com/coreos_etcd/etcd:v3
ports:
- "2379:2379"
- "4001:4001"
- "2380:2380"
environment:
- ETCDCTL_API=3
- TZ=CST-8
- LANG=zh_CN.UTF-8
command:
/usr/local/bin/etcd
-name node1
-data-dir /etcd-data
-advertise-client-urls http://${host_ip}:2379,http://${host_ip}:4001
-listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001
-initial-advertise-peer-urls http://${host_ip}:2380
-listen-peer-urls http://0.0.0.0:2380
-initial-cluster-token docker-etcd
-initial-cluster node1=http://${host_ip}:2380
-initial-cluster-state new
volumes:
- "/data/config/etcd/ca-certificates/:/etc/ssl/certs"
- "/data/conf/etcd/data:/etcd-data"
etcd v3 基本api
- 1.進(jìn)入容器
docker exec -it etcd sh
- 2.設(shè)置鍵步做、修改鍵
etcdctl put /maple value
- 3.刪除鍵
etcdctl del /maple
- 4.刪除所有/test前綴的節(jié)點
etcdctl del /test --prefix
- 5.查詢
Key
etcdctl get /test/ok
- 6.前綴查詢
etcdctl get /test/ok --prefix
- 7.watch key
etcdctl watch /maple/services
- 8.watch子節(jié)點
etcdctl watch /maple/services --prefix
- 9.申請租約、授權(quán)租約
申請租約
etcdctl lease grant 40
result: lease 4e5e5b853f528859 granted with TTL(40s)
授權(quán)租約
etcdctl put --lease=4e5e5b853f528859 /maple/s 123
撤銷
etcdctl lease revoke 4e5e5b853f5286cc
租約續(xù)約
etcdctl lease keep-alive 4e5e5b853f52892b