StaticHostProvider、Chroot命名空間

在使用ZooKeeper構(gòu)造方法時,用戶傳入的ZooKeeper服務器地址列表,即connectString參數(shù),通常是這樣一個使用英文狀態(tài)逗號分隔的多個IP地址和端口的字符串:

  • 192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181

    從這個地址串中我們可以看出,ZooKeeper客戶端允許我們將服務器的所有地址都配置在一個字符串上,于是一個問題就來了:ZooKeeper客戶端在連接服務器的過程中,是如何從這個服務器列表中選擇服務器機器的呢?是按序訪問,還是隨機訪問呢?
    
    ZooKeeper客戶端內(nèi)部在接收到這個服務器地址列表后,會將其首先放入一個ConnectStringParser對象中封裝起來。ConnectStringParser是一個服務器地址列表的解析器,該類的基本結(jié)構(gòu)如下:
    
image.png
    ConnectStringParser解析器將會對傳入的connectString做兩個主要處理:解析chrootPath;保存服務器地址列表。

Chroot:客戶端隔離命名空間

     在3.2.0及其之后版本的ZooKeeper中,添加了“Chroot”特性,該特性允許每個客戶端為自己設(shè)置一個命名空間(Namespace)。如果一個ZooKeeper客戶端設(shè)置了Chroot,那么該客戶端對服務器的任何操作,都將會被限制在其自己的命名空間下。

    舉個例子來說,如果我們希望為應用X分配/apps/X下的所有子節(jié)點,那么該應用可以將其所有ZooKeeper客戶端的Chroot設(shè)置為/apps/X的。一旦設(shè)置了Chroot之后,那么對這個客戶端來說,所有的節(jié)點路徑都以/apps/X為根節(jié)點,他和ZooKeeper發(fā)起的所有請求中相關(guān)的節(jié)點路徑,都將是一個相對路徑——相對于/apps/X的路徑。例如通過ZooKeeper客戶端API創(chuàng)建節(jié)點/test_chroot,那么實際上在服務端被創(chuàng)建的節(jié)點是/apps/X/test_chroot。通過設(shè)置Chroot,那么實際上在服務端被創(chuàng)建的節(jié)點是/apps/X/test_chroot。通過設(shè)置Chroot,我們能夠?qū)⒁粋€客戶端因夠用與ZooKeeper服務端的一棵子樹相對應,在那些多個因夠用共用一個ZooKeeper集群的場景下,這對于實現(xiàn)不同應用之間的相互隔離非常有幫助。

    客戶端可以通過在connecString中添加后綴的方式來設(shè)置Chroot,如下所示:
  • 192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181/apps/X

    將這樣一個connectString傳入客戶端的ConnectStringParser后就能夠解析出Chroot并保存在chrootPath屬性中。
    

HostProvider:地址列表管理器

    在ConnectStringParser解析器中會對服務器地址做一個簡單的處理,并將服務器地址和相應的端口封裝成一個InetSocketAddress對象,以ArrayList形式保存在ConnectStringParser.serverAddress屬性中。然后,經(jīng)過處理的地址列表會被進一步封裝到StaticHostProvider類中。

    在講解StaticHostProvider之前,我們首先來看其對應的接口:HostProvider。HostProvider類定義了一個客戶端的服務器地址管理器:
image.png
    其各接口方法的定義說明如下表所示。

| 接口方法 | 說明 |
| int size() | 該方法用于返回當前服務器地址列表的個數(shù) |
| InetSocketAddress next(long spinDelay) | 該方法用于返回一個服務器地址InetSocketAddress,以便客戶端進行服務器連接 |
| void onConnected() | 這時一個回調(diào)方法,如果客戶端與服務器成功創(chuàng)建連接,就通過調(diào)用這個方法來通知HostProvider |

    ZooKeeper規(guī)定,任何對于該接口的實現(xiàn)必須滿足以下3點,這里簡稱為“HostProvider三要素”。
  • next()方法必須要有合法的返回值。

ZooKeeper規(guī)定,凡是對該方法的調(diào)用,必須要返回一個合法的InetSocketAddress對象。也就是說,不能返回null或其他不合法的InetSocketAddress。

  • next()方法必須返回已解析的InetSocketAddress對象。

在上面我們已經(jīng)提到,服務器的地址列表已經(jīng)被保存在ConnectStringParser.serverAddresses中,但是需要注意的一點是,此時里面存放的都是沒有被解析的InetSocketAddress。在進一步傳遞到HostProvider后,HostProvider需要負責來這個InetSocketAddress列表進行解析,不一定是在next()方法中來解析,但是無論如何,最終在next()方法中返回的必須是已被解析的InetSocketAddress對象。

  • size()方法不能返回0。

ZooKeeper規(guī)定了該方法不能返回0,也就是說,HostProvider中必須至少有一個服務器地址。

StaticHostProvider

    接下來我們看看ZooKeeper客戶端中對HostProvider的默認實現(xiàn):StaticHostProvider,其數(shù)據(jù)結(jié)構(gòu)如下圖所示。
image.png

解析服務器地址

    針對ConnectStringParser.serverAddresses集合中那些沒有被解析的服務器地址,StaticHostProvider首先會對這些地址逐個進行解析,然后再放入serverAddresses集合中去。同時,使用Collections工具類的shuffle方法來將這個服務器地址列表進行隨機的打散。

獲取可用的服務器地址

    通過調(diào)用StaticHostProvider的next()方法,能夠從StaticHostProvider中獲取一個可用的服務器地址。這個next()方法并非簡單的從serverAddress中依次獲取一個服務器地址,而是先將隨機打散后的服務器地址列表拼裝成一個環(huán)形循環(huán)隊列,如下圖所示。注意,這個隨機過程是一次性的,也就是說,之后的使用過程中一直是按照這樣的順序來獲取服務器地址的。
image
    舉個例子來說,假如客戶端傳入這樣一個地址列表:“host1,host2,host3,host4,host5”。經(jīng)過一輪隨機打散后,可能的一種順序變成了“host2,host4,host1,host5,host3”,并且形成了上圖所示的循環(huán)隊列。此外,HostProvider還會為該循環(huán)隊列創(chuàng)建兩個游標:currentIndex和lastIndex。currentIndex表示循環(huán)隊列中當前遍歷到的那個元素位置,lastIndex則表示當前正在使用的服務器地址位置。初始化的時候,currentIndex和lastIndex的值都為-1。

    在每次嘗試獲取一個服務器地址的時候,都會首先將currentIndex游標向前移動1位,如果發(fā)現(xiàn)游標移動超過了整個地址列表的長度,那么就重置為0,回到開始的位置重新開始,這樣一來,就實現(xiàn)了循環(huán)隊列。當然,對于那些服務器地址列表提供得比較少的場景,StaticHostProvider中做了一個小技巧,就是如果發(fā)現(xiàn)當前游標的位置和上次已經(jīng)使用過的地址位置一樣,即當currentIndex和lastIndex游標值相同時,就進行spinDelay毫秒時間的等待。

    總的來說,StaticHostProvider就是不斷地從上圖所示的環(huán)形地址列表隊列中去獲取一個地址,整個過程非常類似于“Round Robin”的調(diào)度策略。

對HostProvider的幾個設(shè)想

    StaticHostProvider只是ZooKeeper官方提供的對于地址列表管理器的默認實現(xiàn)方式,也是最通用和最簡單的一種實現(xiàn)方式。讀者如果有需要的話,完全可以在滿足上面提到的“HostProvider三要素”的前提下,實現(xiàn)自己的服務器地址列表管理器。

配置文件方式

    在ZooKeeper默認的實現(xiàn)方式中,是通過在構(gòu)造方法中傳入服務器地址列表的方式來實現(xiàn)地址列表的設(shè)置,但其實通常開發(fā)人員更習慣于將例如IP地址這樣的配置信息保存在一個單獨的配置文件中統(tǒng)一管理起來。針對這樣的需求,我們可以自己實現(xiàn)一個HostProvider,通過在應用啟動的時候加載這個配置文件來實現(xiàn)對服務器地址列表的獲取。

動態(tài)變更的地址列表管理器

    在ZooKeeper的使用過程中,我們會碰到這樣的問題:ZooKeeper服務器集群的整體遷移或個別機器的變更,會導致大批客戶端應用也跟著一起進行變更。出現(xiàn)這個尷尬局面的本質(zhì)原因是因為我們將一些可能會動態(tài)變更的IP地址寫死在程序中了。因此,實現(xiàn)動態(tài)變更的地址列表管理器,對于提升ZooKeeper客戶端用戶使用體驗非常重要。

    為了解決這個問題,最簡單的一種方式就是實現(xiàn)這樣一個HostProvider:地址列表管理器能夠定時從DNS或一個配置管理中心上解析出ZooKeeper服務器地址列表,如果這個地址列表變更了,那么就同時更新到serverAddresses集合中去,這樣在下次需要獲取服務器地址(即調(diào)用next()方法)的時候,就自然而然使用了新的服務器地址,隨著時間推移,慢慢的就能夠在保證客戶端透明的情況下實現(xiàn)ZooKeeper服務器機器的變更。

實現(xiàn)同機房優(yōu)先策略

    隨著業(yè)務增長,系統(tǒng)規(guī)模不斷擴大,我們對于服務器機房的需求也日益旺盛。同時,隨著系統(tǒng)穩(wěn)定性和系統(tǒng)容災等問題越來越被重視,很多互聯(lián)網(wǎng)公司會出現(xiàn)多個機房,甚至是異地機房。多機房,在提高系統(tǒng)穩(wěn)定性和容災能力的同時,也給我們帶來了一個新的困擾:如何解決不同機房之間的延時。我們以目前主流的采用光電波傳輸?shù)木W(wǎng)絡帶寬架構(gòu)(光纖中光速大約為20萬公里每秒,千兆帶寬)為例,對于杭州和北京之間相隔1500公里的兩個機房計算其網(wǎng)絡延時:

(15002)/(2010000)=15(毫秒)

    需要注意的是,這個15毫秒僅僅是一個理論上的最小值,在實際的情況中,我們的網(wǎng)絡線路并不能實現(xiàn)直線鋪設(shè),同時信號的干擾、光電信號的轉(zhuǎn)換以及自身的容錯修復對網(wǎng)絡通信都會有不小的影響,導致了在實際情況中,兩個機房之間可能達到30~40毫秒,甚至更大的延時。

    所以在目前大規(guī)模的分布式系統(tǒng)設(shè)計中,我們開始考慮引入“同機房優(yōu)先”的策略。所謂的“同機房優(yōu)先”是指服務的消費者優(yōu)先消費同一個機房中提供的服務。舉個例子來說,一個服務F在杭州機房和北京機房中都有部署,那么對于杭州機房中的服務消費者,會優(yōu)先調(diào)用杭州機房中的服務,對于北京機房的客戶端也一樣。

    對于ZooKeeper集群來說,為了達到容災要求,通常會將集群中的機器分開部署在多個機房中,因此同樣面臨上述網(wǎng)絡延時問題。對于這種情況,就可以實現(xiàn)一個能夠優(yōu)先和同機房ZooKeeper服務器創(chuàng)建的HostProvider。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容