Robbin是在Spring Cloud中的一個組件,是由Netfix發布的負載均衡器,有助於控制HTTP和TCP客戶端的行為。它給我們提供了默認的輪詢、隨機等負載均衡算法。同時也可以由我們定義自己的算法。
由於Robbin已經被集成在Eureka里面,因此我們這個樣例的代碼都是在《微服務Eureka使用詳解》的基礎上進行。
參考博客:https://blog.csdn.net/u013089490/article/details/83786844、https://blog.csdn.net/dwhdome/article/details/86477961
負載均衡樣例
(1)我們首先啟動好在《微服務Eureka使用詳解》中編寫的三個服務:服務注冊中心,user服務,roles服務。訪問Eureka的管理頁面可以看到如下內容:

(2)下面我們先來修改User服務(只修改controller):
@GetMapping("users/{id}") public String getUser(@PathVariable("id") String id) { String str = "7001User.id" + id; System.out.println(str); return str; }
啟動服務,它的端口是7001。
然后再復制一個User項目,將Controller內容調整為如下:
@GetMapping("users/{id}") public String getUser(@PathVariable("id") String id) { String str = "7002User.id" + id; System.out.println(str); return str; }
以及將配置文件中的端口修改為7002
server.port=7002
啟動該應用。
這時我們再查看Eureka服務頁面:

可以清楚的看到USER服務的可用區域(Availability Zones)已經從(1)變成了(2)。狀態(status)已經變成了兩個服務地址7001和7002。
(3)Roles服務的負載均衡在《微服務Eureka使用詳解》中已經配置過了,我們這里查看一下即可。
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
(4)訪問Roles服務路徑~/roles/{id},可用連續多次訪問,這里假如我連續訪問5次:
/roles/1 /roles/2 /roles/3 /roles/4 /roles/5
可用看到返回的結果:
7001User.id1
7002User.id2
7001User.id3
7002User.id4
7001User.id5
兩個User服務,7001端口和7002端口是默認處於一個輪詢的狀態。假設這一次訪問7001端口,下一次就訪問7002端口,以此類推。
修改負載均衡策略
負責負載均衡策略的頂級接口:
com.netflix.loadbalancer.IRule
所有的負責均衡算法均實現了這個接口,它的實現類如下:

默認情況下,使用的是
com.netflix.loadbalancer.RoundRobinRule:以輪詢的方式進行負載均衡。
常用的還有
com.netflix.loadbalancer.RandomRule:隨機策略
com.netflix.loadbalancer.RetryRule:重試策略。
com.netflix.loadbalancer.WeightedResponseTimeRule:權重策略。會計算每個服務的權重,越高的被調用的可能性越大。
com.netflix.loadbalancer.BestAvailableRule:最佳策略。遍歷所有的服務實例,過濾掉故障實例,並返回請求數最小的實例返回。
com.netflix.loadbalancer.AvailabilityFilteringRule:可用過濾策略。過濾掉故障和請求數超過閾值的服務實例,再從剩下的實力中輪詢調用。
如果我們要實現自己的策略,可以繼承IRule接口,下面我們來以RoundRobinRule為例查看一下如何實現負載均衡策略。
(1)IRule接口
public interface IRule { // 返回經過負載均衡后最終調用的服務 Server choose(Object var1); void setLoadBalancer(ILoadBalancer var1); ILoadBalancer getLoadBalancer(); }
(2)RoundRobinRule類
我們先看最重要的choose(Object)方法
public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); }
里面調用了我們另一個choose(ILoadBalancer, Object)方法
public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { log.warn("no load balancer"); return null; } else { Server server = null; int count = 0; while(true) { if (server == null && count++ < 10) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if (upCount != 0 && serverCount != 0) { int nextServerIndex = this.incrementAndGetModulo(serverCount); server = (Server)allServers.get(nextServerIndex); if (server == null) { Thread.yield(); } else { if (server.isAlive() && server.isReadyToServe()) { return server; } server = null; } continue; } log.warn("No up servers available from load balancer: " + lb); return null; } if (count >= 10) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; } } }
在上面的方法中,主要內容是在while(true)內獲取下一個server,獲取的方法是incrementAndGetModulo(int)。然后根據方法返回的服務下標,從服務集合中找到對應的server,如果server存在且存活,會直接使用這個server。如果server不存在或不存在,則會再循環獲取下一個。直到循環10次,或着沒有從服務注冊中心找到可用的服務,會返回null。
核心的incrementAndGetModulo(int)方法
private int incrementAndGetModulo(int modulo) { int current; int next; do { current = this.nextServerCyclicCounter.get(); //nextServerCyclicCounter是AtomicInteger對象,默認值0,可保證線程安全性 next = (current + 1) % modulo; //每次往后移一位,取集合中的下一個server。這里要注意的是從1開始,即數組中的第二個server會被第一個調用。 } while(!this.nextServerCyclicCounter.compareAndSet(current, next)); //操作完成后用CAS操作將next賦值給nextServerCyclicCounter return next; }
(3)可用順便再看一下RetryRule類。
public Server choose(ILoadBalancer lb, Object key) { long requestTime = System.currentTimeMillis(); long deadline = requestTime + this.maxRetryMillis; Server answer = null; answer = this.subRule.choose(key); //內部仍然是使用了輪詢策略。 if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) { InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis()); while(!Thread.interrupted()) { answer = this.subRule.choose(key); if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) { break; } Thread.yield(); } task.cancel(); } return answer != null && answer.isAlive() ? answer : null; }
這個類采用的是重試策略,可以看到里面其實仍是采用了輪詢策略,只不過如果輪詢的server無法訪問,或者不存活,會在指定的時間(500)內一直獲取下一個server,直到找到一個存活的server。
注意:上面所說的故障服務,是由Eureka注冊中心來判斷。即使服務已經掛掉,但是Eureka的實例未過期,仍會被判斷為正常。但是實際的返回可能是null等。
如果我們要實現自己的負載均衡策略,也可以通過繼承IRule接口,在配置文件中進行配置。
