前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Netflix] 四十二、Ribbon的LoadBalancer五大组件之:IPing心跳检测

[享学Netflix] 四十二、Ribbon的LoadBalancer五大组件之:IPing心跳检测

作者头像
YourBatman
发布2020-03-18 20:38:53
2.4K1
发布2020-03-18 20:38:53
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

生命太短暂,不要去做一些根本没有人想要的东西。

代码下载地址: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模块:

代码语言:javascript
复制
<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所以可以定位到目标服务器,并且还有一些状态标志属性。

代码语言:javascript
复制
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的:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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方法就不用介绍了,下面用一个例子简单说明一下即可。另外Serverribbon-eureka工程下是有实现类的:DiscoveryEnabledServer,本处不做讨论。


特别注意
代码语言:javascript
复制
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不去重嘛)。


代码示例
代码语言:javascript
复制
@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”服务器以检查其是否活动的接口,类似于心跳检测。

代码语言:javascript
复制
public interface IPing {
	// 检查给定的Server是否为“活动的”,这为在负载平衡时选出一个可用的候选Server
	public boolean isAlive(Server server);
}

ribbon-loadbalancer内的继承图谱如下(Spring Cloud换下一样):

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

PingConstant

永远返回一个bool常量:true or false。

代码语言:javascript
复制
public class PingConstant implements IPing {
	boolean constant = true;
	... // 给constant赋值
	@Override
	public boolean isAlive(Server server) {
		return constant;
	}
}

基本可忽略它,并无实际应用场景。


NoOpPing

它比PingConstant更狠,永远返回true。

代码语言:javascript
复制
public class NoOpPing implements IPing {
    @Override
    public boolean isAlive(Server server) {
        return true;
    }
}

它和下面的DummyPing效果上是一样的。


AbstractLoadBalancerPing

顾名思义,和LoadBalancer有关的一种实现,用于探测服务器节点的适用性。

代码语言:javascript
复制
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的一个空实现~

代码语言:javascript
复制
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。

代码语言:javascript
复制
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里是对此方法的唯一调用处。不妨把这块“伪代码”拿出来看看:

代码语言:javascript
复制
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这个内部类里, |

代码语言:javascript
复制
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()的调用处是:

代码语言:javascript
复制
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某一台机器的意义并不大。

代码语言:javascript
复制
public interface IPingStrategy {
    boolean[] pingServers(IPing ping, Server[] servers);
}

使用IPing对传入的servers分别进行ping,返回结果。所以可以理解它就是一个批量操作而已,它的唯一被使用的地方是在BaseLoadBalancer里用于“挑选出”所有的up服务器。

需要说明的是,若你的机器实例非常多,用并行去ping是一个比较好的优化方案,那么你就需要自定义实现IPingStrategy此接口,然后把你定义的策略和BaseLoadBalancer绑定起来替换掉默认的实现即可(默认为串行)。


总结

Ribbon的LoadBalancer五大组件之:IPing心跳检测就先介绍到这。IPing是最简单、最容易理解的一个组件,它用于解决探活、心跳检测问题,这是微服务体系中的必备元素。当然,默认使用的DummyPing并没有现实意义,因此若你是架构师,你可以写一个标准实现,使得你们的微服务更加灵敏、更加的健康

分隔线
分隔线

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 前言
  • 正文
    • LoadBalancer负载均衡器五大组件
      • Server
        • 特别注意
        • 代码示例
      • IPing
        • PingConstant
        • NoOpPing
        • AbstractLoadBalancerPing
        • PingUrl
      • IPing#isAlive()方法何时调用?有何用?
        • IPingStrategy
        • 总结
        相关产品与服务
        负载均衡
        负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com