Spring Cloud Ribbon負載均衡

c旋兒發表於2019-05-29

一、簡介

Spring Cloud Ribbon是一個基於HTTP 和 TCP的客戶端負載工具,它基於Netflix Ribbon實現,我們可以使用它來進行遠端服務負載均衡的呼叫。它不像Zuul 和 Eureka 等可以獨立部署,它雖然是一個工具類框架,但是幾乎所有的Spring Cloud微服務架構和基礎設施都離不開它,包括後面所介紹的Feign 遠端呼叫,也是基於Ribbon實現的工具

二、客戶端負載均衡

負載均衡是在一個架構中非常重要,而且不得不去實施的內容。因為負載均衡對系統的高可用,網路壓力的緩解和處理能力擴容的重要手段之一。通常負載均衡分為兩種:硬體負載均衡軟體負載均衡,硬體負載均衡一般是通過硬體來實現,在伺服器節點之間安裝特定的負載均衡裝置,比如F5。 而軟體負載均衡是採用軟體控制的手段實現的,它實在伺服器之間安裝某種特定功能的軟體來完成特定的請求分開工作,比如Nginx等。無論硬體負載還是軟體負載,只要是服務端負載均衡都能以下圖的架構方式構建起來:

Spring Cloud Ribbon負載均衡

​ 硬體負載均衡的裝置和軟體負載均衡的模組都會維護一個下掛可用的服務清單,通過心跳檢測剔除故障的服務節點以保證清單中都是可以訪問的服務端節點。當客戶傳送請求到負載均衡的裝置時。裝置按照服務負載均衡的演算法(隨機訪問,輪詢訪問,權重訪問,最少訪問次數演算法)來找到對應的服務端。

​ 而客戶端負載均衡和服務端負載均衡最大的不同點在於上面所提到服務清單的儲存位置。在客戶端負載均衡中,所有客戶端節點都維護著自己要訪問的服務清單,而這些服務清單都來自注冊中心,比如我們上一章介紹的Eureka服務端。

​ 通過Spring Cloud Ribbon的封裝,我們在微服務架構中使用負載均衡就比較簡單,只需要下面兩步:

  • 服務提供者只需要啟動多個服務例項並註冊到一個註冊中心或是多個相關聯的服務註冊中心
  • 服務消費者直接呼叫被@LoadBalanced註解修飾過的RestTemplate來實現面向服務的介面呼叫。

三、RestTemplate詳解

​ 在上一章中,我們已經引入了Spring Cloud Ribbon實現了客戶端負載均衡的一個簡單的例項,其中,我們使用了一個非常有用的物件RestTemplate。該物件會使用Ribbon的自動化配置,同時通過配置@LoadBalanced開啟客戶端負載均衡。下面我們將詳細介紹RestTemplate 針對幾種不同的請求型別和引數型別的服務呼叫實現。

準備工作

在上一篇部落格中,我們搭建了一個註冊中心一個服務提供者一個ribbon消費者客戶端,現在我們也需要這三個元件來做Ribbon 服務消費

GET請求

在RestTemplate中,對GET請求可以通過如下兩個方法進行呼叫實現。

第一種:getForEntity()函式,該方法返回的是ResponseEntity,該物件是Spring對HTTP請求響應的封裝,其中主要儲存了HTTP的幾個重要元素,比如HTTP請求狀態碼的列舉物件HttpStatus(常用的404,500這些錯誤),在它的父類HttpEntity中還儲存著HTTP請求的頭資訊物件HttpHeaders以及泛型型別集合的請求體物件。

它的一般形式有三種:

/*
* url是遠端服務端的路徑,responseType是返回值型別,urlVariables是可變引數,給服務端傳遞的引數
*/
getForEntity(String url, Class<T> responseType, Object... urlVariables)
  
/*
* 可以使用Map封裝引數傳遞給客戶端
*/
getForEntity(String url, Class<T> responseType, Map<String, ?> urlVariables)
  
/*
* 也是一直接使用uri地址
*/
getForEntity(URI url, Class<T> responseType) throws RestClientException

/*
* getForObject 用法和getForEntity基本相同
*/
getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException

getForObject(String url, Class<T> responseType, Map<String, ?> urlVariables) throws RestClientException

getForObject(URI url, Class<T> responseType) throws RestClientException

URI 和 URL 的關係:

URI : 統一資源標誌符:

URL: 統一資源定位符****

URN : 統一資源名稱****

三者之間的關係:

一般用法

  • getForEntity

Ribbon 消費者

        /**
     * 文章基於spring-boot-starter-parent 1.3.7 版本
     * 如果讀者使用1.5.9 以上的版本,可以用GetMapping
     * @return
     */
    @RequestMapping(value = "/ribbon-consumer1", method = RequestMethod.GET)
    public ResponseEntity<String> helloUser(){
      // 返回值是String型別,所以對應第一個逗號後面的型別
      // /user/{1} 中的{1}表示的是第一個引數,傳的值是didi
      // 也可以用getForEntity().getBody() 方法,此時返回值就只是一個String型別
        return restTemplate.getForEntity("http://server-provider/user/{1}",String.class,"didi");
    }

        
    @RequestMapping(value = "/ribbon-consumer2", method = RequestMethod.GET)
    public ResponseEntity<User> helloUser2(){
        // 返回值是一個User型別
        // 多個引數之間用& 隔開
        return restTemplate.getForEntity("http://server-provider/user2?id=001&name=didi",User.class);
    }

        // 傳遞一個Map型別的物件
    @RequestMapping(value = "/ribbon-consumer3", method = RequestMethod.GET)
    public ResponseEntity<String> helloUser3(){
        Map params = new HashMap();
        params.put("name","data");
        // {name}表示的是params中的key
        return restTemplate.getForEntity("http://server-provider/user3?name={name}", String.class,params);
    }

        // 其實最核心的就是通過uri進行呼叫,上面所有的寫法都會轉換為下面這種寫法
        // 也就是說下面這種寫法是最根本的。
    @RequestMapping(value = "/ribbon-consumer4", method = RequestMethod.GET)
    public ResponseEntity<String> helloUser4(){
        UriComponents uriComponents = UriComponentsBuilder.fromUriString(
                "http://server-provider/user4?name={name}")
                .build()
                .expand("lx")
                .encode();
        URI uri = uriComponents.toUri();
        return restTemplate.getForEntity(uri,String.class);
    }

User 物件

public class User {

    private Integer id;
    private String name;

    public User(){}
    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
        get and set...
}

服務提供者

來看一下服務提供者的程式碼:

    // 返回的型別是String
    @RequestMapping(value = "/user/{name}", method = RequestMethod.GET)
    public String helloUser(@PathVariable("name") String name){
        return "Hello " + name;
    }

    // 返回的型別是User
    @RequestMapping(value = "/user2", method = RequestMethod.GET)
    public User helloUser(User user){
        return user;
    }

    @RequestMapping(value = "/user3", method = RequestMethod.GET)
    public String helloUser1(@RequestParam("name") String name){
        return "Hello " + name;
    }

    @RequestMapping(value = "/user4", method = RequestMethod.GET)
    public String helloUser2(@RequestParam("name") String name){
        return "Hello " + name;
    }
  • getForObject()

Ribbon 消費者

        @RequestMapping(value = "/ribbonGet", method = RequestMethod.GET)
    public String ribbonGet(){
        // {1} 和 {2} 都是佔位符,分別代表著 001 和 lx的值 
        return restTemplate.getForObject("http://server-provider/ribbon?id={1}&name={2}",String.class,
                new Object[]{"001","lx"});
    }

        // 和上面用法基本相同
    @RequestMapping(value = "/ribbonGet2", method = RequestMethod.GET)
    public String ribbonGet2(){
        Map params = new HashMap();
        params.put("id","001");
        params.put("name","lx");
        return restTemplate.getForObject("http://server-provider/ribbon?id={id}&name={name}",String.class,
                params);
    }

    @RequestMapping(value = "/ribbonGet3", method = RequestMethod.GET)
    public String ribbonGet3(){
        UriComponents uriComponents = UriComponentsBuilder.fromUriString(
                "http://server-provider/ribbon?id={id}&name={name}")
                .build()
                .expand("001","lx")
                .encode();
        URI uri = uriComponents.toUri();
        return restTemplate.getForObject(uri,String.class);
    }

服務提供者

        // 上面所有的url共用下面一個方法
        @RequestMapping(value = "/ribbon", method = RequestMethod.GET)
    public String acceptRibbon(@RequestParam("id")String id,
                               @RequestParam("name") String name){

        System.out.println("id = " + id + "name = " + name);
        return "Hello " + id + " World " + name;
    }

POST請求

瞭解完GET請求後,再來看一下POST請求:

RestTemplate中,POST請求可以用一下幾種方式來實現

// postForEntity
postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)   
postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException

// postForObject
postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
postForObject(URI url, Object request, Class<T> responseType) throws RestClientException

// postForLocation
postForLocation(String url, Object request, Object... urlVariables) throws RestClientException
postForLocation(String url, Object request, Map<String, ?> urlVariables) throws RestClientException 
postForLocation(URI url, Object request) throws RestClientException

Ribbon服務端

        /**
     * 文章基於spring-boot-starter-parent 1.3.7 版本
     * 如果讀者使用1.5.9 以上的版本,可以用 PostMapping
     * @return
     */
    @RequestMapping(value = "/ribbonPost", method = RequestMethod.POST)
    public User ribbonPost(){
        User user = new User(001,"lx");
        return restTemplate.postForEntity("http://server-provider/rpost",user,User.class)
                .getBody();
    }

    @RequestMapping(value = "/ribbonPost2", method = RequestMethod.POST)
    public User ribbonPost2(){
        User user = new User(001,"lx");
        UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://server-provider/location")
                .build()
                .expand(user)
                .encode();
        URI uri = uriComponents.toUri();
        return restTemplate.postForEntity(uri,user,User.class).getBody();
    }

    @RequestMapping(value = "/ribbonPost3", method = RequestMethod.POST)
    public String ribbonPost3(){
        User user = new User(001,"lx");
        // 佔位符石str, 服務端可以用 @PathVariable獲取
        return restTemplate.postForEntity("http://server-provider/rbPost/{str}",user,String.class,"hello")
                .getBody();
    }

    @RequestMapping(value = "/ribbonPost4", method = RequestMethod.POST)
    public String ribbonPost4(){
        Map<String,String> params = new HashMap<>();
        params.put("id","001");
        params.put("name","lx");
        return restTemplate.postForEntity("http://server-provider/mapPost",params,String.class).getBody();
    }

    /**
     *  restTemplate.postForObject()方法與上面用法幾乎相同
     *  postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
     *  postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
     *  postForEntity(URI url, Object request, Class<T> responseType)
     *  
     *. postForLocation 也相似,這裡就不再舉例說明了
     */

服務提供者

     @RequestMapping(value = "/rpost", method = RequestMethod.POST)
    public User accpetRibbonPost(@RequestBody User user){
        log.info("id = " + user.getId() + " name = " + user.getName());
        return user;
    }

    @RequestMapping(value = "/location", method = RequestMethod.POST)
    public User acceptRibbonPost2(@RequestBody User user){
        log.info("id = " + user.getId() + " name = " + user.getName());
        return user;
    }

    @RequestMapping(value = "/rbPost/{str}", method = RequestMethod.POST)
    public String accpetRibbonPost3(@PathVariable String str, @RequestBody User user){
        log.info("str = " + str);
        log.info("id = " + user.getId() + " name = " + user.getName());
        return str + " " + user.getId() + " " + user.getName();
    }

    @RequestMapping(value = "/mapPost", method = RequestMethod.POST)
    public String acceptRibbonPost4(@RequestBody Map map){
        String id = (String)map.get("id");
        String name = (String)map.get("name");
        return "id = " + id + " name = " + name;
    }

PUT請求

Restful中的put請求經常用來修改某些屬性的值,他和POST請求相似

一般形式

/*
* 它的形式比較少,只有一種比較形式
*/
put(String url, Object request, Object... urlVariables) throws RestClientException
put(String url, Object request, Map<String, ?> urlVariables) throws RestClientException
put(URI url, Object request) throws RestClientException

Ribbon服務端

    @RequestMapping(value = "/putRibbon", method = RequestMethod.PUT)
  public void putRibbon(){
    restTemplate.put("http://server-provider/ribbonPut",new User(21,"lx"));
  }

這裡只採用了一種簡單形式,用法和Post很相似,沒有再詳細說明

PUT請求沒有返回值,可以理解為只把需要的值傳過去就可以,修改成功不成功與我沒有關係

服務提供者

    
    @RequestMapping(value = "/ribbonPut", method = RequestMethod.PUT)
  public void acceptRibbonPut(@RequestBody User user){
    log.info("user.id = " + user.getId() + " user.name = " + user.getName());
  }

DELETE請求

delete請求在Restful API中一般用於根據id刪除某條資訊,用法也比較簡單,沒有返回值

一般形式

delete(String url, Object... urlVariables) throws RestClientException
delete(String url, Map<String, ?> urlVariables) throws RestClientException
delete(URI url) throws RestClientException

Ribbon服務端

  @RequestMapping(value = "/deleteRibbon", method = RequestMethod.DELETE)
  public void deleteUser(){
    User user = new User(21,"lx");
    restTemplate.delete("http://server-provider/ribbonDelete/{1}",user.getId());
  }

服務提供者

    @RequestMapping(value = "/ribbonDelete/{id}", method = RequestMethod.DELETE)
  public void deleteRibbon(@PathVariable Integer id){
    log.info("delete user " + id);
  }

參考資料:

https://www.jb51.net/article/138563.htm#comments

《Spring Cloud 微服務實戰》

相關文章