Spring Cloud Ribbon入門
阿新 • • 發佈:2018-12-13
一、簡介
Spring Cloud Ribbon是一個基於Http和TCP的客戶端負載均衡工具,它是基於Netflix Ribbon實現的。它不像服務註冊中心、配置中心、API閘道器那樣獨立部署,但是它幾乎存在於每個微服務的基礎設施中。理解Ribbon對於我們使用Spring Cloud來講非常的重要,因為負載均衡是對系統的高可用、網路壓力的緩解和處理能力擴容的重要手段之一。
在Spring Cloud中,有兩種服務呼叫方式,一種是Ribbon+RestTemplate,另一種是Feign。文字先講解下基於Ribbon+RestTemplate的用法。
當Ribbon與Eureka配合使用時,Ribbon可自動從Eureka Server獲取服務提供者地址列表,並基於負載均衡演算法,請求其中一個服務提供者例項。
二、入門案例
1、引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
注意:Eureka預設集成了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 // 需要使用負載均衡,必須與Bean一同使用
public RestTemplate balanceRestTemplate() {
return new RestTemplate();
}
@Primary //自動裝配時當出現多個Bean候選者時,被註解為@Primary的Bean將作為首選者,否則將丟擲異常
@Bean //需要多個RestTemplate, 有的RestTemplate使用負載均衡,有的不使用,不使用的不增加@LoadBalanced註解
public RestTemplate noBalanceRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
}
3、編寫 Controller——演示使用負載均衡和不使用負載均衡的用法及區別
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, 這個類已經在RibbonConsumerApplication中初始化,不使用負載均衡
@Autowired
private RestTemplate noBalanceRestTemplate;
// 注入restTemplate, 這個類已經在RibbonConsumerApplication中初始化,並且使用負載均衡
@Autowired // 預設按照型別注入,如果需要按照名字注入需要使用@Qualifier註解
//@LoadBalanced //使用帶有負載均衡的RestTemplate
@Qualifier("balanceRestTemplate")
private RestTemplate balanceRestTemplate;
// 以下注入負載均衡客戶端LoadBalancerClient是一個介面,下面只有一個RibbonLoadBalancerClient實現類
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RibbonLoadBalancerClient ribbonLoadBalancerClient;
/**
* 不使用ribbon的舊呼叫方式
*
* @author Kevin
* @Title: old
* @return
* @return: String
*/
@GetMapping("/ribbon/old/get/{id}")
public String old(@PathVariable("id") String id) {
// 使用noBalanceRestTemplate是非負載均衡的,所以沒問題
String result = noBalanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class);
System.out.println("[hardcode1]" + result);
// 由於balanceRestTemplate已經使用了Ribbon做負載均衡,所以使用硬編碼方式就不允許了,會提示: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做客戶端負載均衡
// 使用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的結果,代表負載均衡之後要選擇的服務例項");
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的結果,代表負載均衡之後要選擇的服務例項");
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的結果,代表負載均衡之後要選擇的服務例項");
instance = ribbonLoadBalancerClient.choose("mima-cloud-producer");
System.out.println("host:" + instance.getHost() + ",port:" + instance.getPort() + ",serviceId=" + instance.getServiceId() + ",uri=" + instance.getUri());
try {
// 根據負載均衡後的服務,構建一個訪問url
// 第二個引數不能為null
System.out.println("根據負載均衡後的服務,構建一個訪問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協議還是https協議
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) {
// -----------------以下程式碼使用硬編碼方式呼叫服務
// 如果restTemplate已經使用了Ribbon做負載均衡,也就是使用了@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負載均衡只能通過服務名呼叫
result = balanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class);
System.out.println("[balanceRestTemplate-hardcode1]" + result);
//異常訪問,Ribbon負載均衡只能通過服務名呼叫
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 為服務名,啟動兩個服務節點如下:
http://localhost:9907/
http://localhost:9908/