生命太短暫,不要去做一些根本沒有人想要的東西。
–> 返回專欄總目錄 <–
代碼下載地址:https://github.com/f641385712/netflix-learning
目錄
前言
大家熟知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方法就不用介紹了,下面用一個例子簡單說明一下即可。
代碼示例
@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
默認也是使用的它作爲默認實現,也就是說根本就木有心跳的效果嘍。
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
並沒有現實意義,因此若你是架構師,你可以寫一個標準實現,使得你們的微服務更加靈敏、更加的健康。
聲明
原創不易,碼字不易,多謝你的點贊、收藏、關注。把本文分享到你的朋友圈是被允許的,但拒絕抄襲
。你也可【左邊掃碼/或加wx:fsx641385712】邀請你加入我的 Java高工、架構師 系列羣大家庭學習和交流。
- [享學Netflix] 一、Apache Commons Configuration:你身邊的配置管理專家
- [享學Netflix] 二、Apache Commons Configuration事件監聽機制及使用ReloadingStrategy實現熱更新
- [享學Netflix] 三、Apache Commons Configuration2.x全新的事件-監聽機制
- [享學Netflix] 四、Apache Commons Configuration2.x文件定位系統FileLocator和FileHandler
- [享學Netflix] 五、Apache Commons Configuration2.x別樣的Builder模式:ConfigurationBuilder
- [享學Netflix] 六、Apache Commons Configuration2.x快速構建工具Parameters和Configurations
- [享學Netflix] 七、Apache Commons Configuration2.x如何實現文件熱加載/熱更新?
- [享學Netflix] 八、Apache Commons Configuration2.x相較於1.x使用上帶來哪些差異?
- [享學Netflix] 九、Archaius配置管理庫:初體驗及基礎API詳解
- [享學Netflix] 十、Archaius對Commons Configuration核心API Configuration的擴展實現
- [享學Netflix] 十一、Archaius配置管理器ConfigurationManager和動態屬性支持DynamicPropertySupport
- [享學Netflix] 十二、Archaius動態屬性DynamicProperty原理詳解(重要)
- [享學Netflix] 十三、Archaius屬性抽象Property和PropertyWrapper詳解
- [享學Netflix] 十四、Archaius如何對多環境、多區域、多雲部署提供配置支持?
- [享學Netflix] 十五、Archaius和Spring Cloud的集成:spring-cloud-starter-netflix-archaius
- [享學Netflix] 十六、Hystrix斷路器:初體驗及RxJava簡介
- [享學Netflix] 十七、Hystrix屬性抽象以及和Archaius整合實現配置外部化、動態化
- [享學Netflix] 十八、Hystrix配置之:全局配置和實例配置
- [享學Netflix] 十九、Hystrix插件機制:SPI接口介紹和HystrixPlugins詳解
- [享學Netflix] 二十、Hystrix跨線程傳遞數據解決方案:HystrixRequestContext
- [享學Netflix] 二十一、Hystrix指標數據收集(預熱):滑動窗口算法(附代碼示例)
- [享學Netflix] 二十二、Hystrix事件源與事件流:HystrixEvent和HystrixEventStream
- [享學Netflix] 二十三、Hystrix桶計數器:BucketedCounterStream
- [享學Netflix] 二十四、Hystrix在滑動窗口內統計:BucketedRollingCounterStream、HealthCountsStream
- [享學Netflix] 二十五、Hystrix累計統計流、分發流、最大併發流、配置流、功能流(附代碼示例)
- [享學Netflix] 二十六、Hystrix指標數據收集器:HystrixMetrics(HystrixDashboard的數據來源)
- [享學Netflix] 二十七、Hystrix何爲斷路器的半開狀態?HystrixCircuitBreaker詳解
- [享學Netflix] 二十八、Hystrix事件計數器EventCounts和執行結果ExecutionResult
- [享學Netflix] 二十九、Hystrix執行過程核心接口:HystrixExecutable、HystrixObservable和HystrixInvokableInfo
- [享學Netflix] 三十、Hystrix的fallback回退/降級邏輯源碼解讀:getFallbackOrThrowException
- [享學Netflix] 三十一、Hystrix觸發fallback降級邏輯的5種情況及代碼示例
- [享學Netflix] 三十二、Hystrix拋出HystrixBadRequestException異常爲何不會觸發熔斷?
- [享學Netflix] 三十三、Hystrix執行目標方法時,如何調用線程池資源?
- [享學Netflix] 三十四、Hystrix目標方法執行邏輯源碼解讀:executeCommandAndObserve
- [享學Netflix] 三十五、Hystrix執行過程集大成者:AbstractCommand詳解
- [享學Netflix] 三十六、Hystrix請求命令:HystrixCommand和HystrixObservableCommand
- [享學Netflix] 三十七、源生Ribbon介紹 — 客戶端負載均衡器
- [享學Netflix] 三十八、Ribbon核心API源碼解析:ribbon-core(一)IClient請求客戶端
- [享學Netflix] 三十九、Ribbon核心API源碼解析:ribbon-core(二)IClientConfig配置詳解
- [享學Netflix] 四十、Ribbon核心API源碼解析:ribbon-core(三)RetryHandler重試處理器
- [享學Netflix] 四十一、Ribbon核心API源碼解析:ribbon-core(四)ClientException客戶端異常