Spring Clould負載均衡重要元件:Ribbon中重要類的用法
Ribbon是Spring Cloud Netflix全家桶中負責負載均衡的元件,它是一組類庫的集合。通過Ribbon,程式設計師能在不涉及到具體實現細節的基礎上“透明”地用到負載均衡,而不必在專案裡過多地編寫實現負載均衡的程式碼。
比如,在某個包含Eureka和Ribbon的叢集中,某個服務(可以理解成一個jar包)被部署在多臺伺服器上,當多個服務使用者同時呼叫該服務時,這些併發的請求能被用一種合理的策略轉發到各臺伺服器上。
事實上,在使用Spring Cloud的其它各種元件時,我們都能看到Ribbon的痕跡,比如Eureka能和Ribbon整合,而在後文裡將提到的提供閘道器功能Zuul元件在轉發請求時,也可以整合Ribbon從而達到負載均衡的效果。
從程式碼層面來看,Ribbon有如下三個比較重要的介面。
第一,ILoadBalancer,這也叫負載均衡器,通過它,我們能在專案里根據特定的規則合理地轉發請求,常見的實現類有BaseLoadBalancer。
第二,IRule,這個介面有多個實現類,比如RandomRule和RoundRobinRule,這些實現類具體地定義了諸如“隨機“和”輪詢“等的負載均衡策略,我們還能重寫該接口裡的方法來自定義負載均衡的策略。
在BaseLoadBalancer類裡,我們能通過IRule的實現類設定負載均衡的策略,這樣該負載均衡器就能據此合理地轉發請求。
第三,IPing介面,通過該介面,我們能獲取到當前哪些伺服器是可用的,我們也能通過重寫該接口裡的方法來自定義判斷伺服器是否可用的規則。在BaseLoadBalancer類裡,我們同樣能通過IPing的實現類設定判斷伺服器是否可用的策略。
1 ILoadBalancer:負載均衡器介面
在Ribbon裡,我們還可以通過ILOadBalancer這個介面以基於特定的負載均衡策略來選擇伺服器。
通過下面的ILoadBalancerDemo.java,我們來看下這個介面的基本用法。這個類是放在4.2部分建立的RabbionBasicDemo專案裡,程式碼如下。
1 //省略必要的package和import程式碼 2 public class ILoadBalancerDemo { 3 public static void main(String[] args){ 4 //建立ILoadBalancer的物件 5 ILoadBalancer loadBalancer = new BaseLoadBalancer(); 6 //定義一個伺服器列表 7 List<Server> myServers = new ArrayList<Server>(); 8 //建立兩個Server物件 9 Server s1 = new Server("ekserver1",8080); 10 Server s2 = new Server("ekserver2",8080); 11 //兩個server物件放入List型別的myServers物件裡 12 myServers.add(s1); 13 myServers.add(s2); 14 //把myServers放入負載均衡器 15 loadBalancer.addServers(myServers); 16 //在for迴圈裡發起10次呼叫 17 for(int i=0;i<10;i++){ 18 //用基於預設的負載均衡規則獲得Server型別的物件 19 Server s = loadBalancer.chooseServer("default"); 20 //輸出IP地址和埠號 21 System.out.println(s.getHost() + ":" + s.getPort()); 22 } 23 } 24 }
在第5行裡,我們建立了BaseLoadBalancer型別的loadBalancer物件,而BaseLoadBalancer是負載均衡器ILoadBalancer介面的實現類。
在第6到第13行裡,我們建立了兩個Server型別的物件,並把它們放入了myServers裡,在第15行裡,我們把List型別的myServers物件放入了負載均衡器裡。
在第17到22行的for迴圈裡,我們通過負載均衡器模擬了10次選擇伺服器的動作,具體而言,是在第19行裡,通過loadBalancer的chooseServer方法以預設的負載均衡規則選擇伺服器,在第21行裡,我們是用“列印”這個動作來模擬實際的“使用Server物件處理請求”的動作。
上述程式碼的執行結果如下所示,其中我們能看到,loadBalancer這個負載均衡器把10次請求均攤到了2臺伺服器上,從中確實能看到 “負載均衡”的效果。
第二,IRule,這個介面有多個實現類,比如RandomRule和RoundRobinRule,這些實現類具體地定義了諸如“隨機“和”輪詢“等的負載均衡策略,我們還能重寫該接口裡的方法來自定義負載均衡的策略。
在BaseLoadBalancer類裡,我們能通過IRule的實現類設定負載均衡的策略,這樣該負載均衡器就能據此合理地轉發請求。
第三,IPing介面,通過該介面,我們能獲取到當前哪些伺服器是可用的,我們也能通過重寫該接口裡的方法來自定義判斷伺服器是否可用的規則。在BaseLoadBalancer類裡,我們同樣能通過IPing的實現類設定判斷伺服器是否可用的策略。
1 ekserver2:8080 2 ekserver1:8080 3 ekserver2:8080 4 ekserver1:8080 5 ekserver2:8080 6 ekserver1:8080 7 ekserver2:8080 8 ekserver1:8080 9 ekserver2:8080 10 ekserver1:8080
2 IRule:定義負載均衡規則的介面
在Ribbon裡,我們可以通過定義IRule介面的實現類來給負載均衡器設定相應的規則。在下表裡,我們能看到IRule介面的一些常用的實現類。
實現類的名字 |
負載均衡的規則 |
RandomRule |
採用隨機選擇的策略 |
RoundRobinRule |
採用輪詢策略 |
RetryRule |
採用該策略時,會包含重試動作 |
AvailabilityFilterRule |
會過濾些多次連線失敗和請求併發數過高的伺服器 |
WeightedResponseTimeRule |
根據平均響應時間為每個伺服器設定一個權重,根據該權重值優先選擇平均響應時間較小的伺服器 |
ZoneAvoidanceRule |
優先把請求分配到和該請求具有相同區域(Zone)的伺服器上 |
在下面的IRuleDemo.java的程式裡,我們來看下IRule的基本用法。
1 //省略必要的package和import程式碼 2 public class IRuleDemo { 3 public static void main(String[] args){ 4 //請注意這是用到的是BaseLoadBalancer,而不是ILoadBalancer介面 Java架構交流學習圈:681065582幫助突破瓶頸 提升思維能力 5 BaseLoadBalancer loadBalancer = new BaseLoadBalancer(); 6 //宣告基於輪詢的負載均衡策略 7 IRule rule = new RoundRobinRule(); 8 //在負載均衡器裡設定策略 9 loadBalancer.setRule(rule); 10 //如下定義3個Server,並把它們放入List型別的集合中 11 List<Server> myServers = new ArrayList<Server>(); 12 Server s1 = new Server("ekserver1",8080); 13 Server s2 = new Server("ekserver2",8080); 14 Server s3 = new Server("ekserver3",8080); 15 myServers.add(s1); 16 myServers.add(s2); 17 myServers.add(s3); 18 //在負載均衡器裡設定伺服器的List 19 loadBalancer.addServers(myServers); 20 //輸出負載均衡的結果 21 for(int i=0;i<10;i++){ 22 Server s = loadBalancer.chooseServer(null); 23 System.out.println(s.getHost() + ":" + s.getPort()); 24 } 25 } 26 }
這段程式碼和上文裡的ILoadBalancerDemo.java很相似,但有如下的差別點。
1 在第5行裡,我們是通過BaseLoadBalancer這個類而不是介面來定義負載均衡器,原因是該類包含setRule方法。
2 在第7行定義了一個基於輪詢規則的rule物件,並在第9行裡把它設定進負載均衡器。
3 在第19行裡,我們是把包含3個Server的List物件放入負載均衡器,而不是之前的兩個。由於這裡存粹是為了演示效果,所以我們就放入一個根本不存在的“ekserver3”伺服器。
執行該程式後,我們可以看到有10次輸出,而且確實是按“輪詢”的規則有順序地輸出3個伺服器的名字。如果我們把第7行的程式碼改成如下,那麼就會看到 “隨機”地輸出伺服器名。
IRule rule = new RandomRule();
3 IPing:判斷伺服器是否可用的介面
在專案裡,我們一般會讓ILoadBalancer介面自動地判斷伺服器是否可用(這些業務都封裝在Ribbon的底層程式碼裡),此外,我們還可以用Ribbon元件裡的IPing介面來實現這個功能。
在下面的IRuleDemo.java程式碼裡,我們將演示IPing介面的一般用法。
1 //省略必要的package和import程式碼 2 class MyPing implements IPing { 3 public boolean isAlive(Server server) { 4 //如果伺服器名是ekserver2,則返回false 5 if (server.getHost().equals("ekserver2")) { 6 return false; 7 } 8 return true; 9 } 10 }
第2行定義的MyPing類實現了IPing介面,並在第3行重寫了其中的isAlive方法。
在這個方法裡,我們根據伺服器名來判斷,具體而言,如果名字是ekserver2,則返回false,表示該伺服器不可用,否則返回true,表示當前伺服器可用。
11 public class IRuleDemo { 12 public static void main(String[] args) { 13 BaseLoadBalancer loadBalancer = new BaseLoadBalancer(); Java架構交流學習圈:681065582幫助突破瓶頸 提升思維能力 14 //定義IPing型別的myPing物件 15 IPing myPing = new MyPing(); 16 //在負載均衡器裡使用myPing物件 17 loadBalancer.setPing(myPing); 18 //同樣是建立三個Server物件並放入負載均衡器 19 List<Server> myServers = new ArrayList<Server>(); 20 Server s1 = new Server("ekserver1", 8080); 21 Server s2 = new Server("ekserver2", 8080); 22 Server s3 = new Server("ekserver3", 8080); 23 myServers.add(s1); 24 myServers.add(s2); 25 myServers.add(s3); 26 loadBalancer.addServers(myServers); 27 //通過for迴圈多次請求伺服器 28 for (int i = 0; i < 10; i++) { 29 Server s = loadBalancer.chooseServer(null); 30 System.out.println(s.getHost() + ":" + s.getPort()); 31 } 32 } 33 }
在第12行的main函式裡,我們在第15行建立了IPing型別的myPing物件,並在第17行把這個物件放入了負載均衡器。通過第18到第26行的程式碼,我們建立了三個伺服器,並把它們也放入負載均衡器。
在第28行的for迴圈裡,我們依然是請求並輸出伺服器名。由於這裡的負載均衡器loadBalancer中包含了一個IPing型別的物件,所以在根據策略得到伺服器後,會根據myPing裡的isActive方法來判斷該伺服器是否可用。
由於在這個方法裡,我們定義了ekServer2這臺伺服器不可用,所以負載均衡器loadBalancer物件始終不會把請求傳送到該伺服器上,也就是說,在輸出結果中,我們不會看到“ekserver2:8080”的輸出。
從中我們能看到IPing介面的一般用法,我們可以通過重寫其中的isAlive方法來定義“判斷伺服器是否可用“的邏輯,在實際專案裡,判斷的依據無非是”伺服器響應是否時間過長“或”發往該伺服器的請求數是否過多“,而這些判斷方法都封裝在IRule介面以及它的實現類裡,所以在一般的場景中我們用到IPing介面。