ZooKeeper 避坑实践:由于jute.maxbuffer 设置问题导致的集群不可用

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 微服务引擎 MSE 面向业界主流开源微服务项目, 提供注册配置中心和分布式协调(原生支持 Nacos/ZooKeeper/Eureka )、云原生网关(原生支持Higress/Nginx/Envoy,遵循Ingress标准)、微服务治理(原生支持 Spring Cloud/Dubbo/Sentinel,遵循 OpenSergo 服务治理规范)能力。

背景

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

image.png

image.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.
  • Whenjute.maxbufferin the client side is greater than the server side, the client wants to write the data exceedsjute.maxbufferin the server side, the server side will getjava.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,可以通过检索关键字快速确认。

image.png

  1. 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 选主时间告警以及节点不可用告警

首先进入告警管理页面,创建新的告警

image.png



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

image.png


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

image.png


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


运营活动

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

image.png

image.png

相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
1月前
|
分布式计算 Java Hadoop
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
61 1
|
1月前
|
分布式计算 监控 Hadoop
Hadoop-29 ZooKeeper集群 Watcher机制 工作原理 与 ZK基本命令 测试集群效果 3台公网云服务器
Hadoop-29 ZooKeeper集群 Watcher机制 工作原理 与 ZK基本命令 测试集群效果 3台公网云服务器
37 1
|
1月前
|
分布式计算 Hadoop Unix
Hadoop-28 ZooKeeper集群 ZNode简介概念和测试 数据结构与监听机制 持久性节点 持久顺序节点 事务ID Watcher机制
Hadoop-28 ZooKeeper集群 ZNode简介概念和测试 数据结构与监听机制 持久性节点 持久顺序节点 事务ID Watcher机制
41 1
|
1月前
|
分布式计算 Hadoop
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
45 1
|
1月前
|
存储 SQL 消息中间件
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
46 0
|
4月前
|
存储 数据库
zookeeper 集群环境搭建及集群选举及数据同步机制
zookeeper 集群环境搭建及集群选举及数据同步机制
72 2
|
3月前
|
开发工具
部署安装zookeeper集群
部署安装zookeeper集群
|
5月前
|
Java 网络安全
分布式系统详解--框架(Zookeeper-简介和集群搭建)
分布式系统详解--框架(Zookeeper-简介和集群搭建)
128 0
|
5月前
|
存储 监控 负载均衡
Zookeeper 详解:分布式协调服务的核心概念与实践
Zookeeper 详解:分布式协调服务的核心概念与实践
243 0
|
6月前
|
存储 大数据 Apache
深入理解ZooKeeper:分布式协调服务的核心与实践
【5月更文挑战第7天】ZooKeeper是Apache的分布式协调服务,确保大规模分布式系统中的数据一致性与高可用性。其特点包括强一致性、高可用性、可靠性、顺序性和实时性。使用ZooKeeper涉及安装配置、启动服务、客户端连接及执行操作。实际应用中,面临性能瓶颈、不可伸缩性和单点故障等问题,可通过水平扩展、集成其他服务和多集群备份来解决。理解ZooKeeper原理和实践,有助于构建高效分布式系统。

相关产品

  • 微服务引擎