微服務現在是一個很火的話題,好像不管項目的大小,適用范圍都在往微服務上去靠。這也使得現在如果不會微服務出去都沒法和別人聊了。
僅從我自己的工作經歷來看,盡管我們的項目也是微服務化了的。但是說實話在業務開發過程中并沒有體會到微服務的開發和單體項目開發中存在很大的區別(僅業務開發這一塊來說)。
微服務的學習單獨的寫幾個demo并沒有啥用,現在的框架封裝程度都很高,就算是代碼跑起來來依舊是云里霧里。微服務的學習更多的是搞清楚微服務中每一個組件到底是什么?為什么有這些組件?沒有會怎么樣?功能大概是怎么實現的?
搞清楚來這些基本上就可以說對微服務有個大體的掌握了。
為什么需要注冊中心
在微服務中首先需要面對的問題就是不同的服務之間如何進行通信呢?在單體服務中如果不同的服務之間需要通信,一般就是服務將接口暴露,然后其他的服務通過http進行請求,那么很明顯在微服務中也可以這樣。
但這里存在的問題在于單體服務中我們需要請求的目標是我們在請求的url中直接寫死的,因為服務不多可以這樣。而微服務中需要考慮的是存在大量服務時手動維護服務列表是否合適?如果服務橫向擴展時如何通知其他的服務?服務宕機后,如何及時下線等等問題。
注冊中心的功能
注冊中心的存在是為了更好更方便的管理應用中的每一個服務,是各個分布式節點之間的紐帶。注冊中心主要提供以下核心功能:
- 服務注冊與發現:動態的增減服務節點,服務節點增減后動態的通知服務消費者,而不需要由消費者來更新配置。
- 服務配置:動態修改服務配置,并將其推送到服務提供者和服務消費者而不需要重啟服務。
- 健康檢查和服務摘除:主動的檢查服務健康情況,對于宕機的服務將其摘除服務列表。
注冊中心的實現
注冊中心的主要功能就是保存服務的具體信息,然后由服務消費者讀取這些信息。從整體的流程上來說大概就是這樣:

除了將服務注冊到注冊中心,實際還存在服務的反注冊。
目前對注冊中心的實現分為兩種模式,分別為客戶端(應用內注冊)和服務端(應用外注冊)。
客戶端注冊中心
目前客戶端實現的注冊中心比較有代表性的就是eureka,雖然eureka2.0版本在此前宣布閉源,但目前好像沒有太大的影響。后續的話可以考慮阿里開源的nacos。
eureka是一個基于JAVA語言實現的費用與服務發現與注冊的組件,包含服務端和客戶端兩部分。
服務端主要用來存儲服務信息,定時進行服務檢查。而客戶端用于完成向服務端注冊服務信息以及從服務端拉取服務信息等。

上圖是eureka官給出的eureka的架構圖。
從圖中可以很明顯的看到eureka的服務端也是作為一個服務部署,而客戶端則是通過sdk的方式接入應用。客戶端向服務端進行服務注冊以及更新服務等,同時客戶端從服務端獲取服務信息,不同服務之間根據獲得的服務信息進行遠程調用。
為了滿足服務的高可用,通過移動多個eureka server服務,不同eureka server相互注冊實現服務的高可用。
服務端注冊中心
eureka是由注冊中心提供服務端和客戶端的SDK,業務端通過引入注冊中心提供的SDK,來實現服務的注冊和發現。而服務端的注冊中心則是通過應用外的組件來實現服務注冊功能的。目前用的比較多的服務端注冊中心組件如zookeeper、consul等。這里以zookeeper為例。
Zookeeper 是 Apache Hadoop 的子項目,是一個樹型的目錄服務,支持變更推送,適合作為 Dubbo 服務的注冊中心,工業強度較高,可用于生產環境,是目前Dubbo官方主推搭配的注冊中心。

上圖是dubbo官網提供的zookeeper作為注冊中心的基本原理。
以dubbo為根目錄,創建一服務接口全限定名的目錄,在其下創建存放服務提供者接口信息的providers目錄、存放服務消費者接口信息的consumers目錄、存放服務配置信息的configurators目錄等。
服務提供者啟動的時候在providers下創建一條服務信息,該信息可以被consumer獲取。通過zk提供的watcher機制注冊一個watcher在服務提供者的providers目錄下,這樣當服務出現擴容或者宕機的時候就可以立即被consumer消費。
注冊中心的問題
在微服務中,注冊中心作為一個存儲所有服務中心的地方必然不可能是單機的。因為如果注冊中心無法使用,那么服務提供者就無法對外暴露自己的服務,消費者也無法獲取自己需要調用的服務的具體地址,整個應用將會崩潰。首先需要保證的就是注冊中心的高可用。
一致性和可用性的抉擇
說到高可用,CAP理論是繞不過去的,CAP簡單來說就是我們需要在服務的可用性和服務間的一致性進行一個抉擇。是犧牲可用性來保證強一致性還是保證可用性犧牲強一致性呢?
CP類型注冊中心
Zookeeper是一個典型的CP類型的高可用組件。zookeeper實現來paxos算法,zookeeper集群有一個節點作為leader,如果leader節點掛了zk會通過ZAB協議來進行leader選舉,但是在選舉的過程中zk是不能對外提供服務的。
而且當因為網絡分區導致zk集群出現腦裂問題時,由于ZAB協議需要半數以上的節點參與,所以可能會有部分或者全部的zk節點無法對外提供服務。但是zk可以保證所有可用節點都數據都是一致,即使節點崩潰,在恢復后也會和其他節點保持一致,這就是zk犧牲了可用性而保證的強一致性。
AP類型注冊中心
而類似與eureka的注冊中心則是AP類型的。eureka實現高可用是通過啟動多個eureka server服務,每一個eureka server即是提供者也是消費者,每個eureka server將自己作為服務注冊給其他的eureka server,這樣每個eureka server都是其他server 的replica。eureka這種模式中每個節點都是平等的,不存在leader、flower。
當集群中某一個server節點宕機,請求可以直接轉移到其他節點。即使發生了網絡分區,盡管不同分區之間無法進行通信,但是在每一個分區內的節點是通信并且可以正常對外提供服務,這樣就保證了在server節點宕機的情況下的注冊中心的可用性。
而問題在于由于不同分區無法通信,就導致可能一部分節點的數據和另一部分有差別,這就是犧牲了強一致性來換取系統可用性。
注冊中心的選擇
首先我們應該明確的是其實注冊中心的存在與否應該是不能直接影響服務的調用的。服務之間的調用時通過http或者rpc等協議直接調用的,注冊中心只是提供一個調用地址。服務是否可以正常使用不能直接靠注冊中心節點信息來決定。
即使注冊中心出現問題,只要服務本身沒有宕機,理論上來說還是應該正常對外提供服務。所以很明顯對于我們來說AP類型的注冊中心是要優于CP類型的注冊中心的。
當然在實際實現過程中很多框架都會盡可能的去修補這些問題,比如eureka會定時的同步各個節點的數據,盡可能的做到數據一致性。dubbo的zk注冊中心實現過程中也會本地緩存服務信息并不是完全通過zk信息來決定是否剔除服務等。所以實際在選擇時還是需要根據自身實際以及是否還有其他需求來確定。
混合語言開發
在微服務中,每一個服務都是一個獨立的整體。各個服務之間并不向單體應用中的不同模塊大都要求是同一語言實現。現如今各大公司開發語言是多樣化的,不同的業務線開發的語言可能都不盡相同,混合語言是我們必須要面對的一個問題,所以出現了多語言之間服務調用的問題。
對于像eureka這樣的應用內的注冊中心。由于服務的注冊與發現都是依賴于SDK,所以如果要使用eureak那需要對應的語言實現的SDK,目前有不少語言都有提供如Python、node.js。目前springclould的Sidecar組件也可以做到將其他語言納入到springclould體系中來。
對于應用外的注冊中心,一般會由這些中間件提供客戶端操作實現。然后由語言本身來實現服務注冊和治理等功能。目前很多語言實際上也有相關的開源工程。
節點存活判斷
前面已經說過了保證CP類型的強一致性注冊中心以及AP類型保證可用性的注冊中心。而對于一個注冊中心來說最重要的就是管理其中的節點信息。服務啟動的時候需要將服務信息記錄,當服務出現問題的時候需要將服務摘除。
但是問題在于如何實現這個摘除的操作,目前注冊中心的實現都是通過心跳檢測來判斷服務的健康狀態。正常情況下如果服務宕機了,心跳檢測無法和服務通信判斷該節點無法正常服務,將服務從注冊中心摘除,這個過程是沒有問題的。但是如果發生網絡問題,比如網絡抖動或者網絡分區,這時候心跳檢測肯定是無法正常通信的,但如果就因此直接將服務摘除那肯定是有問題的,因為節點實際上是能夠正常提供服務的。所以需要有機制來確保這個過程不會粗暴的將節點從注冊中心摘除。
客戶端判斷
目前很多的框架實現都會在本地緩存微服務的節點信息。實際上使用這些節點的是各個服務,服務是否能正常使用,這些節點才是最有發言權的。
客戶端緩存了節點信息,當服務端判定服務出現問題后發出更新請求時,客戶端并不立刻刪除,可以先做一個標識。后續的業務請求過來后,仍舊判定該服務時可用的,只有當發出的請求無法收到正常回應時才將該服務摘除。由客戶端來決定節點是否可用,不過這需要容錯機制來支持。
動態設置心跳檢測
在網絡頻繁抖動的情況下,注冊中心的節點信息會快速變化,也會給客戶端發送大量的信息,當服務較多的時候可能會有大量的帶寬被占用導致正常的請求都無法處理。
所以需要一種動態的調整注冊中心心跳檢測頻率的機制,當檢測到網絡抖動頻繁時,根據網絡情況調整心跳檢測的頻率,比如調整為正常情況下的1/2,10/1等。在網絡正常時心跳檢測也恢復正常。
小結
注冊中心是微服務架構中一個很關鍵的組件,其保證來各個服務之間正常調用,以及服務的橫向擴容等功能。在進行注冊中心選型時需要考慮業務場景。
作者:不能摸魚啦
鏈接:
https://juejin.im/post/5ebe0ae36fb9a0432a3c43b6