将target目錄下的包含依賴的jar包上傳到nacos CMDB插件目錄:
{nacos.home}/plugins/cmdb
在服務進行多機房或者多地域部署時,跨地域的服務通路往往延遲較高,一個城市内的機房間的典型網絡延遲在1ms左右,而跨城市的網絡延遲,例如上海到北京大概為30ms。此時自然而然的一個想法就是能不能讓服務消費者和服務提供者進行同地域通路。
我們在集團内部的實踐中,這樣的需求是通過和CMDB打通來實作的。Nacos的服務發現元件中,對接CMDB,然後通過配置的通路規則,來實作服務消費者到服務提供者的同地域優先。
圖1 服務的同地域優先通路
這實際上就是一種負載均衡政策,在Nacos的規劃中,豐富的服務端的可配置負載均衡政策是我們的重要發展方向,這與目前已有的注冊中心産品不太一樣。在設計如何在開源的場景中,支援就近通路的時候,與企業自帶的CMDB內建是我們考慮的一個核心問題。除此之外,我們也在考慮将Nacos自身擴充為一個實作基礎功能的CMDB。無論如何,我們都需要能夠從某個地方擷取IP的環境資訊,這些資訊要麼是從企業的CMDB中查詢而來,要麼是從自己内置的存儲中查詢而來。
CMDB插件機制
先不考慮如何将CMDB的資料應用于負載均衡,我們需要首先在Nacos裡将CMDB的資料通過某種方法擷取。在實際使用中,基本上每個公司都會通過購買或者自研搭建自己的CMDB,那麼為了能夠解耦各個企業的CMDB具體實作,一個比較好的政策是使用SPI機制,約定CMDB的抽象調用接口,由各個企業添加自己的CMDB插件,無需任何代碼上的重新建構,即可在運作狀态下對接上企業的CMDB。
圖2 Nacos CMDB SPI機制原理
如圖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插件開發流程
參考
https://github.com/nacos-group/nacos-examples,這裡已經給出了一個示例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
{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。
圖3 服務詳情
然後我們修改服務的“服務路由類型”,并配置為基于同site優先的服務路由:
圖4 編輯服務路由類型
這裡我們将服務路由類型選擇為标簽,然後輸入标簽的表達式:
CONSUMER.label.site = PROVIDER.label.site
• 1
這個表達式的格式和我們抽象的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。