CMDB在企業中,一般用於存放與機器裝置、應用、服務等相關的後設資料。當企業的機器及應用達到一定規模後就需要這樣一個系統來儲存和管理它們的後設資料。有一些廣泛使用的屬性,例如機器的IP、主機名、機房、應用、region等,這些資料一般會在機器部署時錄入到CMDB,運維或者監控平臺會使用這些資料進行展示或者相關的運維操作。
在服務進行多機房或者多地域部署時,跨地域的服務訪問往往延遲較高,一個城市內的機房間的典型網路延遲在1ms左右,而跨城市的網路延遲,例如上海到北京大概為30ms。此時自然而然的一個想法就是能不能讓服務消費者和服務提供者進行同地域訪問。我們在集團內部的實踐中,這樣的需求是通過和CMDB打通來實現的。在Nacos的服務發現元件中,對接CMDB,然後通過配置的訪問規則,來實現服務消費者到服務提供者的同地域優先。
這實際上就是一種負載均衡策略,在Nacos的規劃中,豐富的服務端的可配置負載均衡策略是我們的重要發展方向,這與當前已有的註冊中心產品不太一樣。在設計如何在開源的場景中,支援就近訪問的時候,與企業自帶的CMDB整合是我們考慮的一個核心問題。除此之外,我們也在考慮將Nacos自身擴充套件為一個實現基礎功能的CMDB。無論如何,我們都需要能夠從某個地方獲取IP的環境資訊,這些資訊要麼是從企業的CMDB中查詢而來,要麼是從自己內建的儲存中查詢而來。
CMDB外掛機制
先不考慮如何將CMDB的資料應用於負載均衡,我們需要首先在Nacos裡將CMDB的資料通過某種方法獲取。在實際使用中,基本上每個公司都會通過購買或者自研搭建自己的CMDB,那麼為了能夠解耦各個企業的CMDB具體實現,一個比較好的策略是使用SPI機制,約定CMDB的抽象呼叫介面,由各個企業新增自己的CMDB外掛,無需任何程式碼上的重新構建,即可在執行狀態下對接上企業的CMDB。
如圖2所示,Nacos定義了一個SPI介面,裡面包含了與第三方CMDB約定的一些方法。使用者依照約定實現了相應的SPI介面後,將實現打成jar包放置到Nacos安裝目錄下,重啟Nacos即可讓Nacos與CMDB的資料打通。整個流程並不複雜,但是理解CMDB SPI介面裡方法和相應概念的含義不太簡單。在這裡對CMDB機制的相關概念和介面含義做一個詳細說明。
CMDB抽象概念
實體(Entity)
實體是作為CMDB裡資料的承載方,在一般的CMDB中,一個實體可以指一個IP、應用或者服務。而這個實體會有很多屬性,例如IP的機房資訊,服務的版本資訊等。
實體型別(Entity Type)
我們並不限定實體一定是IP、應用或者服務,這取決於實際的業務場景。Nacos有計劃在未來支援不同的實體型別,不過就目前來說,服務發現需要的實體型別是IP。
標籤(Label)
Label是我們抽象出的Entity屬性,Label定義為一個描述Entity屬性的K-V鍵值對。Label的key和value的取值範圍一般都是預先定義好的,當需要對Label進行變更,如增加新的key或者value時,需要呼叫單獨的介面並觸發相應的事件。一個常見的Label的例子是IP的機房資訊,我們認為機房(site)是Label的key,而機房的集合(site1, site2, site3)是Label的value,這個Label的定義就是:site: {site1, site2, site3}。
實體事件(Entity Event)
實體的標籤的變更事件。當CMDB的實體屬性發生變化,需要有一個事件機制來通知所有訂閱方。為了保證實體事件攜帶的變更資訊是最新準確的,這個事件裡只會包含變更的實體的標識以及變更事件的型別,不會包含變更的標籤的值。
CMDB約定介面
在設計與CMDB互動介面的時候,我們參考了內部對CMDB的訪問介面,並與若干個外部客戶進行了討論。我們最終確定了以下要求第三方CMDB外掛必須實現的介面:
獲取標籤列表
Set<String> getLabelNames();複製程式碼
這個方法將返回CMDB中需要被Nacos識別的標籤名集合,CMDB外掛可以按需決定返回什麼標籤個Nacos。不在這個集合的標籤將會被Nacos忽略,即使這個標籤出現在實體的屬性裡。我們允許這個集合會在執行時動態變化,Nacos會定時去呼叫這個介面重新整理標籤集合。
獲取實體型別
Set<String> getEntityTypes();複製程式碼
獲取CMDB裡的實體的型別集合,不在這個集合的實體型別會被Nacos忽略。服務發現模組目前需要的實體類似是ip,如果想要通過打通CMDB資料來實現服務的高階負載均衡,請務必在返回集合裡包含“ip”。
獲取標籤詳情
Label getLabel(String labelName);複製程式碼
獲取標籤的詳細資訊。返回的Label類裡包含標籤的名字和標籤值的集合。如果某個實體的這個標籤的值不在標籤值集合裡,將會被視為無效。
查詢實體的標籤值
String getLabelValue(String entityName, String entityType, String labelName);
Map<String, String> getLabelValues(String entityName, String entityType);複製程式碼
這裡包含兩個方法,一個是獲取實體某一個標籤名對應的值,一個是獲取實體所有標籤的鍵值對。引數裡包含實體的值和實體的型別。注意,這個方法並不會在每次在Nacos內部觸發查詢時去呼叫,Nacos內部有一個CMDB資料的快取,只有當這個快取失效或者不存在時,才會去訪問CMDB外掛查詢資料。為了讓CMDB外掛的實現儘量簡單,我們在Nacos內部實現了相應的快取和重新整理邏輯。
查詢實體
Map<String, Map<String, Entity>> getAllEntities();
Entity getEntity(String entityName, String entityType);複製程式碼
查詢實體包含兩個方法:查詢所有實體和查詢單個實體。查詢單個實體目前其實就是查詢這個實體的所有標籤,不過我們將這個方法與獲取所有標籤的方法區分開來,因為查詢單個實體方法後面可能會進行擴充套件,比查詢所有標籤獲取的資訊要更多。
查詢所有實體則是一次性將CMDB的所有資料拉取過來,該方法可能會比較消耗效能,無論是對於Nacos還是CMDB。Nacos內部呼叫該方法的策略是通過可配置的定時任務週期來定時拉取所有資料,在實現該CMDB外掛時,也請關注CMDB服務本身的效能,採取合適的策略。
查詢實體事件
List<EntityEvent> getEntityEvents(long timestamp);複製程式碼
這個方法意在獲取最近一段時間內實體的變更訊息,增量的去拉取變更的實體。因為Nacos不會實時去訪問CMDB外掛查詢實體,需要這個拉取事件的方法來獲取實體的更新。引數裡的timestamp為上一次拉取事件的時間,CMDB外掛可以選擇使用或者忽略這個引數。
CMDB外掛開發流程
參考 github.com/nacos-group…,這裡已經給出了一個示例plugin實現。
具體步驟如下:
新建一個maven工程,引入依賴nacos-api:
<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-api</artifactId> <version>0.7.0</version> </dependency>複製程式碼
引入打包外掛:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin>複製程式碼
定義實現類,繼承com.alibaba.nacos.api.cmdb.CmdbService,並實現相關方法。
在src/main/resource/目錄下新建目錄:META-INF/services
在src/main/resources/META-INF/services目錄下新建檔案com.alibaba.nacos.api.cmdb.CmdbService,並在檔案裡將第三步中建立的實現類全名寫入該檔案:
程式碼自測完成後,執行命令進行打包:
mvn package assembly:single -Dmaven.test.skip=true複製程式碼
將target目錄下的包含依賴的jar包上傳到nacos CMDB外掛目錄:
{nacos.home}/plugins/cmdb複製程式碼
在nacos的application.properties裡開啟載入外掛開關:
nacos.cmdb.loadDataAtStart=true複製程式碼
- 重啟nacos Server,即可載入到您實現的nacos-cmdb外掛獲取您的CMDB資料。
使用Selector實現同機房優先訪問
在拿到CMDB的資料之後,就可以運用CMDB資料的強大威力來實現多種靈活的負載均衡策略了,下面舉例來說明如何使用CMDB資料和Selector來實現就近訪問。
假設目前Nacos已經通過CMDB拿到了一些IP的機房資訊,且它們對應的標籤資訊如下:
11.11.11.11
site: x11
22.22.22.22
site: x12
33.33.33.33
site: x11
44.44.44.44
site: x12
55.55.55.55
site: x13複製程式碼
11.11.11.11、22.22.22.22、33.33.33.33、44.44.44.44和55.55.55.55.55都包含了標籤site,且它們對應的值分別為x11、x12、x11、x12、x13。我們先註冊一個服務,下面掛載IP11.11.11.11和22.22.22.22。
然後我們修改服務的“服務路由型別”,並配置為基於同site優先的服務路由:
這裡我們將服務路由型別選擇為標籤,然後輸入標籤的表示式:
CONSUMER.label.site = PROVIDER.label.site複製程式碼
這個表示式的格式和我們抽象的Selector機制有關,具體將會在另外一篇文章中介紹。在這裡您需要記住的就是,任何一個如下格式的表示式:
CONSUMER.label.labelName = PROVIDER.label.labelName複製程式碼
將能夠實現基於同labelName優先的負載均衡策略。
然後假設服務消費者的IP分別為33.33.33.33、44.44.44.44和55.55.55.55,它們在使用如下介面查詢服務例項列表:
naming.selectInstances("nacos.test.1", true)複製程式碼
那麼不同的消費者,將獲取到不同的例項列表。33.33.33.33獲取到11.11.11.11,44.44.44.44將獲取到22.22.22.22,而55.55.55.55將同時獲取到11.11.11.11和22.22.22.22。
以上,便是我們在Nacos中通過打通CMDB,實現就近訪問的實踐。Nacos是阿里巴巴開源的服務註冊與配置管理產品,專案地址:github.com/alibaba/nac…