前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ZooKeeper数据不一致的定位过程

ZooKeeper数据不一致的定位过程

作者头像
spilledyear
发布2021-09-07 17:02:09
1.4K0
发布2021-09-07 17:02:09
举报
文章被收录于专栏:小白鼠小白鼠

现象

ZooKeeper读写过程中,重新选主,然后节点重启后,数据不一致了。例如原来有节点A,B,C。

创建临时节点znode1,节点A、B、C上均可见,此时节点B是leader。

重启A、B、C三个节点后,发现临时节点znode1在A、C上可见,但是在B上不可见,且重启ZooKeeper进程无法解决。

分析

通过分析ZooKeeper的事务log可以看出,B节点的log比A、C多了几项,这几项为CloseSession类型的事务。也就是说在B节点这里,相关的临时节点已经被删除,但是在A、C那里,相关的临时节点没有被删除。

此时A、B、C的日志看起来是这样的:

A日志:txn1,txn2,txn3,txn8,txn9,txn10

B日志:txn1,txn2,txn3,txn4,txn5,txn6,txn7,txn8,txn9,txn10

C日志:txn1,txn2,txn3,txn8,txn9,txn10

也就是说B的txn4,txn5,txn6,txn7这几条日志,A、C都没有。

说好的一致性呢!!!

继续分析,那么在B节点创建CloseSession相关事务日志的时候,发生什么了呢?从B节点的日志中,发现在B节点创建日志(假设是txn4)后,不久就发生了重新选主,原因是网络不通。

重新选主的结果,B还是leader,于是B就开始和A、C同步日志。同步的时候,会把日志的范围打印出来,我看了一下,发现A只把txn4之前的日志同步过去了。

这不科学啊!

接下来又去看源代码,发现同步日志的范围,是以内存里的最大日志编号来决定了,注意是内存,而不是硬盘里真实的最大编号。那么重新选主后,由于ZooKeeper Server关闭了,按理说新的ZooKeeper Server会重新加载日志,并且把内存里的最大编号也更新到最新的。

然而为什么没有呢?

继续看代码,原来在关闭ZooKeeper Server的时候,有一个哥们,为了提高性能(我猜测),并没有把server相关的db(对应硬盘和内存里的数据)也关闭。这样新的ZooKeeper Server在new的时候,就可以直接用这个db。也正是因为这样,db里内存部分的数据,跟硬盘里的数据,没有匹配上。我一看更新的时间,2017年2月,哥们啊,ZooKeeper源代码真的不敢乱改。

到这里我基本上已经确定这个bug是由于这个哥们的改动造成的了。然后提bug之前,我看了一眼github上ZooKeeper 3.4.12版本的代码,果不其然,已经有人fix掉这个bug了。

原来的代码是这样的:

代码语言:javascript
复制
/**
     * Shut down the server instance
     * @param fullyShutDown true if another server using the same database will not replace this one in the same process
     */
    public synchronized void shutdown(boolean fullyShutDown) {
        if (!canShutdown()) {
            LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!");
            return;
        }
        LOG.info("shutting down");

        // new RuntimeException("Calling shutdown").printStackTrace();
        setState(State.SHUTDOWN);
        // Since sessionTracker and syncThreads poll we just have to
        // set running to false and they will detect it during the poll
        // interval.
        if (sessionTracker != null) {
            sessionTracker.shutdown();
        }
        if (firstProcessor != null) {
            firstProcessor.shutdown();
        }

        if (fullyShutDown && zkDb != null) {
            zkDb.clear();
        }
        // else there is no need to clear the database
        //  * When a new quorum is established we can still apply the diff
        //    on top of the same zkDb data
        //  * If we fetch a new snapshot from leader, the zkDb will be
        //    cleared anyway before loading the snapshot

        unregisterJMX();
    }

新的代码已经改成这样了:

代码语言:javascript
复制
    /**
     * Shut down the server instance
     * @param fullyShutDown true if another server using the same database will not replace this one in the same process
     */
    public synchronized void shutdown(boolean fullyShutDown) {
        if (!canShutdown()) {
            LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!");
            return;
        }
        LOG.info("shutting down");

        // new RuntimeException("Calling shutdown").printStackTrace();
        setState(State.SHUTDOWN);
        // Since sessionTracker and syncThreads poll we just have to
        // set running to false and they will detect it during the poll
        // interval.
        if (sessionTracker != null) {
            sessionTracker.shutdown();
        }
        if (firstProcessor != null) {
            firstProcessor.shutdown();
        }

        if (zkDb != null) {
            if (fullyShutDown) {
                zkDb.clear();
            } else {
                // else there is no need to clear the database
                //  * When a new quorum is established we can still apply the diff
                //    on top of the same zkDb data
                //  * If we fetch a new snapshot from leader, the zkDb will be
                //    cleared anyway before loading the snapshot
                try {
                    //This will fast forward the database to the latest recorded transactions
                    zkDb.fastForwardDataBase();
                } catch (IOException e) {
                    LOG.error("Error updating DB", e);
                    zkDb.clear();
                }
            }
        }

        unregisterJMX();
    }

相关的bug报告

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-7-10 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 现象
  • 分析
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档