生命太短暫,不要去做一些根本沒有人想要的東西。
代碼下載下傳位址:https://github.com/f641385712/netflix-learning
目錄 - 前言
- 正文
- LoadBalancer負載均衡器五大元件
- Server
- 特别注意
- 代碼示例
- IPing
- PingConstant
- NoOpPing
- AbstractLoadBalancerPing
- DummyPing
- PingUrl
- IPing#isAlive()方法何時調用?有何用?
- IPingStrategy
- 總結
- 聲明
- LoadBalancer負載均衡器五大元件
- Server
- 特别注意
- 代碼示例
- IPing
- PingConstant
- NoOpPing
- AbstractLoadBalancerPing
- DummyPing
- PingUrl
- IPing#isAlive()方法何時調用?有何用?
- IPingStrategy
- 聲明
前言
大家熟知Ribbon是因為
Spring Cloud
,并且它的刻闆印象就是一個用戶端負載均衡器。前幾篇文章對
ribbon-core
進行了源碼解析,你會發現并沒有任何指明讓Ribbon和負載均衡挂上鈎。
Ribbon
它的實際定位是更為抽象的:不限定協定的請求轉發。比如它可以內建
ribbon-httpclient/transport
等子產品來實作請求的控制、轉發。但是,但是,但是Ribbon之是以出名是因為它的負載均衡做得非常的好,是以大家對它的認知大都就是
Ribbon=負載均衡
。存在即合理,這麼了解也沒什麼問題。
正文
既然負載均衡是Ribbon的真正核心,那麼從本文開始就學習它的最終的部分,這便就是
ribbon-loadbalancer
子產品:
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
<version>2.3.0</version>
</dependency>
複制
包依賴如下:

LoadBalancer負載均衡器五大元件
圍繞着
LoadBalancer
負載均衡器有幾個核心元件,這便是大名鼎鼎的五大核心元件,如下圖所示:
-
:用戶端用于快速檢查伺服器當時是否處于活動狀态(心跳檢測)IPing
-
:負載均衡政策,用于确定從伺服器清單傳回哪個伺服器IRule
-
:可以響應用戶端的特定服務的伺服器清單ServerList
-
:可以動态獲得的具有所需特征的候選伺服器清單的過濾器ServerListFilter
-
:用于執行動态伺服器清單更新ServerListUpdater
說明:其實它們三也都是接口,但并沒有遵循
ServerList/ServerListFilter/ServerListUpdater
開頭的命名規範,但是
I
都遵循有此規範,是以,這種規範上面不要一位的強求吧。
IPing/IRule/ILoadBalancer
下面将圍繞這五大核心元件一一展開,比如本文将來到
IPing
元件的學習,在學習之初需要普及一些基本概念。
Server
既然要負載均衡,那必然是在多台
Server
之前去均衡。顧名思義,它代表一台伺服器/執行個體,包含
Host:port
是以可以定位到目标伺服器,并且還有一些狀态标志屬性。
public class Server {
// 未知Zone區域,這是每台Server的預設區域
public static final String UNKNOWN_ZONE = "UNKNOWN";
// 如192.168.1.1 / www.baidu.com
private String host;
private int port = 80;
// 有可能是http/https 也有可能是tcp、udp等
private String scheme;
// id表示唯一。host + ":" + port -> localhost:8080
// 注意沒有http://字首 隻有host和端口
// getInstanceId執行個體id使用的就是它。因為ip+端口可以唯一确定一個執行個體
private volatile String id;
// Server所屬的zone區域
private String zone = UNKNOWN_ZONE;
// 标記是否這台機器是否是活着的
// =========請注意:它的預設值是false=========
private volatile boolean isAliveFlag;
// 标記這台機器是否可以準好可以提供服務了(活着并不代表可以提供服務了)
private volatile boolean readyToServe = true;
// 構造器
public Server(String host, int port) {
this(null, host, port);
}
public Server(String scheme, String host, int port) {
this.scheme = scheme;
this.host = host;
this.port = port;
this.id = host + ":" + port;
isAliveFlag = false;
}
// 因為一個id就可确定一台Server,是以這麼構造是ok的
public Server(String id) {
setId(id);
isAliveFlag = false;
}
}
複制
以上标記了一台Server的必要屬性,其中需要注意的是
isAliveFlag
屬性,它預設是false,若想這台
Server
能備用是需要設定為true的:
Server:
// 此方法并非是synchronization同步的,是以其實存線上程不安全的情況
// (volatile解決不了線程同步問題)
// 官方解釋是:遵照last win的原則也是合理的
public void setAlive(boolean isAliveFlag) {
this.isAliveFlag = isAliveFlag;
}
public boolean isAlive() {
return isAliveFlag;
}
複制
Server
的每個屬性設定都沒有
synchronization
同步控制,是因為它統一依照last win的原則來處理接口,否則效率太低了。
該類裡面最主要是對URL的處理,包括host和ip:
Server:
// 從字元串裡解析傳ip和端口号
// http://www.baidu.com -> www.baidu.com + 80
// https://www.baidu.com/api/v1/node -> www.baidu.com + 443
// localhost:8080 -> localhost + 8080
static Pair<String, Integer> getHostPort(String id) {
...
}
// 規範化id,依賴于上面的getHostPort()方法
// 任何uri(id)最終都會被規範為 ip + ":" + port的方式
static public String normalizeId(String id) { ... }
// 不解釋,也是依賴于getHostPort(id)喽
public void setId(String id) { ... }
複制
其它get/set方法就不用介紹了,下面用一個例子簡單說明一下即可。另外
Server
在
ribbon-eureka
工程下是有實作類的:
DiscoveryEnabledServer
,本處不做讨論。
特别注意
Server:
@Override
public String toString() {
return this.getId();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Server))
return false;
Server svc = (Server) obj;
return svc.getId().equals(this.getId());
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + (null == this.getId() ? 0 : this.getId().hashCode());
return hash;
}
複制
特别注意以上三個方法,他們的值有且僅和id相關,so隻要id一樣會被認為是“同一個”對象(當然實際位址值是不同的),是以在裝入Set的時候需要特别注意哦(list沒事,list不去重嘛)。
代碼示例
@Test
public void fun1() {
Server server = new Server("www.yourbatman.com", 886);
System.out.println(server.getId()); // www.yourbatman.com:886
System.out.println(server.getHost()); // www.yourbatman.com
System.out.println(server.getPort()); // 886
System.out.println(server.getHostPort()); // www.yourbatman.com:886
System.out.println(server.getScheme()); // null
server.setId("localhost:8080");
System.out.println(server.getId()); // localhost:8080
System.out.println(server.getHost()); // localhost
System.out.println(server.getPort()); // 8080
System.out.println(server.getHostPort()); // localhost:8080
System.out.println(server.getScheme()); // null
server.setId("https://www.baidu.com");
System.out.println(server.getId()); // www.baidu.com:443
System.out.println(server.getHost()); // www.baidu.com
System.out.println(server.getPort()); // 443
System.out.println(server.getHostPort()); // www.baidu.com:443
System.out.println(server.getScheme()); // https
}
複制
因為Server它并不規定具體協定,比如可以是http、https、tcp、udp等,是以scheme有可能是任何值(甚至為null都可),有ip和端口号就夠了。
IPing
定義如何“ping”伺服器以檢查其是否活動的接口,類似于心跳檢測。
public interface IPing {
// 檢查給定的Server是否為“活動的”,這為在負載平衡時選出一個可用的候選Server
public boolean isAlive(Server server);
}
複制
在
ribbon-loadbalancer
内的繼承圖譜如下(Spring Cloud換下一樣):
PingConstant
永遠傳回一個bool常量:true or false。
public class PingConstant implements IPing {
boolean constant = true;
... // 給constant指派
@Override
public boolean isAlive(Server server) {
return constant;
}
}
複制
基本可忽略它,并無實際應用場景。
NoOpPing
它比
PingConstant
更狠,永遠傳回true。
public class NoOpPing implements IPing {
@Override
public boolean isAlive(Server server) {
return true;
}
}
複制
它和下面的
DummyPing
效果上是一樣的。
AbstractLoadBalancerPing
顧名思義,和
LoadBalancer
有關的一種實作,用于探測伺服器節點的适用性。
public abstract class AbstractLoadBalancerPing implements IPing, IClientConfigAware {
AbstractLoadBalancer lb;
public void setLoadBalancer(AbstractLoadBalancer lb){
this.lb = lb;
}
public AbstractLoadBalancer getLoadBalancer(){
return lb;
}
@Override
public boolean isAlive(Server server) {
return true;
}
}
複制
它是使用較多的ping政策的父類,很明顯,請子類複寫isAlive()方法。它要求必須要關聯上一個負載均衡器
AbstractLoadBalancer
。若你要實作自己的Ping規則,進行心跳檢測,建議通過繼承該類來實作。
DummyPing
Dummy
:仿制品,假的,仿真的。它是
AbstractLoadBalancerPing
的一個空實作~
public class DummyPing extends AbstractLoadBalancerPing {
@Override
public boolean isAlive(Server server) {
return true;
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
複制
它是預設的ping實作,
Spring Cloud
預設也是使用的它作為預設實作,也就是說根本就木有心跳的效果喽。
說明:在子產品下有
ribbon-eureka
這個實作,它基于服務注冊中心來判斷服務的健康狀态
NIWSDiscoveryPing
PingUrl
它位于
ribbon-httpclient
這個包裡面。它使用發送真實的Http請求的方式來做健康檢查,若傳回的狀态碼是200就證明能夠ping通,傳回true。
public class PingUrl implements IPing {
String pingAppendString = "";
// 是否使用https
boolean isSecure = false;
// 期待的傳回值。若為null,那隻要是200就行,否則要進行比較
String expectedContent = null;
// 發送http請求
@Override
public boolean isAlive(Server server) {
String urlStr = "";
if (isSecure){
urlStr = "https://";
}else{
urlStr = "http://";
}
urlStr += server.getId();
urlStr += getPingAppendString();
... // 使用Apache HC發送http請求。若狀态碼傳回200就表示成功了
}
}
複制
因為
ribbon-httpclient
包并不推薦在生産上使用了,是以此實作僅做了解即可,實際并不會使用到(畢竟
ribbon-httpclient
包已經不推薦使用了)。
IPing#isAlive()方法何時調用?有何用?
我們已經知道了IPing的目的是用來做
健康檢查
,是以它到底是什麼時候被調用,以及有什麼用呢?
如截圖所示:
BaseLoadBalancer
裡是對此方法的唯一調用處。不妨把這塊“僞代碼”拿出來看看:
BaseLoadBalancer:
private static class SerialPingStrategy implements IPingStrategy {
@Override
public boolean[] pingServers(IPing ping, Server[] servers) {
...
for (int i = 0; i < numCandidates; i++) {
...
results[i] = ping.isAlive(servers[i]);
...
}
return results;
}
}
複制
|
IPingStrategy#pingServers()
方法唯一調用處:依舊在
BaseLoadBalancer.Pinger
這個内部類裡,
|
BaseLoadBalancer.Pinger:
class Pinger {
...
public void runPinger() throws Exception {
boolean[] results = null;
...
results = pingerStrategy.pingServers(ping, allServers);
...
// 這裡就是核心:隻有ping後是活着的,就會把這個機器添加到up清單裡
// 換句話說若是false,
boolean isAlive = results[i];
if (isAlive) {
newUpList.add(svr);
}
...
}
...
}
複制
這就是
isAlive()
方法的作用:true -> 表示該機器是up的,進而得到新的up清單就是最新的可用的機器清單了。
定位到了它有何用,那麼它的執行入口在哪兒呢?如何執行的呢?可以确定的是:它必然是任務排程,定時執行的。接上面
BaseLoadBalancer.Pinger#runPinger()
的調用處是:
BaseLoadBalancer:
// 任務Task
class PingTask extends TimerTask {
@Override
public void run() {
new Pinger(pingStrategy).runPinger();
}
}
// 這裡是它的PingTask的唯一調用處
void setupPingTask() {
...
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
...
}
複制
一切浮出水面了:
IPing#isAlive()
方法是由Timer定時調用的,
pingIntervalSeconds
預設值是30s,也就說30s會去心跳一次Server,看它活着與否。當然你可以通過key:
NFLoadBalancerPingInterval
自己配置(機關是秒)。
IPingStrategy
定義用于ping所有伺服器的政策,畢竟一般來說單單ping某一台機器的意義并不大。
public interface IPingStrategy {
boolean[] pingServers(IPing ping, Server[] servers);
}
複制
使用
IPing
對傳入的servers分别進行ping,傳回結果。是以可以了解它就是一個批量操作而已,它的唯一被使用的地方是在
BaseLoadBalancer
裡用于“挑選出”所有的up伺服器。
需要說明的是,若你的機器執行個體非常多,用并行去ping是一個比較好的優化方案,那麼你就需要自定義實作
IPingStrategy
此接口,然後把你定義的政策和
BaseLoadBalancer
綁定起來替換掉預設的實作即可(預設為串行)。
總結
Ribbon的LoadBalancer五大元件之:IPing心跳檢測就先介紹到這。
IPing
是最簡單、最容易了解的一個元件,它用于解決探活、心跳檢測問題,這是微服務體系中的必備元素。當然,預設使用的
DummyPing
并沒有現實意義,是以若你是架構師,你可以寫一個标準實作,使得你們的微服務更加靈敏、更加的健康。