:從入門到原理)
1. 引言在微服務架構(gòu)中一個服務往往會被部署成多個實例以提升系統(tǒng)的可用性和吞吐量。當客戶端調(diào)用某個服務時如何從多個實例中選擇一個進行請求就成了一個關(guān)鍵問題。Spring Cloud Ribbon 正是為了解決這個問題而誕生的客戶端負載均衡組件。Ribbon 是 Netflix 開源的一款基于 HTTP 和 TCP 的客戶端負載均衡工具。它內(nèi)嵌在服務消費方通過一系列內(nèi)置的負載均衡策略幫助開發(fā)者輕松實現(xiàn)服務調(diào)用的負載均衡。雖然 Spring Cloud 官方在后續(xù)版本中推薦使用 Spring Cloud LoadBalancer 作為替代但 Ribbon 在大量存量項目中依然廣泛使用理解其原理對閱讀和維護老項目非常有幫助。本文將帶你從零開始手把手搭建一個基于 Spring Cloud Ribbon 的負載均衡示例并深入剖析其核心工作原理。2. 環(huán)境準備在開始編碼之前我們需要準備好以下環(huán)境JDK 1.8 及以上Maven 3.6 及以上IntelliJ IDEA 或其他主流 IDE一個可用的注冊中心本文以 Eureka 為例本文使用的 Spring Cloud 版本為 Hoxton.SR12對應的 Spring Boot 版本為 2.3.12.RELEASE。這兩個版本是 Ribbon 時代比較穩(wěn)定且常用的組合。3. 創(chuàng)建服務提供者首先我們創(chuàng)建一個服務提供者模塊provider-service它負責對外提供具體的業(yè)務接口。3.1 引入依賴在pom.xml中引入必要的依賴dependenciesdependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency/dependencies3.2 編寫啟動類SpringBootApplicationEnableEurekaClientpublicclassProviderApplication{publicstaticvoidmain(String[]args){SpringApplication.run(ProviderApplication.class,args);}}3.3 編寫業(yè)務接口RestControllerpublicclassHelloController{Value(${server.port})privateStringport;GetMapping(/hello)publicStringhello(){returnHello from provider, port: port;}}3.4 配置文件server:port:8081spring:application:name:provider-serviceeureka:client:service-url:defaultZone:http://localhost:8761/eureka/為了讓負載均衡效果更明顯我們可以將服務提供者啟動兩個實例端口分別設置為 8081 和 8082。在 IDEA 中可以通過配置多個啟動項來實現(xiàn)也可以使用--server.port8082參數(shù)啟動第二個實例。4. 創(chuàng)建服務消費者接下來創(chuàng)建服務消費者模塊consumer-service它將通過 Ribbon 調(diào)用服務提供者。4.1 引入依賴dependenciesdependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-ribbon/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency/dependencies4.2 編寫啟動類SpringBootApplicationEnableEurekaClientpublicclassConsumerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(ConsumerApplication.class,args);}}4.3 配置 RestTemplate在啟動類中注入一個帶有LoadBalanced注解的RestTemplateBeanBeanLoadBalancedpublicRestTemplaterestTemplate(){returnnewRestTemplate();}LoadBalanced是 Ribbon 的核心注解它會讓RestTemplate具備負載均衡的能力。加上這個注解后RestTemplate發(fā)出的請求會經(jīng)過 Ribbon 的攔截器自動完成服務實例的選擇。4.4 編寫調(diào)用接口RestControllerpublicclassConsumerController{AutowiredprivateRestTemplaterestTemplate;GetMapping(/call)publicStringcall(){Stringurlhttp://provider-service/hello;returnrestTemplate.getForObject(url,String.class);}}注意這里的 URL 使用的是服務名稱provider-service而不是具體的 IP 和端口。Ribbon 會從注冊中心獲取該服務名對應的實例列表然后根據(jù)負載均衡策略選擇一個實例進行調(diào)用。4.5 配置文件server:port:8080spring:application:name:consumer-serviceeureka:client:service-url:defaultZone:http://localhost:8761/eureka/5. 啟動與測試6. 結(jié)合 OpenFeign 使用 Ribbon除了RestTemplateSpring Cloud 還提供了 OpenFeign 這一聲明式 HTTP 客戶端。OpenFeign 底層同樣依賴 Ribbon 實現(xiàn)負載均衡使用起來更加簡潔優(yōu)雅。6.1 引入依賴在服務消費者的pom.xml中引入 OpenFeign 依賴dependenciesdependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependency/dependencies注意spring-cloud-starter-openfeign會自動傳遞引入 Ribbon 相關(guān)依賴因此無需再單獨添加spring-cloud-starter-netflix-ribbon。6.2 開啟 Feign 客戶端在啟動類上添加EnableFeignClients注解SpringBootApplicationEnableEurekaClientEnableFeignClientspublicclassConsumerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(ConsumerApplication.class,args);}}6.3 編寫 Feign 接口創(chuàng)建一個 Feign 接口通過聲明式注解直接調(diào)用服務提供者FeignClient(nameprovider-service)publicinterfaceProviderClient{GetMapping(/hello)Stringhello();}FeignClient注解中的name屬性指定了要調(diào)用的服務名稱。Feign 會將該名稱交給 Ribbon由 Ribbon 從注冊中心獲取實例列表并完成負載均衡。6.4 在 Controller 中調(diào)用RestControllerpublicclassFeignConsumerController{AutowiredprivateProviderClientproviderClient;GetMapping(/feign/call)publicStringcall(){returnproviderClient.hello();}}啟動服務后多次訪問http://localhost:8080/feign/call同樣可以看到請求被輪流分發(fā)到 8081 和 8082 兩個實例上。6.5 配置 Ribbon 策略Feign 使用 Ribbon 時可以通過配置文件為指定服務設置負載均衡策略provider-service:ribbon:NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule上面的配置將provider-service的負載均衡策略設置為隨機策略。也可以像第 6.4 節(jié)那樣通過配置類全局注入自定義的IRuleBean兩種方式對 Feign 同樣生效。6.6 Feign 與 RestTemplate 的適用場景方式優(yōu)點適用場景RestTemplate靈活度高可動態(tài)拼接 URL適合簡單調(diào)用調(diào)用邏輯簡單、URL 需要動態(tài)變化的場景OpenFeign聲明式接口代碼簡潔支持注解配置易于維護服務間調(diào)用較多、接口固定的微服務場景簡單來說如果只是偶爾調(diào)用一兩個服務使用RestTemplate足夠如果服務間調(diào)用關(guān)系復雜、接口眾多推薦使用 OpenFeign它能讓代碼更加清晰、可維護性更高。按照以下步驟啟動所有服務啟動 Eureka 注冊中心默認端口 8761。啟動兩個服務提供者實例端口分別為 8081 和 8082。啟動服務消費者端口 8080。啟動完成后在瀏覽器中多次訪問http://localhost:8080/call觀察返回結(jié)果Hello from provider, port: 8081 Hello from provider, port: 8082 Hello from provider, port: 8081 Hello from provider, port: 8082可以看到請求被輪流分發(fā)到了兩個不同的服務實例上說明 Ribbon 的負載均衡已經(jīng)生效。默認情況下Ribbon 使用的是輪詢策略所以兩個實例會交替被訪問。6. Ribbon 核心原理了解了基本用法之后我們來深入剖析 Ribbon 的工作原理。6.1 核心組件Ribbon 的核心組件包括ILoadBalancer負載均衡器負責維護服務實例列表并根據(jù)策略選擇實例。IRule負載均衡策略接口定義了如何從實例列表中選擇一個實例。IPing心跳檢測接口用于檢查服務實例是否存活。ServerList服務實例列表負責從注冊中心獲取實例信息。ServerListUpdater服務實例列表更新器定時刷新實例列表。6.2 工作流程Ribbon 的工作流程可以概括為以下幾個步驟服務消費者發(fā)起請求URL 中使用服務名稱。LoadBalanced注解的RestTemplate被 Ribbon 的攔截器攔截。攔截器將服務名稱交給ILoadBalancer處理。ILoadBalancer從ServerList中獲取可用的服務實例列表。IRule根據(jù)配置的負載均衡策略從實例列表中選擇一個實例。Ribbon 將請求轉(zhuǎn)發(fā)到選中的實例并返回結(jié)果。下面用一張流程圖來展示這個過程客戶端發(fā)起請求RestTemplate 攔截器ILoadBalancer 負載均衡器ServerList 獲取實例列表IRule 選擇實例轉(zhuǎn)發(fā)請求到目標實例返回響應結(jié)果6.3 負載均衡策略Ribbon 內(nèi)置了多種負載均衡策略常用的有以下幾種策略類說明RoundRobinRule輪詢策略按順序依次選擇實例默認策略RandomRule隨機策略隨機選擇一個實例WeightedResponseTimeRule根據(jù)響應時間加權(quán)響應時間越短權(quán)重越大RetryRule帶重試的輪詢策略失敗后自動重試BestAvailableRule選擇并發(fā)量最小的實例AvailabilityFilteringRule先過濾掉故障實例再輪詢選擇6.4 自定義負載均衡策略如果內(nèi)置策略不滿足需求我們可以自定義負載均衡策略。只需要實現(xiàn)IRule接口即可publicclassMyRuleextendsAbstractLoadBalancerRule{OverridepublicServerchoose(Objectkey){ListServerserversgetLoadBalancer().getAllServers();// 自定義選擇邏輯例如始終選擇第一個實例returnservers.get(0);}}然后在配置類中注入ConfigurationpublicclassRibbonConfig{BeanpublicIRuleribbonRule(){returnnewMyRule();}}7. 常見問題與注意事項在使用 Ribbon 的過程中有幾個常見問題需要特別注意。7.1 服務名大小寫問題Ribbon 在匹配服務名時是區(qū)分大小寫的。如果注冊中心中的服務名是provider-service那么調(diào)用時也必須使用完全一致的名稱否則會報No instances available錯誤。7.2 超時配置Ribbon 默認的連接超時和讀取超時時間可能不適合所有場景可以通過配置進行調(diào)整ribbon:ConnectTimeout:3000ReadTimeout:50007.3 饑餓加載默認情況下Ribbon 是在第一次調(diào)用時才去注冊中心拉取服務列表這會導致第一次請求較慢??梢酝ㄟ^開啟饑餓加載來解決ribbon:eager-load:enabled:trueclients:provider-service8. 總結(jié)本文從零開始完整演示了如何使用 Spring Cloud Ribbon 實現(xiàn)客戶端負載均衡。我們首先搭建了服務提供者和服務消費者然后通過LoadBalanced注解讓RestTemplate具備負載均衡能力最后深入剖析了 Ribbon 的核心組件和工作原理。Ribbon 作為 Spring Cloud Netflix 時代的核心組件雖然官方已進入維護模式但理解它的設計思想對于學習 Spring Cloud LoadBalancer 以及閱讀存量項目代碼仍然非常有幫助。希望本文能幫助你更好地掌握 Ribbon 的使用和原理。