共计 5295 个字符,预计需要花费 14 分钟才能阅读完成。
作者:子葵
背景
在日常运维 ZooKeeper 中,常常会遇到长时间无奈选主,复原时过程启动又退出,进而导致内存暴涨,CPU 飙升,GC 频繁,影响业务可用性,这些问题有可能和 jute.maxbuffer 的设置无关。本篇文章就深刻 ZooKeeper 源码,一起探索一下 ZooKeeper 的 jute.maxbuffer 参数的最佳实际。
剖析
首先咱们通过 ZooKeeper 的官网上看到 jute.maxbuffer 的形容:
jute.maxbuffer :
(Java system property:jute.maxbuffer).
……, It specifies the maximum size of the data that can be stored in a znode. The unit is: byte. The default is 0xfffff(1048575) bytes, or just under 1M.
When jute.maxbuffer in the client side is greater than the server side, the client wants to write the data exceeds jute.maxbuffer in the server side, the server side will get java.io.IOException: Len error
When jute.maxbuffer in the client side is less than the server side, the client wants to read the data exceeds jute.maxbuffer in the client side, the client side will get java.io.IOException: Unreasonable length or Packet len is out of range!
从官网的形容中咱们能够晓得,jute.maxbuffer 可能限度 Znode 大小,须要在 Server 端和 Client 端正当设置,否则有可能引起异样。
但事实并非如此,咱们在 ZooKeeper 的代码中寻找 jute.maxbuffer 的定义和援用:
public static final int maxBuffer = Integer.getInteger("jute.maxbuffer", 0xfffff);
在 org.apache.jute.BinaryInputArchive 类型中通过 System Properties 读取到 jute.maxbuffer 的值,能够看到默认值是 1M,checkLength 办法援用了此动态值:
// Since this is a rough sanity check, add some padding to maxBuffer to
// make up for extra fields, etc. (otherwise e.g. clients may be able to
// write buffers larger than we can read from disk!)
private void checkLength(int len) throws IOException {if (len < 0 || len > maxBufferSize + extraMaxBufferSize) {throw new IOException(UNREASONBLE_LENGTH + len);
}
}
只有参数 len 超过 maxBufferSize 和 extraMaxBufferSize 的和,就会抛出 Unreasonable length 的异样,在生产环境中这个异样往往会导致非预期的选主或者 Server 无奈启动。
再看一下 extraMaxBufferSize 的赋值:
static {
final Integer configuredExtraMaxBuffer =
Integer.getInteger("zookeeper.jute.maxbuffer.extrasize", maxBuffer);
if (configuredExtraMaxBuffer < 1024) {extraMaxBuffer = 1024;} else {extraMaxBuffer = configuredExtraMaxBuffer;}
}
能够看到 extraMaxBufferSize 默认会应用 maxBuffer 的值,并且最小值为 1024(这里是为了和以前的版本兼容),因而在默认的状况下,checkLength 办法抛出异样的阈值是 1M + 1K。
接着咱们看一下 checkLength 办法的援用链:
有两个中央援用到了 checkLength 办法:即 org.apache.jute.BinaryInputArchive 类型的 readString 和 readBuffer 办法。
public String readString(String tag) throws IOException {
......
checkLength(len);
......
}
public byte[] readBuffer(String tag) throws IOException {
......
checkLength(len);
......
}
而这两个办法在简直所有的 org.apache.jute.Recod 类型中都有援用,也就是说 ZooKeeper 中简直所有的序列化对象在反序列化的时候都会进行 checkLength 查看,因而能够得出结论 jute.maxbuffer 不仅仅限度 Znode 的大小,而是所有调用 readString 和 readBuffer 的 Record 的大小。
这其中就蕴含 org.apache.zookeeper.server.quorum.QuorumPacket 类型。
此类型是在 Server 进行 Proposal 时传输数据应用的序列化类型,蕴含写申请产生的 Txn 在 Server 之间进行同步时传递数据都是通过此类型进行序列化的,如果事务太大就会导致 checkLength 失败抛出异样,如果是一般的写申请,因为在申请收到的时候就会 checkLength,因而在预处理申请的时候就能够防止产生过大的 QuorumPacket,然而如果是 CloseSession 申请,在这种状况下就可能出现异常。
咱们能够通过 PreRequestProcessor 的 processRequest 办法看到生成 CloseSessionTxn 的过程:
protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize) throws KeeperException, IOException, RequestProcessorException {
......
case OpCode.closeSession:
long startTime = Time.currentElapsedTime();
synchronized (zks.outstandingChanges) {Set<String> es = zks.getZKDatabase().getEphemerals(request.sessionId);
for (ChangeRecord c : zks.outstandingChanges) {if (c.stat == null) {
// Doing a delete
es.remove(c.path);
} else if (c.stat.getEphemeralOwner() == request.sessionId) {es.add(c.path);
}
}
if (ZooKeeperServer.isCloseSessionTxnEnabled()) {request.setTxn(new CloseSessionTxn(new ArrayList<String>(es)));
}
......
}
CloseSession 申请很小个别都能够通过 checkLength 的查看,然而 CloseSession 产生的事务却有可能很大,能够通过 org.apache.zookeeper.txn.CloseSessionTxn 类型的定义可知此 Txn 中蕴含所有此 Session 创立的 ephemeral 类型的 Znode,因而,如果一个 Session 创立了很多 ephemeral 类型的 Znode,当此 Session 有一个 CloseSession 的申请通过 Server 解决的时候,Leader 向 Follower 进行 proposal 的时候就会呈现一个特地大的 QuorumPacket,导致在反序列化的时候进行 checkLength 查看的时候会抛出异样,能够通过 Follower 的 followLeader 办法看到,在出现异常的时候,Follower 会断开和 Leader 的连贯:
void followLeader() throws InterruptedException {
......
......
......
// create a reusable packet to reduce gc impact
QuorumPacket qp = new QuorumPacket();
while (this.isRunning()) {readPacket(qp);
processPacket(qp);
}
} catch (Exception e) {LOG.warn("Exception when following the leader", e);
closeSocket();
// clear pending revalidations
pendingRevalidations.clear();}
} finally {
......
......
}
当超过半数的 follower 都因为 QuorumPacket 过大而无奈反序列化的时候就会导致集群从新选主,并且如果本来的 Leader 在选举中获胜,那么这个 Leader 就会在从磁盘中 load 数据的时候,从磁盘中读取事物日志的时候,读取到刚刚写入的特地大的 CloseSessionTxn 的时候 checkLength 失败,导致 Leader 状态又从新进入 LOOKING 状态,集群又开始从新选主,并且始终继续此过程,导致集群始终处于选主状态
起因
集群非预期选主,继续选主或者 server 无奈启动,通过以上剖析有可能就是在 jute.maxbuffer 设置不合理,连贯到集群的某一个 client 创立了特地多的 ephemeral 类型节点,并且当这个 session 收回 closesession 申请的时候,导致 follower 和 Leader 断连。最终导致集群选主失败或者集群无奈失常启动。
最佳实际倡议
首先如何发现集群是因为 jute.maxbuffer 设置不合理导致的集群无奈失常选主或者无奈失常启动?
1. 在 checkLength 办法查看失败的时候会抛出异样,关键字是 Unreasonable length,同时 follower 会断开和 Leader 的连贯,关键字是 Exception when following the leader,能够通过检索关键字疾速确认。
2. ZooKeeper 在新版本中提供了 last_proposal_size metrics 指标,能够通过此指标监控集群的 proposal 大小数据,当有 proposal 大于 jute.maxbuffer 的值的时候就须要排查问题。
jute.maxbuffer 如何正确设置?
1. 官网文档首先倡议咱们在客户端和服务端正确的设置 jute.maxbuffer,最好保持一致,防止非预期的 checkLength 查看失败。
2. 官网文档倡议 jute.maxbuffer 的值不宜过大,大的 Znode 可能会导致 Server 之间同步数据超时,在大数据申请达到 Server 的时候就被拦挡掉。
3. 在理论的生产环境中,为了保障生产环境的稳固,如果 jute.maxbuffer 的值设置过小,服务端有可能继续不可用,须要须要更改 jute.maxbuffer 的值能力失常启动,因而这个值也不能太小。
4. Dubbo 低版本存在反复注册问题,当反复注册达到肯定的量级,就有可能触发这个阈值(1M),Dubbo 单节点注册的 Path 长度依照 670 字节计算,默认阈值最多包容 1565 次反复注册,因而在业务侧须要躲避反复注册的问题。综上,在应用的 ZooKeeper 的过程中,jute.maxbuffer 的设置还须要思考到单个 session 创立过多的 ephemeral 节点这一种状况,合理配置 jute.maxbuffer 的值。
在 MSE ZooKeeper 中,能够通过控制台快捷批改 jute.maxbuffer 参数:
设置 MSE ZooKeeper 选主工夫告警以及节点不可用告警。首先进入告警治理页面,创立新的告警:
分组抉择 ZooKeeper 专业版,告警项抉择选主工夫,而后设置阈值:
POD 状态告警,分组抉择 ZooKeeper 专业版,告警项抉择 ZooKeeper 单 POD 状态:
配置节点不可用和选主工夫告警,及时发现问题进行排查。
经营流动
重磅推出 MSE 专业版,更具性价比,可间接从根底版一键平滑降级到专业版!