{
    "componentChunkName": "component---src-templates-blog-blog-detail-tsx",
    "path": "/blog/distributed-system-test-3",
    "result": {"pageContext":{"blog":{"id":"Blogs_88","title":"分布式系统测试那些事儿 - 信心的毁灭与重建","tags":["TiDB","分布式系统测试","测试工具"],"category":{"name":"观点洞察"},"summary":"本话题系列文章整理自 PingCAP Infra Meetup 第 26 期刘奇分享的《深度探索分布式系统测试》议题现场实录。文章较长，为方便大家阅读，会分为上中下三篇，本文为下篇。","body":"> 本话题系列文章整理自 PingCAP Infra Meetup 第 26 期刘奇分享的《深度探索分布式系统测试》议题现场实录。文章较长，为方便大家阅读，会分为上中下三篇，本文为下篇。\n\n##### -接中篇-\n\nScyllaDB 有一个开源的东西，是专门用来给文件系统做 Failure Injection 的, 名字叫做 CharybdeFS。如果你想测试你的系统，就是文件系统在哪不断出问题，比如说写磁盘失败了，驱动程序分配内存失败了，文件已经存在等等，它都可以测模拟出来。\n\n**CharybdeFS: A new fault-injecting file system for software testing**\n\nSimulate the following errors:\n\n- disk IO error (EIO)\n- driver out of memory error (ENOMEM)\n- file already exists (EEXIST)\n- disk quota exceeded (EDQUOT)\n\n再来看看 Cloudera，下图是整个 Cloudera 的一个 Failure Injection 的结构。\n\n![Failure Injection](https://img1.www.pingcap.com/prod/1_b550a81caa.png)\n\n一边是 Tools，一边是它的整个的 Level 划分。比如说整个 Cluster， Cluster 上面有很多 Host，Host 上面又跑了各种 Service，整个系统主要用于测试 HDFS， HDFS 也是很努力的在做有效的测试。然后每个机器上部署一个 AgenTEST，就用来注射那些可能出现的错误。\n\n看一下它们作用有多强大。\n\n**Cloudera: Simulate the following errors:**\n\n+ Packets loss/corrupt/reorder/duplicate/delay\n+ Bandwidth limit: Limit the network bandwidth for the specified address and port.\n+ DNSFail: Apply an injection to let the DNS fail.\n+ FLOOD: Starts a DoS attack on the specified port.\n+ BLOCK: Blocks all the packets directed to 10.0.0.0/8 (used internally by EC2).\n+ SIGSTOP: Pause a given process in its current state.\n+ BurnCPU/BurnIO/FillDISK/RONLY/FIllMEM/CorruptHDFS\n+ HANG: Hang a host running a fork bomb.\n+ PANIC: Force a kernel panic.\n+ Suicide: Shut down the machine.\n\n数据包是可以丢的，可以坏的，可以 reorder 的，比如说你发一个 A，再发一个 B，它可以给你 reorder，变成先发了 B 再发了 A，然后看你应用程序有没有正确的处理这种行为。接着发完一次后面再给你重发，然后可以延迟，这个就比较简单。目前这个里面的大部分，TiKV 都有实现，还有带宽的限制，就比如说把你带宽压缩成 1M。以前我们遇到一个问题很有意思，发现有人把文件存到 Redis 里面，但 Redis 是带多个用户共享的，一个用户就能把整个 Redis 带宽给打满了，这样其他人的带宽就很卡，那这种很卡的时候 Redis 可能出现的行为是什么呢？我们并不需要一个用户真的去把它打满，只要用这种工具，瞬间就能出现我把你的带宽限制到原来的 1%，假设别人在跟你抢带宽，你的程序行为是什么？马上就能出来，也不需要配很复杂的环境。这极大的提高了测试效率，同时能测试到很多 corner case。\n\n然后 DNS fail。那 DNS fail 会有什么样的结果？有测过吗？可能都没有想过这个问题，但是在一个真正的分布式系统里面，每一点都是有可能出错的。还有 FLOOD，假设你现在被攻击了，整个系统的行为是什么样的？然后一不小心被这个 IP table 给 block 了，该怎么办。这种情况我们确实出现过。我们一上来并发，两万个连接一打出去，然后发现大部分都连不上，后来一看 IP table 自动启用了一个机制，然后把你们都 block。当然我们后面查了半个小时左右，才把问题查出来。但这种实际上应该是在最开始设计的时候就应该考虑的东西。\n\n如果你的进程被暂停了，比如说大家在云上跑在 VM 里面，整个 VM 为了升级，先把你整个暂停了，升级完之后再把你恢复的时候会怎么样？那简单来讲，就是如果假设你程序是有 GC 的，GC 现在把我们的程序卡了五秒，程序行为是正常的吗？五十秒呢？这个很有意思的就是，BurnCPU，就是再写一个程序，把 CPU 全占了，然后让你这个现在的程序只能使用一小部分的 CPU 的时候，你程序的行为是不是正常的。正常来讲，你可能说我 CPU 不是瓶颈啊，我瓶颈在 IO，当别人跟你抢 CPU，把你这个 CPU 压的很低的时候，到 CPU 是瓶颈的时候，正常你的程序的这个行为是不是正常的？还有 IO，跟你抢读的资源，跟你抢写的资源，然后 filedisk 把磁盘写满，写的空间很少。比如说对数据库而言，你创建你的 redo log 的时候，都已经满了会怎么样？然后我突然把磁盘设为只读，就你突然一个写入会出错，但是你接下来正常的读写行为是不是对的？很典型的一个例子，如果一个数据库你现在写入，磁盘满了，那外面读请求是否就能正常响应。  Fill memory，就是瞬间把这个 memory 给压缩下来，让你下次 malloc 的时候可能分布不到内存。这个就和业务比较相关了，就是破坏 HDFS 的文件。其它的就是 Hang、Panic，然后还有自杀，直接关掉机器，整个系统的行为是什么样的？\n\n现在比较痛苦的一点是大家各自为政，每一家都做一套，但是没有办法做成一个通用的东西给所有的人去用。包括我们自己也做了一套，但是确实没有办法和其他的语言之间去 share，最早提到的那个 libfu 库实际上是在 C 语言写的，那所有 C 相关的都可以去 call 那个库。\n\n**Distributed testing**\n\n+ Namazu\n\t+ ZooKeeper:\n\t\t- Found ZOOKEEPER-2212, ZOOKEEPER-2080 (race): (blog article)\n\t+ Etcd:\n\t\t- Found etcdctl bug #3517 (timing specification), fixed in #3530. The fix also resulted a hint of #3611， Reproduced flaky tests {#4006, #4039}\n\t+ YARN: Found YARN-4301 (fault tolerance)， Reproduced flaky tests{1978, 4168, 4543, 4548, 4556}\n\n然后 Namazu。大家肯定觉得 ZooKeeper 很稳定呀， Facebook 在用、阿里在用、京东在用。大家都觉得这个东西也是很稳定的，直到这个工具出现了，然后轻轻松松就找到 bug 了，所有的大家认为的这种特别稳定的系统，其实 bug 都还挺多的，这是一个毁三观的事情，就是你觉得东西都很稳定，都很 stable，其实不是的。从上面，我们能看到 Namazu 找到的 Etcd 的几个 bug，然后 YARN 的几个 bug，其实还有一些别的。\n\n**How TiKV use namazu**\n\n+ Use nmz container / non-container mode to disturb cluster.\n\t- Run container mode in CI for each commit. (1 hour)\n\t- Run non-container mode for a stable version. (1 week+)\n+ Use `extreme` policy for process inspector\n\t- Pick up some processes and execute them with SCHED_RR scheduler. others are executed with SCHED_BATCH scheduler\n+ Use [0, 30s] delay for filesystem inspector\n\n接下来说一下 TiKV 用 Namazu 的一些经验。因为我们曾经在系统上、在云上面出现过一次写入磁盘花了五十几秒才完成的情况，所以我们需要专门的工具模拟这个磁盘的抖动。有时候一次写入可能确实耗时比较久，那这种时候是不是 OK 的。大家如果能把这种东西统统用上，我觉得还能为很多开源系统找出一堆 bug。\n\n稍微介绍一下我们现在运行的基本策略，比如说我们会用 0 到 30 秒的这个 delay （就是每一次你往文件系统的交互，比如说读或者写，那么我们会给你产生随机的 0 到 30 秒的 delay ），但我们正常应该还是需要去测三十秒到几分钟的延迟的情况，是否会让整个系统崩掉了。\n\n**How TiKV simulate network transport**\n\n+ Drop/Delay messages randomly\n+ Isolate Node\n+ Partition [1, 2, 3, 4, 5] -> [1, 2, 3]  +  [4, 5]\n+ Out of order messages\n+ Filter messages\n+ Duplicate and send redundant messages\n\n怎么模拟网络呢？假设你有网络，里面有五台机器，那我现在想做一个脑裂怎么做？不能靠拔网线对吧？比如在 TiKV 的测试框架中，我们就可以直接通过 API 把 5 个节点脑裂成两部分，让 1, 2, 3 号节点互相联通，4, 5 号节点也能联通，这两个分区彼此是隔离的，非常的方便。其实原理很简单，这种情况是用程序自己去模拟，假如是你发的包，自动给你丢掉，或者直接告诉你 unreachable，那这个时候你就知道这个网络就脑裂了，然后你怎么做？就是只允许特定类型的消息进来，把其他的都丢掉，这样一来你可以保证有些 bug 是必然重现的。这个框架给了我们极大的信心用来模拟并重现各种 corner case，确保这些 corner case 在单元测试中每次都能被覆盖到。\n\n**How to test Rocksdb**\n\n+ Treat storage as a black box.\n+ Three steps(7*24):\n\t- Fill data, Random kill -9\n\t- Restart\n\t- Consistent check.\n+ Results:\n\t- Found 2 bugs. Both fixed\n\n然后说说我们怎么测 RocksDB。 RocksDB 在大家印象中是很稳定的，但我们最近发现了两个 bug。测的方法是这样的：我们往 RocksDB 里面填数据，然后随机的一段时间去把它 kill 掉，kill 掉之后我们重启，重新启动之后去检测我们刚才 fail 的 data 是不是一致的，然后我们发现两个可能造成数据丢失的 bug，但是官方的响应速度非常快，几天就都 fix 了。可是大家普遍运行的是这么 stable 的系统，为什么还会这么容易找到 bug？就说这个测试，如果是一直有这个测试的 cover，那么这两个 bug 可能很快就能够被发现。\n\n这是我们一个基本的，也就是当成一个纯黑盒的测。大家在测数据库的时候，基本也是当黑盒测。比如说 MySQL 写入数据，kill 掉，比如说我 commit 一个事务，数据库告诉我们 commit 成功，我把数据库 kill 掉，我再去查我刚才提交的数据一样能查到。这是一个正常的行为，如果查不到，说明整个系统有问题。\n\n**More tools**\n\n+ american fuzzy lop\n\n![american fuzzy lop](https://img1.www.pingcap.com/prod/2_48114d1965.png)\n\n\n其实还有一些更加先进的工具，大家平时觉得特别稳定的东西，都被摧残的不行。Nginx 、NGPD、tcpdump 、LibreOffice ，如果有用 Linux 的同学可能知道，还有 Flash、sqlite。这个东西一出来，当时大家很兴奋，说怎么一下子找了这么多 bug，为什么以前那么稳定的系统这么不堪一击，会觉得这个东西它还挺智能的。就比如说你程序里面有个 if 分支，它是这样的，假如你程序有一百条指令，它先从前面一直走，走到某条分支指令的时候，它是一直持续探索，一个分支走不下去，它会一直在这儿持续探索，再给你随机的输入，直到我探索进去了，我记下来了下次我知道我用这个输入可以进去特定的分支。那我可以再往下走，比如说你 if 分支进去之后里面还有 if ，那你传统手段可能探测不进去了但它可以，它记录一下，我这个可以进去，然后我重来，反正我继续输入这个，我再往里面走，一旦我探测到一个新的分支，我再记住，我再往里面走。所以它一出来的时候大家都说这个真厉害，一下发现这么多 bug。但最激动的不是这些人，最激动的是黑客，为什么？因为突然有很多栈溢出、堆溢出漏洞被发现了，然后就可以写一堆工具去攻击线上的这么多系统。所以很多的技术的推进在早期的时候是黑客做出来，但是他们的目的当然不一定是为了测试 bug，而是为了怎么黑一个系统进去，这是他们当时做的，所以这个工具也是非常强大、非常有意思的，大家可以拿去研究一下自己的系统。\n\n大家印象里面各种文件系统是很稳定的，可是当用 American fuzzy lop 来测试的时候，被惊呆了。 Btrfs 连 5 秒都没有坚持到就跪了，大家用的最多的 Ext4 是最坚挺的，也才抗了两个小时！！！\n\n![Time to first bug](https://img1.www.pingcap.com/prod/3_cac3b6b973.png)\n\n再来说说 Google，Google 怎么做测试对外讲的不多，最近 Chrome team 开源了他们的 Fuzz 测试工具 OSS-Fuzz，这个工具强大的地方在于自动化做的极好：\n\n+ 发现 bug 后自动创建 issue\n+ bug 解决后自动 verify\n\n更惊人的是 OSS-Fuzz 集群一周可以跑 ~4 trillion test cases 更多细节大家可以看这篇文章：[Announcing OSS-Fuzz: Continuous Fuzzing for Open Source Software](https://opensource.googleblog.com/2016/12/announcing-oss-fuzz-continuous-fuzzing.html)\n\n另外有些工具能让分布式系统开发人员的生活变得更美好一点。\n\n**Tracing tools may help you**\n\n+ Google Dapper\n+ Zipkin\n+ OpenTracing\n\n还有 Tracing，比如说我一个 query 过来，然后经过这么多层，经过这么多机器，然后在不同的地方，不同环节耗时多久，实际上这个在分布式系统里面，有个专门的东西做 Tracing ，就是 distribute tracing tools。它可以用一条线来表达你的请求在各个阶段耗时多长，如果有几段，那么分到几个机器，分别并行的时候好了多长时间。大体的结构是这样的：\n\n![distribute tracing tools](https://img1.www.pingcap.com/prod/4_a854e9e16b.png)\n\n\n这里是一个具体的例子：\n\n![图例](https://img1.www.pingcap.com/prod/5_bd990b46bc.png)\n\n很清晰，一看就知道了，不用去看 log，这事其实一点也不新鲜，Google 十几年前就做了一个分布式追踪的工具。然后开源社区要做一个实现叫做 Zipkin，好像是 java 还是什么写的，又出了新的叫 OpenTracing，是 Go 写的。我们现在正准备上这个系统，用来追踪 TiDB 的请求在各个阶段的响应时间。\n\n最后想说一下，大家研究系统发现 bug 多了之后，不要对系统就丧失了信心，毕竟 bug 一直在那里，只是从前没有发现，现在发现得多了，总体上新的测试方法让系统的质量比以前好了很多。好像有点超时了，先聊到这里吧，还有好多细节没法展开，下次再聊。\n\n##### -本系列完结-\n\n> 相关阅读：  \n[分布式系统测试那些事儿 - 理念](https://pingcap.com/zh/blog/distributed-system-test-1)；  \n> [分布式系统测试那些事儿 - 错误注入](https://pingcap.com/zh/blog/distributed-system-test-2)。","date":"2016-12-07","author":"刘奇","fillInMethod":"writeDirectly","customUrl":"distributed-system-test-3","file":null,"relatedBlogs":[]}}},
    "staticQueryHashes": ["1327623483","1820662718","3081853212","3430003955","3649515864","4265596160","63159454"]}