天天看點

[享學Netflix] 四十二、Ribbon的LoadBalancer五大元件之:IPing心跳檢測

生命太短暫,不要去做一些根本沒有人想要的東西。

代碼下載下傳位址:https://github.com/f641385712/netflix-learning

目錄
  • 前言
  • 正文
    • 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>           

複制

包依賴如下:

[享學Netflix] 四十二、Ribbon的LoadBalancer五大元件之:IPing心跳檢測

LoadBalancer負載均衡器五大元件

圍繞着

LoadBalancer

負載均衡器有幾個核心元件,這便是大名鼎鼎的五大核心元件,如下圖所示:

[享學Netflix] 四十二、Ribbon的LoadBalancer五大元件之:IPing心跳檢測
  • 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換下一樣):

[享學Netflix] 四十二、Ribbon的LoadBalancer五大元件之:IPing心跳檢測
[享學Netflix] 四十二、Ribbon的LoadBalancer五大元件之:IPing心跳檢測

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的目的是用來做

健康檢查

,是以它到底是什麼時候被調用,以及有什麼用呢?

[享學Netflix] 四十二、Ribbon的LoadBalancer五大元件之: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

并沒有現實意義,是以若你是架構師,你可以寫一個标準實作,使得你們的微服務更加靈敏、更加的健康。

[享學Netflix] 四十二、Ribbon的LoadBalancer五大元件之:IPing心跳檢測