StatefulSet DNS 问题
用过 StatefulSet 的童鞋应该知道, 重启之后 Pod Name 是不变的, 但会被分配新的 Pod IP。
举个例子, 假如你部署了 3 个 ES 实例, 分别是 es-0/es-1/es-2, 其中 es-2 的 IP 是 10.2.16.15
, 这是如果 es-2 服务异常退出, 接着被 k8s 重新拉起之后, es-2 将会有新的 IP, 比如是 10.2.16.16
.
这就可能导致一个很大的问题, 就是客户端的实现能不能处理这个变化。也即是说, 如果客户端没有 dns 缓存刷新的支持, 或者对 dns 解析的处理不当, 就会造成服务的不可用.
例子: ES TransportClient
比如 ES 的 TransportClient 实现( 5.4.0 )就不能应对这种情况。
如果我们使用 org.elasticsearch.client.transport.TransportClient.addTransportAddress()
,
首先需要构造一个 InetSocketTransportAddress
, 之后会被保存在 org.elasticsearch.cluster.node.DiscoveryNode.address
中.
然而, ES 并不会重新解析新地址, ES 使用了 netty4, 连接使用的是:
io.netty.bootstrap.Bootstrap#connect(java.net.SocketAddress)
public ChannelFuture connect(SocketAddress remoteAddress) { |
上面的 doResolveAndConnect()
方法内部主要调用了 doResolveAndConnect0()
:
io.netty.bootstrap.Bootstrap#doResolveAndConnect0()
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) { |
如上, 有个判断条件, resolver.isResolved(remoteAddress)
, 其实现最后通过 java.net.InetSocketAddress#isUnresolved
来判断是否已经解析过:
public final boolean isUnresolved() { |
如上, 只是简单判断 java.net.InetSocketAddress.InetSocketAddressHolder.addr
是否为 null. 因此需要我们手工来处理 DNS 变更的问题.
简单的 Fix
如果 ES 是非嗅探模式, 需要加上定时检测、清理和重连.
比如通过 TransportClient.connectedNodes()
和 TransportClient.listedNodes()
获取 DNS IP 已变化的 node, 把它们移除并重新加入新的地址.
要注意的是在 remove 时 TransportAddress.equals()
的实现, 它最终的逻辑是在 java.net.InetSocketAddress.InetSocketAddressHolder.equals()
:
@Override |
最后
使用 StatefulSet 带来的 dns IP 变化问题, 将会是一个普遍的问题, 而不仅仅出现在 ES Client 上.
这就要求我们对依赖的库实现了解比较清楚, 同时在开发时也要考虑和处理这种异常, 而且必须进行完善的异常测试, 以保证自己程序的可用性.