上文介紹了服務如何通過Eureka實現注冊,以及如何從Eureka獲取已經注冊的服務列表。那么拿到注冊服務列表后, 如何進行服務調用?一個簡單的實現是可以從被調用服務的實例列表中選擇一個服務實例,通過其hostname(或IP),端口,及API的路徑拼接成完整的url,通過http client來完成調用。但生產環境中,為了高性能、高可用等要素,服務的調用一般涉及負載均衡、故障轉移、失敗重試等實現,因此引入實現這些功能的客戶端組件也成為了微服務架構中的必備要素。Spring Cloud中可通過Ribbon與Feign來實現服務間的調用。
本系列文章與示例均基於最新的Spring Cloud Hoxton版編寫。
Ribbon
Ribbon是一個可實現負載均衡的Web客戶端。我們一般理解的負載均衡是在服務端實現的,如Nginx(但這都是相對的,如果相對后端服務來說,也可以把Nginx當做一個實現了負載均衡的客戶端), 而Ribbon是客戶端的負載均衡實現。
Ribbon的核心概念是命名的客戶端(named client),Spring Cloud會為每個命名客戶端創建一個子應用上下文(ApplicationContext),在該上下文中,通過RibbonClientConfiguration創建ILoadBalancer,RestClient,ServerListFilter等Bean。
Spring Cloud Netflix提供的默認的Ribbon bean及說明
Bean類型 | 默認實現類 | 說明 |
---|---|---|
IClientConfig | DefaultClientConfigImpl | Ribbon客戶端配置加載實現,加載各實現bean及客戶端連接超時、通訊超時等配置 |
IRule | ZoneAvoidanceRule | 基於zone與可用性來過濾服務器的規則實現 |
IPing | DummyPing | 判斷服務器是否存活的實現,默認總是返回true |
ServerList | ConfigurationBasedServerList | 獲取服務器列表的實現,默認基於配置 |
ServerListFilter | ZonePreferenceServerListFilter | 服務器過濾實現,默認過濾出與客戶端在同一個zone中的服務器列表 |
ILoadBalancer | ZoneAwareLoadBalancer | 負載均衡實現,默認根據zone的請求負載量排除掉負載最高的zone,從剩下的zone中選擇一個根據給定的Rule選擇其中一個服務器 |
ServerListUpdater | PollingServerListUpdater | 動態的服務器列表更新器 |
Spring Cloud允許我們通過聲明一個configuration來對客戶端進行自定義,來調整或覆蓋上述默認實現,如
@Configuration |
這樣,客戶端將由RibbonClientConfiguration 與 CustomConfiguration中定義的組件一起組成,且CustomConfiguration 中的組件會覆蓋前者。
注意CustomConfiguration 必須是@Configuration 修飾的類,且不能被main application context的 @ComponentScan 掃描,否則會被所有@RibbonClients 共享
如果要為所有Ribbon Clients定制默認配置,則可使用@RibbonClients 注解
(defaultConfiguration = DefaultRibbonConfig.class) |
也可以通過配置屬性來定制Ribbon Client,支持的配置屬性
<clientName>.ribbon.NFLoadBalancerClassName: ILoadBalancer接口實現類 |
比如對於一個服務名稱為users的配置
users: |
配置屬性的優先級 > configuration指定配置類的優先級 > 默認RibbonClientConfiguration的優先級, 即同樣的實現,前者覆蓋后者。
當Eureka與Ribbon同時存在時,ribbonServerList會被 DiscoveryEnabledNIWSServerList覆蓋,從Eureka來獲取server list,同時 NIWSDiscoveryPing也會替換IPing接口,代理Eureka來確定服務器是否處於運行狀態。
Ribbon的超時與重試配置
- <clientName>.ribbon.ConnectTimeout: 請求連接超時時間,默認2000
- <clientName>.ribbon.ReadTimeout: 請求處理超時時間,默認5000
- <clientName>.ribbon.MaxAutoRetries: 在同一台服務器上的重試次數,排除第一次調用,默認0
- <clientName>.ribbon.MaxAutoRetriesNextServer: 切換服務器的重試次數,默認1
- <clientName>.ribbon.OkToRetryOnAllOperations: 對所有請求都進行重試,默認false
當項目中添加了Spring Retry的依賴,則會啟用重試機制。當請求失敗時,會再嘗試訪問當前服務器(次數由MaxAutoRetries配置),如果不行,就換一個服務器進行訪問,如果還是不行,再換服務器訪問(更換次數由MaxAutoRetriesNextServer配置),如果還是不行,則返回請求失敗。
Ribbon的負載均衡策略
前文提到Ribbon的負載均衡默認實現為ZoneAwareLoadBalancer,那么Ribbon提供的負載均衡策略還有哪些? 羅列如下
- BestAvailableRule: 排除掉斷路器打開的服務器,選取並發請求最小的服務器
- AvailabilityFilteringRule: 過濾掉斷路器打開或活躍連接數超過限制(通過<clientName>.<nameSpace>.ActiveConnectionsLimit配置,默認為Integer.MAX_VALUE)的服務器
- WeightedResponseTimeRule: 根據平均響應時間來動態為服務器賦予權值,實現基於權重的輪詢
- RetryRule: 對選擇負載均衡策略添加重試機制
- RoundRobinRule: 簡單輪詢
- RandomRule: 隨機輪詢
- ZoneAvoidanceRule: 結合區域與可用性來選擇服務器,也是默認實現
可通過如下配置修改Ribbon的負載均衡策略
client-name: |
案例演示
本文案例演示基於上文搭建的springcloud-eureka 與 springcloud-eureka-client 兩個示例項目 (源碼),依次啟動兩個項目,然后將springcloud-eureka-client項目的端口 server.port改為8081,新開一個springboot運行配置,如圖
以8081端口再起一個springcloud-eureka-client的服務實例。這是查看Eureka頁面 http://localhost:8761/, 可以看到hello-service服務注冊了兩個實例
新建springcloud-ribbon項目 (源碼)
pom.xml中引入依賴
<dependency> |
編寫測試接口, LoadBalanceClient 是Ribbon的API
@RestController |
啟動springcloud-ribbon, 調用測試接口 http://localhost:8082/ribbon, 可以看到返回結果交替顯示 http://CN-201911061714:8080, http://CN-201911061714:8081 (CN-201911061714是我電腦的hostname,你的可能不一樣),可見Ribbon實現了客戶端的負載均衡。
一些知識點
-
Ribbon如果對所有請求進行重試,則需要保證接口的冪等性(多次調用產生的結果是一致的)
-
每一個命名的Ribbon客戶端都有一個相應的由Spring cloud維護的子應用上下文,默認是lazy load的(第一次請求客戶端時才load),可以通過如下配置更改為啟動立即加載
ribbon:
eager-load:
enabled: true
clients: client1, client2, client3 -
client.ribbon.* 針對單個客戶端進行配置,針對所有客戶端默認配置,則使用ribbon.*
-
當結合斷路器使用時, 斷路器的超時時間要大於Ribbon的超時時間,不然不會觸發重試(還沒重試就觸發斷路器打開了)
-
除了Ribbon,能做負載均衡訪問的Web客戶端還有@LoadBalance 注解的RestTemplate, 與Feign
認真生活,快樂分享
歡迎關注微信公眾號:空山新雨的技術空間