本文通过几个实际的例子,介绍如何解决存储系统中的数据错误。为了方便说明,假设在这个存储系统中有子系统A->B->C,依次为上层、中层和底层模块。该系统是分布式的,每个节点都由A->B->C这样的层次结构组成。应用(用户)数据按照一定的规则分布在各个节点上。其中子系统C是新引入的,且经过了充分的单元测试。由于没有经过集成测试,仍然是出问题概率最大的部分。
Case1。在文件1.txt中写入999,打一个快照,写入888再打一个快照,写入777再打一个快照,最后写入666。如果将卷回滚到快照sp-888,1.txt中的内容应该是888。
如果实际测试的结果不是888,应该以怎样的思路来解决这个问题呢?虽然经过层层转化,例如,文件->虚拟设备->卷等,由于输入的参数(文件位置、卷、等等)是固定的,所以最后在子系统C中写入或者读出的位置一定是一样的。那么只需要在C中打印读写IO日志,包括上述各个关键的参数,以及针对性的打印一下用户数据buffer的前三个字符即可。由于测试用例比较简单,读写的999,888关键字都会在日志中有所体现。然后再对比分析一下日志,很容易就可以看出问题的大概原因。
Case2。上面的用例通过以后,继续增加一下用例的难度,来简单测试一下数据库。将Mysql的数据目录放在该存储系统提供的盘中。还是按照Case1的思路,在数据库中插入数据999,打一个快照,插入888再打一个快照,插入777再打一个快照,最后插入666。如果将卷回滚到sp-888,数据库中的数据应该是888。
如果实际测试的结果是,回滚以后数据库不能正常启动。这个问题看起来就比Case1复杂一些了,那么应该以怎样的思路来解决这个问题呢?首先将目标定在数据库的数据目录可能坏掉了。数据库的目录中有那么多文件,到底是哪一个文件的什么位置坏掉了呢?所以解决这问题的关键是要找到坏掉的文件及其位置,这点和Case1是一致的。
具体的做法如下,在插入888之后,将数据库服务反复停止和开启是没有问题的(读的是操作系统缓存,所以数据是好的)。所以在这一步将数据库目录data备份到本地目录data0。然后继续执行后续操作,插入777打快照,插入666,然后回滚到sp-888。这时候数据库服务不能启动,将数据库目录备份到本地data1。对比data0和data1目录的二进制,找到其中一个错误的文件以及错误的位置。
那怎么确定这个错误的文件在存储系统的中的偏移位置呢?利用工具md5sum对这个错误文件算一下md5,促使针对这个文件的读IO下发到存储系统中。这个地方要注意操作系统缓存,如果没有IO下发,可以umount再mount文件系统。如此一来,错误的位置找到了,之后的工作就比较容易了,只需要按照固定单位(如4KB)打印一下crc32即可。然后对照日志慢慢分析,即可解决问题。
Case3。测试用例和Case1一样。
问题是,回滚到sp-888,重新mount卷,偶尔提示找不到分区。这是一个偶现的问题,又应该以怎么样的思路来解决?卷都不能挂载出来,没法对比文件,又如何知道问题出在什么地方呢?子系统A的某同事说,这个问题之前他遇到过,可能是他们的问题,遂拿着问题去分析了。后来分析了一阵子,结合大家的分析情况,大概结论是1)问题可能出在测试脚本上。2)性能较好的机器不会出这个问题。3)分区表是系统在管理,怎么分析呢?
其实这个结论对于解决问题没多大帮助意义。仔细思考以后,我认为还是数据错了。且是分区表数据错误了,那么还是得找到数据错误的位置。那得找一个分区表工具来看看,分区表在什么位置呢?实际这个工具不难找,fdisk -l列一下分区,这个工具就一定会读分区表的。通过对比日志,发现分区表实际就是一个磁盘的第一个4K(簇)(这个小知识本应该知道的,只是久了忘记了)。每执行一次fdisk -l,就会读一次分区表位置。那么位置找到了,分析问题也就不难了。详细分析细节就不再深入了......
总结来看:
1)找到数据错误的位置。
2)不要盲目解决问题,要从合理的方向入手直接面对问题。
3)以上问题还不算太棘手,假如在海量数据读写场景下,问题又是小概率偶现的情况下,解决问题的挑战就更大了。
作者:张洋
原文:企业存储技术
推荐阅读
欢迎关注企业存储技术极术专栏