2021升級版SpringCloud教程從入門到實戰精通「H版&alibaba&鏈路追蹤&日誌&事務&鎖」java
教程全目錄「含視頻」:https://gitee.com/bingqilinpeishenme/Java-Wikigit
Ribbon使用+原理+整合Nacos權重+實戰優化 一篇搞定
Ribbon基本使用
簡介
Ribbon是一個客戶端負載均衡工具,封裝Netflix Ribbon組件,可以提供客戶端負載均衡能力。算法
理解Ribbon最重要的就是理解客戶端這個概念,所謂客戶端負載均衡工具不一樣於Nginx(服務端負載均衡),Ribbon和應用程序綁定,自己不是獨立的服務,也不存儲服務列表,須要負載均衡的時候,會經過應用程序獲取註冊服務列表,而後經過列表進行負載均衡和調用。spring
- Nginx獨立進程作負載均衡,經過負載均衡策略,將請求轉發到不一樣的服務上
- 客戶端負載均衡,經過在客戶端保存服務列表信息,而後本身調用負載均衡策略,分攤調用不一樣的服務
基本使用
Ribbon的負載均衡有兩種方式編程
- 和 RestTemplate 結合 Ribbon+RestTemplate
- 和 OpenFeign 結合
Ribbon的核心子模塊後端
- ribbon-loadbalancer:能夠獨立使用或者和其餘模塊一塊兒使用的負載均衡API
- ribbon-core:Ribbon的核心API
訂單服務集成Ribbon
訂單服務調用商品服務bash
配置過程 分兩步服務器
-
在訂單服務中導入ribbon的依賴併發
<!--ribbon--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
-
配置 RestTemplateapp
訂單服務調用商品服務
-
訂單服務調用商品服務的連接 不能寫成ip+端口號,須要寫成商品服務的服務名稱
-
重啓 訂單服務 測試負載均衡
Ribbon負載均衡簡單版實現的流程
- RestTemplate發送的請求是服務名稱http://nacos-product/product/getProductById/1
- 獲取
@LoadBalanced
註解標記的RestTemplate
RestTemplate
添加一個攔截器,當使用RestTemplate
發起http調用時進行攔截- 根據url中的服務名稱 以及自身的負載均衡策略 去訂單服務的服務列表中找到一個要調用的ip+端口號 localhost:8802
- 訪問該目標服務,並獲取返回結果
服務列表其實是個map
Ribbon負載均衡原理 [瞭解]
獲取@LoadBalanced註解標記的RestTemplate。
Ribbon將全部標記@LoadBalanced
註解的RestTemplate
保存到一個List集合當中,具體源碼以下:
@LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList();
具體源碼位置是在LoadBalancerAutoConfiguration
中。
RestTemplate添加一個攔截器
攔截器不是Ribbon的功能
RestTemplate添加攔截器須要有兩個步驟,首先是定義一個攔截器,其次是將定義的攔截器添加到RestTemplate中。
定義一個攔截器
實現ClientHttpRequestInterceptor
接口就具有了攔截請求的功能,該接口源碼以下:
public interface ClientHttpRequestInterceptor { /** *實現該方法,在該方法內完成攔截請求後的邏輯內容。 *對於ribbon而言,在該方法內完成了根據具體規則從 *服務集羣中選取一個服務,並向該服務發起請求的操做。 */ ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException; }
ribbon中對應的實現類是LoadBalancerInterceptor
具體源碼以下:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; //省略構造器代碼... @Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); /** *攔截請求,並調用loadBalancer.execute()方法 *在該方法內部完成server的選取。向選取的server *發起請求,並得到返回結果。 */ return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); } }
將攔截器添加到RestTemplate中
RestTemplate
繼承了InterceptingHttpAccessor
,在InterceptingHttpAccessor
中提供了獲取以及添加攔截器的方法,具體源碼以下:
public abstract class InterceptingHttpAccessor extends HttpAccessor { /** * 全部的攔截器是以一個List集合形式進行保存。 */ private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(); /** * 設置攔截器。 */ public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) { this.interceptors = interceptors; } /** * 獲取當前的攔截器。 */ public List<ClientHttpRequestInterceptor> getInterceptors() { return interceptors; } //省略部分代碼... }
經過這兩個方法咱們就能夠將剛纔定義的LoadBalancerInterceptor
添加到有@LoadBalanced
註解標識的RestTemplate
中。具體的源碼以下(LoadBalancerAutoConfiguration)省略部分代碼:
public class LoadBalancerAutoConfiguration { /** * 獲取全部帶有@LoadBalanced註解的restTemplate */ @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); /** * 建立SmartInitializingSingleton接口的實現類。Spring會在全部 * 單例Bean初始化完成後回調該實現類的afterSingletonsInstantiated() * 方法。在這個方法中會爲全部被@LoadBalanced註解標識的 * RestTemplate添加ribbon的自定義攔截器LoadBalancerInterceptor。 */ @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer( final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { @Override public void afterSingletonsInstantiated() { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } } }; } /** * 建立Ribbon自定義攔截器LoadBalancerInterceptor * 建立前提是當前classpath下不存在spring-retry。 * 因此LoadBalancerInterceptor是默認的Ribbon攔截 * 請求的攔截器。 */ @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } /** * 添加攔截器具體方法。首先獲取當前攔截器集合(List) * 而後將loadBalancerInterceptor添加到當前集合中 * 最後將新的集合放回到restTemplate中。 */ @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } }
至此知道了ribbon攔截請求的基本原理,接下來咱們看看Ribbon是怎樣選取server的。
Ribbon選取server原理概覽
經過上面的介紹咱們知道了當發起請求時ribbon會用LoadBalancerInterceptor
這個攔截器進行攔截。在該攔截器中會調用LoadBalancerClient.execute()
方法,該方法具體代碼以下:
@Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { /** *建立loadBalancer的過程能夠理解爲組裝選取服務的規則(IRule)、 *服務集羣的列表(ServerList)、檢驗服務是否存活(IPing)等特性 *的過程(加載RibbonClientConfiguration這個配置類),須要注意 *的是這個過程並非在啓動時進行的,而是當有請求到來時纔會處理。 */ ILoadBalancer loadBalancer = getLoadBalancer(serviceId); /** * 根據ILoadBalancer來選取具體的一個Server。 * 選取的過程是根據IRule、IPing、ServerList * 做爲參照。 */ Server server = getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); return execute(serviceId, ribbonServer, request); }
經過代碼咱們可知,首先建立一個ILoadBalancer
,這個ILoadBalancer
是Ribbon的核心類。能夠理解成它包含了選取服務的規則(IRule
)、服務集羣的列表(ServerList
)、檢驗服務是否存活(IPing
)等特性,同時它也具備了根據這些特性從服務集羣中選取具體一個服務的能力。 Server server = getServer(loadBalancer);
這行代碼就是選取舉一個具體server。 最終調用了內部的execute
方法,該方法代碼以下(只保留了核心代碼):
@Override public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { try { //發起調用 T returnVal = request.apply(serviceInstance); statsRecorder.recordStats(returnVal); return returnVal; } catch (IOException ex) { statsRecorder.recordStats(ex); throw ex; } catch (Exception ex) { statsRecorder.recordStats(ex); ReflectionUtils.rethrowRuntimeException(ex); } return null; }
接下來看下request.apply(serviceInstance)
方法的具體作了那些事情(LoadBalancerRequestFactory中):
@Override public ClientHttpResponse apply(final ServiceInstance instance) throws Exception { HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer); //省略部分代碼... /** * 發起真正請求。 */ return execution.execute(serviceRequest, body); }
看到這裏總體流程的原理就說完了,接下來咱們結合一張圖來回顧下整個過程:
首先獲取全部標識@LoadBalanced
註解的RestTemplate
(能夠理解成獲取那些開啓了Ribbon負載均衡功能的RestTemplate
),而後將Ribbon默認的攔截器LoadBalancerInterceptor
添加到RestTemplate
中,這樣當使用RestTemplate
發起http請求時就會起到攔截的做用。當有請求發起時,ribbon默認的攔截器首先會建立ILoadBalancer
(裏面包含了選取服務的規則(IRule
)、服務集羣的列表(ServerList
)、檢驗服務是否存活(IPing
)等特性)。在代碼層面的含義是加載RibbonClientConfiguration
配置類)。而後使用ILoadBalancer
從服務集羣中選擇一個服務,最後向這個服務發送請求。
Ribbon負載均衡規則
Ribbon默認負載均衡規則
根據上述Ribbon的原理,能夠知道IRule接口負責負載均衡的實現,具體以下:
規則名稱 特色 AvailabilityFilteringRule 過濾掉一直鏈接失敗的被標記爲circuit tripped的後端Server,並 過濾掉那些高併發的後端Server或者使用一個AvailabilityPredicate 來包含過濾server的邏輯,其實就是檢查status裏記錄的各個server 的運行狀態 BestAvailableRule 選擇一個最小的併發請求的server,逐個考察server, 若是Server被tripped了,則跳過 RandomRule 隨機選擇一個Server ResponseTimeWeightedRule 已廢棄,做用同WeightedResponseTimeRule WeightedResponseTimeRule 權重根據響應時間加權,響應時間越長,權重越小,被選中的可能性越低 RetryRule 對選定的負載均衡策略加上重試機制,在一個配置時間段內當 選擇Server不成功,則一直嘗試使用subRule的方式選擇一個 可用的Server RoundRobinRule 輪詢選擇,輪詢index,選擇index對應位置的Server ZoneAvoidanceRule 默認的負載均衡策略,即複合判斷Server所在區域的性能和Server的可用性 選擇Server,在沒有區域的環境下,相似於輪詢(RandomRule) 其中RandomRule表示隨機策略、RoundRobinRule表示輪詢策略、WeightedResponseTimeRule表示加權策略、BestAvailableRule表示請求數最少策略等等
隨機源碼:
輪詢源碼:
修改默認的自定義規則
默認是輪詢 能夠修改成任意的規則
修改成隨機算法
-
建立具備負載均衡功能的RestTemplate實例
[@Bean](https://my.oschina.net/bean) @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
使用RestTemplate進行rest操做的時候,會自動使用負載均衡策略,它內部會在RestTemplate中加入LoadBalancerInterceptor這個攔截器,這個攔截器的做用就是使用負載均衡。
默認狀況下會採用輪詢策略,若是但願採用其它策略,則指定IRule實現,如:
[@Bean](https://my.oschina.net/bean) public IRule ribbonRule() { return new BestAvailableRule(); }
這種方式對OpenFeign也有效。
修改成按照Nacos配置的權重進行負載均衡
-
在nacos中對集羣進行權重的配置
-
在項目中,選擇使用 NacosRule
Ribbon實戰優化
飢餓加載
Ribbon默認懶加載,意味着只有在發起調用的時候纔會建立客戶端
ribbon: eager-load: # 開啓ribbon飢餓加載 enabled: true # 配置user-center使用ribbon飢餓加載,多個使用逗號分隔 clients: user-center
參數調優
主要調整請求的超時時間,是否重試
若是業務沒有作冪等性的話建議把重試關掉:ribbon.MaxAutoRetriesNextServer=0
# 從註冊中心刷新servelist的時間 默認30秒,單位ms ribbon.ServerListRefreshInterval=15000 # 請求鏈接的超時時間 默認1秒,單位ms ribbon.ConnectTimeout=30000 # 請求處理的超時時間 默認1秒,單位ms ribbon.ReadTimeout=30000 # 對全部操做請求都進行重試,不配置這個MaxAutoRetries不起做用 默認false #ribbon.OkToRetryOnAllOperations=true # 對當前實例的重試次數 默認0 # ribbon.MaxAutoRetries=1 # 切換實例的重試次數 默認1 ribbon.MaxAutoRetriesNextServer=0
若是
MaxAutoRetries=1
和MaxAutoRetriesNextServer=1
請求在1s內響應,超過1秒先同一個服務器上重試1次,若是仍是超時或失敗,向其餘服務上請求重試1次。那麼整個ribbon請求過程的超時時間爲:ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)
若是你以爲這篇內容對你挺有有幫助的話:
-
點贊支持下吧,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)
-
歡迎在留言區與我分享你的想法,也歡迎你在留言區記錄你的思考過程。
-
以爲不錯的話,也能夠關注 編程鹿 的我的公衆號看更多文章和講解視頻(感謝你們的鼓勵與支持🌹🌹🌹)