数据密集型应用系统设计 读书笔记 第三部分

(1)批处理系统:
[1]MapReduce与分布式文件系统:MapReduce作用在分布式文件系统上读写文件,在Hadoop的MapReduce实现中,该文件系统称为HDFS。
MapReduce的两个回调函数:<1>Mapper:从记录中提取关键词和值,生成任意数量的键值对;<2>Reducer:生成输出记录。
MapReduce的分布式执行:Hadoop MapReduce的并行化基于分区,作业的输入通常是HDFS中的一个目录,输入目录中的每一个文件可视为一个分区,由一个单独的map任务来处理。MapReduce调度器会尝试在输入文件副本所在的某台机器上执行map任务,避免输入文件通过网络进行复制。在mapper任务中,需要为每一个处理完的键值对进行分区(partition),指定后续由哪一个reducer处理这条记录。处理完成的记录将储存在机器的环形内存缓冲区中,当缓冲区占用大小达到阈值时(80%),由单独的溢出写线程执行溢出写操作,在内存中进行分块、排序,并持久化到硬盘,硬盘文件类似于SSTable。当整个map完成溢出写任务后,将所有的临时文件合并为最终的输出文件(类似于LSM-Tree中第n层到第n+1层的合并)。Reduce端将不断拉取map task的最终结果,交由reducer处理。从mapper到reducer之间的过程称为shuffle。reducer可以以任意逻辑处理这些记录,并将结果写入HDFS的输出目录。
Map/Reduce端的join:<1>Reduce端的join:一组mapper扫描join的左端,另一组mapper扫描join的右端,mapper产出的key为join的连接条件,reducer可以执行真正的join逻辑。<2>Map端的join:当用户数据库可以完全放入内存时,在map端join可以提高性能。
批处理工作流的输出:<1>生成搜索索引:索引一旦创建就是不可改变的,如果索引的文档集合发生修改,则可以选择定期重新运行整个索引工作流,如果只有少量文档发生变化,这种方法在计算上可能比较昂贵(可以使用基于流处理的增量索引来应对少量更改的情况)。<2>创建全新的派生数据库:批处理的输入不可变,一旦出现错误可以回滚到初始状态,也可以安全的自动重试,更易于维护。
对比Hadoop与分布式数据库:<1>分布式数据库专注于在一个机器集群上并行执行SQL查询,而Hadoop支持由用户自定义的MapReduce代码,可以以任意方式处理HDFS中的源数据。<2>Hadoop将数据不加区分的直接存放在HDFS中,之后再去考虑如何进一步处理数据,不同的消费者可以根据自己的需求处理源数据。分布式数据库会对数据设计详细的模式(以便于执行SQL)。<3>与在线系统(分布式数据库)相比,批处理对故障的敏感度较低,因为遇到失败的任务,它们不会立刻影响用户,而且总是可以重新运行。
[2]超越MapReduce:
中间状态实体化:<1>MapReduce作业只有在前面的作业(生成其输入的作业)完全完成后才能启动,因此需要花费更长的等待时间。<2>Mapper的作用是根据输入生成任意数目的键值对,因此在串行执行的MapReduce中,中间的Mapper常常是冗余的。<3>使用HDFS存储中间状态(临时数据)是大材小用。<4>优点:中间状态提供了容错机制,Spark,Flink和Tez避免将中间状态写入HDFS,因此各自需要提供独立的容错机制。
相关技术的演化:<1>Apache Pig:将操作(Pig中的流程组织形式)组成DAG,属于MapReduce的上层封装。Pig代码会被编译为MapReduce执行,因此只提高了代码编写效率,没有提高执行效率。<2>Apache Tez:解决了MapReduce中最主要的两个问题:不需要每次将作业输出的内容实体化在HDFS上;去除了MapReduce中多个作业之间多余的map阶段。<3>Apache Spark:Spark与Hadoop最大的不同点在于,Hadoop使用硬盘来存储数据,而Spark使用内存来存储数据。Spark提供了一个集群的分布式内存抽象:RDD(Resilient Distributed DataSet,弹性分布式数据集),在RDD上可执行两种操作:转换和动作,转换将之前的RDD转变为一个新的RDD,动作是返回一个结果,例如collect、reduce、count等。RDD中的resilient(弹性)指RDD的数据不一定以物理形式存在,也可能以逻辑形式存在,只有查询或者对RDD执行动作时才会真正计算出这些数据,因此即使RDD出现故障,也可以自动从源数据中将该RDD恢复出来。
(2)流处理系统:
[1]发送事件流:
事件可以被编码为文本字符串、JSON或某种二进制编码形式(二进制JSON,二进制XML,Thrift,Protobuf,Avro等)。
消息代理:实质上是一种为了处理消息流而优化的数据库,可以适应不断变化的客户端(发送者/消费者连接、断开、崩溃等),并处理消息的持久性问题(自身崩溃)。
消息代理与数据库对比:<1>数据库直到要求时才删除消息,而大多数消息代理会自动删除成功传递的消息,保持存储较小的数据集。<2>如果消费者过慢,使代理需要缓存大量数据(代理不再存储较小的数据集),可能会把部分消息唤出到硬盘,导致处理速度下降。<3>在向数据库写入新数据时,客户端是没有感知的,除非主动查询,而代理发生数据变化时会通知客户端。
通过以下两个问题来区分不同的消息系统:<1>如果生产者发送消息的速度比消费者处理消息的速度更快,会发生什么:系统丢弃消息、将缓存在队列中、系统进行流量控制。如果消息被缓存在队列中,则队列增长时会发生什么,消息是否会被写入硬盘。如果消息被写入硬盘,磁盘访问将如何影响系统的性能。<2>如果节点崩溃或暂时离线会发生什么:是否会有消息丢失。
确认和重新传递:为了保持存储较小的数据集,消费者必须在处理完消息后显式的告诉代理,使代理将消息从队列中移除,但是会导致如下问题:<1>若消费者已经完成消息处理,但通知代理时出现网络错误,代理认为消息未处理,将消息发送给另一位消费者,造成重复处理。<2>即使正确消息重传,重传消息也会导致消费者消费消息的顺序与生产者生产消息的顺序不一致,若消息之间具有因果关系,会导致因果关系混乱。<3>如果确认消息后就从代理中删除消息,则无法再次运行同一个消费者,并期望得到与之前相同的结果(与批处理可重复执行不同)。
基于日志的消息存储:例如Apache Kafka,生产者通过将消息追加到日志末尾来发送消息,消费者通过依次读取日志来接收消息。为了突破单个磁盘所能提供的吞吐量上限,可以将日志进行分区,每个日志都是一个单独的分区,主题定义为一组分区(一组日志),它们都携带相同类型的信息。每个分区内的消息是完全有序的,不同分区的消息没有顺序保证。
对比日志与传统消息系统:<1>在日志消息系统中,每个消费者都会消费分配给它的日志的所有内容,因此消费者负载均衡时,消费者数目最多等于分区数目,多余的消费者会被闲置。消费者以单线程的方式顺序读取日志中的消息,因此如果单个消息处理缓慢,会阻碍该分区后续的消息处理。而传统消息系统中,若一个消费者对某条消息处理缓慢,后续消息可以发送给其他空闲的消费者。<2>在日志消息系统中,代理不需要跟踪每条消息消费完成的确认,只需要定期记录消费者的偏移量。如果消费者处理了部分信息、未提交偏移量后就崩溃了,这部分信息会被重复处理。<3>基于日志的消息代理会定期回收磁盘空间,如果一个消费者速度难以跟上生产者速度,以至于消费者偏移量已经指向了已被删除的片段,则消费者会错失一些消息。
[2]数据库与流:
变更数据捕获:变更数据捕获记录了写入数据库的所有更改,以可复制到其他系统的形式来提取数据,并且可以在写入时立刻将数据作为流来发布。从本质上讲,变更数据捕获使(异构数据系统中的)一个数据库成为主节点,其他系统成为从节点。Databus基于变更数据捕获,已在大规模环境下得到部署。实现变更数据捕获:初始快照+日志压缩。变更数据捕获的缺点是:事件日志的消费者通常是异步的,所以用户可能出现读写不一致的情况。
[3]流处理的容错:处理一个或多个输入流以产生一个或多个输入流。
<pre>流式系统的可靠度语义可以据此来分类:单条记录在系统中被处理的次数保证。一个流式系统可能提供保证必定是以下三种之一(不管系统是否出现故障):
至多一次(At most once): 每条记录要么被处理一次,要么就没有处理。
至少一次(At least once): 每条记录至少被处理过一次(一次或多次)。这种保证能确保没有数据丢失,比“至多一次”要强。但有可能出现数据重复。
精确一 次(Exactly once): 每条记录都精确地只被处理一次 – 也就是说,既没有数据丢失,也不会出现数据重复。这是三种保证中最强的一种。
<1>Spark Streaming微批处理:
Spark Streaming是对核心Spark API的扩展,能实现对数据流的流式处理。
Spark Streaming接受实时数据流,并将数据划分为DStream。DStream是一组RDD序列,每个RDD都是不可变、可重算(可容错)、分布式的数据集。
Spark主要操作一些可容错的系统(例如HDFS),由于RDD也是可容错的,因此整个系统是可容错的。
Spark Streaming的数据源可能并不可靠,导致系统可能无法保证exactly-once。可以保证exactly-once的数据源:HDFS,Kafka(Spark中的Kafka Derict API可以为Kafka数据源提供“exactly-once”的保证)。
输出exactly-once保证(防止单条输出数据被多次写入外部实体):幂等性或事务,需要做一些额外工作。在Spark Streaming中,部分输出算子可以提供幂等性支持,Spark Streaming的下游系统可能提供事务支持,用户也可以实现自己的事务。
<2>Apache Flink校验点:
Apache Flink提供exactly-once的容错机制,但是也可降级为at-least-once。
Apache Flink的exactly-once基于SNAPSHOT(Checkpoint)以及barrier。如果程序失败(由于机器,网络或软件故障),Flink将停止分布式流数据流。系统重新启动算子并将其重置为最新的成功检查点, 输入流将重置为状态SNAPSHOT的点。
注意:要使此机制实现其完全保证,数据流源(例如消息队列或代理)需要能够将流回滚到定义的最近点(具有可容错性,例如HDFS)。除了HDFS这种持久存储系统外,Apache Kafka也具有这种能力,Flink与Kafka的连接器利用了这种能力。
operator一旦从传入流接收到barrier n,就无法处理该流中的任何其他记录,直到它也从其他输入接收到barrier n为止,这些(该流中无法处理的)记录被暂时放到输入缓冲区中。一旦从所有输入流中都接收到了barrier n,operator会向输出流发送barrier以分组,并生成一个SNAPSHOT n以供系统崩溃时恢复,完成后恢复处理输入流中的记录(可能已储存在输入缓冲区中)。
服务降级为at-least-once:支持exactly-once需要优先处理完成的输入流等待后面的输入流,会造成额外的延迟(一般为几毫秒)。当降级为at-least-once时,优先处理完成的输入流会直接处理SNAPSHOT n+1的数据,而不会等待其他输入流的barrier n到达,因此生成SNAPSHOT n时,会包含部分应属于SNAPSHOT n+1的数据。当出现故障时,根据SNAPSHOT n恢复,其中已包括了SNAPSHOT n+1的数据,再处理本应属于SNAPSHOT n+1的数据时会出现重复。
<3>Apache Kafka幂等性和原子提交(事务支持):
同样,从发布消息的(持久性)保证和消费消息的保证两方面考虑。
发布消息的保证:
发布消息时,会将该消息commit到log中,只要有一个broker备份了消息写入的partition,并且保持alive状态,消息就不会丢失。
Kafka的producer具有幂等性的传递选项,以保证重传不会在log中引入重复条目。producer也新增了使用类似事务性的语义,将消息发送到多个topic partition:要么所有的消息都被成功的写入到了log,要么一个都没写进去。该语义一般用于Kafka内部各个topic之间的消息传递。
消费消息的保证:
Kafka内部各个topic之间消费,同样可以基于事务实现exactly-once。
在写入外部系统的应用场景中,可以选择将offset和数据写入同一个数据系统,从而消除了异构数据系统的一致性问题,也可以使用两阶段提交(2PC)来解决一致性问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值