簡介
Spring cloud Ribbon是一個基於HTTP和TCP的客戶端負載均衡工具,它是基於Netflix的Riboon實現的。Ribbon是客戶端負載均衡器,這有別語例如Nginx服務端負載均衡器。Ribbon本身提供了不通負載均衡策略使用不通的應用場景。
客戶端負載均衡和服務端負載均衡的區別
客戶端負載均衡和服務端負載均衡最大的不同在於服務清單所在的位置。客戶端負載均衡中,客戶端中都維護着自己要訪問的服務段清單,而這些清單都來源於服務注冊中心,但是服務端負載均衡的服務清單是無法自己來維護的。
負載均衡器分類
- BaseLoadBalancer
BaseLoadBalancer類時Ribbon服務均衡器的基礎實現類,在該類中定義了很多關於負載均衡器光宇的基礎內容。
- DynamicServiceListLoadBalancer
DynamicServiceListLoadBalancer負載均衡器時對BaseLoadBalancer的擴展。
- ZoneAwareLoadBalancer
ZoneAwareLoadBalancer負載均衡器時對DynamicServiceListLoadBalancer的擴展。
負載均衡策略
IRule時負載均衡策略的接口,AbstractLoadBalancerRule是負載均衡策略的抽象類。下面我們看一下幾個具體的實現類:
- RandomRule:
實現了從服務實例清單中隨機選擇一個服務實例的功能
- RoundRobinRule:
實現了按照線性輪詢的方式一次選擇每個服務實例的功能
- RetryTule:
實現了一個具備重試機制的實例選擇功能
- WeightedRespinseTimeRule:
該策略是對RoundRobinRule的擴展,增加了根據實例的運行情況來計算權重,並根據權重來挑選實例,已達到更優的分配效果。
- ClientConfigEnabledRoundRobinRule
該策略較為特殊,我們一般不直接使用它。因為它本身並沒有實現什么特殊的處理邏輯,真如代碼中所示,在他的內部定義了一個RoundRobinRule策略,而choose函數的實現也正是使用了RoundRobinRule的線性輪詢機制,所以它實現的功能實際上RoundeRobinRule相同。
雖然不能直接使用該策略,但是可以通過繼承該策略,默認的choose就實現了線性輪詢機制,但是可以在子類中實現更高級的策略
- BestAvailableRule
該策略通過遍歷負載均衡器中維護的所有實例,會過濾調故障的實例,並找出並發請求數最小的一個,所以該策略的特征是選擇出最空閑的實例
- PredicateBaseRule
先通過子類中實現的Predicate邏輯來過濾一部分服務實例,然后再以線性輪詢的方式從過濾后的實例清單中選出一個。至於如何過濾,需要我們在AbstractServerPredicate的子類中實現apply方法來確定具體的實現策略。
- ZoneAvoidanceRule
它是PredicateBaseRule的具體實現類,從它的源碼中可以看到它是通過CompositePredicate來進行服務實例清單額過濾的。這是一個組合過濾條件,在其構造函數中,ZoneAvoidancePredicate為主過濾條件,AvailabilityPredicate為次過濾條件初始化了組合過濾條件的實例。
ZoneAvoidanceRule在實現的時候並沒有像AvailabilityFilteringRule那樣重寫choose函數來優化,所以它完全遵循了弗雷的過濾主邏輯:“先過濾清單,再輪詢選擇”。請中國過濾清單的條件就是我們上面提到的以ZoneAvoidancepredicate為主過濾條件,AvailabilityPredicate為次過濾條件的組合過濾條件CompositePredicate.從CompositePredicate的代碼片段中,我們可以看到它定義的一個主過濾條件AbstraServicePredicate Delegate以及一組次過濾條件列表listfallbacks,所以它的此過濾列表是可以擁有多個的,並且由於它采用了List存儲所以次過濾條件是按順序執行的。
在獲取過濾結果的實現函數getEligibleServiers中,它的處理邏輯如下所示:
1.使用主過濾條件對所有的實例過濾並返回過濾后的實例清單
2.一次使用此過濾條件列表中的過濾條件對主過濾的結果進行過濾
3.每次過濾之后(包括著過濾條件和此過濾條件),都需要判斷下面兩個條件,只要有一個符合就不再進行過濾,將當前結果返回供線性輪詢算法選擇:過濾后的實例總數>=最小過濾實例數(minimalFiltereServers,默認為1);過濾后的實例比例>最小過濾百分比(minimalFilteredPercentage,默認為0)。
RestTemple詳解
要使用ribbon實現負載均衡策略,restTemple是必不可少的,我們使用它實現對服務的消費訪問和負載均衡。因為我們是使用restful接口訪問的,所以這里主要介紹restful接口的幾種使用:
- GET
第一種:getForEntity
1.getForEntity(String url,Class responseType,Object... urlVariables):
此方法有三個參數:其中url為請求地址,responseType為響應body的包裝類型,urlVariables為url中的參數綁定。主要做法是:在url中使用占位符並配合urlVariables參數實現GET請求的參數綁定。
示例:
RestTemplate restTemplate=new RestTemplate(); ResponseEntity<User> responseEntity=restTemplate.getForEntity("http://user-service/user?name={1}",User.class,"zhangsan"); User body=responseEntity.getBody();
2.getForEntity(String url,Class responseType,Map urlVariables):
此方法和上面的方法很像,只是參數類型不一樣,使用了Map類型,所以在使用該方法綁定參數是需要在占位符中指定map中參數的key值。
示例:
RestTemplate restTemplate=new RestTemplate(); Map<String,String> params=new HashMap<>(); params.put("name","zhangsan"); params.put("age",2); ResponseEntity<User> responseEntity=restTemplate.getForEntity("http://user-service/user?name={name}?age={age}",User.class,params); User body=responseEntity.getBody();
3.getForEntity(URI url,Class responseType)
此方法用url對象替代了上面的url和urlVariables參數來指定訪問地址和參數的綁定。
示例:
RestTemplate restTemplate=new RestTemplate(); UriComponents uriComponents=UriComponentsBuilder.fromUriString("http://user_service/user?name={name}").build().expand("zhangsan").encode(); URI uri=uriComponents.toUri(); ResponseEntity<User> responseEntity=restTemplate.getForEntity(uri,User.class,"zhangsan"); User body=responseEntity.getBody();
第二種:getForObject:
此方法是和不需要關注出body外的其他內容時使用,因為之方法會直接返回響應體的body內容並進行對象封裝,實現請求直接返回包裝好的對象內容弄個。
1.getForObject(String url,Class responseType,Object... urlVariables)
2.getForObject(String url,Class responseType,Map urlVariables):
3.getForObject(URI url,Class responseType)
這三種使用方法和getForEntity類似,只舉一個例子:
RestTemplate restTemplate=new RestTemplate(); User result=restTemplate.getForObject(uri,User.class);
- POST
此類型的請求參數中和getForEntity大部分一致,只是多了一個參數request。request參數可以是個普通對象,也可以是一個HttpEntity對象。如果時一個普通對象,而非HttpEntity對象的時候,RestTemplate會將請求對象轉換為一個HttpEntity對象來處理,其中Object就是request的類型,request內容會被事為完整的body來處理;而如果request是HttpEntity對象,那么就是被當作一個完成的HTTP請求對象來處理,這個request中不僅包含了body的內容,也包含了header的內容。
第一種:postForEntity
1.postForEntity(String url,Object request,Class responseType,Object... urlVariables)
2.postForEntity(String url,Object request,Class responseType,Map urlVariables)
3.postForEntity(URI url,Object request,Class responseType)
示例:
RestTemplate restTemplate=new RestTemplate(); User user=new User("zhangsan",30); ResponseEntity<User> responseEntity=restTemplate.postForEntity("http://user-service/user",user,User.class); User body=responseEntity.getBody();
第二種:postForObject
1.postForEntity(String url,Object request,Class responseType,Object... urlVariables)
2.postForEntity(String url,Object request,Class responseType,Map urlVariables)
3.postForEntity(URI url,Object request,Class responseType)
示例:
RestTemplate restTemplate=new RestTemplate(); User user=new User("zhangsan",30); User responseEntity=restTemplate.postForObject("http://user-service/user",user,User.class);
- PUT
在RestTemplate中,對PUT請求可以通過put方法進行調用實現。
1.put(String url,Object request,Object... urlVariables)
2.put(String url,Object request,Map urlVariables)
3.put(URI url,Object request)
示例:
RestTemplate restTemplate=new RestTemplate(); Long id=10001L; User user=new User("zhangsan",30); restTemplate.put("http://user-service/user/{1}",user,id);
- DELETE
在RestTemplate中,對DELTTE請求可以通過delete方法進行調用實現。
1.put(String url,Object request,Object... urlVariables)
2.put(String url,Object request,Map urlVariables)
3.put(URI url,Object request)
示例:
RestTemplate restTemplate=new RestTemplate(); Long id=10001L; restTemplate.put("http://user-service/user/{1}",id);
示例
通過Spring cloud Ribbon的封裝,我們在微服務架構中使用客戶端負載均衡調用時非常簡單的,只需要兩步:
1.服務提供者只需要將服務注冊到注冊中心或者多個相關聯的服務注冊中心
2.服務消費者直接通過調用被@LoandBalanced注解修飾過的RestTemlate來實現面向服務的接口調用即可
- pom,xml配置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency> -->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 應用主類修改
@EnableDiscoveryClient @SpringBootApplication public class RibbonApplicate{ /** * 實例化ribbon使用的RestTemplate * @return */ @Bean @LoadBalanced public RestTemplate rebbionRestTemplate(){ return new RestTemplate(); } public static void main (String[] args){ SpringApplication.run(RibbonApplication.class,args); } }
- 測試類
public class RibbonController{ @autowired RestTemplate restTemplate; @Getmapping("/helloRibbon") public String helloRibbon(){ //訪問hello-service服務的hello接口 return restTemplate.getForEntity("http://hello-service/hello",String.class).getBody(); } }
- application.properties配置