作者:子葵

背景

在日常运维 ZooKeeper 中,经常会遇到长时间无法选主,恢复时进程发动又退出,从而导致内存暴涨,CPU飙升,GC频繁,影响业务可用性,这些问题有或许和 jute.maxbuffer 的设置有关。本篇文章就深化 ZooKeeper 源码,一起探求一下ZooKeeper 的 jute.maxbuffer 参数的最佳实践。

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

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,能够经过检索关键字快速确认。

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

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 参数:

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

设置 MSE ZooKeeper 选主时间告警以及节点不可用告警。首要进入告警管理页面,创立新的告警:

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

分组挑选 ZooKeeper 专业版,告警项挑选选主时间,然后设置阈值:

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

POD 状况告警,分组挑选 ZooKeeper 专业版,告警项挑选 ZooKeeper 单 POD状况:

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

装备节点不可用和选主时间告警,及时发现问题进行排查。

运营活动

重磅推出 MSE 专业版,更具性价比,可直接从基础版一键滑润升级到专业版!

ZooKeeper 避坑实践:如何调优 jute.maxbuffer

ZooKeeper 避坑实践:如何调优 jute.maxbuffer