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

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
可观测可视化 Grafana 版,10个用户账号 1个月
性能测试 PTS,5000VUM额度
简介: 本篇文章就深入 ZooKeeper 源码,一起探究一下ZooKeeper 的 jute.maxbuffer 参数的最佳实践。

作者:子葵


背景


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


1.png

2.png


分析


首先我们通过 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,可以通过检索关键字快速确认。


3.png


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

4.png


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

5.png


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


6.png


POD 状态告警,分组选择 ZooKeeper 专业版,告警项选择 ZooKeeper 单 POD状态:


6.png


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


运营活动


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


7.png


8.png


相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
存储 Java 文件存储
ZooKeeper 避坑实践: SnapCount设置不合理导致磁盘爆满,服务不可用
本篇通过深入解读ZooKeeper 数据文件生成机制,以及ZooKeeper 中和数据文件生成相关的参数,探究一下 解决 ZooKeeper 磁盘问题的最佳实践。
ZooKeeper 避坑实践: SnapCount设置不合理导致磁盘爆满,服务不可用
|
9月前
|
存储 大数据 Apache
深入理解ZooKeeper:分布式协调服务的核心与实践
【5月更文挑战第7天】ZooKeeper是Apache的分布式协调服务,确保大规模分布式系统中的数据一致性与高可用性。其特点包括强一致性、高可用性、可靠性、顺序性和实时性。使用ZooKeeper涉及安装配置、启动服务、客户端连接及执行操作。实际应用中,面临性能瓶颈、不可伸缩性和单点故障等问题,可通过水平扩展、集成其他服务和多集群备份来解决。理解ZooKeeper原理和实践,有助于构建高效分布式系统。
|
8月前
|
存储 监控 负载均衡
Zookeeper 详解:分布式协调服务的核心概念与实践
Zookeeper 详解:分布式协调服务的核心概念与实践
333 0
|
监控 Dubbo Cloud Native
MSE Sync 实践:上海网鱼 ZooKeeper 集群平滑迁移上云,性能稳定性大幅提升
上海网鱼使用 MSE Sync,将迁移效率提高100%。 使用 MSE ZooKeeper 后, 可维护性,监控能力,稳定性也大幅提高。
MSE Sync 实践:上海网鱼 ZooKeeper 集群平滑迁移上云,性能稳定性大幅提升
|
数据中心 流计算
ZooKeeper 避坑实践: Zxid溢出导致选主
ZooKeeper 本身提供当前处理的最大的 Zxid,通过 stat 接口可查看到当前处理的最大的 zxid 的值,通过此值可以计算当前 zxid 距离溢出值还有多少差距。MSE 提供风险管理以及集群选主相关告警,提前预防和及时感知选主风险,避免业务损失。
ZooKeeper 避坑实践: Zxid溢出导致选主
|
运维 监控 安全
ZooKeeper 避坑指南: ZooKeeper 3.4.6 版本 BUG 导致的数据不一致问题
ZooKeeper 避坑指南: ZooKeeper 3.4.6 版本 BUG 导致的数据不一致问题
|
Cloud Native Dubbo Java
ZooKeeper 避坑实践: Zxid溢出导致集群重新选主
微服务引擎 MSE 面向业界主流开源微服务项目, 提供注册配置中心和分布式协调(原生支持 Nacos/ZooKeeper/Eureka )、云原生网关(原生支持Higress/Nginx/Envoy,遵循Ingress标准)、微服务治理(原生支持 Spring Cloud/Dubbo/Sentinel,遵循 OpenSergo 服务治理规范)能力。
ZooKeeper 避坑实践: Zxid溢出导致集群重新选主
|
存储 Java 文件存储
ZooKeeper 避坑实践:SnapCount 设置不合理导致磁盘爆满,服务不可用
本篇通过深入解读 ZooKeeper 数据文件生成机制,以及 ZooKeeper 中和数据文件生成相关的参数,探究一下 解决 ZooKeeper 磁盘问题的最佳实践。
ZooKeeper 避坑实践:SnapCount 设置不合理导致磁盘爆满,服务不可用
|
5月前
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2
|
5月前
|
负载均衡 Java 应用服务中间件
微服务分布式系统架构之zookeeper与dubbor-1
微服务分布式系统架构之zookeeper与dubbor-1

相关产品

  • 微服务引擎