DDIA:批处理和 MPP 数据库千丝万缕( 二 )

  • 数据库过载 。一个 MapReduce 任务通常会并行地跑很多个子任务 。如果所有 Mapper 和 Reducer,以批处理产生输出的速率,并发地将输出写到同一个数据库,则该数据库很容会被打爆(overwhelmed) 。此时,其查询性能大概率非常差,也因此难以对外提供正常服务,从而给系统的其他组件带来运维问题 。
  • 可能产生副作用 。通常来说,MapReduce 对外提供简单的“全有或全无(all-or-nothing)”的输出保证:如果整个任务成功,即使子任务一时失败重试,但最终的输出也会看起来像运行了一次;如果整个任务失败,则没有任何输出 。但直接从任务内部将输出写入外部服务,会产生外部可见的副作用 。在这种情况下,你就必须考虑任务的部分成功状态可能会暴露给其他系统,并要理解 Hadoop 内部重试和推测执行的复杂机制 。
  • 一个更好的方案是,在批处理任务内部生成全新的数据库,并将其以文件的形式写入分布式系统的文件夹中 。一旦任务成功执行,这些数据文件就会称为不可变的(immutable),并且可以批量加载(bulk loading)进只处理只读请求的服务中 。很多 KV 存储都支持使用 MapReduce 任务构建数据库文件,比如 Voldemort,Terrapin,ElephantDB 和 HBase bulk loading 。另外 RocksDB 支持 ingest SST 文件,也是类似的情况 。
    直接构建数据库底层文件 , 就是一个 MapReduce 应用的绝佳案例:使用 Mapper 抽取 key,然后利用该 key 进行排序 , 已经覆盖了构建索引中的大部分流程 。由于大部 KV 存储都是只读的(通过批处理任务一次写入后,即不可变) , 这些存储的底层数据结构可以设计的非常简单 。例如,不需要 WAL(参见让 B 树更可靠) 。
    当数据加载进 Voldemort 时 , 服务器可以利用老文件继续对外提供服务 , 新文件会从分布式文件系统中拷贝的 Voldemort 服务本地 。一旦拷贝完成 , 服务器可以立即将外部查询请求原子地切到新文件上 。如果导入过程中发生了任何问题,也可以快速地切回,使用老文件提供服务 。因为老文件是不可变的,且没有立即被删除 。
    批处理输出的哲学本章稍早我们讨论过 Unix 的设计哲学,它鼓励在做实验时使用显式的数据流:每个程序都会读取输入,然后将输出写到其他地方 。在这个过程中,输入保持不变 , 先前的输出被变换为新的输出 , 并且没有任何其他的副作用 。这意味着,你可以任意多次的重新跑一个命令,每次可以对命令或者参数进行下微调,或者查看中间结果进行调试,而不用担心对你原来系统的状态造成任何影响 。
    MapReduce 任务在处理输出时,遵从同样的哲学 。通过不改变输入、不允许副作用(比如输出到外部文件),批处理不仅可以获得较好的性能,同时也变得容易维护: