3、Spring Cloud之軟負載均衡 Ribbon

前言

上一節咱們已經學習了Eureka 註冊中心,其實咱們也使用到了Ribbon ,只是當時咱們沒有細講,因此咱們如今一塊兒來學習一下Ribbon。html

什麼是Ribbon

以前接觸到的負載均衡都是硬負載均衡,什麼是硬負載均衡呢?硬負載均衡就是在以往的大型系統中,會有單獨一套系統來負責負載均衡策略,咱們因此的請求都會先走到負載均衡的系統上,進行分配到不一樣的服務器處理。
好比咱們熟悉的nginx 。其實就能夠算做一個負載均衡的系統,客戶端請求的接口會先經過nginx 的負載均衡策略分配到不一樣的服務器上。
在這裏插入圖片描述
那Ribbon 不是這樣的嗎?那又是怎樣的呢?
Ribbon 是和 Eureka 同樣是Netflix 推出的開源產品,它能夠和Eureka 完成無縫結合,Ribbon 主要實現客戶端負載均衡。
那什麼是客戶端負載均衡呢?
就是在客戶端請求的時候,就經過均衡策略將請求分發到不一樣的服務器上,以下圖
在這裏插入圖片描述
這個圖是根據上節的Eureka 的架構圖改編來的,主要的流程仍是沒有變,服務消費者和服務提供者都會註冊到服務中心,而後服務消費者會從服務中心獲取可用實例列表 ,這裏就會經過負載均衡策略選擇其中一個實例進行訪問。java

好了咱們來用代碼來看一下。linux

demo

咱們爲了簡化,註冊中心服務端,咱們仍是用上節的單節點。怎麼配置我不說了。而後咱們新建一個module 用來作服務提供者,其實也能夠用上一節的服務提供者,可是我怕揉在一塊兒很差,因此就全都分開了,不過不少代碼都是同樣的。nginx

服務提供者

咱們新建一個模塊後pom.xml 文件以下:git

<parent>
        <groupId>cn.quellanan</groupId>
        <artifactId>SpringCloud</artifactId>
        <version>1.0.0</version>
    </parent>

    <groupId>com.quellanan.springcloud</groupId>
    <artifactId>ribbon-provider-9004</artifactId>
    <version>1.0.0</version>
    <name>ribbon-provider-9004</name>
    <description>ribbon-provider-9004 服務提供者</description>

    <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-eureka-client</artifactId>
        </dependency>
    </dependencies>

主要就是引入了eureka-client 能夠註冊到註冊中心去,而後啓動類加上@EnableEurekaClient 註解
在這裏插入圖片描述
在配置文件中加上配置以下:程序員

server.port=9004
spring.application.name=ribbon-provider
eureka.client.service-url.defaultZone=http://localhost:8000/eureka/

咱們在建立一個測試類:HelloControllergithub

@RestController
@Slf4j
public class HelloController {

    @Value("${server.port}")
    private String port;

    @RequestMapping("/hello")
    public String hello(){
        log.info(port);
        return "hello "+port;
    }
}

這樣咱們一個服務提供者就弄好了,爲了看出負載均衡的效果,咱們還須要建立一個同樣的服務提供者。或者不一樣的端口啓動。咱們將端口改成9005.其餘的和上面同樣。web

服務消費者

服務提供者有了,咱們再來建立一個服務消費者的模塊。pom.xml 較服務提供者就多了一個ribbon 的依賴spring

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

而後啓動類中加上@EnableEurekaClient 和 RestTemplatejson

@SpringBootApplication
@EnableEurekaClient
public class RibbonConsumerApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(RibbonConsumerApplication.class, args);
    }

}

@LoadBalanced 註解就是來實現客戶端負載均衡的。

配置文件中加入以下配置:

server.port=9003
#服務名,在註冊時所用
spring.application.name=ribbon-consumer
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/

最後咱們來寫一個調用服務提供者的接口,建立一個IndexController類,內容以下

@RestController
public class IndexController {
    private static final String applicationName = "ribbon-provider";

    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping("/index")
    public String getHello(){
        String url = "http://"+ applicationName +"/hello";
        return  restTemplate.getForObject(url,String.class);
    }
}

測試

咱們如今來啓動服務中心,兩個服務提供者,一個服務消費者。啓動以後咱們輸入:

http://localhost:8000/

在這裏插入圖片描述
重點看下ribbon-provider 有兩個端口,分別對應的咱們的兩個服務提供者。他們的appliaction.name是相同的。
再來調下面接口看看

http://localhost:9003/index

在這裏插入圖片描述
能夠看到每次調用訪問了不一樣的服務提供者,兩個服務端提供者是輪尋調用的。從而實現客戶端的負載均衡

RestTemplate

上面說的負載均衡,其實仍是RestTemplate 對象加上@LoadBalanced來實現的。而且前面只是簡單的調用,沒有涉及參數和請求方式,接下來咱們看看常見的請求方式和有參數的調用。

Get 請求

其實咱們以前寫的就是get 請求的方式,咱們在來寫一個有參數的請求

@RequestMapping("index2")
    public String getHello2(){
        String url = "http://"+ applicationName +"/hello2?name={1}";
        return  restTemplate.getForObject(url,String.class,"quellanan");
    }

能夠看到url 中請求的參數有佔位符代替,getForObject或者getForEntity的第三個參數就是咱們實際傳的參數。這裏說明一下getForObject 是直接獲取返回的內容,而getForEntity返回的是一個http對象,包含相應狀態碼,想要回去內容須要getForEntity().getBody() 才行。

那若是多個參數的呢?
多個參數的經常使用的有兩種方式,一個是和上面同樣,直接在後面加參數就行了以下:

@RequestMapping("index3")
    public String getHello3(){
        //多個參數拼接
        String url = "http://"+ applicationName +"/hello3?name={1}&age={2}";
        return  restTemplate.getForObject(url,String.class,"quellanan","18");
    }

還有一種方式就是將參數封裝到map 中,傳過去。同樣的也能夠解析

@RequestMapping("index4")
    public String getHello4(){
        //多參數組裝
        Map<String,String> parms=new HashMap<>();
        parms.put("name","quellanan");
        parms.put("age","18");
        String url = "http://"+ applicationName +"/hello3?name={name}&age={age}";
        return  restTemplate.getForObject(url,String.class,parms);
    }

咱們在提供者中寫兩個方法便於測試

@RequestMapping("/hello2")
    public String hello2(@RequestParam("name") String name){
        log.info(name);
        return "hello "+name+port;
    }
    @RequestMapping("/hello3")
    public String hello3(@RequestParam("name") String name,@RequestParam("age") String age){
        log.info(name+age);
        return "hello "+name+age+port;
    }

咱們啓動來看下結果
在這裏插入圖片描述
能夠看到參數是傳遞成功的啦。

Post 請求

post 請求和get 請求差很少,我這裏就將參數封裝到map中了
postForEntity

@RequestMapping("index6")
    public String getHello6(){
        //postForEntity
        JSONObject params=new JSONObject();
        params.put("name","quellanan");
        params.put("age","18");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity request = new HttpEntity(params.toJSONString(), headers);
        String url = "http://"+ applicationName +"/hello4";
        return  restTemplate.postForEntity(url,request,String.class).getBody();
    }

postForObject

@RequestMapping("index7")
    public String getHello7(){
        //postForObject
        JSONObject params=new JSONObject();
        params.put("name","quellanan");
        params.put("age","18");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity request = new HttpEntity(params.toJSONString(), headers);
        String url = "http://"+ applicationName +"/hello4";
        return  restTemplate.postForObject(url,params,String.class);
    }

主要是先將參數封裝在JSONObject 中,而後設置HttpHeaders 和HttpEntity ,而後請求。
咱們採用的application/json 的格式,咱們在服務提供者中加一個方法。用來接收json格式的參數。

@RequestMapping("/hello4")
    public String hello4(@RequestBody Map<String, Object> parms){
        return "hello "+parms.get("name")+parms.get("age")+port;
    }

如今咱們啓動看下效果。
在這裏插入圖片描述
證實都是能夠正常傳遞的。

Feign 簡化微服務調用

上面咱們能夠看到,咱們進行微服務調用,不論是使用get 或者post 方法,帶有參數過多就會致使代碼變得很臃腫,因此咱們就可使用一樣是netflix 推出的Feign 來簡化微服務調用,Feign 結合了Ribbon 以及Hystrix.Ribbon的功能它都有,而且還進行了封裝,更加方便咱們使用。因此咱們使用的時候,只用引入 Feign 的依賴就能夠。

那咱們如今對上面的這些進行調整一下。

pom.xml

在咱們的ribbon-consumer 的pom 文件中刪除ribbon 的依賴,增長feign 的依賴。

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

增長@EnableFeignClients

在啓動類中增長@EnableFeignClients 註解
在這裏插入圖片描述
在啓動類中增長了@EnableFeignClients 註解,就能夠在項目中使用Fegin 啦,那咱們怎麼使用呢?
其實還要使用@FeignClient 註解,這個註解是在具體的應用中注入的,用來指定服務提供者的服務名稱。而且這個註解只能做用 interface 上。
前面咱們調用服務提供者的接口須要寫url,參數,返回類型等等很是的繁瑣,因此Fegin 就幫咱們進行了簡化,讓咱們調用服務提供者的接口,能夠像自身調用同樣,很是放方便。

HelloService

咱們來建立一個HelloService 的接口。內容以下

@FeignClient("ribbon-provider")
public interface HelloService {

    @RequestMapping("/hello")
    public String hello();

    @RequestMapping("/hello2")
    public String hello2(@RequestParam(value = "name") String name);

    @RequestMapping("/hello3")
    public String hello3(@RequestParam(value = "name") String name,@RequestParam(value = "age") String age);

    @RequestMapping("/hello4")
    public String hello4(@RequestBody Map<String, Object> parms);

}

能夠看到,上面的接口內容主要是針對服務提供者暴露出的幾個接口進行調用。
在這裏插入圖片描述
對比服務消費者中的接口,和服務提供者中的接口,能夠發現實際上是服務提供者中的接口對消費者中的HelloService 的實現。這個待會再說。從HelloService 中咱們能夠看到,咱們調用服務提供者的接口同樣的採用@RequestMapping,@RequestParam,@RequestBody來操做,這樣更加簡潔和方便。

FeginController

咱們再建立一個FeginController 來測試一下,內容以下:

@RestController
public class FeginController {

    @Autowired
    public HelloService helloService;


    @RequestMapping("/fegin")
    public String getHello(){
        return  helloService.hello();
    }

    @RequestMapping("/fegin2")
    public String getHello2(){
        String name="quellanan";
        return  helloService.hello2(name);
    }


    @RequestMapping("/fegin3")
    public String getHello3(){
        String name="quellanan";
        String age="18";
        return  helloService.hello3(name,age);
    }

    @RequestMapping("/fegin4")
    public String getHello4(){
        Map<String, Object> parms=new HashMap<>();
        parms.put("name","quellanan");
        parms.put("age","18");
        return  helloService.hello4(parms);
    }
}

能夠看到就是普通的controller層調用service 層。

測試

好了咱們如今來測試一下。
分別輸入以下地址來看看效果:

http://localhost:9003/fegin
http://localhost:9003/fegin2
http://localhost:9003/fegin3
http://localhost:9003/fegin4

在這裏插入圖片描述

繼承

好了,來講最後一個問題,剛剛上面咱們說了服務消費者中的HelloService 和服務提供者的HelloController很像,感受像是HelloController 實現了HelloService 。不錯,當咱們正式開發的時候,會發現接口調用很是多,而且也很複雜,若是按照上面的方式來的話,會存在不少的重複代碼且很容易出錯,因此咱們能夠將服務調用單獨提取成一個模塊麼,而後分別在服務提供者和服務消費者中引入其依賴,而後在消費中的HelloService 繼承其對應的接口。而在服務提供者中實現其對應的接口。固然@FeignClient仍是在服務消費者之中的。

這裏只是提供了一種思路,沒有給出實現方式,感興趣的能夠看看《Spring cloud 微服務實戰》,也能夠和我討論下嘿嘿。

番外

總算是寫完了,算是對ribbon 和fegin 有了一些瞭解,最起碼如今可使用他們,並將其使用到到項目中沒有什麼問題啦。

代碼上傳到github:
https://github.com/QuellanAn/springcloud

後續加油♡

很榮幸,今年參加了CSDN博客之星評選活動,幫忙投下票,能夠投5票 ,謝謝您

CSDN博客之星評選活動

最後啦,
歡迎你們關注我的公衆號 "程序員愛酸奶"

分享各類學習資料,包含java,linux,大數據等。資料包含視頻文檔以及源碼,同時分享本人及投遞的優質技術博文。

若是你們喜歡記得關注和分享喲❤

file

相關文章
相關標籤/搜索