1.概述
转载:[7]elasticsearch源码深入分析——client通信流程与负载均衡
2.如何与集群(Cluster)通信
2.1 使用Node与cluster通信
Node node = NodeBuilder.nodeBuilder()
.clusterName("yourclustername")
.client(true)
.node();
Client client = node.client();
因为该节点是仅仅是作为一个客户端而不用保存数据,所以必须设置client(true)
。
2.2 使用TransportClient与cluster通信
在5.0.0
版本之前可以通过如下的代码来构建TransportClient,
通过指定名称来创建
Settings settings = Settings.settingsBuilder()
.put("cluster.name", "esclient")
.put("client.transport.sniff", true)
.build();
TransportClient transportClient = TransportClient.builder()
.settings(settings).build();
通过IP地址来创建
TransportClient transportClient = TransportClient.builder()
.build()
.addTransportAddress(newInetSocketTransportAddress("192.168.1.100", 9300));
通过同网段嗅探来创建
Settings settings = Settings.settingsBuilder()
.put("client.transport.sniff", true)
.build();
TransportClient transportClient = TransportClient.builder()
.settings(settings).builder();
如果设置client.transport.sniff
为true
,表示客户端去嗅探整个cluster
的状态,把集群中其它机器的ip地址加到客户端中,这样做的好处是一般你不用手动设置集群里所有集群的ip到连接客户端,它会自动帮你添加,并且自动发现新加入集群的机器。
但在5.0.0
版本之后新增了PreBuiltTransportClient
类,而TransportClient
变为Abstract
类型,且被PreBuiltTransportClient
继承。该类的主要意图是,指定在创建TransportClient
时必须加载以下的插件:
Netty4Plugin
ReindexPlugin
PercolatorPlugin
MustachePlugin
ParentJoinPlugin
可见这些插件是节点的必备插件。所以5.0.0版本以后的TransportClient的创建方式变为:
PreBuiltTransportClient(Settings settings, Class<? extends Plugin>... plugins);
注:集群名称和嗅探模式可以在Settings
中设置,而TransportAddress依旧可以通过addTransportAddress
方法来设置。
TransportClient中TransportClient加载插件的代码
可以看到用到了代理,TransportClient的部分API都是TransportClientNodesService进行代理的
初始化TransportClientNodesService
2.3 client单次请求流程
客户端请求的详细流程如下:
先是实例化部分:
PreBuiltTransportClient(Settings, plugins, hostFailureListenter)
实例化super
实例化TransportClient
TransportClient
执行buildTemplate
方法buildTemplate
方法中实例化TransportClientNodesService
类的对象nodesService
然后是请求部分:
-
请求从
AbstractClient
的不同请求方法中进入(如bulk,clearScroll,delete,explain,fieldCaps,get,index,multiGet,mutiSearch,multiTermVectors,search,searchScroll,termVectors,update
) -
执行
AbstractClient
的execute(action, Request,listener)
-
执行
TransportClient
的doExecute
方法,执行TransportClient
中proxy
的execute -
执行
TransportProxyClient
的execute
方法 -
执行
TransportClientNodesService
实例nodesService
的execute
方法 -
调用
NodeListenerCallback
回调方法doWithNode
-
执行
TransportActionNodeProxy
的execute
方法 -
执行
TransportService
的sendRequest
方法 -
TransportService
调用sendRequest
后的回调依次回传TransportActionNodeProxy
NodeListenerCallback
TransportClientNodesService
TransportClient
整个客户端模块的简要流程如下:
-
client
提供了客户端的操作接口,比如count() -
代理端TransportClientNodesService的execute()随机一个节点出来
-
代理端TransportClientNodesService通过transportService发送请求
2.4 Client的负载均衡
Client
的负载均衡是通过TransportClientNodesService
类实现的。TransportClientNodesService
实例维护一组DiscoveryNode
引用,每次客户端请求的时候,会根据负载均衡算法选中一个节点(DiscoveryNode
),发送请求。常用的负载算法有Random,Round robin,Hash,StaticWeighted
等。ES的客户端负载使用了Round robin
算法。
此外TransportClientNodesService还负责嗅探,维护集群节点列表,选举节点的工作。
TransportClientNodesService的实例化首先注入了集群名称,线程池,最小兼容版本,客户端传输采样时间间隔,ping超时时间
。然后配置了节点采样模型NodeSampler
。NodeSampler接口很简单,只有一个sample()
方法,它的实现类有2个SniffNodesSampler和SimpleNodeSampler
,我们在初始化里已经看到了,如果"sniff"配置项是true的话使用SniffNodesSampler类。
两个实现类如下
- 嗅探同一集群中的所有节点(SniffNodesSampler,client会主动发现集群里的其他节点,即使节点不在配置文件中,会创建fully connect)
- 或者是只关注配置文件配置的节点(SimpleNodeSampler,ping listedNodes(也就是配置中设置的节点)中的所有node,区别在于这里创建的都是light connect)
简单的说,SimpleNodeSampler会限制当前可用client一定是在配置中设置的节点中的,这样的意图是让集群中的某些节点专门用来负责接收用户请求
,而SniffNodesSampler会使用所有发现的节点,让其参与负载,即使这个节点不在配置中。
得到集群节点列表后,代理端TransportClientNodesService在每次execute时,就可以通过getNodeNumber方法随机获取节点。
如下图:
execute方法调用getNodeNumber
关键的节点选择代码
2.5 数据写入
节点是如何写入的呢? 在TransportClient的buildTemplate方法中,实例化TransportService
的步骤中,通过networkModule的getRransportInterceptor
方法得到的TransportInterceptor
实例就是通过nettychannel
写入数据的地方,如下图:
实例化TransportClient
实例化TransportService
数据还是通过上文的NodeSampler实例来写入的,FutureTransportResponseHandler设置回调操作,如下图:
NodeSampler
遍历所有的数据节点,写入到新节点里面
数据写入