一、Curator是NetFlix公司開源的一套Zookeeper客戶端框架,其特點(diǎn)如下:
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊、雅安服務(wù)器托管、營銷軟件、網(wǎng)站建設(shè)、永州網(wǎng)站維護(hù)、網(wǎng)站推廣。
1、連接重連
2、反復(fù)注冊Watcher
3、NodeExistsException處理
二、常用API
API文檔對應(yīng)地址:
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);//Zookeeper每1秒檢查一次session狀態(tài),如果斷掉的話,重新連接,如果連續(xù)3次均沒連上,則session失效
CuratorFramework client = CuratorFrameworkFactory.newClient(connectingString,sessionTimeout,connectionTimeOut,retryPolicy);
//創(chuàng)建節(jié)點(diǎn)
client.create().forPath(path);
//創(chuàng)建節(jié)點(diǎn)并設(shè)置值
client.create().forPath(path,"init".getBytes());
//創(chuàng)建臨時節(jié)點(diǎn)
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
//創(chuàng)建臨時節(jié)點(diǎn)并遞歸創(chuàng)建父節(jié)點(diǎn)
client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);
//刪除節(jié)點(diǎn)
client.delete().forPath(path);
//刪除節(jié)點(diǎn)并遞歸刪除其所有子節(jié)點(diǎn)
client.delete().deletingChildrenIfNeeded().forPath(path);
//刪除節(jié)點(diǎn),強(qiáng)制指定版本進(jìn)行刪除
client.delete().withVersion(version).forPath(path);
//強(qiáng)制保證刪除
client.delete().guaranteed().forPath(path);
//獲取某個節(jié)點(diǎn)數(shù)據(jù)
client.getData().forPath(path);
//讀取一個節(jié)點(diǎn)的數(shù)據(jù)內(nèi)容,同時按獲取到該節(jié)點(diǎn)的stat
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath(path);
//更新一個節(jié)點(diǎn)的數(shù)據(jù)內(nèi)容
client.setData().withVersion(version).forPath(path);
//異步
client.create().creatingParentsIfNeeded().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
}
}).forPath(path,"init".getBytes());
三、監(jiān)聽器
1、NodeCache:監(jiān)聽節(jié)點(diǎn)變化
NodeCache cache = new NodeCache(client,path,false);
cache.start(true); //第一次啟動的時候就會從Zookeeper上同步數(shù)據(jù)
cache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println(cache.getCurrentData().getData());
}
});
2、PathChildrenCache:監(jiān)控某個節(jié)點(diǎn)子節(jié)點(diǎn)的變化【無法對父節(jié)點(diǎn)以及二級節(jié)點(diǎn)數(shù)據(jù)進(jìn)行監(jiān)控】
PathChildrenCache childrenCache = new PathChildrenCache(client,path,true);
childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
System.out.println("節(jié)點(diǎn)變更類型:"+pathChildrenCacheEvent.getType()+"對應(yīng)節(jié)點(diǎn):"+pathChildrenCacheEvent.getData().getPath());
}
});
四、LeaderLatch
LeaderLatch?于實(shí)現(xiàn)Leader的選舉
– 觸發(fā)方法isLeader(),表示成為leader
– 觸發(fā)notLeader(),表示失去leader權(quán)限
– 場景:參考xxx-callback
public void afterPropertiesSet() throws Exception {
if (leaderLatch != null) {
return;
}
leaderLatch = new LeaderLatch(curatorClient,//zk客戶端實(shí)例
PATH,//選舉根節(jié)點(diǎn)路徑
OSUtils.getServerIp() + "_" + UUID.randomUUID().toString()); //客戶端ID,用來標(biāo)記客戶端,即客戶端編號,名稱
leaderLatch.addListener(new LeaderLatchListener() {
@Override
public void isLeader() { //搶主成功時觸發(fā)
ContextHolder.setLeader(true);
LOGGER.info("im leader");
}
@Override
public void notLeader() { //搶主失敗時觸發(fā)
ContextHolder.setLeader(false);
LOGGER.info("im not leader");
}
});
leaderLatch.start();
}
五、分布式鎖
1、模擬并發(fā)
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i=0;i10;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
}catch (Exception e){
}
System.out.println(System.currentTimeMillis());
}
}).start();
}
countDownLatch.countDown();
2、分布式鎖控制
client.start();
String lock_path = "/lock";
final InterProcessMutex lock = new InterProcessMutex(client,lock_path);
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i=0;i10;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
lock.acquire();
}catch (Exception e){
}
System.out.println(System.currentTimeMillis());
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
countDownLatch.countDown();
六、分布式計數(shù)器
DistributedAtomicInteger atomicInteger = new DistributedAtomicInteger(client,path,new RetryNTimes(1000,3));
atomicInteger.add(8);
思考:
1、開關(guān)同步采用Zookeeper是否可以 ?
2、ABTest?
3、分批任務(wù)計算?
4、配置中心
在了解 ZK 底層原理之前,咱們先簡單了解常用的 ZK 命令,熟悉常用 ZK 命令有利于排查相關(guān)問題或了解基于 ZK 自研系統(tǒng)等場景。比如在開發(fā)的時候,發(fā)現(xiàn)有些Dubbo服務(wù)無法被調(diào)用,這有可能是服務(wù)沒有注冊到ZK或者斷開連接;也有可能公司有自研的系統(tǒng)使用 ZK 作為配置中心,熟悉 ZK 命令就能知道是如何做到服務(wù)發(fā)現(xiàn)注冊和配置動態(tài)更新。
話不多說,咱們先來了解常見的 ZK 命令吧!
實(shí)際上,ZK并沒有help命令,你可以隨意敲一兩個字符也會這樣顯示,只不過基于使用Linux的習(xí)慣,姑且認(rèn)為輸入help能打印出ZK支持的命令吧。
ls 命令可以查看指定目錄下的節(jié)點(diǎn),使用可選的參數(shù),能夠更加詳細(xì)的看到節(jié)點(diǎn)的相關(guān)信息
stat / 等價于 ls -s /
和 ls 命令相似的,加上-w參數(shù)添加監(jiān)聽
在ZK 3.5版本之后,新增了容器和TTL節(jié)點(diǎn),分別是使用 -c 和 -t 創(chuàng)建。所以讀者們要注意你當(dāng)前使用的版本,如果版本低于3.5的,是沒有容器和TTL節(jié)點(diǎn)。
特別說明一下容器節(jié)點(diǎn)和TTL節(jié)點(diǎn)的使用:
另外關(guān)于 TTL節(jié)點(diǎn) 的使用,需要特別注意的是,如果使用默認(rèn)的配置文件啟動zk,想創(chuàng)建有存活時間的節(jié)點(diǎn),比如執(zhí)行 create -t 10 /test 是會報 KeeperErrorCode = Unimplemented for XXX 這樣的錯誤。解決辦法是需要在ZK啟動前,先在配置文件加上 extendedTypesEnabled=true 然后重啟ZK(集群部署的話,所有ZK都需要修改配置文件再重啟)
配置后重啟,執(zhí)行 create -t 10 /test 這樣的命令就不會報錯啦
例子:get -s /demo
例子:先查詢節(jié)點(diǎn)版本號,模擬并發(fā)下修改同一節(jié)點(diǎn)
get -s /demo 可知當(dāng)前 dataVersion = 1
客戶端1:set -v 1 /demo demo-data1
客戶端2:set -v 1 /demo demo-data2
客戶端1比客戶端2先執(zhí)行,客戶端2再執(zhí)行的話,這時顯示報錯
-v version:和 set 命令相似,-v 參數(shù)用于判斷當(dāng)前操作的版本
例子:先創(chuàng)建一個delNode節(jié)點(diǎn),然后刪除
在前面使用create命令的時候,有一個acl參數(shù)是設(shè)置節(jié)點(diǎn)權(quán)限的,那么我們應(yīng)該怎么設(shè)置?
舉個例子: create /testAcl demo world:anyone:crwda
這行命令的意思是,創(chuàng)建 testAcl 這個節(jié)點(diǎn),節(jié)點(diǎn)值為demo,其權(quán)限策略是所有人都可以執(zhí)行 crwda 操作。那么接下來,咱們先看下 ACL 是什么東東?
ACL 全稱是 Access Control List ,也就是訪問控制列表,ACL可以設(shè)置節(jié)點(diǎn)的操作權(quán)限。那么控制權(quán)限的粒度是怎樣呢?
對于節(jié)點(diǎn) ACL 權(quán)限控制,是通過使用: scheme:id:perm 來標(biāo)識(也就是例子中的格式 - world:anyone:crwda),其含義是:
Scheme 有哪些授權(quán)策略?
ID 授權(quán)對象有哪些?
Permission 權(quán)限有哪些?
根據(jù)上面的參數(shù)可知,我們可以通過給所有人、特定的賬號密碼、特定的 ip 設(shè)置節(jié)點(diǎn)權(quán)限,這樣能夠更加方面地管理節(jié)點(diǎn)的訪問。
值得注意的是,節(jié)點(diǎn)可以設(shè)置多種授權(quán)策略,但對于上下節(jié)點(diǎn)而言,權(quán)限的設(shè)置只對當(dāng)前節(jié)點(diǎn)有效。換言之,權(quán)限不存在繼承關(guān)系,即使節(jié)點(diǎn)被設(shè)置權(quán)限,但不會影響上下節(jié)點(diǎn)原來的權(quán)限!
上面執(zhí)行了 create /testAcl demo world:anyone:crwda 命令給節(jié)點(diǎn)設(shè)置權(quán)限,那怎么看節(jié)點(diǎn)的權(quán)限咧?
很簡單,執(zhí)行 getAcl 節(jié)點(diǎn)路徑 就可以查看對應(yīng)節(jié)點(diǎn)的權(quán)限,比如 getAcl /testAcl,執(zhí)行結(jié)果如下
除了在執(zhí)行create命令創(chuàng)建節(jié)點(diǎn)的時候設(shè)置權(quán)限,還可以通過 setAcl 指定節(jié)點(diǎn)設(shè)置權(quán)限,比如我想指定/testAcl這個節(jié)點(diǎn)只可以通過特定 IP操作,并且限制執(zhí)行權(quán)限為crdw,那么可以執(zhí)行 setAcl /testAcl ip:127.0.0.1:crwd ,再次執(zhí)行 getAcl /testAcl 結(jié)果如下:
ZK 的命令還有部分沒有演示,這并不阻礙咱們學(xué)習(xí)ZK的原理,先掌握常見的命令,如果日后有其他場景的話,再根據(jù)特定的命令學(xué)習(xí)就可以啦。
無意中發(fā)現(xiàn)有 Zookeeper的客戶端,感興趣的讀者可以玩一下~ 友情提醒,可能在節(jié)點(diǎn)數(shù)量很多的時候,打開很慢,甚至卡死,所以這個可視化工具可以在自己本地玩玩,不建議應(yīng)用在生產(chǎn)上哈。這也側(cè)面的說明,學(xué)會 ZK 命令的重要性(認(rèn)真臉.jpg)
解壓縮后,進(jìn)入ZooInspector的build目錄,執(zhí)行 java -jar zookeeper-dev-ZooInspector.jar 就可以啟動工具。
連接上 ZK 后,就可以看到節(jié)點(diǎn)的信息和節(jié)點(diǎn)的ACL,具體玩法,可以再自己摸索哈~
好了,以上是 ZK 常見命令的基本使用和可視化工具的基本使用。
參考資料:
《從Paxos到Zookeeper分布式一致性原理與實(shí)踐》
如果覺得文章不錯的話,麻煩點(diǎn)個贊哈,你的鼓勵就是我的動力!對于文章有哪里不清楚或者有誤的地方,歡迎在評論區(qū)留言~
利用ZK框架設(shè)計的web應(yīng)用程序具備豐富的胖客戶端特性和簡單的設(shè)計模型.ZK包括一個基于AJAX可自動進(jìn)行交互式操作的事件驅(qū)動引擎和一套兼容XUL的組件.利用直觀的事件驅(qū)動模型,你可以用具有XUL特性的組件來表示你的應(yīng)用程序并通過由用戶觸發(fā)的監(jiān)聽事件來操作這些組件,就像開發(fā)桌面應(yīng)用程序一樣簡單.ZK還可以與現(xiàn)存一些框架和技術(shù)相結(jié)合如:JSF和Portals.
本文是Jason Wilder對于常見的服務(wù)發(fā)現(xiàn)項(xiàng)目 Zookeeper , Doozer , Etcd 所寫的一篇博客,其原文地址如下: Open-Source Service Discovery 。
服務(wù)發(fā)現(xiàn)是大多數(shù)分布式系統(tǒng)以及面向服務(wù)架構(gòu)(SOA)的一個核心組成部分。這個難題,簡單來說,可以認(rèn)為是:當(dāng)一項(xiàng)服務(wù)存在于多個主機(jī)節(jié)點(diǎn)上時,client端如何決策獲取相應(yīng)正確的IP和port。
在傳統(tǒng)情況下,當(dāng)出現(xiàn)服務(wù)存在于多個主機(jī)節(jié)點(diǎn)上時,都會使用靜態(tài)配置的方法來實(shí)現(xiàn)服務(wù)信息的注冊。但是當(dāng)大型系統(tǒng)中,需要部署更多服務(wù)的時候,事情就顯得復(fù)雜得多。在一個實(shí)時的系統(tǒng)中,由于自動或者人工的服務(wù)擴(kuò)展,或者服務(wù)的新添加部署,還有主機(jī)的宕機(jī)或者被替換,服務(wù)的location信息可能會很頻繁的變化。
在這樣的場景下,為了避免不必要的服務(wù)中斷,動態(tài)的服務(wù)注冊和發(fā)現(xiàn)就顯得尤為重要。
關(guān)于服務(wù)發(fā)現(xiàn)的話題,已經(jīng)很多次被人所提及,而且也的確不斷的在發(fā)展。現(xiàn)在,筆者介紹一下該領(lǐng)域內(nèi)一些open-source或者被經(jīng)常被世人廣泛討論的解決方案,嘗試?yán)斫馑鼈兊降资侨绾喂ぷ鞯摹L貏e的是,我們會較為專注于每一個解決方案的一致性算法,到底是強(qiáng)一致性,還是弱一致性;運(yùn)行時依賴;client的集成選擇;以后最后這些特性的折中情況。
本文首先從幾個強(qiáng)一致性的項(xiàng)目于開始,比如Zookeeper,Doozer,Etcd,這些項(xiàng)目主要用于服務(wù)間的協(xié)調(diào),同時又可用于服務(wù)的注冊。
隨后,本文將討論一些在服務(wù)注冊以及發(fā)現(xiàn)方面比較有意思的項(xiàng)目,比如:Airbnb的SmartStack,Netflix的Eureka,Bitly的NSQ,Serf,Spotify and DNS,最后是SkyDNS。
問題陳述
在定位服務(wù)的時候,其實(shí)會有兩個方面的問題:服務(wù)注冊(Service Registration)和服務(wù)發(fā)現(xiàn)(Service Discovery)。
服務(wù)注冊—— 一個服務(wù)將其位置信息在中心注冊節(jié)點(diǎn)注冊的過程。該服務(wù)一般會將它的主機(jī)IP地址以及端口號進(jìn)行注冊,有時也會有服務(wù)訪問的認(rèn)證信息,使用協(xié)議,版本號,以及關(guān)于環(huán)境的一些細(xì)節(jié)信息。
服務(wù)發(fā)現(xiàn)—— client端的應(yīng)用實(shí)例查詢中心注冊節(jié)點(diǎn)以獲知服務(wù)位置的過程。
每一個服務(wù)的服務(wù)注冊以及服務(wù)發(fā)現(xiàn),都需要考慮一些關(guān)于開發(fā)以及運(yùn)營方面的問題:
監(jiān)控—— 當(dāng)一個已注冊完畢的服務(wù)失效的時候,如何處理。一些情況下,在一個設(shè)定的超時定時(timeout)后,該服務(wù)立即被一個其他的進(jìn)程在中心注冊節(jié)點(diǎn)處注銷。這種情況下,服務(wù)通常需要執(zhí)行一個心跳機(jī)制,來確保自身的存活狀態(tài);而客戶端必然需要能夠可靠處理失效的服務(wù)。
負(fù)載均衡—— 如果多個相同地位的服務(wù)都注冊完畢,如何在這些服務(wù)之間均衡所有client的請求負(fù)載?如果有一個master節(jié)點(diǎn)的話,是否可以正確處理client訪問的服務(wù)的位置。
集成方式—— 信息注冊節(jié)點(diǎn)是否需要提供一些語言綁定的支持,比如說,只支持Java?集成的過程是否需要將注冊過程以及發(fā)現(xiàn)過程的代碼嵌入到你的應(yīng)用程序中,或者使用一個類似于集成助手的進(jìn)程?
運(yùn)行時依賴—— 是否需要JVM,ruby或者其他在你的環(huán)境中并不兼容的運(yùn)行時?
可用性考慮—— 如果系統(tǒng)失去一個節(jié)點(diǎn)的話,是否還能正常工作?系統(tǒng)是否可以實(shí)時更新或升級,而不造成任何系統(tǒng)的癱瘓?既然集群的信息注冊節(jié)點(diǎn)是架構(gòu)中的中心部分,那該模塊是否會存在單點(diǎn)故障問題?
強(qiáng)一致性的Registries
首先介紹的三個服務(wù)注冊系統(tǒng)都采用了強(qiáng)一致性協(xié)議,實(shí)際上為達(dá)到通用的效果,使用了一致性的數(shù)據(jù)存儲。盡管我們把它們看作服務(wù)的注冊系統(tǒng),其實(shí)它們還可以用于協(xié)調(diào)服務(wù)來協(xié)助leader選舉,以及在一個分布式clients的集合中做centralized locking。
Zookeeper
Zookeeper是一個集中式的服務(wù),該服務(wù)可以維護(hù)服務(wù)配置信息,命名空間,提供分布式的同步,以及提供組化服務(wù)。Zookeeper是由Java語言實(shí)現(xiàn),實(shí)現(xiàn)了強(qiáng)一致性(CP),并且是使用 Zab協(xié)議 在ensemble集群之間協(xié)調(diào)服務(wù)信息的變化。
Zookeeper在ensemble集群中運(yùn)行3個,5個或者7個成員。眾多client端為了可以訪問ensemble,需要使用綁定特定的語言。這種訪問形式被顯性的嵌入到了client的應(yīng)用實(shí)例以及服務(wù)中。
服務(wù)注冊的實(shí)現(xiàn)主要是通過命令空間(namespace)下的 ephemeral nodes 。ephemeral nodes只有在client建立連接后才存在。當(dāng)client所在節(jié)點(diǎn)啟動之后,該client端會使用一個后臺進(jìn)程獲取client的位置信息,并完成自身的注冊。如果該client失效或者失去連接的時候,該ephemeral node就從樹中消息。
服務(wù)發(fā)現(xiàn)是通過列舉以及查看具體服務(wù)的命名空間來完成的。Client端收到目前所有注冊服務(wù)的信息,無論一個服務(wù)是否不可用或者系統(tǒng)新添加了一個同類的服務(wù)。Client端同時也需要自行處理所有的負(fù)載均衡工作,以及服務(wù)的失效工作。
Zookeeper的API用起來可能并沒有那么方便,因?yàn)檎Z言的綁定之間可能會造成一些細(xì)小的差異。如果使用的是基于JVM的語言的話, Curator Service Discovery Extension 可能會對你有幫助。
由于Zookeeper是一個CP強(qiáng)一致性的系統(tǒng),因此當(dāng)網(wǎng)絡(luò)分區(qū)(Partition)出故障的時候,你的部分系統(tǒng)可能將出出現(xiàn)不能注冊的情況,也可能出現(xiàn)不能找到已存在的注冊信息,即使它們可能在Partition出現(xiàn)期間仍然正常工作。特殊的是,在任何一個non-quorum端,任何讀寫都會返回一個錯誤信息。
Doozer
Doozer是一個一致的分布式數(shù)據(jù)存儲系統(tǒng),Go語言實(shí)現(xiàn),通過 Paxos算法 來實(shí)現(xiàn)共識的強(qiáng)一致性系統(tǒng)。這個項(xiàng)目開展了數(shù)年之后,停滯了一段時間,而且現(xiàn)在也關(guān)閉了一些fork數(shù),使得fork數(shù)降至160 。.不幸的是,現(xiàn)在很難知道該項(xiàng)目的實(shí)際發(fā)展?fàn)顟B(tài),以及它是否適合使用于生產(chǎn)環(huán)境。
Doozer在集群中運(yùn)行3,5或者7個節(jié)點(diǎn)。和Zookeeper類似,Client端為了訪問集群,需要在自身的應(yīng)用或者服務(wù)中使用特殊的語言綁定。
Doozer的服務(wù)注冊就沒有Zookeeper這么直接,因?yàn)镈oozer沒有那些ephemeral node的概念。一個服務(wù)可以在一條路徑下注冊自己,如果該服務(wù)不可用的話,它也不會自動地被移除。
現(xiàn)有很多種方式來解決這樣的問題。一個選擇是給注冊進(jìn)程添加一個時間戳和心跳機(jī)制,隨后在服務(wù)發(fā)現(xiàn)進(jìn)程中處理那些超時的路徑,也就是注冊的服務(wù)信息,當(dāng)然也可以通過另外一個清理進(jìn)程來實(shí)現(xiàn)。
服務(wù)發(fā)現(xiàn)和Zookeeper很類似,Doozer可以羅列出指定路徑下的所有入口,隨后可以等待該路徑下的任意改動。如果你在注冊期間使用一個時間戳和心跳,你就可以在服務(wù)發(fā)現(xiàn)期間忽略或者刪除任何過期的入口,也就是服務(wù)信息。
和Zookeeper一樣,Doozer是一個CP強(qiáng)一致性系統(tǒng),當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)故障時,會導(dǎo)致同樣的后果。
Etcd
Etcd 是一個高可用的K-V存儲系統(tǒng),主要應(yīng)用于共享配置、服務(wù)發(fā)現(xiàn)等場景。Etcd可以說是被Zookeeper和Doozer催生而出。整個系統(tǒng)使用Go語言實(shí)現(xiàn),使用Raft算法來實(shí)現(xiàn)選舉一致,同時又具有一個基于HTTP+JSON的API。
Etcd,和Doozer和Zookeeper相似,通常在集群中運(yùn)行3,5或者7個節(jié)點(diǎn)。client端可以使用一種特定的語言進(jìn)行綁定,同時也可以通過使用HTTP客戶端自行實(shí)現(xiàn)一種。
服務(wù)注冊環(huán)節(jié)主要依賴于使用一個key TTL來確保key的可用性,該key TTL會和服務(wù)端的心跳捆綁在一起。如果一個服務(wù)在更新key的TTL時失敗了,那么Etcd會對它進(jìn)行超時處理。如果一個服務(wù)變?yōu)椴豢捎脿顟B(tài),client會需要處理這樣的連接失效,然后嘗試另連接一個服務(wù)實(shí)例。
服務(wù)發(fā)現(xiàn)環(huán)節(jié)設(shè)計到羅列在一個目錄下的所有key值,隨后等待在該目錄上的所有變動信息。由于API接口是基于HTTP的,所以client應(yīng)用會的Etcd集群保持一個long-polling的連接。
由于Etcd使用 Raft一致性協(xié)議 ,故它應(yīng)該是一個強(qiáng)一致性系統(tǒng)。Raft需要一個leader被選舉,然后所有的client請求會被該leader所處理。然而,Etcd似乎也支持從non-leaders中進(jìn)行讀取信息,使用的方式是在讀情況下提高可用性的未公開的一致性參數(shù)。在網(wǎng)絡(luò)分區(qū)故障期間,寫操作還是會被leader處理,而且同樣會出現(xiàn)失效的情況。
在 2021-10-17 dubbogo 基礎(chǔ)使用 中,介紹了如何用 go 寫一個部署到 zookeeper 的 dubbo 服務(wù),這次編寫一個 go 語言的 dubbo 調(diào)用端。并且支持同時連接到多個 zookeeper ,根據(jù)需要調(diào)用不同 zk 上的同一個服務(wù)
這里我們部署兩個 zookeeper ,端口號分別是 2181 和 2182
這里我們配置了兩個 UserService 的引用,并且設(shè)置了不同的別名。指定了每個引用連接各自的 zk .
注意,這里 service.UserService 為服務(wù)提供方的接口,我們直接繼承他
服務(wù)1 的日志如下:
服務(wù)2 的日志如下:
github-dubbogodemo
此文是根據(jù)周洋在【高可用架構(gòu)群】中的分享內(nèi)容整理而成,轉(zhuǎn)發(fā)請注明出處。
周洋,360手機(jī)助手技術(shù)經(jīng)理及架構(gòu)師,負(fù)責(zé)360長連接消息系統(tǒng),360手機(jī)助手架構(gòu)的開發(fā)與維護(hù)。
不知道咱們?nèi)好裁磿r候改為“Python高可用架構(gòu)群”了,所以不得不說,很榮幸能在接下來的一個小時里在Python群里討論golang....
360消息系統(tǒng)介紹
360消息系統(tǒng)更確切的說是長連接push系統(tǒng),目前服務(wù)于360內(nèi)部多個產(chǎn)品,開發(fā)平臺數(shù)千款app,也支持部分聊天業(yè)務(wù)場景,單通道多app復(fù)用,支持上行數(shù)據(jù),提供接入方不同粒度的上行數(shù)據(jù)和用戶狀態(tài)回調(diào)服務(wù)。
目前整個系統(tǒng)按不同業(yè)務(wù)分成9個功能完整的集群,部署在多個idc上(每個集群覆蓋不同的idc),實(shí)時在線數(shù)億量級。通常情況下,pc,手機(jī),甚至是智能硬件上的360產(chǎn)品的push消息,基本上是從我們系統(tǒng)發(fā)出的。
關(guān)于push系統(tǒng)對比與性能指標(biāo)的討論
很多同行比較關(guān)心go語言在實(shí)現(xiàn)push系統(tǒng)上的性能問題,單機(jī)性能究竟如何,能否和其他語言實(shí)現(xiàn)的類似系統(tǒng)做對比么?甚至問如果是創(chuàng)業(yè),第三方云推送平臺,推薦哪個?
其實(shí)各大廠都有類似的push系統(tǒng),市場上也有類似功能的云服務(wù)。包括我們公司早期也有erlang,nodejs實(shí)現(xiàn)的類似系統(tǒng),也一度被公司要求做類似的對比測試。我感覺在討論對比數(shù)據(jù)的時候,很難保證大家環(huán)境和需求的統(tǒng)一,我只能說下我這里的體會,數(shù)據(jù)是有的,但這個數(shù)據(jù)前面估計會有很多定語~
第一個重要指標(biāo):單機(jī)的連接數(shù)指標(biāo)
做過長連接的同行,應(yīng)該有體會,如果在穩(wěn)定連接情況下,連接數(shù)這個指標(biāo),在沒有網(wǎng)絡(luò)吞吐情況下對比,其實(shí)意義往往不大,維持連接消耗cpu資源很小,每條連接tcp協(xié)議棧會占約4k的內(nèi)存開銷,系統(tǒng)參數(shù)調(diào)整后,我們單機(jī)測試數(shù)據(jù),最高也是可以達(dá)到單實(shí)例300w長連接。但做更高的測試,我個人感覺意義不大。
因?yàn)閷?shí)際網(wǎng)絡(luò)環(huán)境下,單實(shí)例300w長連接,從理論上算壓力就很大:實(shí)際弱網(wǎng)絡(luò)環(huán)境下,移動客戶端的斷線率很高,假設(shè)每秒有1000分之一的用戶斷線重連。300w長連接,每秒新建連接達(dá)到3w,這同時連入的3w用戶,要進(jìn)行注冊,加載離線存儲等對內(nèi)rpc調(diào)用,另外300w長連接的用戶心跳需要維持,假設(shè)心跳300s一次,心跳包每秒需要1w tps。單播和多播數(shù)據(jù)的轉(zhuǎn)發(fā),廣播數(shù)據(jù)的轉(zhuǎn)發(fā),本身也要響應(yīng)內(nèi)部的rpc調(diào)用,300w長連接情況下,gc帶來的壓力,內(nèi)部接口的響應(yīng)延遲能否穩(wěn)定保障。這些集中在一個實(shí)例中,可用性是一個挑戰(zhàn)。所以線上單實(shí)例不會hold很高的長連接,實(shí)際情況也要根據(jù)接入客戶端網(wǎng)絡(luò)狀況來決定。
第二個重要指標(biāo):消息系統(tǒng)的內(nèi)存使用量指標(biāo)
這一點(diǎn)上,使用go語言情況下,由于協(xié)程的原因,會有一部分額外開銷。但是要做兩個推送系統(tǒng)的對比,也有些需要確定問題。比如系統(tǒng)從設(shè)計上是否需要全雙工(即讀寫是否需要同時進(jìn)行)如果半雙工,理論上對一個用戶的連接只需要使用一個協(xié)程即可(這種情況下,對用戶的斷線檢測可能會有延時),如果是全雙工,那讀/寫各一個協(xié)程。兩種場景內(nèi)存開銷是有區(qū)別的。
另外測試數(shù)據(jù)的大小往往決定我們對連接上設(shè)置的讀寫buffer是多大,是全局復(fù)用的,還是每個連接上獨(dú)享的,還是動態(tài)申請的。另外是否全雙工也決定buffer怎么開。不同的策略,可能在不同情況的測試中表現(xiàn)不一樣。
第三個重要指標(biāo):每秒消息下發(fā)量
這一點(diǎn)上,也要看我們對消息到達(dá)的QoS級別(回復(fù)ack策略區(qū)別),另外看架構(gòu)策略,每種策略有其更適用的場景,是純粹推?還是推拉結(jié)合?甚至是否開啟了消息日志?日志庫的實(shí)現(xiàn)機(jī)制、以及緩沖開多大?flush策略……這些都影響整個系統(tǒng)的吞吐量。
另外為了HA,增加了內(nèi)部通信成本,為了避免一些小概率事件,提供閃斷補(bǔ)償策略,這些都要考慮進(jìn)去。如果所有的都去掉,那就是比較基礎(chǔ)庫的性能了。
所以我只能給出大概數(shù)據(jù),24核,64G的服務(wù)器上,在QoS為message at least,純粹推,消息體256B~1kB情況下,單個實(shí)例100w實(shí)際用戶(200w+)協(xié)程,峰值可以達(dá)到2~5w的QPS...內(nèi)存可以穩(wěn)定在25G左右,gc時間在200~800ms左右(還有優(yōu)化空間)。
我們正常線上單實(shí)例用戶控制在80w以內(nèi),單機(jī)最多兩個實(shí)例。事實(shí)上,整個系統(tǒng)在推送的需求上,對高峰的輸出不是提速,往往是進(jìn)行限速,以防push系統(tǒng)瞬時的高吞吐量,轉(zhuǎn)化成對接入方業(yè)務(wù)服務(wù)器的ddos攻擊所以對于性能上,我感覺大家可以放心使用,至少在我們這個量級上,經(jīng)受過考驗(yàn),go1.5到來后,確實(shí)有之前投資又增值了的感覺。
消息系統(tǒng)架構(gòu)介紹
下面是對消息系統(tǒng)的大概介紹,之前一些同學(xué)可能在gopher china上可以看到分享,這里簡單講解下架構(gòu)和各個組件功能,額外補(bǔ)充一些當(dāng)時遺漏的信息:
架構(gòu)圖如下,所有的service都 written by golang.
幾個大概重要組件介紹如下:
dispatcher service根據(jù)客戶端請求信息,將應(yīng)網(wǎng)絡(luò)和區(qū)域的長連接服務(wù)器的,一組IP傳送給客戶端??蛻舳烁鶕?jù)返回的IP,建立長連接,連接Room service.
room Service,長連接網(wǎng)關(guān),hold用戶連接,并將用戶注冊進(jìn)register service,本身也做一些接入安全策略、白名單、IP限制等。
register service是我們?nèi)謘ession存儲組件,存儲和索引用戶的相關(guān)信息,以供獲取和查詢。
coordinator service用來轉(zhuǎn)發(fā)用戶的上行數(shù)據(jù),包括接入方訂閱的用戶狀態(tài)信息的回調(diào),另外做需要協(xié)調(diào)各個組件的異步操作,比如kick用戶操作,需要從register拿出其他用戶做異步操作.
saver service是存儲訪問層,承擔(dān)了對redis和mysql的操作,另外也提供部分業(yè)務(wù)邏輯相關(guān)的內(nèi)存緩存,比如廣播信息的加載可以在saver中進(jìn)行緩存。另外一些策略,比如客戶端sdk由于被惡意或者意外修改,每次加載了消息,不回復(fù)ack,那服務(wù)端就不會刪除消息,消息就會被反復(fù)加載,形成死循環(huán),可以通過在saver中做策略和判斷。(客戶端總是不可信的)。
center service提供給接入方的內(nèi)部api服務(wù)器,比如單播或者廣播接口,狀態(tài)查詢接口等一系列api,包括運(yùn)維和管理的api。
舉兩個常見例子,了解工作機(jī)制:比如發(fā)一條單播給一個用戶,center先請求Register獲取這個用戶之前注冊的連接通道標(biāo)識、room實(shí)例地址,通過room service下發(fā)給長連接 Center Service比較重的工作如全網(wǎng)廣播,需要把所有的任務(wù)分解成一系列的子任務(wù),分發(fā)給所有center,然后在所有的子任務(wù)里,分別獲取在線和離線的所有用戶,再批量推到Room Service。通常整個集群在那一瞬間壓力很大。
deployd/agent service用于部署管理各個進(jìn)程,收集各組件的狀態(tài)和信息,zookeeper和keeper用于整個系統(tǒng)的配置文件管理和簡單調(diào)度
關(guān)于推送的服務(wù)端架構(gòu)
常見的推送模型有長輪訓(xùn)拉取,服務(wù)端直接推送(360消息系統(tǒng)目前主要是這種),推拉結(jié)合(推送只發(fā)通知,推送后根據(jù)通知去拉取消息).
拉取的方式不說了,現(xiàn)在并不常用了,早期很多是nginx+lua+redis,長輪訓(xùn),主要問題是開銷比較大,時效性也不好,能做的優(yōu)化策略不多。
直接推送的系統(tǒng),目前就是360消息系統(tǒng)這種,消息類型是消耗型的,并且對于同一個用戶并不允許重復(fù)消耗,如果需要多終端重復(fù)消耗,需要抽象成不同用戶。
推的好處是實(shí)時性好,開銷小,直接將消息下發(fā)給客戶端,不需要客戶端走從接入層到存儲層主動拉取.
但純推送模型,有個很大問題,由于系統(tǒng)是異步的,他的時序性無法精確保證。這對于push需求來說是夠用的,但如果復(fù)用推送系統(tǒng)做im類型通信,可能并不合適。
對于嚴(yán)格要求時序性,消息可以重復(fù)消耗的系統(tǒng),目前也都是走推拉結(jié)合的模型,就是只使用我們的推送系統(tǒng)發(fā)通知,并附帶id等給客戶端做拉取的判斷策略,客戶端根據(jù)推送的key,主動從業(yè)務(wù)服務(wù)器拉取消息。并且當(dāng)主從同步延遲的時候,跟進(jìn)推送的key做延遲拉取策略。同時也可以通過消息本身的QoS,做純粹的推送策略,比如一些“正在打字的”低優(yōu)先級消息,不需要主動拉取了,通過推送直接消耗掉。
哪些因素決定推送系統(tǒng)的效果?
首先是sdk的完善程度,sdk策略和細(xì)節(jié)完善度,往往決定了弱網(wǎng)絡(luò)環(huán)境下最終推送質(zhì)量.
SDK選路策略,最基本的一些策略如下:有些開源服務(wù)可能會針對用戶hash一個該接入?yún)^(qū)域的固定ip,實(shí)際上在國內(nèi)環(huán)境下不可行,最好分配器(dispatcher)是返回散列的一組,而且端口也要參開,必要時候,客戶端告知是retry多組都連不上,返回不同idc的服務(wù)器。因?yàn)槲覀儠?jīng)常檢測到一些case,同一地區(qū)的不同用戶,可能對同一idc內(nèi)的不同ip連通性都不一樣,也出現(xiàn)過同一ip不同端口連通性不同,所以用戶的選路策略一定要靈活,策略要足夠完善.另外在選路過程中,客戶端要對不同網(wǎng)絡(luò)情況下的長連接ip做緩存,當(dāng)網(wǎng)絡(luò)環(huán)境切換時候(wifi、2G、3G),重新請求分配器,緩存不同網(wǎng)絡(luò)環(huán)境的長連接ip。
客戶端對于數(shù)據(jù)心跳和讀寫超時設(shè)置,完善斷線檢測重連機(jī)制
針對不同網(wǎng)絡(luò)環(huán)境,或者客戶端本身消息的活躍程度,心跳要自適應(yīng)的進(jìn)行調(diào)整并與服務(wù)端協(xié)商,來保證鏈路的連通性。并且在弱網(wǎng)絡(luò)環(huán)境下,除了網(wǎng)絡(luò)切換(wifi切3G)或者讀寫出錯情況,什么時候重新建立鏈路也是一個問題??蛻舳税l(fā)出的ping包,不同網(wǎng)絡(luò)下,多久沒有得到響應(yīng),認(rèn)為網(wǎng)絡(luò)出現(xiàn)問題,重新建立鏈路需要有個權(quán)衡。另外對于不同網(wǎng)絡(luò)環(huán)境下,讀取不同的消息長度,也要有不同的容忍時間,不能一刀切。好的心跳和讀寫超時設(shè)置,可以讓客戶端最快的檢測到網(wǎng)絡(luò)問題,重新建立鏈路,同時在網(wǎng)絡(luò)抖動情況下也能完成大數(shù)據(jù)傳輸。
結(jié)合服務(wù)端做策略
另外系統(tǒng)可能結(jié)合服務(wù)端做一些特殊的策略,比如我們在選路時候,我們會將同一個用戶盡量映射到同一個room service實(shí)例上。斷線時,客戶端盡量對上次連接成功的地址進(jìn)行重試。主要是方便服務(wù)端做閃斷情況下策略,會暫存用戶閃斷時實(shí)例上的信息,重新連入的 時候,做單實(shí)例內(nèi)的遷移,減少延時與加載開銷.
客戶端保活策略
很多創(chuàng)業(yè)公司愿意重新搭建一套push系統(tǒng),確實(shí)不難實(shí)現(xiàn),其實(shí)在協(xié)議完備情況下(最簡單就是客戶端不回ack不清數(shù)據(jù)),服務(wù)端會保證消息是不丟的。但問題是為什么在消息有效期內(nèi),到達(dá)率上不去?往往因?yàn)樽约篴pp的push service存活能力不高。選用云平臺或者大廠的,往往sdk會做一些?;畈呗?,比如和其他app共生,互相喚醒,這也是云平臺的push service更有保障原因。我相信很多云平臺旗下的sdk,多個使用同樣sdk的app,為了實(shí)現(xiàn)服務(wù)存活,是可以互相喚醒和保證活躍的。另外現(xiàn)在push sdk本身是單連接,多app復(fù)用的,這為sdk實(shí)現(xiàn),增加了新的挑戰(zhàn)。
綜上,對我來說,選擇推送平臺,優(yōu)先會考慮客戶端sdk的完善程度。對于服務(wù)端,選擇條件稍微簡單,要求部署接入點(diǎn)(IDC)越要多,配合精細(xì)的選路策略,效果越有保證,至于想知道哪些云服務(wù)有多少點(diǎn),這個群里來自各地的小伙伴們,可以合伙測測。
go語言開發(fā)問題與解決方案
下面講下,go開發(fā)過程中遇到挑戰(zhàn)和優(yōu)化策略,給大家看下當(dāng)年的一張圖,在第一版優(yōu)化方案上線前一天截圖~
可以看到,內(nèi)存最高占用69G,GC時間單實(shí)例最高時候高達(dá)3~6s.這種情況下,試想一次悲劇的請求,經(jīng)過了幾個正在執(zhí)行g(shù)c的組件,后果必然是超時... gc照成的接入方重試,又加重了系統(tǒng)的負(fù)擔(dān)。遇到這種情況當(dāng)時整個系統(tǒng)最差情況每隔2,3天就需要重啟一次~
當(dāng)時出現(xiàn)問題,現(xiàn)在總結(jié)起來,大概以下幾點(diǎn)
1.散落在協(xié)程里的I/O,Buffer和對象不復(fù)用。
當(dāng)時(12年)由于對go的gc效率理解有限,比較奔放,程序里大量short live的協(xié)程,對內(nèi)通信的很多io操作,由于不想阻塞主循環(huán)邏輯或者需要及時響應(yīng)的邏輯,通過單獨(dú)go協(xié)程來實(shí)現(xiàn)異步。這回會gc帶來很多負(fù)擔(dān)。
針對這個問題,應(yīng)盡量控制協(xié)程創(chuàng)建,對于長連接這種應(yīng)用,本身已經(jīng)有幾百萬并發(fā)協(xié)程情況下,很多情況沒必要在各個并發(fā)協(xié)程內(nèi)部做異步io,因?yàn)槌绦虻牟⑿卸仁怯邢?,理論上做協(xié)程內(nèi)做阻塞操作是沒問題。
如果有些需要異步執(zhí)行,比如如果不異步執(zhí)行,影響對用戶心跳或者等待response無法響應(yīng),最好通過一個任務(wù)池,和一組常駐協(xié)程,來消耗,處理結(jié)果,通過channel再傳回調(diào)用方。使用任務(wù)池還有額外的好處,可以對請求進(jìn)行打包處理,提高吞吐量,并且可以加入控量策略.
2.網(wǎng)絡(luò)環(huán)境不好引起激增
go協(xié)程相比較以往高并發(fā)程序,如果做不好流控,會引起協(xié)程數(shù)量激增。早期的時候也會發(fā)現(xiàn),時不時有部分主機(jī)內(nèi)存會遠(yuǎn)遠(yuǎn)大于其他服務(wù)器,但發(fā)現(xiàn)時候,所有主要profiling參數(shù)都正常了。
后來發(fā)現(xiàn),通信較多系統(tǒng)中,網(wǎng)絡(luò)抖動阻塞是不可免的(即使是內(nèi)網(wǎng)),對外不停accept接受新請求,但執(zhí)行過程中,由于對內(nèi)通信阻塞,大量協(xié)程被 創(chuàng)建,業(yè)務(wù)協(xié)程等待通信結(jié)果沒有釋放,往往瞬時會迎來協(xié)程暴漲。但這些內(nèi)存在系統(tǒng)穩(wěn)定后,virt和res都并沒能徹底釋放,下降后,維持高位。
處理這種情況,需要增加一些流控策略,流控策略可以選擇在rpc庫來做,或者上面說的任務(wù)池來做,其實(shí)我感覺放在任務(wù)池里做更合理些,畢竟rpc通信庫可以做讀寫數(shù)據(jù)的限流,但它并不清楚具體的限流策略,到底是重試還是日志還是緩存到指定隊(duì)列。任務(wù)池本身就是業(yè)務(wù)邏輯相關(guān)的,它清楚針對不同的接口需要的流控限制策略。
3.低效和開銷大的rpc框架
早期rpc通信框架比較簡單,對內(nèi)通信時候使用的也是短連接。這本來短連接開銷和性能瓶頸超出我們預(yù)期,短連接io效率是低一些,但端口資源夠,本身吞吐可以滿足需要,用是沒問題的,很多分層的系統(tǒng),也有http短連接對內(nèi)進(jìn)行請求的
但早期go版本,這樣寫程序,在一定量級情況,是支撐不住的。短連接大量臨時對象和臨時buffer創(chuàng)建,在本已經(jīng)百萬協(xié)程的程序中,是無法承受的。所以后續(xù)我們對我們的rpc框架作了兩次調(diào)整。
第二版的rpc框架,使用了連接池,通過長連接對內(nèi)進(jìn)行通信(復(fù)用的資源包括client和server的:編解碼Buffer、Request/response),大大改善了性能。
但這種在一次request和response還是占用連接的,如果網(wǎng)絡(luò)狀況ok情況下,這不是問題,足夠滿足需要了,但試想一個room實(shí)例要與后面的數(shù)百個的register,coordinator,saver,center,keeper實(shí)例進(jìn)行通信,需要建立大量的常駐連接,每個目標(biāo)機(jī)幾十個連接,也有數(shù)千個連接被占用。
非持續(xù)抖動時候(持續(xù)逗開多少無解),或者有延遲較高的請求時候,如果針對目標(biāo)ip連接開少了,會有瞬時大量請求阻塞,連接無法得到充分利用。第三版增加了Pipeline操作,Pipeline會帶來一些額外的開銷,利用tcp的全雙特性,以盡量少的連接完成對各個服務(wù)集群的rpc調(diào)用。
4.Gc時間過長
Go的Gc仍舊在持續(xù)改善中,大量對象和buffer創(chuàng)建,仍舊會給gc帶來很大負(fù)擔(dān),尤其一個占用了25G左右的程序。之前go team的大咖郵件也告知我們,未來會讓使用協(xié)程的成本更低,理論上不需要在應(yīng)用層做更多的策略來緩解gc.
改善方式,一種是多實(shí)例的拆分,如果公司沒有端口限制,可以很快部署大量實(shí)例,減少gc時長,最直接方法。不過對于360來說,外網(wǎng)通常只能使用80和433。因此常規(guī)上只能開啟兩個實(shí)例。當(dāng)然很多人給我建議能否使用SO_REUSEPORT,不過我們內(nèi)核版本確實(shí)比較低,并沒有實(shí)踐過。
另外能否模仿nginx,fork多個進(jìn)程監(jiān)控同樣端口,至少我們目前沒有這樣做,主要對于我們目前進(jìn)程管理上,還是獨(dú)立的運(yùn)行的,對外監(jiān)聽不同端口程序,還有配套的內(nèi)部通信和管理端口,實(shí)例管理和升級上要做調(diào)整。
解決gc的另兩個手段,是內(nèi)存池和對象池,不過最好做仔細(xì)評估和測試,內(nèi)存池、對象池使用,也需要對于代碼可讀性與整體效率進(jìn)行權(quán)衡。
這種程序一定情況下會降低并行度,因?yàn)橛贸貎?nèi)資源一定要加互斥鎖或者原子操作做CAS,通常原子操作實(shí)測要更快一些。CAS可以理解為可操作的更細(xì)行為粒度的鎖(可以做更多CAS策略,放棄運(yùn)行,防止忙等)。這種方式帶來的問題是,程序的可讀性會越來越像C語言,每次要malloc,各地方用完后要free,對于對象池free之前要reset,我曾經(jīng)在應(yīng)用層嘗試做了一個分層次結(jié)構(gòu)的“無鎖隊(duì)列”
上圖左邊的數(shù)組實(shí)際上是一個列表,這個列表按大小將內(nèi)存分塊,然后使用atomic操作進(jìn)行CAS。但實(shí)際要看測試數(shù)據(jù)了,池技術(shù)可以明顯減少臨時對象和內(nèi)存的申請和釋放,gc時間會減少,但加鎖帶來的并行度的降低,是否能給一段時間內(nèi)的整體吞吐量帶來提升,要做測試和權(quán)衡…
在我們消息系統(tǒng),實(shí)際上后續(xù)去除了部分這種黑科技,試想在百萬個協(xié)程里面做自旋操作申請復(fù)用的buffer和對象,開銷會很大,尤其在協(xié)程對線程多對多模型情況下,更依賴于golang本身調(diào)度策略,除非我對池增加更多的策略處理,減少忙等,感覺是在把runtime做的事情,在應(yīng)用層非常不優(yōu)雅的實(shí)現(xiàn)。普遍使用開銷理論就大于收益。
但對于rpc庫或者codec庫,任務(wù)池內(nèi)部,這些開定量協(xié)程,集中處理數(shù)據(jù)的區(qū)域,可以嘗試改造~
對于有些固定對象復(fù)用,比如固定的心跳包什么的,可以考慮使用全局一些對象,進(jìn)行復(fù)用,針對應(yīng)用層數(shù)據(jù),具體設(shè)計對象池,在部分環(huán)節(jié)去復(fù)用,可能比這種無差別的設(shè)計一個通用池更能進(jìn)行效果評估.
消息系統(tǒng)的運(yùn)維及測試
下面介紹消息系統(tǒng)的架構(gòu)迭代和一些迭代經(jīng)驗(yàn),由于之前在其他地方有過分享,后面的會給出相關(guān)鏈接,下面實(shí)際做個簡單介紹,感興趣可以去鏈接里面看
架構(gòu)迭代~根據(jù)業(yè)務(wù)和集群的拆分,能解決部分灰度部署上線測試,減少點(diǎn)對點(diǎn)通信和廣播通信不同產(chǎn)品的相互影響,針對特定的功能做獨(dú)立的優(yōu)化.
消息系統(tǒng)架構(gòu)和集群拆分,最基本的是拆分多實(shí)例,其次是按照業(yè)務(wù)類型對資源占用情況分類,按用戶接入網(wǎng)絡(luò)和對idc布點(diǎn)要求分類(目前沒有條件,所有的產(chǎn)品都部署到全部idc)
系統(tǒng)的測試go語言在并發(fā)測試上有獨(dú)特優(yōu)勢。
對于壓力測試,目前主要針對指定的服務(wù)器,選定線上空閑的服務(wù)器做長連接壓測。然后結(jié)合可視化,分析壓測過程中的系統(tǒng)狀態(tài)。但壓測早期用的比較多,但實(shí)現(xiàn)的統(tǒng)計報表功能和我理想有一定差距。我覺得最近出的golang開源產(chǎn)品都符合這種場景,go寫網(wǎng)絡(luò)并發(fā)程序給大家?guī)淼谋憷?,讓大家把以往為了降低?fù)雜度,拆解或者分層協(xié)作的組件,又組合在了一起。
QA
Q1:協(xié)議棧大小,超時時間定制原則?
移動網(wǎng)絡(luò)下超時時間按產(chǎn)品需求通常2g,3G情況下是5分鐘,wifi情況下5~8分鐘。但對于個別場景,要求響應(yīng)非常迅速的場景,如果連接idle超過1分鐘,都會有ping,pong,來校驗(yàn)是否斷線檢測,盡快做到重新連接。
Q2:消息是否持久化?
消息持久化,通常是先存后發(fā),存儲用的redis,但落地用的mysql。mysql只做故障恢復(fù)使用。
Q3:消息風(fēng)暴怎么解決的?
如果是發(fā)送情況下,普通產(chǎn)品是不需要限速的,對于較大產(chǎn)品是有發(fā)送隊(duì)列做控速度,按人數(shù),按秒進(jìn)行控速度發(fā)放,發(fā)送成功再發(fā)送下一條。
Q4:golang的工具鏈支持怎么樣?我自己寫過一些小程序千把行之內(nèi),確實(shí)很不錯,但不知道代碼量上去之后,配套的debug工具和profiling工具如何,我看上邊有分享說golang自帶的profiling工具還不錯,那debug呢怎么樣呢,官方一直沒有出debug工具,gdb支持也不完善,不知你們用的什么?
是這樣的,我們正常就是println,我感覺基本上可以定位我所有問題,但也不排除由于并行性通過println無法復(fù)現(xiàn)的問題,目前來看只能靠經(jīng)驗(yàn)了。只要常見并發(fā)嘗試,經(jīng)過分析是可以找到的。go很快會推出調(diào)試工具的~
Q5:協(xié)議棧是基于tcp嗎?
是否有協(xié)議拓展功能?協(xié)議棧是tcp,整個系統(tǒng)tcp長連接,沒有考慮擴(kuò)展其功能~如果有好的經(jīng)驗(yàn),可以分享~
Q6:問個問題,這個系統(tǒng)是接收上行數(shù)據(jù)的吧,系統(tǒng)接收上行數(shù)據(jù)后是轉(zhuǎn)發(fā)給相應(yīng)系統(tǒng)做處理么,是怎么轉(zhuǎn)發(fā)呢,如果需要給客戶端返回調(diào)用結(jié)果又是怎么處理呢?
系統(tǒng)上行數(shù)據(jù)是根據(jù)協(xié)議頭進(jìn)行轉(zhuǎn)發(fā),協(xié)議頭里面標(biāo)記了產(chǎn)品和轉(zhuǎn)發(fā)類型,在coordinator里面跟進(jìn)產(chǎn)品和轉(zhuǎn)發(fā)類型,回調(diào)用戶,如果用戶需要阻塞等待回復(fù)才能后續(xù)操作,那通過再發(fā)送消息,路由回用戶。因?yàn)檎麄€系統(tǒng)是全異步的。
Q7:問個pushsdk的問題。pushsdk的單連接,多app復(fù)用方式,這樣的情況下以下幾個問題是如何解決的:1)系統(tǒng)流量統(tǒng)計會把所有流量都算到啟動連接的應(yīng)用吧?而啟動應(yīng)用的連接是不固定的吧?2)同一個pushsdk在不同的應(yīng)用中的版本號可能不一樣,這樣暴露出來的接口可能有版本問題,如果用單連接模式怎么解決?
流量只能算在啟動的app上了,但一般這種安裝率很高的app承擔(dān)可能性大,常用app本身被檢測和殺死可能性較少,另外消息下發(fā)量是有嚴(yán)格控制 的。整體上用戶還是省電和省流量的。我們pushsdk盡量向上兼容,出于這個目的,push sdk本身做的工作非常有限,抽象出來一些常見的功能,純推的系統(tǒng),客戶端策略目前做的很少,也有這個原因。
Q8:生產(chǎn)系統(tǒng)的profiling是一直打開的么?
不是一直打開,每個集群都有采樣,但需要開啟哪個可以后臺控制。這個profling是通過接口調(diào)用。
Q9:面前系統(tǒng)中的消息消費(fèi)者可不可以分組?類似于Kafka。
客戶端可以訂閱不同產(chǎn)品的消息,接受不同的分組。接入的時候進(jìn)行bind或者unbind操作
Q10:為什么放棄erlang,而選擇go,有什么特別原因嗎?我們現(xiàn)在用的erlang?
erlang沒有問題,原因是我們上線后,其他團(tuán)隊(duì)才做出來,經(jīng)過qa一個部門對比測試,在沒有顯著性能提升下,選擇繼續(xù)使用go版本的push,作為公司基礎(chǔ)服務(wù)。
Q11:流控問題有排查過網(wǎng)卡配置導(dǎo)致的idle問題嗎?
流控是業(yè)務(wù)級別的流控,我們上線前對于內(nèi)網(wǎng)的極限通信量做了測試,后續(xù)將請求在rpc庫內(nèi),控制在小于內(nèi)部通信開銷的上限以下.在到達(dá)上限前作流控。
Q12:服務(wù)的協(xié)調(diào)調(diào)度為什么選擇zk有考慮過raft實(shí)現(xiàn)嗎?golang的raft實(shí)現(xiàn)很多啊,比如Consul和ectd之類的。
3年前,還沒有后兩者或者后兩者沒聽過應(yīng)該。zk當(dāng)時公司內(nèi)部成熟方案,不過目前來看,我們不準(zhǔn)備用zk作結(jié)合系統(tǒng)的定制開發(fā),準(zhǔn)備用自己寫的keeper代替zk,完成配置文件自動轉(zhuǎn)數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)自動同步指定進(jìn)程,同時里面可以完成很多自定義的發(fā)現(xiàn)和控制策略,客戶端包含keeper的sdk就可以實(shí)現(xiàn)以上的所有監(jiān)控數(shù)據(jù),profling數(shù)據(jù)收集,配置文件更新,啟動關(guān)閉等回調(diào)。完全抽象成語keeper通信sdk,keeper之間考慮用raft。
Q13:負(fù)載策略是否同時在服務(wù)側(cè)與CLIENT側(cè)同時做的 (DISPATCHER 會返回一組IP)?另外,ROOM SERVER/REGISTER SERVER連接狀態(tài)的一致性|可用性如何保證? 服務(wù)側(cè)?;钣袩o特別關(guān)注的地方? 安全性方面是基于TLS再加上應(yīng)用層加密?
會在server端做,比如重啟操作前,會下發(fā)指令類型消息,讓客戶端進(jìn)行主動行為。部分消息使用了加密策略,自定義的rsa+des,另外滿足我們安全公司的需要,也定制開發(fā)很多安全加密策略。一致性是通過冷備解決的,早期考慮雙寫,但實(shí)時狀態(tài)雙寫同步代價太高而且容易有臟數(shù)據(jù),比如register掛了,調(diào)用所有room,通過重新刷入指定register來解決。
Q14:這個keeper有開源打算嗎?
還在寫,如果沒耦合我們系統(tǒng)太多功能,一定會開源的,主要這意味著,我們所有的bind在sdk的庫也需要開源~
Q15:比較好奇lisence是哪個如果開源?
FreeBSD
本文標(biāo)題:zk的go語言客戶端,go zk
文章URL:http://aaarwkj.com/article34/dsspjpe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)、服務(wù)器托管、網(wǎng)站導(dǎo)航、、域名注冊
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)