天天看點

Spring Cloud Ribbon負載均衡

目錄

    • 一、簡介
    • 二、用戶端負載均衡
    • 三、RestTemplate詳解
      • GET請求
      • POST請求
      • PUT請求
      • DELETE請求

一、簡介

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 微服務實戰》