一、簡介
1. 是什么
- Spring Cloud Ribbon是基于Netflix Ribbon實現(xiàn)的一套客戶端負載均衡的工具。
- 簡單的說,Ribbon是Netflix發(fā)布的開源項目,主要功能是提供客戶端的軟件負載均衡算法和服務調(diào)用。
- 官方文檔
- 目前已進入維護狀態(tài),以后可以通過Open Feign作為替代方案
- 負載均衡+RestTemplate,實現(xiàn)負載均衡調(diào)用
2. 負載均衡
- 負載均衡(Load Balance,LB),即將用戶的請求平攤到多個服務上,從而達到系統(tǒng)的高可用(HA)
- 負載均衡分為兩種方案:集中式LB、進程內(nèi)LB
2.1 集中式LB
- 即服務方和消費方之間使用獨立的LB設施,由該設備負責把訪問請求通過某種策略轉(zhuǎn)發(fā)至服務提供方。
- 比如說Nginx、Gateway、zuul等
2.2 進程內(nèi)LB
- 負載均衡的算法集成到消費方,消費方在注冊中心中獲取可用地址,然后通過LB算法選擇出一個合適的服務器。
- Ribbon就屬于進程內(nèi)LB,它只是一個類庫,集成于消費方進程,消費方通過它來獲取到服務方提供的地址。
二、實驗
Ribbon集成在spring-cloud-starter-netflix-eureka-client中,可以參考eureka的使用。在此基礎上簡單修改一下,就可以完成服務調(diào)用及負載均衡
1. RestTemplate
- 官網(wǎng)
- 通過RestTemplate,可以實現(xiàn)HttpClient的功能,只需要給它提供一個url及返回類型,即可實現(xiàn)遠程方法調(diào)用。
1.1 加入到IOC容器
首先,將其加入到IOC容器中。@LoadBalanced表示開啟負載均衡。
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
1.2 RestTemplate 遠程調(diào)用
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
RestTemplate restTemplate; // 在ioc容器中獲取
@Value("${payment.url}")
String paymentUrl; // 遠程調(diào)用的URL,保存在配置文件中,解耦
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
CommonResult<Payment> result = restTemplate.getForObject(paymentUrl + "/payment/get/" + id, CommonResult.class); // get方法調(diào)用,并且返回封裝成 CommonResult 類型
log.info("Order 查詢 Payment,id:" + id);
return result;
}
}
也可以使用getForEntity()方法,獲取整個響應,自己在響應中獲取想要的內(nèi)容。
@GetMapping("/payment/getEntity/{id}")
public CommonResult<Payment> getPaymentEntityById(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(paymentUrl + "/payment/get/" + id, CommonResult.class);
log.info("獲取到的信息是:" + entity.toString());
log.info("獲取到的StatusCode是:" + entity.getStatusCode());
log.info("獲取到的StatusCodeValue是:" + entity.getStatusCodeValue());
log.info("獲取到的Headers是:" + entity.getHeaders());
if (entity.getStatusCode().is2xxSuccessful()) {
log.info("查詢成功:" + id);
return entity.getBody();
} else {
log.info("查詢失?。? + id);
return new CommonResult<>(CommonResult.FAIlURE, "查詢失敗");
}
}
如果使用post方法,就將get改成post就好了。
1.3 配置文件
url,可以寫具體的地址,表示直接調(diào)用該地址;也可以寫在eureka的服務名,首先在eureka中獲取該服務的所有地址,再通過LB選擇一個。
payment: url: "http://CLOUD-PAYMENT-SERVICE"
2. LoadBalancer
上面通過@LoadBalanced開啟了負載均衡。默認使用輪詢算法,也可以修改成其他算法。
Class | 算法 |
---|---|
com.netflix.loadbalancer.RoundRobinRule | 輪詢,默認算法 |
com.netflix.loadbalancer.RandomRule | 隨機算法,通過產(chǎn)生隨機數(shù)選擇服務器 |
com.netflix.loadbalancer.RetryRule | 先按照RoundRobinRule的策略獲取服務,如果獲取服務失敗則在指定時間內(nèi)會進行重試,獲取可用的服務 |
WeightedResponseTimeRule | 對RoundRobinRule的擴展,響應速度越快的實例選擇權(quán)重越大,越容易被選擇 |
BestAvailableRule | 會先過濾掉由于多次訪問故障而處于斷路器跳閘狀態(tài)的服務,然后選擇一個并發(fā)量最小的服務 |
AvailabilityFilteringRule | 先過濾掉故障實例,再選擇并發(fā)較小的實例 |
ZoneAvoidanceRule | 默認規(guī)則,復合判斷server所在區(qū)域的性能和server的可用性選擇服務器 |
2.1 修改負載均衡算法
如果想讓該算法只針對某個服務,則不能將其放在ComponentScan夠得到的地方,否則會修改所有服務的負載均衡算法。因此,最好在外面再新建一個package,用來放這個LB
@Configuration
public class MyRule {
@Bean
public IRule rule() {
return new RandomRule();
}
}
在主啟動類上,標識一下服務與算法直接的映射關(guān)系
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRule.class)
public class OrderApplication80 {
public static void main(String[] args) {
SpringApplication.run(OrderApplication80.class, args);
}
}
如果嫌這種方法麻煩,也可以使用配置文件的方法
CLOUD-PAYMENT-SERVICE: # 服務名稱
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 算法選擇
3. 負載均衡算法源碼
以默認的RoundRobinRule作為閱讀的源碼,其他的源碼基本上很類似,只是修改的選擇服務器的代碼。
- RoundRobinRule父類為AbstractLoadBalancerRule,AbstractLoadBalancerRule實現(xiàn)了接口IRule
3.1 IRule
public interface IRule {
Server choose(Object var1); // 選擇服務器,最重要的方法
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
3.2 AbstractLoadBalancerRule
基本沒什么作用,只是將公共的部分提取了出來進行實現(xiàn)。
public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
private ILoadBalancer lb; // ILoadBalancer接口,主要的功能就是獲取當前服務器的狀態(tài)、數(shù)量等,為負載均衡算法提供計算的參數(shù)
public AbstractLoadBalancerRule() {
}
public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
public ILoadBalancer getLoadBalancer() {
return this.lb;
}
}
3.3 RoundRobinRule
簡單來說,就是通過一個計數(shù)器,實現(xiàn)了輪詢
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter; // 原子類,用來保存一個計數(shù),記錄現(xiàn)在輪詢到哪了
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
this.nextServerCyclicCounter = new AtomicInteger(0); // 初始化
}
public RoundRobinRule(ILoadBalancer lb) { // 設置LoadBalancer
this();
this.setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key) { // 最重要的方法,選擇服務器并返回
// 下面貼出來
}
private int incrementAndGetModulo(int modulo) { // 對計數(shù)器進行修改,并返回一個選擇值,是輪詢算法的實現(xiàn)
// 下面貼出來
}
public Server choose(Object key) { // 接口的方法,在該類中調(diào)用了另一個方法實現(xiàn)
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {}
}
簡單來說,該方法就是根據(jù)目前的狀態(tài),選擇一個服務器返回。
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) { // 如果沒有LoadBalancer,那就不白費功夫了
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) { // 嘗試十次,如果還找不到server就放棄了
List<Server> reachableServers = lb.getReachableServers(); // 通過LB獲取目前所有可獲取的服務器
List<Server> allServers = lb.getAllServers(); // 獲取實際上的所有服務器
int upCount = reachableServers.size(); // 獲取目前可獲得的服務器數(shù)量
int serverCount = allServers.size(); // 所有服務器的數(shù)量,這是取余的除數(shù)
if (upCount != 0 && serverCount != 0) { // 如果目前有服務器且服務器可用
int nextServerIndex = this.incrementAndGetModulo(serverCount); // 最關(guān)鍵的選擇算法,將目前的的服務器數(shù)量放進去,返回一個選擇的號碼
server = (Server)allServers.get(nextServerIndex); // 根據(jù)下標將服務器取出來
if (server == null) { // 如果取出來為空,表示目前不可用,則進入下一個循環(huán)
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) { // 如果該服務器活著且可以被使用,則直接將其返回
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
簡單來說,就是將目前的計數(shù)器+1取余,獲取一個下標,并返回。為了避免高并發(fā)的危險,采用CAS的方法進行設置。
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get(); // 獲取當前值
next = (current + 1) % modulo; // +1取余
} while(!this.nextServerCyclicCounter.compareAndSet(current, next)); // CAS,如果成功就返回,失敗就再來
return next;
}
以上就是關(guān)于Spring Cloud中 Ribbon 工具的簡要介紹以及調(diào)用 Ribbon 的步驟的詳細內(nèi)容,想要了解更多關(guān)于Spring Cloud Ribbon工具的其他資料請關(guān)注W3Cschool其它相關(guān)文章!也希望大家能夠多多地支持我們!