Spring Cloud:第三章:Ribbon客服端負(fù)載均衡

負(fù)載均衡是對系統(tǒng)的高可用、網(wǎng)絡(luò)壓力的緩解和處理能力擴容的重要手段。理解Ribbon對于我們使用Spring Cloud來講非常的重要。它是一個基于Http和TCP的客戶端負(fù)載均衡工具。它不像服務(wù)注冊中心、配置中心、API網(wǎng)關(guān)那樣獨立部署,但是它幾乎存在于每個微服務(wù)的基礎(chǔ)設(shè)施中。
基于Ribbon+RestTemplate的用法
1、引入依賴

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

注意:Eureka默認(rèn)集成了Ribbon,只需引入Eureka JAR即可。
2、在啟動類中注入配置

package com.mimaxueyuan.consumer.robbin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.web.client.RestTemplate;
 
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonConsumerApplication {
 
    @Bean
    @LoadBalanced // 需要使用負(fù)載均衡,必須與Bean一同使用
    public RestTemplate balanceRestTemplate() {
        return new RestTemplate();
    }
    
    @Primary //自動裝配時當(dāng)出現(xiàn)多個Bean候選者時,被注解為@Primary的Bean將作為首選者,否則將拋出異常 
    @Bean //需要多個RestTemplate, 有的RestTemplate使用負(fù)載均衡,有的不使用,不使用的不增加@LoadBalanced注解
    public RestTemplate noBalanceRestTemplate() {
        return new RestTemplate();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(RibbonConsumerApplication.class, args);
    }
 
}

3、編寫 Controller——演示使用負(fù)載均衡和不使用負(fù)載均衡的用法及區(qū)別

package com.mimaxueyuan.consumer.robbin.controller;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.mimaxueyuan.consumer.entity.User;
 
@RestController
public class RibbonController {
 
    // 注入restTemplate, 這個類已經(jīng)在RibbonConsumerApplication中初始化,不使用負(fù)載均衡
    @Autowired
    private RestTemplate noBalanceRestTemplate;
 
    // 注入restTemplate, 這個類已經(jīng)在RibbonConsumerApplication中初始化,并且使用負(fù)載均衡
    @Autowired // 默認(rèn)按照類型注入,如果需要按照名字注入需要使用@Qualifier注解
    //@LoadBalanced //使用帶有負(fù)載均衡的RestTemplate
    @Qualifier("balanceRestTemplate")
    private RestTemplate balanceRestTemplate;
 
    // 以下注入負(fù)載均衡客戶端LoadBalancerClient是一個接口,下面只有一個RibbonLoadBalancerClient實現(xiàn)類
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Autowired
    private RibbonLoadBalancerClient ribbonLoadBalancerClient;
 
    /**
     * 不使用ribbon的舊調(diào)用方式
     *
     * @author Kevin
     * @Title: old
     * @return
     * @return: String
     */
    @GetMapping("/ribbon/old/get/{id}")
    public String old(@PathVariable("id") String id) {
        
        // 使用noBalanceRestTemplate是非負(fù)載均衡的,所以沒問題
        String result = noBalanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class);
        System.out.println("[hardcode1]" + result);
        
        // 由于balanceRestTemplate已經(jīng)使用了Ribbon做負(fù)載均衡,所以使用硬編碼方式就不允許了,會提示:No instances available for localhost
        result = balanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class);
        System.out.println("[hardcode2]" + result);
        
        return "result";
    }
 
    /**
     * ribbon使用
     *
     * @author Kevin
     * @Title: ribbon 
     * @param id
     * @return
     * @return: String
     */
    @GetMapping("/ribbon/get/{id}")
    public String ribbon(@PathVariable("id") String id) {
        // -----------------以下代碼使用ribbon做客戶端負(fù)載均衡
        // 使用provider的instanceName替代ip和端口的硬編碼
        String result = balanceRestTemplate.getForObject("http://mima-cloud-producer/get/"+id, String.class);
        System.out.println("[ribbon]" + result);
 
        System.out.println("[loadBalancerClient]choose的結(jié)果,代表負(fù)載均衡之后要選擇的服務(wù)實例");
        ServiceInstance instance = loadBalancerClient.choose("mima-cloud-producer");
        System.out.println("host:" + instance.getHost() + ",port:" + instance.getPort() + ",serviceId=" + instance.getServiceId() + ",uri=" + instance.getUri());
 
        System.out.println("[ribbonLoadBalancerClient]choose的結(jié)果,代表負(fù)載均衡之后要選擇的服務(wù)實例");
        instance = ribbonLoadBalancerClient.choose("mima-cloud-producer");
        System.out.println("host:" + instance.getHost() + ",port:" + instance.getPort() + ",serviceId=" + instance.getServiceId() + ",uri=" + instance.getUri());
        
        System.out.println("[ribbonLoadBalancerClient]choose的結(jié)果,代表負(fù)載均衡之后要選擇的服務(wù)實例");
        instance = ribbonLoadBalancerClient.choose("mima-cloud-producer");
        System.out.println("host:" + instance.getHost() + ",port:" + instance.getPort() + ",serviceId=" + instance.getServiceId() + ",uri=" + instance.getUri());
        try {
            // 根據(jù)負(fù)載均衡后的服務(wù),構(gòu)建一個訪問url
            // 第二個參數(shù)不能為null
            System.out.println("根據(jù)負(fù)載均衡后的服務(wù),構(gòu)建一個訪問url");
            URI reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI(""));
            System.out.println("reconstructURI1-yes:" + reconstructURI);
            // 拼寫在請求地址后邊,需要注意是否需要添加/
            reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("/ribbon/get"));
            System.out.println("reconstructURI2-yes:" + reconstructURI);
            reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("http"));
            System.out.println("reconstructURI3-no:" + reconstructURI);
            reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("https"));
            System.out.println("reconstructURI4-no:" + reconstructURI);
            reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("test"));
            System.out.println("reconstructURI5-no:" + reconstructURI);
            // 使用http:/xxx、https:/xxx可以用于切換http協(xié)議還是https協(xié)議
            reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("http:/ribbin/get"));
            System.out.println("reconstructURI6-yes:" + reconstructURI);
            reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("https:/ribbin/get"));
            System.out.println("reconstructURI7-yes:" + reconstructURI);
        } catch (URISyntaxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        return "ribbon's demo,please to see console output";
    }
 
    @GetMapping("/nobalance/get/{id}")
    public String nobalance(@PathVariable("id") String id) {
        // -----------------以下代碼使用硬編碼方式調(diào)用服務(wù)
        // 如果restTemplate已經(jīng)使用了Ribbon做負(fù)載均衡,也就是使用了@LoadBaleced注解,依然使用硬編碼方式就不允許了,會提示:No instances available for localhost
 
        String result = noBalanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class);
        System.out.println("[noBalanceRestTemplate-hardcode1]" + result); //正常訪問
        result = noBalanceRestTemplate.getForObject("http://localhost:9908/get/"+id, String.class);
        System.out.println("[noBalanceRestTemplate-hardcode2]" + result); //正常訪問
 
        try {
            //異常訪問,Ribbon負(fù)載均衡只能通過服務(wù)名調(diào)用
            result = balanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class);
            System.out.println("[balanceRestTemplate-hardcode1]" + result); 
            //異常訪問,Ribbon負(fù)載均衡只能通過服務(wù)名調(diào)用
            result = balanceRestTemplate.getForObject("http://localhost:9908/get/"+id, String.class);
            System.out.println("[balanceRestTemplate-hardcode2]" + result); 
        } catch (Exception e) {
            System.out.println("使用balanceRestTemplate同時使用地址硬編碼錯誤:" + e.getMessage());
        }
        return "ribbon's demo,please to see console output";
    }
 
    @SuppressWarnings("unchecked")
    @GetMapping("listAll")
    public List<User> listAll() {
        // restTemplate怎樣返回一個List對象
        List<User> list = balanceRestTemplate.getForObject("http://mima-cloud-producer/listAll", List.class);
        return list;
    }
 
}

其中 mima-cloud-producer 為服務(wù)名,啟動兩個服務(wù)節(jié)點如下:
http://localhost:9907/
http://localhost:9908/