ElasticSearch6.3.2源码分析之节点连接实现

这篇文章主要分析ES节点之间如何维持连接的。在开始之前,先扯一下ES源码阅读的一些心得:在使用ES过程中碰到某个问题,想要深入了解一下,可源码又太多了,不知道从哪里入手,怎么办?ES启动的入口类是Elasticsearch.java,顺着这个启动流程,到Node类的构造方法:org.elasticsearch.node.Node#Node(org.elasticsearch.env.Environment, java.util.Collection<java.lang.Class<? extends org.elasticsearch.plugins.Plugin>>)在构造方法中开始初始化各种模块(插件)。如果想了解某个分词插件是如何被载的?又或者是线程池模块、搜索模块是如何初始化的,那么就在Node的构造方法里面看相关的代码。

初始化完成之后,就开始启动各种服务,这是在org.elasticsearch.node.Node#start方法中实现。比如本文要介绍的节点连接服务NodeConnectionsService、用于节点之间数据传输的TransportService、用于master选主,节点发现的ZenDiscovery服务。因此,要想了解ES某个功能的运行机制,那就从start方法开始跟踪调试吧。

对于不同的服务而言,有着各自的启动逻辑,这都在它们自己的doStart()方法中实现:比如ZenDiscovery#doStart()、NodeConnectionsService#doStart()、org.elasticsearch.cluster.service.ClusterService#doStart 等等……

现在进入正题,ES各个节点是如何连接,并保持连接状态的。

This component is responsible for connecting to nodes once they are added to the cluster state, and disconnect when they are removed. Also, it periodically checks that all connections are still open and if needed restores them.

当有新节点加入集群时,集群状态会更新,NodeConnectionsService 就会尝试连接这个节点,这在:org.elasticsearch.cluster.NodeConnectionsService#connectToNodes方法中实现。此外,NodeConnectionsService 还会周期性检测节点之间的连接是否是open的,如果节点连接出现了故障,则尝试恢复连接,这是通过后台的周期性任务org.elasticsearch.cluster.NodeConnectionsService.ConnectionChecker实现的。

Note that this component is *not* responsible for removing nodes from the cluster if they disconnect / do not respond to pings. This is done by NodesFaultDetection  Master fault detection is done by MasterFaultDetection

如果节点之间的连接发生了故障,NodeConnectionsService 并不负责移除节点,移除节点的任务交给:NodesFaultDetection 和 MasterFaultDetection(处理master节点的故障)实现。

现在看具体源码:在Node#start方法中创建NodeConnectionsService并启动连接服务:

final NodeConnectionsService nodeConnectionsService = injector.getInstance(NodeConnectionsService.class);
nodeConnectionsService.start();

各个Service都是AbstractLifecycleComponent的子类,重写了 AbstractLifecycleComponent#doStart,具体的启动逻辑都在doStart方法中:

NodeConnectionsService节点连接启动非常简单,就是一个定时任务尝试连接目标节点:org.elasticsearch.cluster.NodeConnectionsService#doStart

@Override
protected void doStart() {
//threadPool.schedule 启动一个定时任务,任务的执行逻辑封装在Runnable任务:ConnectionChecker中
backgroundFuture = threadPool.schedule(reconnectInterval, ThreadPool.Names.GENERIC, new ConnectionChecker());
}

那具体看org.elasticsearch.cluster.NodeConnectionsService.ConnectionChecker的实现:

    class ConnectionChecker extends AbstractRunnable {

        @Override
public void onFailure(Exception e) {
logger.warn("unexpected error while checking for node reconnects", e);
} protected void doRun() {
for (DiscoveryNode node : nodes.keySet()) {
try (Releasable ignored = nodeLocks.acquire(node)) {
//遍历每个节点,验证连接(当新节点加入时,集群状态会更新)
validateAndConnectIfNeeded(node);
}
}
} @Override
public void onAfter() {
if (lifecycle.started()) {
//每一次验证完连接后,在这里重新执行定时任务开启下一轮的连接验证
//从而实现了节点之间周期性验证连接(周期性地检测节点是存在的)
backgroundFuture = threadPool.schedule(reconnectInterval, ThreadPool.Names.GENERIC, this);
}
}
}

ConnectionChecker 就是一个Runnable任务,如果熟悉ES线程池实现的话,ES使用 AbstractRunnable的doRun方法中 封装各种异步操作执行逻辑,即任务的处理在doRun方法中执行,执行成功后由onAfter方法统一处理,如果Runnable任务执行过程中出现了异常则由onFailure统一处理。因此,当一个定时任务执行完成,检测连接成功后,在onAfter方法里面,又开始下一次的连接检测。

再来看具体是如何建立连接的:

    void validateAndConnectIfNeeded(DiscoveryNode node) {
assert nodeLocks.isHeldByCurrentThread(node) : "validateAndConnectIfNeeded must be called under lock";
//先判断要不要发起一次连接/连接检测
if (lifecycle.stoppedOrClosed() || nodes.containsKey(node) == false) { // we double check existence of node since connectToNode might take time...
// nothing to do
} else {
//发起一次连接检测
try {
// connecting to an already connected node is a noop
transportService.connectToNode(node);
nodes.put(node, 0);//如果连接成功了,将失败次数设置成0
} catch (Exception e) {
Integer nodeFailureCount = nodes.get(node);
assert nodeFailureCount != null : node + " didn't have a counter in nodes map";
nodeFailureCount = nodeFailureCount + 1;//连接未成功,失败次数加1
// log every 6th failure
if ((nodeFailureCount % 6) == 1) {
final int finalNodeFailureCount = nodeFailureCount;
//每6次 连接失败 就尝试打印告警日志(默认是10s钟发起一次连接检测任务)
logger.warn(() -> new ParameterizedMessage(
"failed to connect to node {} (tried [{}] times)", node, finalNodeFailureCount), e);
}
nodes.put(node, nodeFailureCount);//保存node节点已经连接失败的次数
}
}
}

重点是这行代码:transportService.connectToNode(node);,实际的连接请求在TcpTransport类中:org.elasticsearch.transport.TcpTransport#connectToNode,看这里面的代码,就知道ES节点是如何连接到另一个节点上去的了。

另外,为了防止多线程并发连接某个节点,在发起连接时,需要先获取这个节点所对应的锁,才能发起连接。ES在ReentrantLock功能基础上,实现了自己的锁,里面的实现细节也非常值得借鉴。

    private static final class KeyLock extends ReentrantLock {
KeyLock(boolean fair) {
super(fair);
} private final AtomicInteger count = new AtomicInteger(1);
}

KeyLock是可重入锁,count 保存当前线程获取锁的次数(重入次数)

当向某个节点发起连接时,如果这个节点所对应的锁不存在,会为该节点创建一把锁,并保存到ConcurrentHashMap中。获取锁的实现逻辑如下:org.elasticsearch.common.util.concurrent.KeyedLock#acquire

    public Releasable acquire(T key) {
while (true) {//注意while true循环, ha ha ....
KeyLock perNodeLock = map.get(key);
if (perNodeLock == null) {
//锁不存在,为这个节点创建一把锁
ReleasableLock newLock = tryCreateNewLock(key);
if (newLock != null) {
return newLock;
}
} else {//锁已经存在了
assert perNodeLock != null;
int i = perNodeLock.count.get();//目前锁的重入次数
//外面是个while true循环, CAS一定会成功
if (i > 0 && perNodeLock.count.compareAndSet(i, i + 1)) {
perNodeLock.lock();
return new ReleasableLock(key, perNodeLock);
}
}
}
}

NodeConnectionsService 初始化了一个KeyedLock对象,用来保存向目标节点发起连接时所创建的锁。

private final KeyedLock<DiscoveryNode> nodeLocks = new KeyedLock<>();

总结:

整个检测连接流程:在Node#start()中启动连接服务,接着向ThreadPool的GENERIC线程池提交一个 检测连接的任务,在 ConnectionChecker#onAfter()方法中 执行下一轮的连接检测(从而实现周期性检测),发送实现的连接请求在:TcpTransport#connectToNode中实现

另外,ES各个节点之间的连接机制的具体实现几乎都在TcpTransport这个类中,心跳发送、断开连接、建立连接等。有时间的可继续深究。

原文:https://www.cnblogs.com/hapjin/p/11395422.html

其他一些文章:

Elasticsearch6.3.2启动过程源码阅读记录

Elasticsearch Transport 模块创建及启动分析

ElasticSearch 线程池类型分析之SizeBlockingQueue

ElasticSearch 线程池类型分析之 ExecutorScalingQueue

ElasticSearch 线程池类型分析之 ResizableBlockingQueue

ElasticSearch6.3.2源码分析之节点连接实现的更多相关文章

  1. 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念 | 百篇博客分析OpenHarmony源码 | v64.01

    百篇博客系列篇.本篇为: v64.xx 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么 ...

  2. Elasticsearch源码分析 | 单节点的启动和关闭

    本文主要简要介绍Elasticsearch单节点的启动和关闭流程.Elasticsearch版本:6.3.2 相关文章 1.Google Guice 快速入门 2.Elasticsearch 中的 G ...

  3. [Hadoop] - TaskTracker源码分析(TaskTracker节点健康状况监控)

    在TaskTracker中对象healthStatus保存了当前节点的健康状况,对应的类是org.apache.hadoop.mapred.TaskTrackerStatus.TaskTrackerH ...

  4. 源码分析-react1-根节点渲染

  5. 源码分析-react2-根节点渲染

    //FiberNode{ alternate : '通过该属性和后面的切片进行比较', child : '改切片的子切片', firstEffect : '当前要加入的切片', stateNode : ...

  6. [Hadoop] - TaskTracker源码分析(状态发送)

    TaskTracker节点向JobTracker汇报当前节点的运行时信息时候,是将运行状态信息同心跳报告一起发送给JobTracker的,主要包括TaskTracker的基本信息.节点资源使用信息.各 ...

  7. ConcurrentHashMap实现原理以及源码分析

    ConcurrentHashMap是HashMap的高并发版本,是线程安全的,而HashMap是非线程安全的 一.底层实现 底层结构跟hashmap一样,都是通过数组+链表+红黑树实现的,不过它要保证 ...

  8. 菜鸟系列Fabric源码学习 — peer节点启动

    Fabric 1.4 源码分析peer节点启动 peer模块采用cobra库来实现cli命令. Cobra提供简单的接口来创建强大的现代化CLI接口,比如git与go工具.Cobra同时也是一个程序, ...

  9. 菜鸟学习Fabric源码学习 — 背书节点和链码容器交互

    Fabric 1.4 源码分析 背书节点和链码容器交互 本文档主要介绍背书节点和链码容器交互流程,在Endorser背书节点章节中,无论是deploy.upgrade或者调用链码,最后都会调用Chai ...

随机推荐

  1. 二十:职责链模式详解(类似于spring的hangler处理请求)

    定义:为了避免请求的发送者和接收者之间的耦合关系,使多个接受对象都有机会处理请求.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止. “看这个定义,就是将一堆可以处理请求的对象连 ...

  2. 小鸟初学Shell编程(二)编写简单的Shell脚本

    Shell脚本 编写Python.PHP脚本通常需要掌握语言的函数,那么Shell脚本则不需要,只需要掌握Linux命令就可以编写Shell脚本,因为Shell脚本就是由多个Linux命令组成,通过将 ...

  3. MVC中IActionFilter过滤器俄罗斯套娃的实现方式

    看mvc的源码我们知道,它是在 ControllerActionInvoker 类中执行 InvokeAction 方法来实现过滤器和action方法执行的. 通过查看源码我们知道,他是通过调用 In ...

  4. Vue计算属性computed的全面解析

    前言 一直以来对computed这个计算属性都只停在一个大概的认知中,最近特意仔细研读相关资料,亲测后逐渐了解了其特性. 正文 computed 特点: 1.初始化/依赖属性(即data属性)改变时执 ...

  5. secruity

    security3.x <?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns=& ...

  6. 关于微信小程序中遇到的各种问题汇总(持续更新)

    1.关于 <input />标签容易忽略的问题: 使用<input />标签时容易忘记绑定bindblur()方法(输入框失去焦点时触发),因为用户用键盘输入时不一定会点击完成 ...

  7. appium 基础:常用api接口(2)

    一.获取手机分辨率 size=driver.get_window_size()#获取手机屏幕大小,分辨率 print(size)#{'width': 720, 'height': 1280} 得到的是 ...

  8. 旅游网dao层

    # 注册功能 # 1.根据用户名查询用户 select * from tab_user where username=#{username} # 2.插入注册用户信息 insert into tab_ ...

  9. NOIP 1999 导弹拦截

    洛谷 P1020 导弹拦截 https://www.luogu.org/problemnew/show/P1020 JDOJ 1411: [NOIP1999]拦截导弹 T1 https://neooj ...

  10. HDU 6588 Function

    [传送门] 求$$\sum_{i=1}^{n} \gcd(\lfloor \sqrt[3]{i} \rfloor, i)$$题解写的很清楚,自己重新推一推. $$\sum_{i=1}^{n} \gcd ...