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項目裡,代碼如下。
//省略必要的package和import代碼public class ILoadBalancerDemo { public static void main(String[] args){ //創建ILoadBalancer的對象ILoadBalancer loadBalancer = new BaseLoadBalancer(); //定義一個服務器列表List<Server> myServers = new ArrayList<Server>(); //創建兩個Server對象Server s1 = new Server("ekserver1",8080); Server s2 = new Server("ekserver2",8080); //兩個server對象放入List類型的myServers對象裡myServers.add(s1); myServers.add(s2); //把myServers放入負載均衡器loadBalancer.addServers(myServers); //在for循環裡發起10次調用for(int i=0;i<10;i++){ //用基於默認的負載均衡規則獲得Server類型的對象Server s = loadBalancer.chooseServer("default"); //輸出IP地址和端口號System.out.println(s.getHost() + ":" + s.getPort()); } } }在第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的實現類設置判斷服務器是否可用的策略。
ekserver2:8080 ekserver1:8080 ekserver2:8080 ekserver1:8080 ekserver2:8080 ekserver1:8080 ekserver2:8080 ekserver1:8080 ekserver2:8080 ekserver1:8080
2 IRule:定義負載均衡規則的接口
在Ribbon裡,我們可以通過定義IRule接口的實現類來給負載均衡器設置相應的規則。在下表裡,我們能看到IRule接口的一些常用的實現類。
實現類的名字 | 負載均衡的規則 |
RandomRule | 採用隨機選擇的策略 |
RoundRobinRule | 採用輪詢策略 |
RetryRule | 採用該策略時,會包含重試動作 |
AvailabilityFilterRule | 會過濾些多次連接失敗和請求並發數過高的服務器 |
WeightedResponseTimeRule | 根據平均響應時間為每個服務器設置一個權重,根據該權重值優先選擇平均響應時間較小的服務器 |
ZoneAvoidanceRule | 優先把請求分配到和該請求具有相同區域(Zone)的服務器上 |
在下面的IRuleDemo.java的程序裡,我們來看下IRule的基本用法。
//省略必要的package和import代碼public class IRuleDemo { public static void main(String[] args){ //請注意這是用到的是BaseLoadBalancer,而不是ILoadBalancer接口BaseLoadBalancer loadBalancer = new BaseLoadBalancer(); //聲明基於輪詢的負載均衡策略IRule rule = new RoundRobinRule(); //在負載均衡器裡設置策略loadBalancer.setRule(rule); //如下定義3個Server,並把它們放入List類型的集合中List<Server> myServers = new ArrayList<Server>(); Server s1 = new Server("ekserver1",8080); Server s2 = new Server("ekserver2",8080); Server s3 = new Server("ekserver3",8080); myServers.add(s1); myServers.add(s2); myServers.add(s3); //在負載均衡器裡設置服務器的List loadBalancer.addServers(myServers); //輸出負載均衡的結果for(int i=0;i<10;i++){ Server s = loadBalancer.chooseServer(null); System.out.println(s.getHost() + ":" + s.getPort()); } } }這段代碼和上文裡的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接口的一般用法。
//省略必要的package和import代碼class MyPing implements IPing { public boolean isAlive(Server server) { //如果服務器名是ekserver2,則返回false if (server.getHost().equals("ekserver2")) { return false; } return true; } }第2行定義的MyPing類實現了IPing接口,並在第3行重寫了其中的isAlive方法。
在這個方法裡,我們根據服務器名來判斷,具體而言,如果名字是ekserver2,則返回false,表示該服務器不可用,否則返回true,表示當前服務器可用。
public class IRuleDemo { public static void main(String[] args) { BaseLoadBalancer loadBalancer = new BaseLoadBalancer(); //定義IPing類型的myPing對象IPing myPing = new MyPing(); //在負載均衡器裡使用myPing對象loadBalancer.setPing(myPing); //同樣是創建三個Server對象並放入負載均衡器List<Server> myServers = new ArrayList<Server>(); Server s1 = new Server("ekserver1", 8080); Server s2 = new Server("ekserver2", 8080); Server s3 = new Server("ekserver3", 8080); myServers.add(s1); myServers.add(s2); myServers.add(s3); loadBalancer.addServers(myServers); //通過for循環多次請求服務器for (int i = 0; i < 10; i++) { Server s = loadBalancer.chooseServer(null); System.out.println(s.getHost() + ":" + s.getPort()); } } }在第12行的main函數里,我們在第15行創建了IPing類型的myPing對象,並在第17行把這個對象放入了負載均衡器。通過第18到第26行的代碼,我們創建了三個服務器,並把它們也放入負載均衡器。
在第28行的for循環裡,我們依然是請求並輸出服務器名。由於這裡的負載均衡器loadBalancer中包含了一個IPing類型的對象,所以在根據策略得到服務器後,會根據myPing裡的isActive方法來判斷該服務器是否可用。
由於在這個方法裡,我們定義了ekServer2這台服務器不可用,所以負載均衡器loadBalancer對象始終不會把請求發送到該服務器上,也就是說,在輸出結果中,我們不會看到“ekserver2:8080”的輸出。
從中我們能看到IPing接口的一般用法,我們可以通過重寫其中的isAlive方法來定義“判斷服務器是否可用“的邏輯,在實際項目裡,判斷的依據無非是”服務器響應是否時間過長“或”發往該服務器的請求數是否過多“,而這些判斷方法都封裝在IRule接口以及它的實現類裡,所以在一般的場景中我們用到IPing接口。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。