本文是Spring Cloud專欄的第四篇文章,瞭解前三篇文章內容有助於更好的理解本文:git
Ribbon是一個基於HTTP和TCP的客戶端負載均衡器,當使用Ribbon對服務進行訪問的時候,他會擴展Eureka客戶端的服務發現功能,實現從Eureka註冊中心獲取服務端列表,並經過Eureka客戶端來肯定服務端是否已經啓動。Ribbon在Eureka客戶端服務發現的基礎上,實現對服務實例的選擇策略,從而實現對服務的負載均衡消費。負載均衡在系統架構中是一個很是重要的內容,由於負載均衡是對系統的高可用、網絡的壓力的緩衝和處理能力擴容的重要手段之一,咱們一般說的負載均衡都是指的是服務端的負載均衡,其中分爲硬件負載均衡和軟件負載均衡。服務器
硬件負載均衡:主要經過服務器節點之間安裝專門用於負載均衡的設備,好比F5,深信服,Array等。網絡
軟件負載均衡:則是經過服務器上安裝一些具備負載功能或模塊的軟件來完成請求分發工做,好比Nginx、LVS、HAProxy等。架構
硬件負載均衡的設備或是軟件負載均衡的軟件模塊都會維護一個下掛可用的服務端清單,經過心跳檢測來剔除故障的服務端節點保證清單中都是能夠正常訪問的服務端節點。當客戶端發送請求到負載均衡的設備時候,該設備按某種算法(好比線性輪詢、按權重負載、按流量負載等)從維護的可用服務端清單中取出一臺服務端地址,而後進行轉發。併發
Ribbon是Netflix發佈的開源項目,主要功能是提供客戶端的軟件負載均衡算法,是一個基於HTTP和TCP的客戶端負載均衡工具。Spring Cloud對Ribbon作了二次封裝,可讓咱們使用 RestTemplate的服務請求,自動轉換成客戶端負載均衡的服務調用。Ribbon支持多種負載均衡算法,還支持自定義的負載均衡算法。Ribbon只是一個工具類框架,比較小巧, Spring Cloud對它封裝後使用也非 常方便,它不像服務註冊中心、配置中心、AP網關那樣須要獨立部署, Ribbon 只須要在代碼直接使用便可。app
Ribbon與 Nginx的區別:負載均衡
都是軟負載
Ribbon是客戶端負載均衡
Nginx是服務器段負載均衡
區別在於:
服務清單所存儲的位置不一樣,在客戶端負載均衡中,全部客戶端節點下的服務端清單,須要本身從服務註冊中心上獲取,好比Eureka服務註冊中心。同服務端負載均衡的架構相似,在客戶端負載均衡中也須要心跳去維護服務端清單的健康性,只是這個步驟須要與服務註冊中心配合完成,在SpringCloud實現的服務治理框架中,默認會建立針對各個服務治理框架到的Ribbon自動化整合配置,好比Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,在實際使用的時候,咱們能夠經過查看這個類的實現,以找到他們的配置詳情來幫助咱們更好的使用它。
經過Spring Cloud Ribbon的封裝,咱們在微服務架構中使用客戶端負載均衡調用很是的簡單,只須要以下兩步:
服務提供者只須要啓動多個服務實例並註冊到一個註冊中心或是多個相關聯的服務註冊中心上
服務消費者直接經過調用被@LoadBalanced註解修飾過的RestTemplate來實現面向服務的接口調用。
這樣咱們就能夠將服務提供者的高可用以及服務消費者的負載均衡用一塊兒實現了。
服務端的負載均衡是提早配置好的:Nginx
客戶端的負載均衡是從註冊中心找的:Ribbon
在SpringCloud中,Ribbon主要與RestTemplate對象配合使用,Ribbon會自動化配置RestTemplate對象,經過@LoadBalance開啓RestTemplate對象調用時的負載均衡,Ribbon所處的做用如圖:
圖片來源網絡
一、前面提到過,經過Spring Cloud Ribbon的封裝,咱們在微服務架構中使用客戶端負載均衡調用很是的簡單,只須要以下兩步:
服務提供者只須要啓動多個服務實例並註冊到一個註冊中心或是多個相關聯的服務註冊中心上
服務消費者直接經過調用被@LoadBalanced註解修飾過的RestTemplate來實現面向服務的接口調用。
二、咱們複製服務提供者(springcloud-service-provider)而且命名爲springcloud-service-provider-02,修改controlle響應結果內容,區別服務提供者(springcloud-service-provider)的內容。修改服務提供者(springcloud-service-provider-02)端口爲8081,具體詳細代碼查看案例源碼。註冊中心咱們之後使用8700單節點,只是爲了方便。
三、在消費者的RestTemplate中添加以下代碼:
//使用Ribbon實現負載均衡調用,默認是輪詢 @LoadBalanced //加入ribbon的支持,那麼在調用時,便可改成使用服務名稱來訪問 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }
四、查看Eureka的web頁面顯示提供者兩個實例
五、啓動消費者,進行訪問如圖:
provider-01和provider-02交替出現,能夠看出默認是輪詢策略。
Ribbon的負載均衡策略是由IRule接口定義,該接口由以下實現:
RandomRule |
隨機 |
RoundRobinRule |
輪詢 |
AvailabilityFilteringRule |
先過濾掉因爲屢次訪問故障的服務,以及併發鏈接數超過閥值的服務,而後對剩下的服務按照輪詢策略進行訪問 |
WeightedResponseTimeRule |
根據平均響應時間計算全部服務的權重,響應時間越快服務權重就越大被選中的機率即越高,若是服務剛啓動時間統計信息不足,,則使用RoundRobinRule策略,待統計信息足夠,會切換到該WeightedResponseTimeRule策略 |
RetryRule |
先按照RoundRobinRule策略分發,若是分發到的服務不能訪問,則在指定的時間內重試,若是不行的話,則分發到其餘可用的服務 |
BestAvailableRule |
先過濾掉因爲屢次訪問的故障的服務,而後選擇一個併發量最小的服務 |
ZoneAvoidanceRule |
綜合判斷服務節點所在區域的性能和服務節點的可用性,來決定選擇哪一個服務 |
TIP:結合Ribbon負載均衡,默認的是輪詢,從新注入IRule能夠實現負載均衡的其餘策略
當咱們從服務消費端去調用服務提供者的服務的時候,使用了一個極其方便的對象叫RestTemplate,當時咱們只使用了 RestTemplate中最簡單的一個功能getForEntity發起了一個get請求去調用服務端的數據,同時,咱們還經過配置@Loadbalanced註解開啓客戶端負載均衡, RestTemplate的功能很是強大, 那麼接下來就來詳細的看一下RestTemplate中幾種常見請求方法的使用。在平常操做中,基於Rest的方式一般是四種狀況,它們分表是
GET請求-查詢數據
POST請求-添加數據
PUT請求-修改數據
DELETE-刪除數據
Get請求能夠有兩種方式
該方法返回一個ResponseEntity<T>對象,ResponseEntity<T>是Spring對HTTP請求響應的封裝,包括了幾個重要的元素,好比響應碼,contentType,contentLength,響應消息體等
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/hello", String.class); String body = forEntity.getBody(); HttpStatus statusCode = forEntity.getStatusCode(); int statusCodeValue = forEntity.getStatusCodeValue(); HttpHeaders headers = forEntity.getHeaders(); System.out.println(body); System.out.println(statusCode); System.out.println(statusCodeValue); System.out.println(headers);
以上代碼, getForEntity方法第—個參數爲要調用的服務的地址,即服務提供者提供的http://SPRINGCLOUD-SERVICE-PROVIDER/provider/hello接口地址,注意這裏是經過服務名調用而不是服務地址,若是改成服務地址就沒法使用Ribbon實現客戶端負載均衡了。getForEntity方法第二個參數String.class表示但願返回的body類型是 String 類型,若是但願返回一個對象,也是能夠的,好比User對象
/** * 調用get請求,返回一個User對象 * @return */ @RequestMapping("/user") public User user(){ //邏輯判斷省略 ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/user", User.class); System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone()); return forEntity.getBody(); }
另外兩個重載方法:
@Override public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables)); } @Override public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables)); }
好比:
/** * 給服務傳參數Get請求 * @return */ @RequestMapping("/getUser") public User getUser(){ //邏輯判斷省略 String [] arr={"2","xxx","4545645456"}; Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); //ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={0}&name={1}&phone={2}", User.class,arr); //ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class,map); /* * restTemplate.getForObject在getForObject在getForEntity在次封裝,直接獲取返回值類型,至關於ResponseEntity中的getBody */ User user1 = restTemplate.getForObject("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class, map); //System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone()); System.out.println(user1.getId()+""+user1.getName()+""+user1.getPhone()); return user1; }
能夠用一個數字作佔位符,最後是一個可變長度的參數,來來替換前面的佔位符也能夠前面使用name={name}這種形式,最後一個參數是一個map,map的key即爲前邊佔位符的名字,map的value爲參數值
與getForEntity使用相似,只不過getForobject是在getForEntity基礎上進行了再次封裝,能夠將http的響應體body信息轉化成指定的對象,方便咱們的代碼開發,當你不須要返回響應中的其餘信息,只須要body體信息的時候,可使用這個更方便,它也有兩個重載的方法,和getForEntity類似
@Override @Nullable public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor); }
上面例子已經涉及到了,此處再也不囉嗦。
restTemplate.postForEntity(); restTemplate.postForObject(); restTemplate.postForLocation();
例如:
/** * 調用POST請求 * @return */ @RequestMapping("/addUser") public User addUser(){ //邏輯判斷省略 String [] arr={"2","xxx","4545645456"}; //不能使用map傳遞參數 Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); /** *要傳的表單信息,參數數據(很坑人) */ MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>(); multiValueMap.add("id",1); multiValueMap.add("name","xxxxx"); multiValueMap.add("phone","000000000"); //使用jdk中的map傳參數,接收不到 ResponseEntity<User> userResponseEntity = restTemplate.postForEntity( "http://SPRINGCLOUD-SERVICE-PROVIDER/provider/addUser", multiValueMap, User.class); System.out.println(userResponseEntity.getBody().getId()+""+userResponseEntity.getBody().getName()+""+userResponseEntity.getBody().getPhone()); return userResponseEntity.getBody(); }
restTemplate.put();
例如:
/** * 調用PUT請求 * @return */ @RequestMapping("/updateUser") public String updateUser(){ //邏輯判斷省略 String [] arr={"2","xxx","4545645456"}; //不能使用map傳遞參數 Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); /** *要傳的表單信息,參數數據(很坑人) */ MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>(); multiValueMap.add("id",1); multiValueMap.add("name","xxxxx"); multiValueMap.add("phone","000000000"); //使用jdk中的map傳參數,接收不到 restTemplate.put("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/updateUser", multiValueMap); return "SUCCESS"; }
restTemplate.delete();
例如:
/** * 調用DELETE請求 * @return */ @RequestMapping("/deleteUser") public String deleteUser(){ //邏輯判斷省略 String [] arr={"2","xxx","4545645456"}; Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); /** *要傳的表單信息,參數數據(很坑人),只有post,PUT請求採用這種map傳參數 */ /* MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>(); multiValueMap.add("id",1); multiValueMap.add("name","xxxxx"); multiValueMap.add("phone","000000000"); */ //使用jdk中的map傳參數,接收不到,不能使用MultiValueMap,接收不到參數 restTemplate.delete("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/deleteUser?id={id}&name={name}&phone={phone}", map); return "SUCCESS"; }
詳細參考案例源碼:https://gitee.com/coding-farmer/springcloud-learn