在es中存在两种形式的客户端,NodeClient和TransportClient。两者的主要区别在于是否加入集群。TransportClient是不会加入集群(作为集群的一个节点)。因此TransportClient跟NodeClient相比算是轻量级的。因为不加入集群,因此访问集群需要指定一个或者多个地址。客户端,顾名思义,就是作为调用的发起方,来向服务器请求数据。因此,我们执行的诸如索引、查询等一系列操作在TransportClient中都提供了对应的接口,这些接口无需过多介绍。
在TransportClient中,最重要的莫过于InternalTransportClient实例和NodeService服务。NodeService负责客户端与服务器的连接。
nodesService = injector.getInstance(TransportClientNodesService.class);
internalClient = injector.getInstance(InternalTransportClient.class);
我们重点关注下NodeService。客户端是如何维护与服务器的连接呢?
在NodeService中有维护者三类节点:listedNode(调用addTransportAddress加入的初始连接节点),nodes(真正于服务器建立了连接的节点),filteredNode(没有连接上的节点,比如由于clusterName不一致)。
当我们调用addTransportAddress加入一个或者地址时,会发生什么呢?关注TransportNodeService的如下方法:
public TransportClientNodesService addTransportAddresses(TransportAddress... transportAddresses) {
synchronized (mutex) {
if (closed) {
throw new ElasticsearchIllegalStateException("transport client is closed, can't add an address");
}
List<TransportAddress> filtered = Lists.newArrayListWithExpectedSize(transportAddresses.length);
for (TransportAddress transportAddress : transportAddresses) {
boolean found = false;
for (DiscoveryNode otherNode : listedNodes) {
if (otherNode.address().equals(transportAddress)) {
found = true;
logger.debug("address [{}] already exists with [{}], ignoring...", transportAddress, otherNode);
break;
}
}
if (!found) {
filtered.add(transportAddress);
}
}
if (filtered.isEmpty()) {
return this;
}
ImmutableList.Builder<DiscoveryNode> builder = ImmutableList.builder();
builder.addAll(listedNodes());
for (TransportAddress transportAddress : filtered) {
DiscoveryNode node = new DiscoveryNode("#transport#-" + tempNodeIdGenerator.incrementAndGet(), transportAddress, minCompatibilityVersion);
logger.debug("adding address [{}]", node);
builder.add(node);
}
listedNodes = builder.build();
nodesSampler.sample();
}
return this;
}
初始状态下listedNodes是没有赋值的,因此所有address都进入了filtered的变量中,之后再调用addTransportAddress的时候listedNode就祈祷过滤作用了,防止重复添加。
加入filtered中的address均是没有潜在的未建立连接的节点(也是我们想要建立连接的节点),用这些节点第一次对listedNodes进行了赋值。调用到了关键的nodeSampler的sample方法。这里插播一下nodesSampler实例。在构造函数中:
if (componentSettings.getAsBoolean("sniff", false)) {
this.nodesSampler = new SniffNodesSampler();
} else {
this.nodesSampler = new SimpleNodeSampler();
}
nodeSampler是根据client.transport,sniff属性的设置来生成具体的策略,默认false。设置为true意味着处理要连接的节点(listedNode)之外,还会主动去发现集群中的其他存活的节点,也就是SniffNodesSampler的doSample方法。这个sniff操作的频率通过nodes_sampler_interval这个选项来控制,默认5s。
主要对两类节点进行ping操作,一类是listedNode,一类是上一次ping之前确认已经建立连接关系的nodes。
if (!transportService.nodeConnected(listedNode)) {
try {
// if its one of the actual nodes we will talk to, not to listed nodes, fully connect
if (nodes.contains(listedNode)) {
logger.trace("connecting to cluster node [{}]", listedNode);
transportService.connectToNode(listedNode);
} else {
// its a listed node, light connect to it...
logger.trace("connecting to listed node (light) [{}]", listedNode);
transportService.connectToNodeLight(listedNode);
}
} catch (Exception e) {
logger.debug("failed to connect to node [{}], ignoring...", e, listedNode);
latch.countDown();
return;
}
}
通过对当前listednode的判断,如果没有建立过连接,就进入了以上的逻辑,进行fully connect还是light connect呢?判断标准在于这个listednode是否是我们要将要实际通讯的node,如果是,就进行fully connect,否则是light connect。fully connect会建立各个专项的channel(bulk,recovery等),而light connect只是为了ping。
之后会通过transport模块发送向listednode发送一个request请求,来获取节点状态。根据返回的节点状态,clusterName不匹配的节点放入filteredNode,而其余节点在通过validateNewNodes来验证,最终赋值给nodes。
protected ImmutableList<DiscoveryNode> validateNewNodes(Set<DiscoveryNode> nodes) {
for (Iterator<DiscoveryNode> it = nodes.iterator(); it.hasNext(); ) {
DiscoveryNode node = it.next();
if (!transportService.nodeConnected(node)) {
try {
logger.trace("connecting to node [{}]", node);
transportService.connectToNode(node);
} catch (Throwable e) {
it.remove();
logger.debug("failed to connect to discovered node [" + node + "]", e);
}
}
}
return new ImmutableList.Builder<DiscoveryNode>().addAll(nodes).build();
}
在validataNewNode中建立了full connect,证明这是我们将要进行实际通信的节点。