天天看点

[享学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心跳检测