作者:子葵

背景

ZooKeeper 作为分布式体系的元数据中心,对外服务的数据一致性需求得到很好的确保,可是一些老版别的 ZooKeeper 在一些情况下或许无法确保数据的一致性,导致依靠 ZooKeeper 的体系出现异常。

某用户运用 3.4.6 版别 ZooKeeper 做使命调度,ZooKeeper 实例的 tps 和 qps 都比较高,事务日志发生的速率很快,即便此用户配置了主动整理的参数,可是主动整理的最小距离仍是赶不上数据发生的速度,导致磁盘爆满。

ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题

在此用户整理了旧日志之后,重启节点,部分事务机器就报出 NodeExist,NoNode 的异常,而且报错只集中在部分机器,此次异常导致用户使命调度体系出现使命重复调度以及使命丢掉问题,发生重大损失。

原因剖析

仔细检查了这些客户端发现这些客户端都衔接在同一台 ZooKeeper 节点上,经过 zkCli 手动排查节点上的数据,比照其他未整理磁盘的 ZooKeeper 节点,整理了磁盘的 ZooKeeper 节点中的数据和其他节点具有差异,此时确定此节点因为一些原因出现了数据不一致问题,导致衔接到此节点的客户端读到了脏数据。

ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题

可是排查日志,没有发现异常日志。因为此节点之前整理过日志,而且重启过,磁盘上的数据被从头加载过,因而怀疑是 ZooKeeper 在发动加载数据的过程中出现了一些异常情况。经过剖析 ZooKeeper 发动中加载数据的代码,继续排查详细原因。

public long restore(DataTree dt, Map<Long, Integer> sessions,
            PlayBackListener listener) throws IOException {
        snapLog.deserialize(dt, sessions);
        FileTxnLog txnLog = new FileTxnLog(dataDir);
        TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1);
        long highestZxid = dt.lastProcessedZxid;
        TxnHeader hdr;
        try {
            while (true) {
        ...
                try {
                    processTransaction(hdr,dt,sessions, itr.getTxn());
                } catch(KeeperException.NoNodeException e) {
                   throw new IOException("Failed to process transaction type: " +
                         hdr.getType() + " error: " + e.getMessage(), e);
                ...
        return highestZxid;
    }

此处是 ZooKeeper 加载磁盘数据的代码,此办法的主要作用是,首先将磁盘中的 snapshot 文件加载进内存,初始化 ZooKeeper 内存中的数据结构,之后将加载事务日志运用日志中对数据的修正,终究还原磁盘中数据的状况。

ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题

可是在 3.4.6 版别的代码中snapLog.deserialize(dt, sessions);这行加载 snapshot 文件的代码有一个返回值,此处没有进行返回值校验,导致在 ZooKeeper 自身找不到有效的 snapshot 文件的情况下仍是会继续加载事务日志,从而导致 ZooKeeper 在空数据的状况下直接运用事务日志,终究导致此节点的数据和其他节点的数据不一致。

ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题

此问题已经在 ZooKeeper 社区有对应的issue,在加载 snapshot 的文件列表为空的情况下,此问题已经得到了修正,可是因为磁盘爆满导致的 snapshot 文件不完整的其他的一些特殊情况下,此问题仍然存在。处理此问题还需求从磁盘运用的视点处理。

issue:

issues.apache.org/jira/browse…

处理方案

为了防止 ZooKeeper 节点的磁盘被快速打满,可以增加磁盘的容量,配合 ZooKeeper 自身的整理机制,可以在一定范围内的 tps 下防止磁盘被写满的情形,可是增大磁盘容量会带来显著的运用成本的进步,而且即便磁盘容量进步了,也或许因为 ZooKeeper 自身整理机制不及时整理,导致磁盘被打满,终究需求经过人工的方式进行磁盘整理,运维起来很杂乱,消耗人力物力,而且集群稳定性得不到显著提升。

ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题

MSE ZooKeeper 供给 ZooKeeper 实例的全托管,MSE ZooKeeper 实例的磁盘运用对用户彻底透明,用户无需担心磁盘爆满问题,以及磁盘运用过程中的杂乱运维。MSE ZooKeeper 经过守时整理,触发运用阈值整理等手段确保 ZooKeeper 实例在运用过程中磁盘始终处于安全水位,防止因为磁盘问题导致的数据不一致,实例不可用等问题。

ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题

MSE ZooKeeper 默认集成 Promethus 监控,供给丰富的目标信息,而且针对写多的场景,MSE ZooKeeper 供给 TopN 大盘,可以快速看到事务热点数据,以及高 tps 的客户端情况,可以经过这些统计数据快速定位事务运用过程中的问题。

ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题