目錄
一、Ribbon簡介
1、是什麼
2、官網資料
3、能幹嗎
二、Ribbon負載均衡示範
1、架構說明
2、pom說明
3、二說RestTemplate的使用
三、Ribbon核心元件IRule
1、IRule接口實作類說明
2、Ribbon負載規則替換
四、Ribbon負載均衡算法
1、原理
2、ribbon源碼
3、手寫負載均衡器
一、Ribbon簡介
1、是什麼
Spring Cloud Ribbon是基于Netflix Ribbon實作的一套用戶端負載均衡的工具。
簡單的說,Ribbon是Netflix釋出的開源項目,主要功能是提供用戶端的軟體負載均衡算法和服務調用。Ribbon用戶端元件提供一系列完善的配置項如連接配接逾時,重試等。簡單的說,就是在配置檔案中列出Load Balancer(簡稱LB)後面所有的機器,Ribbon會自動的幫助你基于某種規則(如簡單輪詢,随機連接配接等)去連接配接這些機器。我們很容易使用Ribbon實作自定義的負載均衡算法。
2、官網資料
https://github.com/Netflix/ribbon/wiki/Getting-Started
Ribbon目前也進入維護模式 https://github.com/Netflix/ribbon
未來替換方案
3、能幹嗎
LB負載均衡(Load Balance)是什麼?
簡單的說就是将使用者的請求平攤的配置設定到多個服務上,進而達到系統的HA(高可用),常見的負載均衡有軟體Nginx,LVS,硬體 F5等
Ribbon本地負載均衡用戶端 VS Nginx服務端負載均衡差別?
- Nginx是伺服器負載均衡,用戶端所有請求都會交給nginx,然後由nginx實作轉發請求。即負載均衡是由服務端實作的
- Ribbon是本地負載均衡,在調用微服務接口時候,會在注冊中心上擷取注冊資訊服務清單之後緩存到JVM本地,進而在本地實作RPC遠端服務調用技術
集中式LB
即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬體,如F5, 也可以是軟體,如nginx), 由該設施負責把通路請求通過某種政策轉發至服務的提供方
程序内LB
将LB邏輯內建到消費方,消費方從服務注冊中心獲知有哪些位址可用,然後自己再從這些位址中選擇出一個合适的伺服器。Ribbon就屬于程序内LB,它隻是一個類庫,內建于消費方程序,消費方通過它來擷取到服務提供方的位址
一句話
負載均衡+RestTemplate調用
二、Ribbon負載均衡示範
1、架構說明
Ribbon在工作時分成兩步:
第一步先選擇 EurekaServer ,它優先選擇在同一個區域内負載較少的server
第二步再根據使用者指定的政策,從server取到的服務注冊清單中選擇一個位址;其中Ribbon提供了多種政策:比如輪詢、随機和根據響應時間權重
總結:Ribbon其實就是一個軟負載均衡的用戶端元件,他可以和其他所需請求的用戶端結合使用,和eureka結合隻是其中的一個執行個體。
2、pom說明
之前寫樣例時候沒有引入spring-cloud-starter-ribbon也可以使用ribbon
猜測spring-cloud-starter-netflix-eureka-client自帶了spring-cloud-starter-ribbon引用,
證明如下: 可以看到spring-cloud-starter-netflix-eureka-client 确實引入了Ribbon
3、二說RestTemplate的使用
官網
https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
getForObject方法/getForEntity方法
getForObject方法:傳回對象為響應體中資料轉化成的對象,基本上可以了解為Json
getForEntity方法:傳回對象為ResponseEntity對象,包含了響應中的一些重要資訊,比如響應頭、響應狀态碼、響應體
加到 cloud-consumer-order80 的controller類裡
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id")Long id){
ResponseEntity<CommonResult> eneity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
if(eneity.getStatusCode().is2xxSuccessful()){
log.info(eneity.getStatusCode()+"\t"+eneity.getHeaders());
return eneity.getBody();
}else {
return new CommonResult<>(444,"操作失敗");
}
}
測試
postForObject方法/postForEntity方法
GET請求方法
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables);
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
<T> T getForObject(URI url, Class<T> responseType);
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
<T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);
POST請求方法
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);
三、Ribbon核心元件IRule
1、IRule接口實作類說明
IRule:根據特定算法中從服務清單中選取一個要通路的服務
- com.netflix.loadbalancer.RoundRobinRule:輪詢
- com.netflix.loadbalancer.RandomRule:随機
- com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的政策擷取服務,如果擷取服務失敗則在指定時間内會進行重試,擷取可用的服務
- WeightedResponseTimeRule:對RoundRobinRule的擴充,響應速度越快的執行個體選擇權重越大,越容易被選擇
- BestAvailableRule:會先過濾掉由于多次通路故障而處于斷路器跳閘狀态的服務,然後選擇一個并發量最小的服務
- AvailabilityFilteringRule:先過濾掉故障執行個體,再選擇并發較小的執行個體
- ZoneAvoidanceRule:預設規則,複合判斷server所在區域的性能和server的可用性選擇伺服器
2、Ribbon負載規則替換
修改cloud-consumer-order80
注意配置細節
官方文檔明确給出了警告:
這個自定義配置類不能放在@ComponentScan所掃描的目前包下以及子包下,否則我們自定義的這個配置類就會被所有的Ribbon用戶端所共享,達不到特殊化定制的目的了。
建立package
com.atguigu.myrule
建立MySelfRule規則類
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//定義為随機
}
}
主啟動類添加@RibbonClient
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "cloud-payment-service",configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
四、Ribbon負載均衡算法
1、原理
負載均衡算法:rest接口第幾次請求數 % 伺服器叢集總數量 = 實際調用伺服器位置下标 ,每次服務重新開機動後rest接口計數從1開始。
List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
如:
List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002 組合成為叢集,它們共計2台機器,叢集總數為2, 按照輪詢算法原理:
當總請求數為1時: 1 % 2 =1 對應下标位置為1 ,則獲得服務位址為127.0.0.1:8001
當總請求數位2時: 2 % 2 =0 對應下标位置為0 ,則獲得服務位址為127.0.0.1:8002
當總請求數位3時: 3 % 2 =1 對應下标位置為1 ,則獲得服務位址為127.0.0.1:8001
2、ribbon源碼
com.netflix.loadbalancer.RoundRobinRule類
public Server choose(ILoadBalancer lb, Object key) {
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();//擷取原子的值
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next)) //CAS
return next;
}
}
3、手寫負載均衡器
80端實作輪詢
接口:從服務清單選出一台機器進行通路
public interface LoadBalancer {
/**
* 從服務清單選出一台機器進行通路
* @param serviceInstances 服務清單
* @return
*/
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
實作類
@Component
public class MyLB implements LoadBalancer{
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement(){
int current;
int next;
do{
current = this.atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 0 : current+1;
}while (!this.atomicInteger.compareAndSet(current,next));
//atomicInteger.compareAndSet(expect, update),
//比較except和update,如果相等傳回true,否則傳回fasle
System.out.println("*********第"+next+"次通路");
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
消費方80controller類加上自己實作的輪詢方法
@Slf4j
@RestController
public class OrderController {
@Resource
private LoadBalancer loadBalancer;
@GetMapping(value = "/consumer/payment/lb")
public String getPayment(){
//獲得服務清單
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances == null || instances.size()<=0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
log.info(" "+uri);
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
}
服務提供者8001,8002controller加上80端調用的方法
@GetMapping(value = "/payment/lb")
public String getPayment(){
return serverPort;
}
注意:測試的時候注釋掉這兩個注釋,不然 restTemplate.getForObject不知道調用哪個服務,報錯如下,從報錯描述可知沒有合适的調用對象。