第11章 流处理系统
批处理系统有一个很大的假设:即输入是有界的,即已知和有限的?小,所以批处理知道它何时完成输?的读取。
实际上,很多数据是?界限的,因为它随着时间的推移而逐渐到达:你的用户在昨天和今天产?了数据,明天他们将继续产?更多的数据。除非你停业,否则这个过程永远都不会结束,所以数据集从来就不会以任何有意义的?式“完成”。因此,批处理程序必须将数据?为地分成固定时间段的数据块, 例如,在每天结束时处理理一天的数据,或者在每小时结束时处理一小时的数据。
日常批处理中的问题是,输?的变更只会在?天之后的输出中反映出来,这对于许多急躁的用户来说太慢了。为了减少延迟,我们可以更频繁地运行处理——比如说,在每秒钟的末尾——或者甚?更连续一些,完全抛开固定的时间切片,当事件发?时就立即进?处理,这就是流处理背后的想法。
发送事件流
当输?是?个?件(一个字节序列),第?个处理步骤通常是将其解析为?系列记录。在流处理的上下文中,记录通常被叫做事件(event),但它本质上是一样的:?个小的,独立的,不可变的对象, 包含某个时间点发生的某件事情的细节。一个事件通常包含?个来?墙上时钟的时间戳,以指明事件发?的时间。
事件可能被编码为文本字符串或JSON,或者某种二进制编码。这种编码允许你存储一个事件,例如将其附加到?个文件,将其插?关系表,或将其写?文档数据库。它还允许你通过 络将事件发送到另?个节点以进行处理。
在批处理中,?件被写?一次,然后可能被多个作业读取。类似地,在流处理术语中,一个事件由?产者生成?次,然后可能由多个消费者或接收者进?处理。在?件系统中,文件名标识?相关记录;在流媒体系统中,相关的事件通常被聚合为?个主题或流。
原则上将,?件或数据库就足以连接生产者和消费者:生产者将其生成的每个事件写入数据存储,且每个消费者定期轮询数据存储,检查自上次运行以来新出现的事件。这实际上正是批处理在每天结束时处理当天数据时所做的事情。
但当我们想要进?低延迟的连续处理时,如果数据存储不是为这种?途专门设计的,那么轮询开销就会很?。轮询的越频繁,能返回新事件的请求?例就越低,?额外开销也就越高。相比之下,最好能在新事件出现时直接通知消费者。
消息系统
向消费者通知新事件的常?方式是使用消息传递系统:生产者发送包含事件的消息,然后将消息推送给消费者。
像生产者和消费者之间的Unix管道或TCP连接这样的直接信道,是实现消息传递系统的简单?法。但是,大多数消息传递系统都在这一基本模型上进?扩展。特别的是,Unix管道和TCP仅连接一个发送者与一个接收者,?一个消息传递系统允许多个生产者节点将消息发送到同一个主题,并允许多个消费者节点接收主题中的消息。
在这个发布/订阅模式中,不同的系统采取各种各样的方法,并没有针对所有?的的通用答案。为了区分这些系统,问一下这两个问题会特别有帮助:
- 如果生产者发送消息的速度比消费者能够处理的速度快会发生什么般来说,有三种选择:系统可以丢掉消息,将消息放入缓冲队列,或使用背压(也称为流量量控;即阻塞生产者,以免其发送更多的消息)。例如Unix管道和TCP使用背压:它们有?个固定?小的缓冲区,如果填满,发送者会被阻塞,直到接收者从缓冲区中取出数据。
如果消息被缓存在队列中,那么理解队列增?会发生什么是很重要的。当队列装不不进内存时系统会崩溃吗是将消息写?磁盘果是这样,磁盘访问又会如何影响消息传递系统的性能/li> - 如果节点崩溃或暂时脱机,会发?什么情况—是否会有消息丢失数据库一样,持久性可能需要写入磁盘和/或复制的某种组合,这是有代价的。如果你能接受有时消息会丢失,则可能在同一硬件上获得更高的吞吐量和更低的延迟。
生产者与消费者之间的直接消息传递
许多消息传递系统使?生产者和消费者之间的直接?络通信,?不通过中间节点。
尽管这些直接消息传递系统在设计它们的环境中运?良好,但是它们通常要求应?用代码意识到消息丢失的可能性。它们的容错程度极为有限:即使协议检测到并重传在 络中丢失的数据包,它们通常也只是假设生产者和消费者始终在线。
如果消费者处于脱机状态,则可能会丢失其不可达时发送的消息。一些协议允许?产者重试失败的消息传递,但当?产者崩溃时,它可能会丢失消息缓冲区及其本应发送的消息,这种?法可能就没?了。
消息代理
?种?泛使?的替代?法是通过消息代理(message broker)(也称为消息队列列)发送消息,消息代理实质上是一种针对处理理消息流?优化的数据库。它作为服务器运行,?产者和消费者作为客户端连接到服务?。生产者将消息写入代理,消费者通过从代理那?读取来接收消息。
通过将数据集中在代理上,这些系统可以更容易地适应不断变化的客户端(连接,断开连接和崩溃),而持久性问题则转移到代理的身上。?一消息代理只将消息保存在内存中,而另一些消息代理(取决于配置)将其写?磁盘,以便在代理崩溃的情况下不会丢失。针对缓慢的消费者,它们通常会允许?上限的排队(而不是丢弃消息或背压),尽管这种选择也可能取决于配置。
排队的结果是,消费者通常是异步的:当生产者发送消息时,通常只会等待代理确 认消息已经被缓存,?不等待消息被消费者处理。向消费者递送消息将发生在未来某个未定的时间点——通常在?分之?秒之内,但有时当消息堆积时会显著延迟。
消息代理与数据库对比
有些消息代理甚?可以使用XA或JTA参与两阶段提交协议务。这个功能与数据库在本质上非常相似,尽管消息代理和数据库之间仍存在实践上很重要的差异:
- 数据库通常保留数据直?显式删除,?大多数消息代理在消息成功递送给消费者时会?动删除消息。这样的消息代理不适合长期的数据存储。
- 由于它们很快就能删除消息,大多数消息代理都认为它们的工作集相当?——即队列很短。如果代理需要缓冲很多消息,?如因为消费者速度较慢(如果内存装不下消息,可能会溢出到磁盘), 每个消息需要更长的处理时间,整体吞吐量可能会恶化。
- 数据库通常?持?级索引和各种搜索数据的?式,而消息代理通常支持按照某种模式匹配主题,订阅其子集。 这可以些机制虽然不同,但本质上都是让客户端可以选择它们想要了解的部分数据。
- 查询数据库时,结果通常基于某个时间点的数据快照;如果另?个客户端随后向数据库写?一些改变了查询结果的内容,则第?个客户端不会发现其先前结果现已过期(除非它重复查询或轮询变更)。相?之下,消息代理不支持任意查询,但是当数据发生变化时(即新消息可用时),它们会通知客户端。
多个消息者
当多个消费者从同一主题中读取消息时,有使?两种主要的消息传递模式:
- 负载均衡
每条消息都被传递给消费者之一,所以处理该主题下消息的工作能被多个消费者共享。代理可以为消费者任意分配消息。当处理消息的代价高昂,希望能并行处理消息时,此模式?常有?。 - 忽略这些滞留事件,因为在正常情况下它们可能只是事件中的一小部分。你可以将丢弃事件的数量作为?个监控指标,并在出现?量丢消息的情况时 警。
- 发布一个更正:滞留事件的更新值,你可能还需要收回以前的输出。
- 事件发?生的时间,取决于设备时钟
- 事件发送往服务器?的时间,取决于设备时钟
- 事件被服务器?接收的时间,取决于服务器?时钟
- 滚动窗?(Tumbling Window)
滚动窗?有着固定的长度,每个事件都仅能属于一个窗口。例如,假设你有一个1分钟的滚动窗口,则所有时间戳在10:03:00和10:03:59之间的事件会被分组到一个窗口中,10:04:00和10:04:59之间的事件被分组到下一个窗口,依此类推。通过将每个事件时间戳四舍五入至最近的分钟来确定它所属的窗?,可以实现1分钟的滚动窗口。 - 跳动窗?(Hopping Window)
跳动窗口也有着固定的?度,但允许窗口重叠以提供?些平滑。例如,一个带有1分钟跳跃步长的5分钟窗口将包含10:03:00至10:07:59之间的事件,?下一个窗?将覆盖10:04:00至10:08之间的事件等等。通过首先计算1分钟的滚动窗?,然后在几个相邻窗?上进行聚合,可以实现这种跳动窗口。 - 滑动窗?(Sliding Window)
滑动窗口包含了彼此间距在特定时长内的所有事件。例如,一个5分钟的滑动窗?应当覆盖10:03:39和10:08:12的事件,因为它们相距不超过5分钟(注意滚动窗?与步长5分钟的跳动窗?可能不会把这两个事件分组到同一个窗口中,因为它们使用固定的边界)。通过维护一个按时间排序的事件缓冲区, 并不断从窗?中移除过期的旧事件,可以实现滑动窗口。 - 会话窗口(Session window)
与其他窗?类型不同,会话窗口没有固定的持续时间,?定义为:将同一用户出现时间相近的所有事件分组在一起,?当用户?段时间没有活动时(例如,如果30分钟内没有事件)窗口结束。 - 流和流join
两个输入流都是由活动事件组成,采用join操作用来搜索在特定时间窗口内发生的相关事件。例如,它可以匹配相同用户在30min内采取的两个动作。如果想要在一个流中查找相关事件,则两个join输入可以是相同的流。 - 流和表join
一个输入流由活动事件组成,而另一个则是数据库变更日志。更新日志维护了数据库的本地最新副本。对于每个活动事件,join操作用来查询数据库并输出一个包含更多信息的事件。 - 表和表join
两个输入流都是数据库更新日志。在这种情况下,一方的每一个变化都与另一方的最新状态join。结果是对两个表之间join的物化视图进行持续的更新。 - 在大多数情况下,构建完全有序的日志,需要所有事件汇集于决定顺序的单个领导节点。如果事件吞吐量大于单台计算机的处理能力,则需要将其分割到多台计算机上。然后两个不同分区中的事件顺序关系就不明确了。
- 如果服务器分布在多个地理位置分散的数据中?上,例如为了容忍整个数据中?掉线,通常在每个数据中?都有单独的主库,因为?络延迟会导致同步的跨数据中心协调效率低下。这意味着源?两个不同数据中?的事件顺序未定义。
- 将应?程序部署为微服务时,常见的设计选择是将每个服务及其持久状态作为独?单元进行部署,服务之间不共享持久状态。当两个事件来自不同的服务时,这些事件间的顺序未定义。
- 某些应?程序在客户端保存状态,该状态在用户输?时?即更新(无需等待服务器确认),甚?可以继续脱机?作。有了这样的应?程序,客户端和服务?很可能以不同的顺序看到事件。
- 在批处理和流处理框架中维护相同的逻辑是很显著的额外工作。
- 由于流管道和批处理管道产生独?的输出,因此需要合并它们以响应?户请求。
- 尽管有能力重新处理整个历史数据集是很好的,但在大型数据集上这样做经常会开销巨大。
- 通过处理最近事件流的相同处理引擎来重放历史事件的能力。例如,基于日志的消息代理可以重放消息,某些流处理?可以从HDFS等分布式?件系统读取输?。 – 对于流处理理?来说,恰好?次语义——即确保输出与未发生故障的输出相同,即使事实上发生故障。与批处理一样,这需要丢弃任何失败任务的部分输出。
- 按事件时间进?窗?化的工具,?不是按处理时间进?窗口化,因为处理历史事件时,处理时间毫无意义。
- 做出严重影响人们生活的貌似公平的决定,这种算法决定难以对其提起诉讼
- 导致歧视和剥削
- 使监视泛滥
- 暴露私密信息
- …
分区日志
基于日志的消息存储
?志只是磁盘上仅支持追加式修改记录的序列。我们可以这样使用日志来实现消息代理:生产者通过将消息追加到?志末尾来发送消息,而消费者通过依次读取?志来接收消息。如果消费者读到?志末尾,则会等待新消息追加的通知。 Unix工具 tail -f 能监视文件被追加写入的数据,基本上就是这样工作的。
为了扩展到?单个磁盘所能提供的更高吞吐量,可以对?志进?分区。不同的分 区可以托管在不同的机?上,且每个分区都拆分出一份能独立于其他分区进?读写的日志。?个主题可以定义为?组携带相同类型消息的分区。这种方法下图所示:
变更数据捕获
最近,?们对变更数据捕获(change data capture, CDC)越来越感兴趣,这是?种观察写入数据库的所有数据变更,并将其提取并转换为可以复制到其他系统中的形式的过程。 CDC是非常有意思的,尤其是当变更能在被写?后?刻用于流时。
例如,你可以捕获数据库中的变更,并不断将相同的变更应?至搜索索引。如果变更日志以相同的顺序应用,则可以预期搜索索引中的数据与数据库中的数据是匹配的。搜索索引和任何其他衍?数据系统只是变更流的消费者,如下图所示。
了解什么时候准备就绪
?事件时间来定义窗口的一个棘手的问题是,你永远也无法确定是不是已经收到了特定窗口的所有事件,还是说还有一些事件正在来的路上。
在一段时间没有看到任何新的事件之后,你可以超时并宣布一个窗口已经就绪,但仍然可能发?这种情况:某些事件被缓冲在另?台机器上,由于?络中断?延迟。你需要能够处理这种在窗?宣告完成之后到达的滞留事件。?体上,你有两种选择:
在某些情况下,可以使用特殊的消息来指示“从现在开始,不会有比t更早时间戳的消息了”(watermark),消费者可以使用它来触发窗口。但是,如果不同机器上的多个生产者都在?成事件,每个?产者都有?己的最小时间戳阈值,则消费者需要分别跟踪每个生产者。在这种情况下,添加和删除生产者都是比较棘?的。
你用谁的时钟
当事件可能在系统内多个地?进行缓冲时,为事件分配时间戳更加困难了。例如,考虑一个移动应用向服务器上 关于?量的事件。该应?可能会在设备处于脱机状态时被使用,在这种情况下,它将在设备本地缓冲事件,并在下一次互联?连接可用时向服务?上 这些事件(可能是?小时甚至?天)。对于这个流的任意消费者?言,它们就如延迟极?的滞留事件一样。
在这种情况下,事件上的事件戳实际上应当是?户交互发生的时间,取决于移动设备的本地时钟。然?用户控制的设备上的时钟通常是不可信的,因为它可能会被无意或故意设置成错误的时间。服务?收到事件的时间可能是更准确的,因为服务器在你的控制之下,但在描述?户交互?面意义不大。
要校正不正确的设备时钟,一种方法是记录三个时间戳:
通过从第三个时间戳中减去第二个时间戳,可以估算设备时钟和服务?时钟之间的偏移(假设?络延迟与所需的时间戳精度相?可忽略不计)。然后可以将该偏移应?于事件时间戳,从?估计事件实际发?的真实时间(假设设备时钟偏移在事件发生时与送往服务?之间没有变化)。
这并不是流处理理独有的问题,批处理有着完全?样的时间推理问题。只是在流处理的上下文中,我们更容易意识到时间的流逝。
窗口类型
当你知道如何确定一个事件的时间戳后,下一步就是如何定义时间段的窗口。然后窗?就可以用于聚合。有?种窗口很常?:
流式join
由于流处理将数据管道泛化为对无限数据集进行增量处理,因此对流进?连接的需求也是完全相同的。然而,新事件随时可能出现在一个流中,这使得流连接要?批处理连接更具挑战性。
根据join的对象不同可以分为以下三类:
join的时间依赖
这?描述的三种连接(流流,流表,表表)有很多共通之处:它们都需要流处理器维护连接一侧的一些状态(搜索与点击事件,用户档案,关注列表),然后当连接另一侧的消息到达时查询该状态。
?于维护状态的事件顺序是很重要的。在分区?志中,单个分区内的事件顺序是保留下来的。但典型情况下是没有跨流或跨分区的顺序保证的。
这就产?了一个问题:如果不同流中的事件发?在近似的时间范围内,则应该按照什么样的顺序进?处理流表连接的例子中,如果?户更新了它们的档案,哪些活动事件与旧档案连接(在档案更新前处理),哪些?与新档案连接(在档案更新之后处理)句话说:你需要对一些状态做连接,如果状态会随着时间推移?变化,那应当使?什么时间点来连接br> 如果跨越流的事件顺序是未定的,则连接会变为不确定性的,这意味着你在同样输入上重跑相同的作业未必会得到相同的结果;当你重跑任务时,输?流上的事件可能会以不同的?式交叉在一起。
在数据仓库中,这个问题被称为缓慢变化的维度(slowly changing dimension, SCD),通常通过特定版本的记录使用唯?的标识符来解决:例如,每当税率改变时都会获得一个新的标识符,而发票在销售时会带有税率的标识符。这种变化使连接变为确定性的,但也会导致日志压缩无法进行:表中所有的记录版本都需要保留留。
流处理的容错
批处理框架可以很容 易地容错:如果MapReduce作业中的任务失败,可以简单地在另?台机?上再次启动,并且丢弃失败任务的输出。这种透明的重试是可能的,因为输?文件是不可变的,每个任务都将其输出写?到HDFS上的独?文件中,?输出仅当任务成功完成后可见。
特别是,批处理容错?法可确保批处理作业的输出与没有出错的情况相同,即使实际上某些任务失败了。看起来好像每条输?记录都被处理了恰好一次——没有记录被跳过,?且没有记录被处理两次。 尽管重启任务意味着实际上可能会多次处理记录,但输出中的可见效果看上去就像只处理过一次。这个原则被称为恰好一次语义(exactly-once semantics),尽管有效?次(effectively-once)可能会是一个更写实的术语。
在流处理中也出现了同样的容错问题,但是处理起来没有那么直观:等待某个任务完成之后再使其输出可见并不是一个可?选项,因为你永远无法处理完一个无限的流。
微批处理和校验点
?个解决方案是将流分解成小块,并像微型批处理一样处理每个块。这种方法被称为微批处理(microbatching),它被?于Spark Streaming。批次的?小通常约为1秒,这是对性能妥协的结果:较?的批次会导致更大的调度与协调开销,而较?的批次意味着流处理?结果可见之前的延迟要更长。
微批处理也隐式提供了一个与批次?小相等的滚动窗?(按处理时间?不是事件时间戳分窗)。任何需要更?窗口的作业都需要显式地将状态从一个微批次转移到下?个微批次。
Apache Flink则使?不同的方法,它会定期?成状态的滚动校验点并将其写入持久存储。如果流算子崩溃,它可以从最近的存档点重启,并丢弃从最近检查点到崩溃之间的所有输出。存档点会由消息流中的壁障(barrier)触发,类似于微批处理之间的边界,但不会强制?个特定的窗?大小。
在流处理框架的范围内,微批处理与校验点?法提供了与批处理一样的恰好?次语义。但是,只要输出离开流处理?(例如,写入数据库,向外部消息代理发送消息,或发送电?邮件),框架就?法抛弃失败批次的输出了。在这种情况下,重启失败任务会导致外部副作用发?两次,只有微批处理或校验点不足以阻止这?问题。
原子提交
为了在出现故障时表现出恰好处理一次的样?,我们需要确保事件处理的所有输出和副作?当且仅当处理成功时才会生效。这些影响包括发送给下游算子或外部消息传递系统的任何消息,任何数据库写?,对算子状态的任何变更,以及对输?消息的任何确认。
这些事情要么都原?地发?生,要么都不发?生,但是它们不应当失去同步。
与XA不同,这些实现不会尝试跨异构技术提供事务,而是通过在流处理框架中同时管理状态变更与消息传递来内化事务。事务协议的开销可以通过在单个事务中处理多个输入消息来分摊。
幂等性
我们的?标是丢弃任何失败任务的部分输出,以便能安全地重试,?不会生效两次。分布式事务是实现这个目标的一种方式,?另一种方式是依赖幂等性。
幂等操作是多次重复执行与单次执行效果相同的操作。例如,将键值存储中的某个键设置为某个特定值是幂等的,而递增一个计数?不是幂等的。
即使?个操作不是天生幂等的,往往可以通过一些额外的元数据做成幂等的。例如,在使用来?Kafka 的消息时,每条消息都有?个持久的,单调递增的偏移量。将值写?外部数据库时可以将这个偏移量带上,这样你就可以判断一条更新是不是已经执?过了,因而避免重复执行。
依赖幂等性意味着隐含了一些假设:重启一个失败的任务必须以相同的顺序重放相同的消息,处理必须是确定性的, 没有其他节点能同时更新相同的值。
故障后重建状态
任何需要状态的流处理——例如,任何窗?聚合以及任何用于连接的表和索引,都必须确保在失败之后能恢复其状态。
一种选择是将状态保存在远程数据存储中,并进行复制,然?每个消息都要查询远程数据库可能会很慢。另一种方法是在流处理?本地保存状态,并定期复制。然后当流处理?从故障中恢复时,新任务可以读取状态副本,恢复处理而不丢失数据。例如,Flink定期捕获算?状态的快照,并将它们写入HDFS等持久存储中。
在某些情况下,甚至可能都不需要复制状态,因为它可以从输?流重建。例如,如果状态是从相当短的窗口中聚合而成,则简单地重放该窗?中的输入事件可能是?够快的。如果状态是通过变更数据捕获来维护的数据库的本地副本,那么也可以从日志压缩的变更流中重建数据库。
然?,所有这些权衡取决于底层基础架构的性能特征:在某些系统中,?络延迟可能低于磁盘访问延迟, 络带宽可能与磁盘带宽相当。没有针对所有情况的普世理想权衡,随着存储和 络技术的发展,本地状态与远程状态的优点也可能会互换。
第12章 数据系统的未来
数据集成
本书中反复出现的主题是,对于任何给定的问题都会有好几种解决?案,所有这些解决?案都有不同的优缺点与利弊权衡。
如果你有一个类似于“我想存储?一些数据并稍后再查询”的问题,那么并没有一种绝对正确的解决方案。但对于不同的具体环境,总会有不同的合适方法。软件实现通常必须选择一种特定的方法。使单条代码路径能做到稳定健壮且表现良好已经是一件非常困难的事情了——尝试在单个软件中完成所有事情,几乎可以保证,实现效果会很差。
因此软件?具的最佳选择也取决于情况。每一种软件,甚?所谓的“通用”数据库,都是针对特定的使?模式设计的。
面对让人眼花缭乱的诸多替代品,第一个挑战就是弄清软件与其适用环境的映射关系。但是,即使你已经完全理解各种工具与其适?环境间的关系,还有?个挑战:在复杂的应用中,数据的用法通常花样百出。不太可能存在适?于所有不同数据应?场景的软件,因此不可避免地需要拼凑?个不同的软件以提供应用所需的功能。
采用派生数据来组合工具
例如,为了处理任意关键词的搜索查询,将OLTP数据库与全?搜索引集成在?起是很常?的需求。尽管一些数据库(例如PostgreSQL)包含了全文索引功能,对于简单的应用完全够了,但更复杂的搜索能?就需要专业的信息检索工具了。相反的是,搜索引通常不适合作为持久的记录系统, 因此许多应用需要组合这两种不不同的?具以满足所有需求。
随着数据不同表示形式的增加,集成问题变得越来越困难。除了数据库和搜索索引之外,也许你需要在分析系统(数据仓库,或批处理和流处理系统)中维护数据副本;维护从原始数据中衍生的缓存,或反规范化的数据版本;将数据灌入机?学习, 分类,排名,或推荐系统中;或者基于数据变更发送通知。
某人认为鸡肋肋而毫无意义的功能可能是别?的核?需求。当你拉高视角,并考虑跨越整个组织范围的数据流时,数据集成的需求往往就会变得明显起来。
派生数据与分布式事务
保持不同数据系统彼此?致的经典?法涉及分布式事务。 与分布式事务相比,使?派生数据系统的?法如何br> 在抽象层面,它们通过不同的方式达到类似的?标。分布式事务通过锁进?互斥来决定写入的顺序,而CDC和事件溯源使?日志进行排序。分布式事务使用原?提交来确保变更只生效一次,而基于?志的系统通常基于确定性重试和幂等性。
最大的不同之处在于事务系统通常提供线性一致性,这包含着有用的保证,例如读己之写。另?方面, 派生数据系统通常是异步更新的,因此它们默认不会提供相同的时序保证。
在愿意为分布式事务付出代价的有限场景中,它们已被成功应用。但是,我认为XA的容错能?和性能很差劲,这严重限制了它的实?性。我相信为分布式事务设计一种更好的协议是可?的。但使这样一种协议被现有?具广泛接受是很有挑战的,且不是立竿见影的事。
在没有?泛支持的良好分布式事务协议的情况下,我认为基于日志的衍?数据是集成不同数据系统的最有前途的方法。然而,诸如读?之写的保证是有用的。
全序的局限
对于?够?的系统,构建一个完全有序的事件日志是完全可行的。但是,随着系统向更大更复杂的工作负载扩展,限制开始出现:
在形式上,决定事件的全局顺序称为全序广播,相当于共识。?多数共识算法都是针对单个节点的吞吐量足以处理整个事件流的情况?设计的,并且这些算法不提供 多个节点共事件排序?作的机制。设计可以扩展到单个节点的吞吐量之上,且在地理散布环境中仍然工作良好的的共识算法仍然是一个开放的研究问题。
批处理和流处理集成
批处理和流处理的输出是衍?数据集。二者有许多共同的原则,主要的根本区别在于流处理?在?限数据集上运?,而批处理输入是已知的有限?小。处理引擎的实现方式也有很多细节上的差异,但是这些区别已经开始模糊。
Spark在批处理引擎上执?流处理,将流分解为微批,而Apache Flink则在流处理理引擎上执?批处理。原则上,?种类型的处理可以?另一种类型来模拟,但是性能特征会有所不同:例如,在跳跃或滑动窗?上,微批可能表现不佳。
保持派生状态
批处理有着很强的函数式风格:它?励确定性的纯函数,其输出仅依赖于输?,除了显式输出外没有副作用,将输入视作不可变的,且输出是仅追加的。流处理与之 类似,但它扩展了算子以允许受管理的,容错的状态。
具有良好定义的输?和输出的确定性函数的原理不仅有利于容错,也简化了有关组织中数据流的推理。?论派生数据是搜索索引,统计模型还是缓存,采用这种观点思考都是很有帮助的:将其视为从一个东?派生出另?个的数据管道,将?个系统的状态变更推送?函数式应用代码 中,并将其效果应?至派生系统中。
原则上,派生数据系统可以同步地维护,就像关系数据库在与被索引表写入操作相同的事务中同步更新辅助索引一样。然而,异步是基于事件?志的系统稳健的原因:它允许系统的?部分故障被抑制在本地,?如果任何?个参与者失败,分布式事务将中止,因此它们倾向于通过将故障传播到系统的其余部分来放大故障。
Lambda架构
如果批处理用于重新处理历史数据,并且流处理用于处理最近的更新,那么如何将这两者结合起来br> Lambda架构是这?面的?个建议,引起了很多关注。
Lambda架构的核?心思想是通过将不可变事件附加到不断增长的数据集来记录传?数据,这类似于事 溯源。为了从这些事件中派生出读取优化的视图, Lambda架构建议并?运行两个不同的系统:批处理系统和独立的流处理系统。
在Lambda方法中,流处理?消耗事件并快速?成对视图的近似更新:批处理?稍后将使用同一组事件并?成派生视图的更正版本。这个设计背后的原因是批处理更简单,因此不易出错,而流处理理器被认为是不太可靠和难以容错的。而且,流处理可以使?
Lambda一些实际问题:
统一批处理和流处理
最近的工作使得Lambda架构能够充分发挥其优点而规避其缺点,那就是允许批处理计算和流计算在同一个系统中实现。
在?个系统中统一批处理和流处理需要以下功能,这些功能越来越?泛:
分拆数据库
略~
端到端的正确性
对于只读取数据的?状态服务,出问题也没什么?不了的:你可以修复该错误并重启服务,?一切都恢复正常。像数据库这样的有状态系统就没那么简单了:它们被设计为永远记住事物,所以如果出现问题,这种(错误的)效果也将潜在地永远持续下去,这意味着它们需要更仔细的思考。
我们希望构建可靠且正确的应?(即使?对各种故障,程序的语义也能被很好地定义与理解)。原子性,隔离性和持久性等事务特性一直是构建正确应用的首选工具。然?这些地基没有看上去那么牢固。
如果你的应用可以容忍偶尔的崩溃,以及以不可预料的方式损坏或丢失数据,那?活就要简单得多,?你可能只要双?合十念阿弥陀佛,期望佛祖能保佑最好的结果。另一?面,如果你需要更强的正确性保证,那么可序列化与原子提交就是久经考验的?法,但它们是有代价的:它们通常只在单个数据中?中工作,并限制了系统能够实现的规模与容错特性。
虽然传统的事务?法并没有走远,但我也相信在使应用正确?灵活地处理错误?面上,事务并不是最后的遗言。
做正确的事情
本章节审视了构建数据密集型应用系统在道德层面的一些问题。虽然数据可以用来帮助人们,但是也可能造成重大的伤害:
我们也面临着数据泄露的风险,而且即使是善意的数据使用也可能会产生某些一箱不到的后果。
由于软件和数据对世界的影响如此之大,我们的工程师必须谨记,我们有责任为我们赖以生存的世界而努力:一个以人性和尊重来对待人的世界。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!