什麼是微服務
大型系統架構中,會拆分多個子系統。簡單來說,這些子系統有兩個功能:提供接口、調用接口,在微服務架構中,將每一個這樣的子系統稱爲一個“微服務”;
每一個服務會部署多個實例(就是多臺機器,且會動態擴容,IP不固定);
這種情況下,需要使用eureka進行服務管理。服務ID/名稱 是唯一的標識, 接口調用前,根據ID在註冊中心找到對應的實例信息(ip端口等),然後再直調服務。
概念入門:
CAP理論,
C 數據的一致性 不是強一致
A 一定時間內一定有數據返回 error 提示信息
P 服務容錯性 一個系統中,我們的服務允許某一塊出錯。不影響系統整體的運行。
Dubbo –cp
Netflix – AP
完成微服務,我們需要做到:
- 高度的自治性.獨立的開發,部署,發佈
- 需要異構性。在協作的過程中,我們需要去思考不同服務最適合的技術(方案)。
- 彈性。容錯性
- 擴展。
- 簡化部署。
- 可組合性。
- 監控和日誌。定位問題。
- 微服務不是萬金油
微服務的特性
Springcloud的生態圈
搭建一個eureka
Spring cloud
官網:https://spring.io/projects/spring-cloud
項目創建地址:https://start.spring.io/
1.新增eureka服務端(可配置多個實現高可用)
2.新增eureka客戶端進行註冊
*註冊中心(非常重要):
1.什麼是註冊中心
註冊中心可以說是微服務架構中的”通訊錄“,它記錄了服務和服務地址的映射關係。在分佈式架構中,服務會註冊到這裏,當服務需要調用其它服務時,就到這裏找到服務的地址,進行調用。
2.爲什麼需要註冊中心
在分佈式系統中,我們不僅僅是需要在註冊中心找到服務和服務地址的映射關係這麼簡單,我們還需要考慮更多更復雜的問題:
服務註冊後,如何被及時發現?
服務宕機後,如何及時下線?
服務如何有效的水平擴展?
服務發現時,如何進行路由?
服務異常時,如何進行降級?
註冊中心如何實現自身的高可用?
這裏問題的解決都依賴於註冊中心。簡單看,註冊中心的功能有點類似於DNS服務器或者負載均衡器,而實際上,註冊中心作爲微服務的基礎組件,可能要更加複雜,也需要更多的靈活性和時效性。
在spring cloud中註冊中心有eureka,consul,zookeeper,nacos等.
我們這堂課就是以eureka來了解和認知註冊中心的:
Eureka的特性和實現
假設現在我們去設計一個eureka,我們要怎麼去分析:
1:註冊流程
自動裝配:
spring-cloud-netflix-eureka-server.jar META-INFO/Spring.factors EurekaServerAutoConfiguration
spring-cloud-netflix-eureka-client.jar META-INFO/Spring.factors EurekaClientAutoConfiguration EurekaDiscoveryClientConfiguration
分析:註冊流程,我們要怎麼去做?
- 加載文件,並獲取配置文件信息.我們有三份配置文件
Server配置信息 server 信息: EurekaServerConfigBean#EurekaServerConfig
Client配置信息 instance信息: EurekaClientAutoConfiguration#EurekaApplicationInfo()#create()*在這個方法裏傳入了一個接口,它的實現類: EurekaInstanceConfigBean
Client配置信息 client: EurekaClientAutoConfiguration# eurekaClientConfigBean
New 一個eurekaClientConfigBean 這裏定義了client的配置
- 實例化一個client端這樣我才能註冊
- 註冊前,我要確定這個client是否先被註冊了,不能重複註冊。
EurekaClientAutoConfiguration#eurekaClient()*實例化客戶端client跟蹤
DiscoveryClient 找定時任務,然後向下看,會有一句提示:啓動所有定時任務,跟蹤進去,找到heartbeat—然後會看到renew,renew規則:如果當前實例是404狀態,就去註冊,如果不是,返回true。
2:註冊中心,怎麼接收請求
註冊中心的請求:
- 心跳請求(刷新,註冊,心跳)
- 存儲服務實例
3:服務實例怎麼存儲。
courrentHashmap中
4:服務中心是怎麼實現的高可用?
高可用這一塊我們先去理解一下:對等. P2P 對等實例來實現的高可用 (peer to peer)
老規矩,我們繼續去尋找:EurekaServerAutoConfiguration下去尋找我們要的東西.
我們會看到eurekaServerContext() 這個方法.通過方法名,我們猜都能猜出來這個就是初始化上下文容器。
那麼我們來看一下它在初始化幹了什麼?它new 了一個對象 進入這個對象繼續深入:
我們可以在這裏看到,這個類初始化的時候會加載一個對象peerEurekaNodes.start()
位置DefaultEurekaServerContext#peerEurekaNodes 那麼我們進入它的start裏面去繼續看,
我們會發現它實際上是調用了updatePeerEurekaNodes(resolvePeerUrls) 這個resolvePeerUrls我們可以看到,它實際上就是我們通過實例獲取到的zone,也就是我們自己定義的集羣地址的集合.
回頭繼續看updatePeerEurekaNodes()方法, 裏面會調用一個共有變量newPeerUrls --夥伴節點地址。它會在這裏定義兩個Set,一個是toAdd(),一個是toshutdown(),這裏面我們可以看,它是去維護了新加入的實例,以及需要移除的實例,然後根據一系列的操作判斷,把已經終止的實例移除,再把新加入的實例寫入。
那麼我們也許會有疑問,如果說我們在運行期某個節點出現了故障,我們該怎麼辦?我們會發現PeerEurekaNodes#start()方法下做了一個定時任務,這個定時任務,會不斷的維護我們的eureka列表。
5:服務中心怎麼同步數據?怎麼防止循環傳播?
同步數據,我覺得我們就需要來看一下它是怎麼維護實例的。跟進一下
PeerAwareInstanceRegistryImpl這個類我們之前看到它怎麼註冊的,我們可以繼續跟進一下它的註冊流程.
進入到PeerAwareInstanceRegistryImpl之後,我們可以看到,它在註冊結束後,還調用了replicateToPeers 複製數據給其他節點,我們進入這個方法去看一下
我們看到了非常熟悉的peerEurekaNodes,它維護了我們集羣下的服務節點。所以我們的同步方法就是在遍歷我們的集羣節點,然後將相應的數據進行同步.(說白了,就是把相應實例再一次註冊給不同的服務端)
這樣做有沒有問題?我們去思考一下,其實是有的,我們把數據給到了其他節點,那麼其他節點能不能接收到我們當前的節點是新加的,還是複製過來的呢?我們繼續看代碼--它還定義了一個isReplication作爲標誌,如果是複製過來的實例,就讓它爲true,這就表示了它是一個複製過來的數據,防止出現死循環。
6:erueka客戶端關閉,eureka的下架
1:cancelScheduledTasks();關停線程池
2:判斷我們的當前客戶端確實註冊在了eureka上面,就去把它的當前狀態改爲DOWN。
3:調用unregister();然後去調用了cancel()方法將服務刪除。
7:服務異常下剔除。
進入initEurekaServerContext會看到 registry.openForTraffic(applicationInfoManager, registryCount);這段代碼引用了openForTraffic()方法
進入方法,我們繼續去追發現它是一個接口,我們去查看實現類會找到InstanceRegistry下的實現,然後發現它實際上是調用了super()方法,所以我們去找到它的父類,看看這裏是怎麼實現的。
1.updateRenewsPerMinThreshold()這裏會去調用一個公式
當前發送過服務的數量X(60/發送心跳的間隔)X定義的閾值
然後他在這裏對我們的instance做了一系列的處理,最後去調用了postInit()方法
進入postinit方法,會發現evictionTaskRef.set(new EvictionTask())初始化了一個EvictionTask。
到這個EvictionTask裏面去看,我們會看到EvictionTask的run方法中調用了一個evict()方法。
這個evict方法就有說法了。這裏就是在處理相關的服務的東西.首先evict方法會去判斷一下isLeaseExpirationEnabled() 也就是是否實現了自我保護機制?如果我們實現了自我保護機制,
可以去看一下,如果沒用啓用,它會直接返回一個true,如果啓用了,這裏需要滿足以下條件,
服務實例個數大於0,每分鐘的心跳需要大於我們的閾值,而我們的閾值就是
當前發送過服務的數量X(60/發送心跳的間隔)X定義的閾值,假設我們服務已經關停,那麼這裏的第二個條件一定不會滿足,所以會返回一個false。所以這裏就會直接return。不去剔除任何服務。所以,在自我保護機制開啓下,eureka不會去主動剔除服務。
回到evict方法裏,繼續去看非自我保護下,我們會看到evict方法定義了一個集合。將已經沒有發送心跳的實例寫入到expiredLeases這個集合中去。
通過expiredLeases集合,調用internalCancel()會開始去一個個去刪除存在於這個集合中的客戶端。
8:自我保護機制原理?我們在什麼情況下去選擇它?
我們繼續進入PeerAwareInstanceRegistryImpl下的 isLeaseExpirationEnabled()我們可以看到,它去讀取了一個配置項isSelfPreservationModeEnabled 這個就是我們的自我保護機制的開啓,自我保護說的是什麼意思?保護的是在節點上的服務,不是第一時間被剔除,默認是超過了90s,再去剔除服務。那麼這樣,會出現一個問題,當我們去訪問時,它有可能拿到的是一個不正常的節點,從而調用失敗,所以我們才需要降級和熔斷機制。
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
1min =60s
1 * (60/30).*0.85 = 1.7