簡(jiǎn)價(jià)
Ribbon是一種客戶端的負(fù)載均衡器。提供了多種負(fù)載均衡的算法,支持多種協(xié)議(HTTP,TCP,UDP),并提供了故障容錯(cuò)的能力。官方網(wǎng)址為:https://github.com/Netflix/ribbon。
Ribbon版Hello World
我們?cè)偈褂玫臅r(shí)候需要指定Server對(duì)象也就是可以做為負(fù)載的服務(wù)器,以及負(fù)載的Rule規(guī)則,默認(rèn)采用的是輪詢的規(guī)則。
代碼如下,里面有詳細(xì)的注釋:
package com.ivan.ribbon;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import com.google.common.collect.Lists;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
import com.netflix.loadbalancer.reactive.ServerOperation;
import rx.Observable;
public class RibbonClient {
public static void main(String[] args) throws Exception {
// 提供服務(wù)的服務(wù)器列表, 這里可以根據(jù)具體的測(cè)試url提供多個(gè)url。
List<Server> servers = Lists.newArrayList(new Server("localhost", 8000), new Server("localhost", 8001));
// 負(fù)載均衡器, 這里可以設(shè)置rule
BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(servers);
//這個(gè)可以提交具體的執(zhí)行命令邏輯。需要傳入具體的負(fù)載均衡器
LoadBalancerCommand<String> command = LoadBalancerCommand.<String> builder().withLoadBalancer(loadBalancer).build();
//連續(xù)執(zhí)行10次,這樣使可以看到具體的效果了。
for (int i = 0; i < 10; i++) {
command.submit(new ServerOperation<String>() {
public Observable<String> call(Server server) {
URL url;
//這里的path是能夠訪問(wèn)的url
String path = "/provider";
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader reader = null;
try {
url = new URL("http://" + server.getHost() + ":" + server.getPort() + path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
inputStream = conn.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
String tempLine = null;
StringBuffer resultBuffer = new StringBuffer();
while ((tempLine = reader.readLine()) != null) {
resultBuffer.append(tempLine);
}
String data = resultBuffer.toString();
System.out.println("data : " + data);
return Observable.just(data);
} catch (Exception e) {
return Observable.error(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
}
}
}
}).toBlocking().first();
}
}
}
負(fù)載均衡規(guī)則
Ribbon 提供了若干個(gè)內(nèi)置的負(fù)載規(guī)則如下圖所示:

- RoundRobinRule: 系統(tǒng)默認(rèn)的規(guī)則, 通過(guò)簡(jiǎn)單的輪詢服務(wù)列表來(lái)選擇服務(wù)器
- AvailabilityFilteringRule: 該規(guī)則會(huì)忽略以下服務(wù)器:
1)無(wú)法連接的服務(wù)器: 在默認(rèn)情況下, 如果 3 次連接失敗, 該服務(wù)器將會(huì)被置為
“ 短路” 的狀態(tài), 該狀態(tài)將持續(xù) 30 秒, 如果再次連接失敗, “ 短路” 狀態(tài)的持
續(xù) 時(shí) 間 將 會(huì) 以 幾 何 級(jí) 增 加 。 可 以 通 過(guò) 修 改
niws.loadbalancer.<clientName>.connectionFailureCountThreshold 屬性, 來(lái)
配置連接失敗的次數(shù)。
2) 并發(fā)數(shù)過(guò)高的服務(wù)器: 如果連接到該服務(wù)器的并發(fā)數(shù)過(guò)高, 也會(huì)被這個(gè)規(guī)則忽
略, 可以通過(guò)修改<clientName>.ribbon.ActiveConnectionsLimit 屬性來(lái)設(shè)定最
高并發(fā)數(shù)。 - WeightedResponseTimeRule: 為每個(gè)服務(wù)器賦予一個(gè)權(quán)重值, 服務(wù)器的響應(yīng)時(shí)間
越長(zhǎng), 該權(quán)重值就是越少, 這個(gè)規(guī)則會(huì)隨機(jī)選擇服務(wù)器, 這個(gè)權(quán)重值有可能會(huì)決定
服務(wù)器的選擇。 - ZoneAvoidanceRule: 該規(guī)則以區(qū)域、 可用服務(wù)器為基礎(chǔ), 進(jìn)行服務(wù)器選擇。 使用
Zone 對(duì)服務(wù)器進(jìn)行分類, 可以理解為機(jī)架或者機(jī)房。 - BestAvailableRule: 忽略“ 短路” 的服務(wù)器, 并選擇并發(fā)數(shù)較低的服務(wù)器。
- RandomRule: 顧名思義, 隨機(jī)選擇可用的服務(wù)器。
- RetryRule: 含有重試的選擇邏輯, 如果使用 RoundRobinRule 選擇服務(wù)器無(wú)法連
接, 那么將會(huì)重新選擇服務(wù)器。
與Spring Cloud整合
1:先在pom.xml文件里加入需要的依賴, 關(guān)鍵信息如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
</dependencies>
2:寫(xiě)啟動(dòng)類,需要加入EnableDiscoveryClient人注解。代碼如下:
package com.ivan.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
*
* 功能描述:
*
* @version 2.0.0
* @author zhiminchen
*/
@SpringBootApplication
@EnableDiscoveryClient
public class App
{
public static void main( String[] args )
{
SpringApplication.run(App.class, args);
}
}
3:寫(xiě)個(gè)自定義負(fù)載的規(guī)則,也可以沒(méi)有這個(gè)類,這樣默認(rèn)就是輪詢的規(guī)則。代碼如下:
package com.ivan.consumer.rule;
import java.util.List;
import org.apache.commons.lang.math.RandomUtils;
import org.springframework.stereotype.Component;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
/**
*
* 功能描述: 自定義Rule,80%的概率選第一臺(tái)服務(wù)器,20%的概率選第二臺(tái)服務(wù)器
*
* @version 2.0.0
* @author zhiminchen
*/
@Component
public class MyRule implements IRule {
private ILoadBalancer lb;
@Override
public Server choose(Object key) {
List<Server> allServer = lb.getAllServers();
int value = RandomUtils.nextInt(10);
Server server = null;
if (value > 8) {
server = allServer.get(0);
} else {
server = allServer.get(1);
}
System.out.println("port is : " + server.getPort());
return server;
}
@Override
public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
@Override
public ILoadBalancer getLoadBalancer() {
return this.lb;
}
}
4:編寫(xiě)服務(wù)調(diào)用者,代碼如下:
package com.ivan.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@Configuration
public class ConsumerController {
//這個(gè)值會(huì)自動(dòng)注入的噢
@Autowired
private LoadBalancerClient client;
//這個(gè)值也會(huì)自動(dòng)注入的噢
@Autowired
private SpringClientFactory factory;
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@RequestMapping(value = "/consumer", method = RequestMethod.GET)
public String consumer() {
RestTemplate template = getRestTemplate();
// 根據(jù)應(yīng)用名稱調(diào)用服務(wù)
String json = template.getForObject("http://provider/provider", String.class);
return json;
}
}
代碼效果應(yīng)該是大部分請(qǐng)求會(huì)調(diào)用到Server為零的服務(wù)器上,也就是說(shuō)我們自定義的Rule起作用了,同時(shí)可以看到控制臺(tái)有相應(yīng)的輸出記錄。
源碼分析
上面的代碼我們會(huì)有兩個(gè)疑問(wèn):
- 為什么我們自定義的Rule,只加上了@Component注解,這個(gè)規(guī)則便能起作用。
-
SpringClientFactory 與 LoadBalancerClient 這兩個(gè)類是如何注入到我們自定義的Controller里的。
因?yàn)镾pring Cloud是基與Spring Boot進(jìn)行構(gòu)建的, 之所以上面的類能夠起作用,核心還是因?yàn)镾pring Boot 的SpringFactoriesLoader 機(jī)制在起作用。我們可以找到spring-cloud-netflix-cord的jar包,里面有個(gè)spring.factories文件,在這個(gè)文件里,我們可以看到會(huì)自動(dòng)加載RibbonAutoConfiguration類。代碼如下圖所示:
image.png
在RibbonAutoConfiguration類里,我們可以看到定義了SpringClientFactory 與 LoadBalancerClient 這兩個(gè)Bean, 這就解釋了為什么我們的應(yīng)用代碼可以注入這兩個(gè)類了。代碼截圖如下圖所示:
image.png
至于我們自定義的Rule能夠起作用,是因?yàn)槲覀兊腟pring容器會(huì)掃描當(dāng)前錄與子錄的代碼,將Component注釋的類自動(dòng)注入到Spring IOC容器類, 如果我們沒(méi)有配置相應(yīng)的Rule, Spring Cloud會(huì)為我們默認(rèn)加載一個(gè)Rule類,這個(gè)類在RibbonClientConfiguration里定義,代碼如下圖:
image.png


