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

Posted 阿里系统软件技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题相关的知识,希望对你有一定的参考价值。

ZooKeeper 作为分布式系统的元数据中心,对外服务的数据一致性需要得到很好的保证,但是一些老版本的 ZooKeeper 在一些情况下可能无法保证数据的一致性,导致依赖 ZooKeeper 的系统出现异常。

作者:子葵

背景

ZooKeeper 作为分布式系统的元数据中心,对外服务的数据一致性需要得到很好的保证,但是一些老版本的 ZooKeeper 在一些情况下可能无法保证数据的一致性,导致依赖 ZooKeeper 的系统出现异常。

某用户使用 3.4.6 版本 ZooKeeper 做任务调度,ZooKeeper 实例的 tps 和 qps 都比较高,事务日志产生的速率很快,即使此用户配置了自动清理的参数,但是自动清理的最小间隔还是赶不上数据产生的速度,导致磁盘爆满。

在此用户清理了旧日志之后,重启节点,部分业务机器就报出 NodeExist,NoNode 的异常,并且报错只集中在部分机器,此次异常导致用户任务调度系统出现任务重复调度以及任务丢失问题,产生重大损失。

原因分析

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

但是排查日志,没有发现异常日志。由于此节点之前清理过日志,并且重启过,磁盘上的数据被重新加载过,因此怀疑是 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 内存中的数据结构,之后将加载事务日志应用日志中对数据的修改,最终还原磁盘中数据的状态。

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

此问题已经在 ZooKeeper 社区有对应的 issue,在加载 snapshot 的文件列表为空的情况下,此问题已经得到了修复,但是由于磁盘爆满导致的 snapshot 文件不完整的其他的一些特殊情况下,此问题依然存在。解决此问题还需要从磁盘使用的角度解决。

issue:

https://issues.apache.org/jira/browse/ZOOKEEPER-2325

解决方案

为了避免 ZooKeeper 节点的磁盘被快速打满,可以增加磁盘的容量,配合 ZooKeeper 本身的清理机制,可以在一定范围内的 tps 下避免磁盘被写满的情景,但是增大磁盘容量会带来显著的使用成本的提高,并且即使磁盘容量提高了,也可能因为 ZooKeeper 本身清理机制不及时清理,导致磁盘被打满,最终需要通过人工的方式进行磁盘清理,运维起来很复杂,耗费人力物力,并且集群稳定性得不到显著提升。

MSE ZooKeeper 提供 ZooKeeper 实例的全托管,MSE ZooKeeper 实例的磁盘使用对用户完全透明,用户无需担心磁盘爆满问题,以及磁盘使用过程中的复杂运维。MSE ZooKeeper 通过定时清理,触发使用阈值清理等手段保证 ZooKeeper 实例在使用过程中磁盘始终处于安全水位,避免由于磁盘问题导致的数据不一致,实例不可用等问题。

MSE ZooKeeper 默认集成 Promethus 监控,提供丰富的指标信息,并且针对写多的场景,MSE ZooKeeper 提供 TopN 大盘,能够快速看到业务热点数据,以及高 tps 的客户端情况,能够通过这些统计数据快速定位业务使用过程中的问题。

zookeeper集群 新手安装指南

Zookeeper集群的角色:  Leader 和  follower  Observer
zk集群最好配成奇数个节点
只要集群中有半数以上节点存活,集群就能提供服务
本事例采用版本:zookeeper-3.4.5 虚拟机:zk1 zk2 zk3
/********************************************************************第一步上传解压*****************************************************************/
1/ 安装到3台虚拟机上
2/ 安装好JDK
3/ 上传安装包。上传用工具比如:xshell
4/ 解压安装文件到指定目录 /usr/local/zookeeper
tar -zxvf zookeeper-3.4.5.tar.gz(解压)
5/ 重命名
mv zookeeper-3.4.5 zookeeper(重命名文件夹zookeeper-3.4.5zookeeper
/*****************************************************第二步配置环境变量***********************************************************/
1/ vi /etc/profile(修改文件)
2/ 添加内容:
export ZOOKEEPER_HOME=/home/hadoop/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
3/ 加载环境配置:
source /etc/profile
/********************************************************************第三步修改配置*************************************************************/
修改Zookeeper配置文件
 
1、用root用户操作修改配置文件名称:
cd zookeeper/conf
cp zoo_sample.cfg zoo.cfg
 
2、 修改配置文件: vi zoo.cfg
 
3、添加内容:
dataDir=/root/apps/zookeeper/zkdata
server.1=zk1:2888:3888     ## (心跳端口、选举端口)
server.2=zk2:2888:3888
server.3=zk3:2888:3888
 /********************************************************************第四步创建日志文件*************************************************************/
4、创建文件夹:
cd /root/apps/zookeeper/zkdata
mkdir zkdata
 
5、在zkdata文件夹下新建myid文件,myid的文件内容为:
cd zkdata
echo 1 > myid
4.2.4 分发安装包到其他机器
scp -r /root/apps [email protected]:/root/
scp -r /root/apps [email protected]zk3:/root/
 
4.2.5 修改其他机器的配置文件
1、修改myid文件
zk2上:echo 2 > myid 修改myid为:2
zk3上:echo 3 > myid 修改myid为:3
 
 
4.2.6 启动(每台机器)
注:
1、事先将三台服务器的防火墙都关掉
2、全网统一hosts映射
先配好一台上的hosts
然后:
scp  /etc/hosts  zk2:/etc
scp  /etc/hosts  zk3:/etc
 
3、然后一台一台地启动
bin/zkServer.sh start
 
 
或者编写一个脚本来批量启动所有机器:
for host in "zk1 zk2 zk3"
do
ssh $host "source/etc/profile;/user/local/zookeeper/bin/zkServer.sh start"
 
4.2.7 查看集群状态
1jps(查看进程)
2zkServer.sh status(查看集群状态,主从信息)
 
 
 
 

以上是关于ZooKeeper 避坑指南: ZooKeeper 3.6.4 版本 BUG 导致的数据不一致问题的主要内容,如果未能解决你的问题,请参考以下文章

ZooKeeper集群部署指南

Zookeeper C API 指南一(转)

zookeeper集群 新手安装指南

ZooKeeper: 简介, 配置及运维指南

ZooKeeper官方文档学习笔记03-程序员指南03

Zookeeper C API 指南四(C API 概览)(转)