day11-微服務面試篇

微服務在面試時被問到的內容相對較少,常見的面試題如下:

  • SpringCloud有哪些常用組件?分別是什么作用?
  • 服務注冊發現的基本流程是怎樣的?
  • Eureka和Nacos有哪些區別?
  • Nacos的分級存儲模型是什么意思?
  • Ribbon和SpringCloudLoadBalancer有什么差異
  • 什么是服務雪崩,常見的解決方案有哪些?
  • Hystix和Sentinel有什么區別和聯系?
  • 限流的常見算法有哪些?
  • 什么是CAP理論和BASE思想?
  • 項目中碰到過分布式事務問題嗎?怎么解決的?
  • AT模式如何解決臟讀和臟寫問題的?
  • TCC模式與AT模式對比,有哪些優缺點

可以發現,這些問題都是圍繞著SpringCloud的相關組件的,其中有些問題我們在課堂上已經介紹過,這里不再贅述。我們重點講解一些之前沒有講過的,與底層實現有關的部分。

講解的思路還是基于SpringCloud的組件分類來講的,主要包括:

  • 分布式事務
  • 注冊中心
  • 遠程調用
  • 服務保護

等幾個方面

1.分布式事務

分布式事務,就是指不是在單個服務或單個數據庫架構下,產生的事務,例如:

  • 跨數據源的分布式事務
  • 跨服務的分布式事務
  • 綜合情況

我們之前解決分布式事務問題是直接使用Seata框架的AT模式,但是解決分布式事務問題的方案遠不止這一種。

1.1.CAP定理

解決分布式事務問題,需要一些分布式系統的基礎知識作為理論指導,首先就是CAP定理。

1998年,加州大學的計算機科學家 Eric Brewer 提出,分布式系統有三個指標:

  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance (分區容錯性)

它們的第一個字母分別是 CAP。Eric Brewer認為任何分布式系統架構方案都不可能同時滿足這3個目標,這個結論就叫做 CAP 定理。

為什么呢?

1.1.1.一致性

Consistency(一致性):用戶訪問分布式系統中的任意節點,得到的數據必須一致。

比如現在包含兩個節點,其中的初始數據是一致的:

暫時無法在飛書文檔外展示此內容

當我們修改其中一個節點的數據時,兩者的數據產生了差異:

暫時無法在飛書文檔外展示此內容

要想保住一致性,就必須實現node01 到 node02的數據 同步:

暫時無法在飛書文檔外展示此內容

1.1.2.可用性

Availability (可用性):用戶訪問分布式系統時,讀或寫操作總能成功。

只能讀不能寫,或者只能寫不能讀,或者兩者都不能執行,就說明系統弱可用或不可用。

1.1.3.分區容錯

Partition,就是分區,就是當分布式系統節點之間出現網絡故障導致節點之間無法通信的情況:

暫時無法在飛書文檔外展示此內容

如上圖,node01和node02之間網關暢通,但是與node03之間網絡斷開。于是node03成為一個獨立的網絡分區;node01和node02在一個網絡分區。

Tolerance,就是容錯,即便是系統出現網絡分區,整個系統也要持續對外提供服務。

1.1.4.矛盾

在分布式系統中,網絡不能100%保證暢通,也就是說網絡分區的情況一定會存在。而我們的系統必須要持續運行,對外提供服務。所以分區容錯性(P)是硬性指標,所有分布式系統都要滿足。而在設計分布式系統時要取舍的就是一致性(C)和可用性(A)了。

假如現在出現了網絡分區,如圖:

暫時無法在飛書文檔外展示此內容

由于網絡故障,當我們把數據寫入node01時,可以與node02完成數據同步,但是無法同步給node03。現在有兩種選擇:

  • 允許用戶任意讀寫,保證可用性。但由于node03無法完成同步,就會出現數據不一致的情況。滿足AP
  • 不允許用戶寫,可以讀,直到網絡恢復,分區消失。這樣就確保了一致性,但犧牲了可用性。滿足CP

可見,在分布式系統中,AC之間只能滿足一個。

1.2.BASE理論

既然分布式系統要遵循CAP定理,那么問題來了,我到底是該犧牲一致性還是可用性呢?如果犧牲了一致性,出現數據不一致該怎么處理?

人們在總結系統設計經驗時,最終得到了一些心得:

  • Basically Available 基本可用:分布式系統在出現故障時,允許損失部分可用性,即保證核心可用。
  • Soft State軟狀態):在一定時間內,允許出現中間狀態,比如臨時的不一致狀態。
  • Eventually Consistent最終一致性:雖然無法保證強一致性,但是在軟狀態結束后,最終達到數據一致。

以上就是BASE理論。

簡單來說,BASE理論就是一種取舍的方案,不再追求完美,而是最終達成目標。因此解決分布式事務的思想也是這樣,有兩個方向:

  • AP思想:各個子事務分別執行和提交,無需鎖定數據。允許出現結果不一致,然后采用彌補措施恢復,實現最終一致即可。例如AT模式就是如此
  • CP思想:各個子事務執行后不要提交,而是等待彼此結果,然后同時提交或回滾。在這個過程中鎖定資源,不允許其它人訪問,數據處于不可用狀態,但能保證一致性。例如XA模式

1.3.AT模式的臟寫問題

我們先回顧一下AT模式的流程,AT模式也分為兩個階段:

第一階段是記錄數據快照,執行并提交事務:

第二階段根據階段一的結果來判斷:

  • 如果每一個分支事務都成功,則事務已經結束(因為階段一已經提交),因此刪除階段一的快照即可
  • 如果有任意分支事務失敗,則需要根據快照恢復到更新前數據。然后刪除快照

這種模式在大多數情況下(99%)并不會有什么問題,不過在極端情況下,特別是多線程并發訪問AT模式的分布式事務時,有可能出現臟寫問題,如圖:

解決思路就是引入了全局鎖的概念。在釋放DB鎖之前,先拿到全局鎖。避免同一時刻有另外一個事務來操作當前數據。

具體可以參考官方文檔:

https://seata.io/zh-cn/docs/dev/mode/at-mode.html

1.4.TCC模式

TCC模式與AT模式非常相似,每階段都是獨立事務,不同的是TCC通過人工編碼來實現數據恢復。需要實現三個方法:

  • try:資源的檢測和預留;
  • confirm:完成資源操作業務;要求 try 成功 confirm 一定要能成功。
  • cancel:預留資源釋放,可以理解為try的反向操作。

1.4.1.流程分析

舉例,一個扣減用戶余額的業務。假設賬戶A原來余額是100,需要余額扣減30元。

階段一( Try ):檢查余額是否充足,如果充足則凍結金額增加30元,可用余額扣除30

初始余額:

余額充足,可以凍結:

此時,總金額 = 凍結金額 + 可用金額,數量依然是100不變。事務直接提交無需等待其它事務。

階段二(Confirm):假如要提交(Confirm),之前可用金額已經扣減,并轉移到凍結金額。因此可用金額不變,直接凍結金額扣減30即可:

此時,總金額 = 凍結金額 + 可用金額 = 0 + 70 = 70元

階段二(Canncel):如果要回滾(Cancel),則釋放之前凍結的金額,也就是凍結金額扣減30,可用余額增加30

1.4.2.事務懸掛和空回滾

假如一個分布式事務中包含兩個分支事務,try階段,一個分支成功執行,另一個分支事務阻塞

如果阻塞時間太長,可能導致全局事務超時而觸發二階段的cancel操作。兩個分支事務都會執行cancel操作:

要知道,其中一個分支是未執行try操作的,直接執行了cancel操作,反而會導致數據錯誤。因此,這種情況下,盡管cancel方法要執行,但其中不能做任何回滾操作,這就是空回滾

對于整個空回滾的分支事務,將來try方法阻塞結束依然會執行。但是整個全局事務其實已經結束了,因此永遠不會再有confirm或cancel,也就是說這個事務執行了一半,處于懸掛狀態,這就是業務懸掛問題。

以上問題都需要我們在編寫try、cancel方法時處理。

1.4.3.總結

TCC模式的每個階段是做什么的?

  • Try:資源檢查和預留
  • Confirm:業務執行和提交
  • Cancel:預留資源的釋放

TCC的優點是什么?

  • 一階段完成直接提交事務,釋放數據庫資源,性能好
  • 相比AT模型,無需生成快照,無需使用全局鎖,性能最強
  • 不依賴數據庫事務,而是依賴補償操作,可以用于非事務型數據庫

TCC的缺點是什么?

  • 有代碼侵入,需要人為編寫try、Confirm和Cancel接口,太麻煩
  • 軟狀態,事務是最終一致
  • 需要考慮Confirm和Cancel的失敗情況,做好冪等處理、事務懸掛和空回滾處理

2.注冊中心

本章主要學習Nacos中的一些特性和原理,以及與Eureka的功能對比。

2.1.環境隔離

企業實際開發中,往往會搭建多個運行環境,例如:

  • 開發環境
  • 測試環境
  • 預發布環境
  • 生產環境

這些不同環境之間的服務和數據之間需要隔離。

還有的企業中,會開發多個項目,共享nacos集群。此時,這些項目之間也需要把服務和數據隔離。

因此,Nacos提供了基于namespace的環境隔離功能。具體的隔離層次如圖所示:

說明:

  • Nacos中可以配置多個namespace,相互之間完全隔離。默認的namespace名為public
  • namespace下還可以繼續分組,也就是group ,相互隔離。 默認的group是DEFAULT_GROUP
  • group之下就是服務和配置了

2.1.1.創建namespace

nacos提供了一個默認的namespace,叫做public

默認所有的服務和配置都屬于這個namespace,當然我們也可以自己創建新的namespace

然后填寫表單:

添加完成后,可以在頁面看到我們新建的namespace,并且Nacos為我們自動生成了一個命名空間id:

我們切換到配置列表頁,你會發現dev這個命名空間下沒有任何配置:

因為之前我們添加的所有配置都在public下:

2.1.2.微服務配置namespace

默認情況下,所有的微服務注冊發現、配置管理都是走public這個命名空間。如果要指定命名空間則需要修改application.yml文件。

比如,我們修改item-service服務的bootstrap.yml文件,添加服務發現配置,指定其namespace

spring:application:name: item-service # 服務名稱profiles:active: devcloud:nacos:server-addr: 192.168.150.101 # nacos地址discovery: # 服務發現配置namespace: 8c468c63-b650-48da-a632-311c75e6d235 # 設置namespace,必須用id# 。。。略

啟動item-service,查看服務列表,會發現item-service出現在dev下:

而其它服務則出現在public下:

此時訪問http://localhost:8082/doc.html,基于swagger做測試:

會發現查詢結果中缺少商品的最新價格信息。

我們查看服務運行日志:

會發現cart-service服務在遠程調用item-service時,并沒有找到可用的實例。這證明不同namespace之間確實是相互隔離的,不可訪問。

當我們把namespace切換回public,或者統一都是以dev時訪問恢復正常。

2.2.分級模型

在一些大型應用中,同一個服務可以部署很多實例。而這些實例可能分布在全國各地的不同機房。由于存在地域差異,網絡傳輸的速度會有很大不同,因此在做服務治理時需要區分不同機房的實例。

例如item-service,我們可以部署3個實例:

  • 127.0.0.1:8081
  • 127.0.0.1:8082
  • 127.0.0.1:8083

假如這些實例分布在不同機房,例如:

  • 127.0.0.1:8081,在上海機房
  • 127.0.0.1:8082,在上海機房
  • 127.0.0.1:8083,在杭州機房

Nacos中提供了集群(cluster)的概念,來對應不同機房。也就是說,一個服務(service)下可以有很多集群(cluster),而一個集群(cluster)中下又可以包含很多實例(instance)。

如圖:

因此,結合我們上一節學習的namespace命名空間的知識,任何一個微服務的實例在注冊到Nacos時,都會生成以下幾個信息,用來確認當前實例的身份,從外到內依次是:

  • namespace:命名空間
  • group:分組
  • service:服務名
  • cluster:集群
  • instance:實例,包含ip和端口

這就是nacos中的服務分級模型。

在Nacos內部會有一個服務實例的注冊表,是基于Map實現的,其結構與分級模型的對應關系如下:

查看nacos控制臺,會發現默認情況下所有服務的集群都是default:

如果我們要修改服務所在集群,只需要修改bootstrap.yml即可:

spring:cloud:nacos:discovery:cluster-name: BJ # 集群名稱,自定義

我們修改item-servicebootstrap.yml,然后重新創建一個實例:

再次查看nacos:

發現8084這個新的實例確實屬于BJ這個集群了。

2.3.Eureka

Eureka是Netflix公司開源的一個服務注冊中心組件,早期版本的SpringCloud都是使用Eureka作為注冊中心。由于Eureka和Nacos的starter中提供的功能都是基于SpringCloudCommon規范,因此兩者使用起來差別不大。

課前資料中提供了一個Eureka的demo:

我們可以用idea打開查看一下:

結構說明:

  • eureka-server:Eureka的服務端,也就是注冊中心。沒錯,Eureka服務端要自己創建項目
  • order-service:訂單服務,是一個服務調用者,查詢訂單的時候要查詢用戶
  • user-service:用戶服務,是一個服務提供者,對外暴露查詢用戶的接口

啟動以后,訪問localhost:10086即可查看到Eureka的控制臺,相對于Nacos來說簡陋了很多:

微服務引入Eureka的方式也極其簡單,分兩步:

  • 引入eureka-client依賴
  • 配置eureka地址

接下來就是編寫OpenFeign的客戶端了,怎么樣?是不是跟Nacos用起來基本一致。

2.4.Eureka和Nacos對比

Eureka和Nacos都能起到注冊中心的作用,用法基本類似。但還是有一些區別的,例如:

  • Nacos支持配置管理,而Eureka則不支持。

而且服務注冊發現上也有區別,我們來做一個實驗:

我們停止user-service服務,然后觀察Eureka控制臺,你會發現很長一段時間過去后,Eureka服務依然沒有察覺user-service的異常狀態。

這與Eureka的健康檢測機制有關。在Eureka中,健康檢測的原理如下:

  • 微服務啟動時注冊信息到Eureka,這點與Nacos一致。
  • 微服務每隔30秒向Eureka發送心跳請求,報告自己的健康狀態。Nacos中默認是5秒一次。
  • Eureka如果90秒未收到心跳,則認為服務疑似故障,可能被剔除。Nacos中則是15秒超時,30秒剔除。
  • Eureka如果發現超過85%比例的服務都心跳異常,會認為是自己的網絡異常,暫停剔除服務的功能。
  • Eureka每隔60秒執行一次服務檢測和清理任務;Nacos是每隔5秒執行一次。

綜上,你會發現Eureka是盡量不剔除服務,避免“誤殺”,寧可放過一千,也不錯殺一個。這就導致當服務真的出現故障時,遲遲不會被剔除,給服務的調用者帶來困擾。

不僅如此,當Eureka發現服務宕機并從服務列表中剔除以后,并不會將服務列表的變更消息推送給所有微服務。而是等待微服務自己來拉取時發現服務列表的變化。而微服務每隔30秒才會去Eureka更新一次服務列表,進一步推遲了服務宕機時被發現的時間。

而Nacos中微服務除了自己定時去Nacos中拉取服務列表以外,Nacos還會在服務列表變更時主動推送最新的服務列表給所有的訂閱者。

綜上,Eureka和Nacos的相似點有:

  • 都支持服務注冊發現功能
  • 都有基于心跳的健康監測功能
  • 都支持集群,集群間數據同步默認是AP模式,即最全高可用性

Eureka和Nacos的區別有:

  • Eureka的心跳是30秒一次,Nacos則是5秒一次
  • Eureka如果90秒未收到心跳,則認為服務疑似故障,可能被剔除。Nacos中則是15秒超時,30秒剔除。
  • Eureka每隔60秒執行一次服務檢測和清理任務;Nacos是每隔5秒執行一次。
  • Eureka只能等微服務自己每隔30秒更新一次服務列表;Nacos即有定時更新,也有在服務變更時的廣播推送
  • Eureka僅有注冊中心功能,而Nacos同時支持注冊中心、配置管理
  • Eureka和Nacos都支持集群,而且默認都是AP模式

3.遠程調用

我們知道微服務間遠程調用都是有OpenFeign幫我們完成的,甚至幫我們實現了服務列表之間的負載均衡。但具體負載均衡的規則是什么呢?何時做的負載均衡呢?

接下來我們一起來分析一下。

3.1.負載均衡原理

在SpringCloud的早期版本中,負載均衡都是有Netflix公司開源的Ribbon組件來實現的,甚至Ribbon被直接集成到了Eureka-client和Nacos-Discovery中。

但是自SpringCloud2020版本開始,已經棄用Ribbon,改用Spring自己開源的Spring Cloud LoadBalancer了,我們使用的OpenFeign的也已經與其整合。

接下來我們就通過源碼分析,來看看OpenFeign底層是如何實現負載均衡功能的。

3.1.1.源碼跟蹤

要弄清楚OpenFeign的負載均衡原理,最佳的辦法肯定是從FeignClient的請求流程入手。

首先,我們在com.hmall.cart.service.impl.CartServiceImpl中的queryMyCarts方法中打一個斷點。然后在swagger頁面請求購物車列表接口。

進入斷點后,觀察ItemClient這個接口:

你會發現ItemClient是一個代理對象,而代理的處理器則是SentinelInvocationHandler。這是因為我們項目中引入了Sentinel導致。

我們進入SentinelInvocationHandler類中的invoke方法看看:

可以看到這里是先獲取被代理的方法的處理器MethodHandler,接著,Sentinel就會開啟對簇點資源的監控:

開啟Sentinel的簇點資源監控后,就可以調用處理器了,我們嘗試跟入,會發現有兩種實現:

這其實就是OpenFeign遠程調用的處理器了。繼續跟入會進入SynchronousMethodHandler這個實現類:

在上述方法中,會循環嘗試調用executeAndDecode()方法,直到成功或者是重試次數達到Retryer中配置的上限。

我們繼續跟入executeAndDecode()方法:

executeAndDecode()方法最終會利用client去調用execute()方法,發起遠程調用。

這里的client的類型是feign.Client接口,其下有很多實現類:

由于我們項目中整合了seata,所以這里client對象的類型是SeataFeignBlockingLoadBalancerClient,內部實現如下:

這里直接調用了其父類,也就是FeignBlockingLoadBalancerClientexecute方法,來看一下:

整段代碼中核心的有4步:

  • 從請求的URI中找出serviceId
  • 利用loadBalancerClient,根據serviceId做負載均衡,選出一個實例ServiceInstance
  • 用選中的ServiceInstanceipport替代serviceId,重構URI
  • 向真正的URI發送請求

所以負載均衡的關鍵就是這里的loadBalancerClient,類型是org.springframework.cloud.client.loadbalancer.LoadBalancerClient,這是Spring-Cloud-Common模塊中定義的接口,只有一個實現類:

而這里的org.springframework.cloud.client.loadbalancer.BlockingLoadBalancerClient正是Spring-Cloud-LoadBalancer模塊下的一個類:

我們繼續跟入其BlockingLoadBalancerClient#choose()方法:

圖中代碼的核心邏輯如下:

  • 根據serviceId找到這個服務采用的負載均衡器(ReactiveLoadBalancer),也就是說我們可以給每個服務配不同的負載均衡算法。
  • 利用負載均衡器(ReactiveLoadBalancer)中的負載均衡算法,選出一個服務實例

ReactiveLoadBalancerSpring-Cloud-Common組件中定義的負載均衡器接口規范,而Spring-Cloud-Loadbalancer組件給出了兩個實現:

默認的實現是RoundRobinLoadBalancer,即輪詢負載均衡器。負載均衡器的核心邏輯如下:

核心流程就是兩步:

  • 利用ServiceInstanceListSupplier#get()方法拉取服務的實例列表,這一步是采用響應式編程
  • 利用本類,也就是RoundRobinLoadBalancergetInstanceResponse()方法挑選一個實例,這里采用了輪詢算法來挑選。

這里的ServiceInstanceListSupplier有很多實現:

其中CachingServiceInstanceListSupplier采用了裝飾模式,加了服務實例列表緩存,避免每次都要去注冊中心拉取服務實例列表。而其內部是基于DiscoveryClientServiceInstanceListSupplier來實現的。

在這個類的構造函數中,就會異步的基于DiscoveryClient去拉取服務的實例列表:

3.1.2.流程梳理

根據之前的分析,我們會發現Spring在整合OpenFeign的時候,實現了org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient類,其中定義了OpenFeign發起遠程調用的核心流程。也就是四步:

  • 獲取請求中的serviceId
  • 根據serviceId負載均衡,找出一個可用的服務實例
  • 利用服務實例的ipport信息重構url
  • 向真正的url發起請求

而具體的負載均衡則是不是由OpenFeign組件負責。而是分成了負載均衡的接口規范,以及負載均衡的具體實現兩部分。

負載均衡的接口規范是定義在Spring-Cloud-Common模塊中,包含下面的接口:

  • LoadBalancerClient:負載均衡客戶端,職責是根據serviceId最終負載均衡,選出一個服務實例
  • ReactiveLoadBalancer:負載均衡器,負責具體的負載均衡算法

OpenFeign的負載均衡是基于Spring-Cloud-Common模塊中的負載均衡規則接口,并沒有寫死具體實現。這就意味著以后還可以拓展其它各種負載均衡的實現。

不過目前SpringCloud中只有Spring-Cloud-Loadbalancer這一種實現。

Spring-Cloud-Loadbalancer模塊中,實現了Spring-Cloud-Common模塊的相關接口,具體如下:

  • BlockingLoadBalancerClient:實現了LoadBalancerClient,會根據serviceId選出負載均衡器并調用其算法實現負載均衡。
  • RoundRobinLoadBalancer:基于輪詢算法實現了ReactiveLoadBalancer
  • RandomLoadBalancer:基于隨機算法實現了ReactiveLoadBalancer

這樣一來,整體思路就非常清楚了,流程圖如下:

暫時無法在飛書文檔外展示此內容

3.2.NacosRule

之前分析源碼的時候我們發現負載均衡的算法是有ReactiveLoadBalancer來定義的,我們發現它的實現類有三個:

其中RoundRobinLoadBalancerRandomLoadBalancer是由Spring-Cloud-Loadbalancer模塊提供的,而NacosLoadBalancer則是由Nacos-Discorvery模塊提供的。

默認采用的負載均衡策略是RoundRobinLoadBalancer,那如果我們要切換負載均衡策略該怎么辦?

3.2.1.修改負載均衡策略

查看源碼會發現,Spring-Cloud-Loadbalancer模塊中有一個自動配置類:

其中定義了默認的負載均衡器:

這個Bean上添加了@ConditionalOnMissingBean注解,也就是說如果我們自定義了這個類型的bean,則負載均衡的策略就會被改變。

我們在hm-cart模塊中的添加一個配置類:

代碼如下:

package com.hmall.cart.config;import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;public class OpenFeignConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, NacosDiscoveryProperties properties,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, properties);}}

注意

這個配置類千萬不要加@Configuration注解,也不要被SpringBootApplication掃描到。

由于這個OpenFeignConfig沒有加@Configuration注解,也就沒有被Spring加載,因此是不會生效的。接下來,我們要在啟動類上通過注解來聲明這個配置。

有兩種做法:

  • 全局配置:對所有服務生效
@LoadBalancerClients(defaultConfiguration = OpenFeignConfig.class)
  • 局部配置:只對某個服務生效
@LoadBalancerClients({@LoadBalancerClient(value = "item-service", configuration = OpenFeignConfig.class)
})

我們選擇全局配置:

DEBUG重啟后測試,會發現負載均衡器的類型確實切換成功:

3.2.2.集群優先

RoundRobinLoadBalancer是輪詢算法,RandomLoadBalancer是隨機算法,那么NacosLoadBalancer是什么負載均衡算法呢?

我們通過源碼來分析一下,先看第一部分:

這部分代碼的大概流程如下:

  • 通過ServiceInstanceListSupplier獲取服務實例列表
  • 獲取NacosDiscoveryProperties中的clusterName,也就是yml文件中的配置,代表當前服務實例所在集群信息(參考2.2小節,分級模型)
  • 然后利用stream的filter過濾找到被調用的服務實例中與當前服務實例clusterName一致的。簡單來說就是服務調用者與服務提供者要在一個集群

為什么?

假如我現在有兩個機房,都部署有item-servicecart-service服務:

假如這些服務實例全部都注冊到了同一個Nacos。現在,杭州機房的cart-service要調用item-service,會拉取到所有機房的item-service的實例。調用時會出現兩種情況:

  • 直接調用當前機房的item-service
  • 調用其它機房的item-service

本機房調用幾乎沒有網絡延遲,速度比較快。而跨機房調用,如果兩個機房相距很遠,會存在較大的網絡延遲。因此,我們應該盡可能避免跨機房調用,優先本地集群調用:

現在的情況是這樣的:

  • cart-service所在集群是default
  • item-service的8081、8083所在集群的default
  • item-service的8084所在集群是BJ

cart-service訪問item-service時,應該優先訪問8081和8082,我們重啟cart-service,測試一下:

可以看到原本是3個實例,經過篩選后還剩下2個實例。

查看Debug控制臺:

同集群的實例還剩下兩個,接下來就需要做負載均衡了,具體用的是什么算法呢?

3.2.3.權重配置

我們繼續跟蹤NacosLoadBalancer源碼:

那么問題來了, 這個權重是怎么配的呢?

我們打開nacos控制臺,進入item-service的服務詳情頁,可以看到每個實例后面都有一個編輯按鈕:

點擊,可以看到一個編輯表單:

我們將這里的權重修改為5:

訪問10次購物車接口,可以發現大多數請求都訪問到了8083這個實例。

4.服務保護

在SpringCloud的早期版本中采用的服務保護技術叫做Hystix,不過后來被淘汰,替換為Spring Cloud Circuit Breaker,其底層實現可以是Spring RetryResilience4J

不過在國內使用較多還是SpringCloudAlibaba中的Sentinel組件。

接下來,我們就分析一下Sentinel組件的一些基本實現原理以及它與Hystix的差異。

4.1.線程隔離

首先我們來看下線程隔離功能,無論是Hystix還是Sentinel都支持線程隔離。不過其實現方式不同。

線程隔離有兩種方式實現:

  • 線程池隔離:給每個服務調用業務分配一個線程池,利用線程池本身實現隔離效果
  • 信號量隔離:不創建線程池,而是計數器模式,記錄業務使用的線程數量,達到信號量上限時,禁止新的請求

如圖:

兩者的優缺點如下:

Sentinel的線程隔離就是基于信號量隔離實現的,而Hystix兩種都支持,但默認是基于線程池隔離。

4.2.滑動窗口算法

在熔斷功能中,需要統計異常請求或慢請求比例,也就是計數。在限流的時候,要統計每秒鐘的QPS,同樣是計數。可見計數算法在熔斷限流中的應用非常多。sentinel中采用的計數器算法就是滑動窗口計數算法。

4.2.1.固定窗口計數

要了解滑動窗口計數算法,我們必須先知道固定窗口計數算法,其基本原理如圖:

說明:

  • 將時間劃分為多個窗口,窗口時間跨度稱為Interval,本例中為1000ms;
  • 每個窗口維護1個計數器,每有1次請求就將計數器+1。限流就是設置計數器閾值,本例為3,圖中紅線標記
  • 如果計數器超過了限流閾值,則超出閾值的請求都被丟棄。

示例:

說明:

  • 第1、2秒,請求數量都小于3,沒問題
  • 第3秒,請求數量為5,超過閾值,超出的請求被拒絕

但是我們考慮一種特殊場景,如圖:

說明:

  • 假如在第5、6秒,請求數量都為3,沒有超過閾值,全部放行
  • 但是,如果第5秒的三次請求都是在4.5~5秒之間進來;第6秒的請求是在5~5.5之間進來。那么從第4.5~5.之間就有6次請求!也就是說每秒的QPS達到了6,遠超閾值。

這就是固定窗口計數算法的問題,它只能統計當前某1個時間窗的請求數量是否到達閾值,無法結合前后的時間窗的數據做綜合統計。

因此,我們就需要滑動時間窗口算法來解決。

4.2.2.滑動窗口計數

固定時間窗口算法中窗口有很多,其跨度和位置是與時間區間綁定,因此是很多固定不動的窗口。而滑動時間窗口算法中只包含1個固定跨度的窗口,但窗口是可移動動的,與時間區間無關。

具體規則如下:

  • 窗口時間跨度Interval大小固定,例如1秒
  • 時間區間跨度為Interval / n ,例如n=2,則時間區間跨度為500ms
  • 窗口會隨著當前請求所在時間currentTime移動,窗口范圍從currentTime-Interval時刻之后的第一個時區開始,到currentTime所在時區結束。

如圖所示:

限流閾值依然為3,綠色小塊就是請求,上面的數字是其currentTime值。

  • 在第1300ms時接收到一個請求,其所在時區就是1000~1500
  • 按照規則,currentTime-Interval值為300ms,300ms之后的第一個時區是500~1000,因此窗口范圍包含兩個時區:500~1000、1000~1500,也就是粉紅色方框部分
  • 統計窗口內的請求總數,發現是3,未達到上限。

若第1400ms又來一個請求,會落在1000~1500時區,雖然該時區請求總數是3,但滑動窗口內總數已經達到4,因此該請求會被拒絕:

假如第1600ms又來的一個請求,處于1500~2000時區,根據算法,滑動窗口位置應該是1000~1500和1500~2000這兩個時區,也就是向后移動:

這就是滑動窗口計數的原理,解決了我們之前所說的問題。而且滑動窗口內劃分的時區越多,這種統計就越準確。

4.3.令牌桶算法

限流的另一種常見算法是令牌桶算法。Sentinel中的熱點參數限流正是基于令牌桶算法實現的。其基本思路如圖:

說明:

  • 以固定的速率生成令牌,存入令牌桶中,如果令牌桶滿了以后,多余令牌丟棄
  • 請求進入后,必須先嘗試從桶中獲取令牌,獲取到令牌后才可以被處理
  • 如果令牌桶中沒有令牌,則請求等待或丟棄

基于令牌桶算法,每秒產生的令牌數量基本就是QPS上限。

當然也有例外情況,例如:

  • 某一秒令牌桶中產生了很多令牌,達到令牌桶上限N,緩存在令牌桶中,但是這一秒沒有請求進入。
  • 下一秒的前半秒涌入了超過2N個請求,之前緩存的令牌桶的令牌耗盡,同時這一秒又生成了N個令牌,于是總共放行了2N個請求。超出了我們設定的QPS閾值。

因此,在使用令牌桶算法時,盡量不要將令牌上限設定到服務能承受的QPS上限。而是預留一定的波動空間,這樣我們才能應對突發流量。

4.4.漏桶算法

漏桶算法與令牌桶相似,但在設計上更適合應對并發波動較大的場景,以解決令牌桶中的問題。

簡單來說就是請求到達后不是直接處理,而是先放入一個隊列。而后以固定的速率從隊列中取出并處理請求。之所以叫漏桶算法,就是把請求看做水,隊列看做是一個漏了的桶。

如圖:

說明:

  • 將每個請求視作"水滴"放入"漏桶"進行存儲;
  • "漏桶"以固定速率向外"漏"出請求來執行,如果"漏桶"空了則停止"漏水”;
  • 如果"漏桶"滿了則多余的"水滴"會被直接丟棄。

漏桶的優勢就是流量整型,桶就像是一個大壩,請求就是水。并發量不斷波動,就如圖水流時大時小,但都會被大壩攔住。而后大壩按照固定的速度放水,避免下游被洪水淹沒。

因此,不管并發量如何波動,經過漏桶處理后的請求一定是相對平滑的曲線:

sentinel中的限流中的排隊等待功能正是基于漏桶算法實現的。

5.作業

嘗試用自己的語言回答下列面試題:

  • SpringCloud有哪些常用組件?分別是什么作用?
  • 服務注冊發現的基本流程是怎樣的?
  • Eureka和Nacos有哪些區別?
  • Nacos的分級存儲模型是什么意思?
  • OpenFeign是如何實現負載均衡的?
  • 什么是服務雪崩,常見的解決方案有哪些?
  • Hystix和Sentinel有什么區別和聯系?
  • 限流的常見算法有哪些?
  • 什么是CAP理論和BASE思想?
  • 項目中碰到過分布式事務問題嗎?怎么解決的?
  • AT模式如何解決臟讀和臟寫問題的?
  • TCC模式與AT模式對比,有哪些優缺點
  • RabbitMQ是如何確保消息的可靠性的?
  • RabbitMQ是如何解決消息堆積問題的?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88328.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88328.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88328.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

昇騰 k8s vnpu配置

參考文檔: https://www.hiascend.com/document/detail/zh/mindx-dl/500/AVI/cpaug/cpaug_018.html 此文檔實現為NPU910B3卡 主機設置靜態虛擬npu 設置虛擬化模式 &#xff01;本命令只支持再物理機執行&#xff0c;取值為0或1&#xff0c;&#xff08;如果是在虛擬機內劃分vNPU…

Redis常用數據結構以及多并發場景下的使用分析:Set類型

文章目錄前言redis中的set結構疑問1 &#xff1a;為什么使用數組后 整體時間復雜度還是O(1)疑問2&#xff1a; set特性是無序的那為什么當元素少的時候 用連續數組 去存儲呢&#xff1f;疑問3&#xff1a;當元素少于512的時候即使用intset存儲的時候 是如何維護唯一性的&#x…

Linux中rw-rw-r--相關的訪問權限講解

下面就是關于 rw-rw-r-- 的知識圖譜式講解。核心節點&#xff1a;rw-rw-r-- (文件權限表示法) 這是一個在 Linux/Unix 操作系統中&#xff0c;通過 ls -l 命令查看到的&#xff0c;用于描述文件或目錄訪問權限的10字符字符串。分支一&#xff1a;字符串的解剖 (Anatomy of the …

C#異常處理:更優雅的方式

C#異常處理&#xff1a;更優雅的方式 在 C# 編程的世界里&#xff0c;異常處理是繞不開的重要環節。程序運行時難免會出現各種意外&#xff0c;若處理不當&#xff0c;可能導致程序崩潰&#xff0c;給用戶帶來糟糕體驗。所以&#xff0c;掌握更優雅的異常處理方式&#xff0c;對…

Qt6中模態與非模態對話框區別

一.阻塞 vs 非阻塞1.模態對話框阻塞父窗口&#xff1a;打開后&#xff0c;用戶必須先處理該對話框&#xff08;關閉或完成操作&#xff09;&#xff0c;才能繼續操作父窗口。應用場景&#xff1a;強制用戶立即響應的場景&#xff0c;如確認對話框、登錄窗口、文件選擇器等。2.非…

處理Web請求路徑參數

目錄 1. 路徑變量&#xff08;Path Variable&#xff09; 2. 查詢參數&#xff08;Query Parameter&#xff09; 3. 表單參數&#xff08;Form Data&#xff09; 4. 請求體JSON參數&#xff08;Request Body JSON&#xff09; 5. 請求頭參數&#xff08;Header Parameters&…

創客匠人:技術賦能下的創始人 IP 打造與內容創作新邏輯

在知識變現的浪潮中&#xff0c;創始人 IP 的核心競爭力始終圍繞內容展開&#xff0c;但內容創作的效率與質量往往成為瓶頸。創客匠人基于對行業的深刻洞察&#xff0c;探索出技術與內容融合的路徑&#xff0c;為創始人 IP 打造提供了新的思路 —— 不再將內容創作視為單純的輸…

Mysql分片:一致性哈希算法

一、一致性哈希的核心原理哈希取模最大的痛點是&#xff1a;當分片數量&#xff08;例如數據庫節點數&#xff09;發生變化時&#xff0c;幾乎所有數據的哈希結果都會改變&#xff0c;導致大規模的數據遷移。一致性哈希就是為了解決這個“伸縮性差”的問題而誕生的。核心思想&a…

前端學習 vben 之 axios interceptors

前端學習 vben 之 axios interceptors interceptor 攔截器&#xff0c;是一種軟件設計模式&#xff0c;核心思想就是在程序執行的特定階段&#xff08;如請求發送前&#xff0c;響應返回后&#xff0c;方法調用前后等&#xff09;自動插入自定義邏輯。實現對核心流程的“攔截”…

【java面試day4】redis緩存-數據持久化

文章目錄問題&#x1f4ac; Question 1相關知識問題 &#x1f4ac; Question 1 Q&#xff1a;redis作為緩存&#xff0c;數據的持久化是怎么做的? A&#xff1a;有兩種機制&#xff0c;一種是RDB&#xff0c;RDB會在指定的時間間隔內將內存中的數據生成快照&#xff0c;保存…

Vue3中element plus默認獲取最近一周和上個月的時間區間并在后端分開傳值

<el-form-item label"結算時間&#xff1a;" prop"datetimerangevalue"><el-date-pickerv-model"datetimerangevalue"value-format"YYYY-MM-DD HH:mm:ss"type"datetimerange"range-separator"至"start-p…

SQLAlchemy數據庫連接密碼特殊字符處理完全指南

引言 在使用SQLAlchemy連接數據庫時&#xff0c;我們通常使用URL格式指定連接信息&#xff0c;如mysqlpymysql://user:passwordhost:port/database。然而&#xff0c;當密碼中包含特殊字符&#xff08;如、#、$、!等&#xff09;時&#xff0c;會導致URL解析錯誤&#xff0c;進…

1.4 ARM安全參考架構(PSA Certified)

目錄1.4.1 PSA Certified概述1.4.2 PSA認證級別詳解1.4.3 PSA與TF-A的關系1.4.4 PSA安全模型實現信任根(RoT)架構關鍵安全服務&#xff1a;1.4.5 認證流程實踐1.4.6 典型應用案例參考資料1.4.1 PSA Certified概述 ARM Platform Security Architecture (PSA) Certified 是一套完…

企業網絡安全的“金字塔”策略:構建全方位防護體系的核心思路

在數字化轉型的浪潮中&#xff0c;企業的網絡安全已從單一的防護措施&#xff0c;發展成為多層次、全方位的安全體系。如何精準應對日益復雜的網絡威脅&#xff0c;成為眾多企業關注的焦點。本文將分享企業構建高效安全防護“金字塔”的核心思路。一、從“排查隱患”到“主動防…

爬蟲-request模塊使用

1.使用和安裝2.代碼測試打印返回的內容&#xff0c;默認是請求體中的標識.text 是打印源代碼設置一下編碼

HTML + CSS + JavaScript

目錄 1 HTML HTML 文件基本結構 HTML 開發工具 HTML 常見標簽 標題標簽&#xff1a;h1 - h6 段落標簽&#xff1a;p 換行標簽&#xff1a;br 圖片標簽&#xff1a;img 超鏈接標簽&#xff1a;a 表格標簽 表單標簽 form 標簽 input 標簽 select 標簽 textarea 標…

Java 與 MySQL 性能優化:MySQL連接池參數優化與性能提升

文章目錄引言一、連接池的基本概念與作用二、關鍵連接參數詳解2.1 max_connections2.2 wait_timeout2.3 interactive_timeout2.4 connect_timeout2.5 thread_cache_size三、連接池參數不合理導致的性能問題3.1 連接耗盡3.2 響應變慢3.3 連接失效3.4 資源浪費四、連接池參數優化…

浪潮CD1000-移動云電腦-RK3528芯片-2+32G-開啟ADB ROOT破解教程

浪潮CD1000-移動云電腦-RK3528芯片-232G-安卓9-開啟ADB ROOT破解教程破解教程&#xff1a;1.先下載好開心電視助手&#xff08;下載地址及其他版本&#xff1a;【工具大全】-【開心電視助手3.8&#xff0f;4.0&#xff0f;4.6&#xff0f;6.0&#xff0f;6.2&#xff0f;6.3&am…

【網絡編程】簡易的 p2p 模型,實現兩臺虛擬機之間的簡單點對點通信,并以小見大觀察 TCP 協議的具體運行

文章目錄基本概念業務拆解代碼實現準備工作實現被動的功能——多線程指針函數實現主動的功能——用戶選擇界面主函數代碼執行效果意外收獲總結推薦一個零聲教育學習教程&#xff0c;個人覺得老師講得不錯&#xff0c;分享給大家&#xff1a;[Linux&#xff0c;Nginx&#xff0c…

react狀態管理庫 - zustand

什么是zustand&#xff1f; zustand 是一個輕量級、快速且可擴展的 React 狀態管理庫&#xff0c;旨在提供一種簡單直接的方式來管理應用狀態&#xff0c;而無需其他解決方案通常伴隨的繁瑣代碼。根據官方 Zustand 文檔&#xff0c;Zustand 是“一個使用簡化 flux 原理的小型、…