数据密集型应用系统设计::派生数据

派生数据

  • 记录系统
    • 一个记录系统也被称为真实数据系统,拥有数据的权威版本
    • 如果另一个系统与记录系统之间存在任何差异,那么以记录系统中的数据值为准
  • 派生数据系统
    • 派生数据系统中的数据则是从另一个系统中获取已有数据并以某种方式进行转换或处理的结果
    • 如果派生数据丢失,用户可以从原始数据源进行重建
    • 例如缓存

批处理系统

  • 在线服务
    • 服务等待客户请求或指令的到达。当收到请求或指令时,服务试图尽可能快地处理它,并发回一个响应。
    • 响应时间通常是服务性能的主要衡量指标,而可用性同样非常重要
  • 批处理系统
    • 批处理系统接收大量的输入数据,运行一个作业来处理数据,并产生输出数据
    • 批处理作业的主要性能衡量标准通常是吞吐量
  • 流处理系统
    • 流处理介于在线与离线/批处理之间(所以有时称为近实时或近线处理)。与批处理系统类似,流处理系统处理输入并产生输出
    • 流式作业在事件发生后不久即可对事件进行处理,而批处理作业则使用固定的一组输入数据进行操作。这种差异使得流处理系统比批处理系统具有更低的延迟。
  • 使用 UNIX 工具进行批处理
    • 使用 awk, sed, grep, sort , uniq 和 xargs 的组合 ,可以在几分钟内完成许多数据分析任务
    • UNIX 设计哲学
      • 每个程序做好一件事。如果要做新的工作,则建立一个全新的程序,而不是通过增加新“特征”使旧程序变得更加复杂。
      • 期待每个程序的输出成为另一个尚未确定的程序的输入。不要将输出与无关信息混淆在一起。避免使用严格的表格状或二进制输入格式。不要使用交互式输入
      • 尽早尝试设计和构建软件,甚至是操作系统,最好在几周内完成。需要扔掉那些笨拙的部分时不要犹豫,并立即进行重建
      • 优先使用工具来减轻编程任务,即使你不得不额外花费时间去构建工具,并且预期在使用完成后会将其中一些工具扔掉
    • 统一接口
      • 如果希望某个程序的输出成为另一个程序的输入,也就意味着这些程序必须使用相同的数据格式,换句话说,需要兼容的接口,在 UNIX 中,这个接口就是文件(更准确地出,是文件描述符)
    • 逻辑与布线分离
      • UNIX 工具的另一个特点是使用标准输入(stdin)和标准输出(stdout)
      • 这允许 shell 用户以任何他们想要的方式连接输入和输出:程序并不知道也不关心输入来自哪里以及输出到哪里。
      • 将输入/输出的布线连接与程序逻辑分开,可以更容易地将小工具组合成更大的系统
    • 透明与测试
      • UNIX 命令的输入文件通常被视为是不可变的。这意味着可以随意运行命令,尝试各种命令行选项,而不会损坏输入文件。
      • 可以在任何时候结束流水线,将输出管道输送到 less ,然后查看它是否具有预期的形式。这种检查能力对调试非常有用。
      • 可以将流水线某个阶段的输出写入文件,并将该文件用作下一阶段的输入。这使得用户可以重新启动后面的阶段,而无需重新运行整个流水线。
  • MapReduce 与分布式文件系统
    • MapReduce 有点像分布在数千台机器上的 UNIX 工具
    • 不修改输入,无副作用,在分布式文件系统上读写文件
    • 分布式文件系统
      • GFS
      • HDFS
      • Amazon S3
      • Azure Blob
      • OpenStack Swift
    • HDFS 包含一个在每台机器上运行的守护进程,并会开放一个网络服务以允许其他节点访问存储在该机器上的文件
    • 容错
      • 多个机器上的相同数据的多个副本
      • 纠错码方案
    • MapReduce 作业执行
      • MapReduce 是一个编程框架,可以使用它编写代码来处理 HDFS 等分布式文件系统中的大型数据集
      • 处理模式
        • 读取一组输入文件,并将其分解成记录
        • 调用 mapper 函数从每个输入记录中提取一个键值对
        • 按关键字将所有的键值对排序
        • 调用 reducer 函数遍历排序后的键值对
      • Mapper
        • 每个输入记录都会调用一次 mapper 程序,其任务是从输入记录中提取关键字和值。对于每个输入,它可以生成任意数量的健值对(包括空记录)。它不会保留从一个输入记录到下一个记录的任何状态,因此每个记录都是独立处理的。
      • Reducer
        • MapReduce 框架使用由 mapper 生成的键值对,收集属于同一个关键字的所有值,并使用迭代器调用 reducer 以使用该值的集合。Reducer 可以生成输出记录
    • MapReduce 的分布式执行
      • 作业的输入通常是 HDFS 中的一个目录,且输入目录中的每个文件或文件块都被视为一个单独的分区,可以由一个单独的 map 任务来处理
      • 让靠近数据的机器就近执行 mapper 任务,减少网络传输的负载,提高了访问局部性
      • map 任务的数量与 reduce 任务的数量不需要一致
      • 为了使相同值的 mapper 输出交给同一个 reduce 处理,采用了关键字哈希分区
      • 键值对必须排序。map 任务基于关键字哈希,按照 reducer 对输出进行分块。每个分区都被写入 mapper 程序所在文件磁盘上的已排序文件。
      • 当 mapper 完成文件输出后,MP 调度器通知 reducer 从 mapper 中获取输出文件。reduce 从 mapper 中获取文件后把他们合并在一起,同时保持数据的排序。
    • Reduce 端的 join 与分组
      • 在批处理的背景下讨论 join 时,我们主要是解决数据集内存在关联的所有事件
      • join 最简单的实现是遍历所有记录,并在远程服务器中查找对应的记录。
      • 更好的方法是获取用户数据库的副本,并将其放入与用户活动事件日志相同的分布式文件系统。然后,可以将用户数据库放在 HDFS 中的一组文件中,并将用户活动记录放在另一组文件中,使用 MapReduce 将所有相关记录集中到一起,从而有效地处理它们。
      • 排序-合并 join
        • mapper 和排序过程确保将执行特定用户 ID join 操作的所有必要数据都放在一起,这样就只需要一次 reducer 调用。因为所有需要的数据已经预先排列好,所以 reducer 是一段相当简单的单线程代码,以高吞吐量和低内存开销来处理记录。
      • 分组
        • 在 mapper 阶段,使其生成的键值对使用所需的分组关键字。然后,分区和排序过程将相同 reducer 中所有具有相同关键字的记录集合在一起
      • 处理数据倾斜
        • 如果 join 输入中存在热键,则可以使用算法进行补偿。在真正开始执行 join 时,mapper 将任何与热键有关的记录发送到随机选择的若干个 reducer 中的一个,对于 join 的其他输入,与热键相关的记录需要被复制到所有处理该关键字的 reducer 中,这种技术将处理热键的工作分散到多个 reducer 上,可以更好地实现并行处理,代价是不得不将 join 的其他输入复制到多个 reducer
        • 使用热键对记录进行分组并汇总时,可以分两个阶段进行分组。第一个 MapReduce 阶段将记录随机发送到 reducer,以便每个 reducer 对热键的记录子集执行分组,并为每个键输出更紧凑的聚合值。然后第二个 MapReduce 作业将来自所有第一阶段 reducer 的值合并为每个键的单一值
    • Map 端的 join 操作
      • 广播哈希 join
        • 实现 map 端 join 的最简单方住特别适合大数据集与小数据集 join,尤其是小数据集能够全部加载到每个 mapper 的内存中
        • 当 mapper 程序执行时,它可以首先将用户数据库从分布式文件系统读取到内存的哈希表中。然后,mapper 程序扫描用户活动事件,并简单地查找哈希表中每个事件的用户 ID
        • Map 任务依然可以有多个: 大数据集的每个文件块对应一个 mapper。每个 mapper 还负责将小数据集全部加载到内存中。
        • 也可以存在磁盘中,由于缓存和索引的原因,和内存查找差不多快。
      • 分区哈希 join
        • 如果以相同方式对 map 端 join 的输入进行分区,则哈希 join 方法可以独立作用于每个分区
  • 批处理工作流的输出
    • 生成搜索索引
    • 批处理输出键值
    • 批处理输出的哲学
      • 如果在代码中引入了漏洞,输出错误或者损坏,那么可以简单地回读到先前版本,然后重新运行该作业,将再次生成正确的输出; 或者更简单的办法是将旧的输出保存在不同的目录中,然后切换回原来的目录
      • 与发生错误即意味着不可挽回的损害相比 ,易于回滚的特性更有利于快速开发新功能。这种使不可逆性最小化的原则对于敏捷开发是有益的
      • 如果 map 或 reduce 任务失败, MapReduce 框架会自动重新安排作业并在同一个输入上再次运行,失败任务的输出则被 MapReduce 框架丢弃
  • 对比 Hadoop 与分布式数据库
    • 存储多样性
      • 分布式文件系统中的文件只是字节序列,可以使用任何数据模型和编码来编写
      • 来自事务处理 系统的数据以某种原始形式转储到分布式文件系统中,然后编写 MapReduce 作业进行数据清理,将其转换为关系表单,并将其导入 MPP 数据仓库以进行分析。数据建模仍然会发生,但它位于一个单独步骤中,与数据收集是分离的。由于分布式文件系统支持以任何格式编码的数据,所以这种解相是可行的。
    • 处理模型多样性
      • 并非所有类型的处理都可以合理地表达为 SQL 查询
      • 由于 Hadoop 平台的开放性,可以在上面实施更多的处理模型
    • 针对频繁故障的设计
      • MapReduce 可以容忍 map 或者 reduce 任务失败,单个失败可以重试。
      • MapReduce 容忍任意失败是为了更好的利用集群资源,允许高优先级任务抢占资源
  • 超越 MapReduce
    • MapReduce 很强大,但依然有些问题
    • 对于某些类型的任务,其他工具可能要快几个数量级
    • 中间状态实体化
      • MapReduce 会把中间结果写入文件,这个过程称为实体化
      • UNIX 管道不存在实体化,输出可以立即成为下一个的输入
      • 几个问题
        • 输出不会立马被利用,任务耗时会比预计的久
        • mapper 冗余,它们只是读取刚刚由 reducer 写入的同一个文件,并为下一个分区和排序阶段做准备。在许多情况下,mapper 代码可能是之前 reducer 的一部分:如果 reducer 的输出被分区和排序的方式与 mapper 输出相同,那么不同阶段的 reducer 可以直接链接在一起,而不需要与 mapper 阶段交错。
        • 把中间状态文件存储在分布式系统中意味着这些文件被复制都多个节点了,对于这样的临时数据来说通常是大材小用了
    • 数据流引擎
      • 为了解决 MapReduce 的这些问题,开发了用于分布式批处理的新的执行引擎
        • Spark,Tez,Flink
        • 它们把整个工作流作为一个作业来处理,而不是把它分解成独立的子作业
      • 通过若干个处理阶段明确地建模数据流,所以这些系统被称为数据流引擎。像 MapReduce 一样,它们通过反复调用用户定义的函数来在单个线程上一次处理一条记录。它们通过对输入进行分区来并行工作,并将一个功能的输出复制到网络上,成为另一个功能的输入 。
      • 流处理引擎可以以更加灵活的方式组装各种函数
      • 对比 MapReduce 模型的几个优点
        • 排序等计算代价昂贵的任务只在实际需要的地方进行,而不是在每个 map 和 reduce 阶段之间默认发生
        • 没有不必要的 map 任务,因为 mapper 所做的工作通常可以合并到前面的 reduce 运算符中
        • 由于工作流中的所有 join 和数据依赖性都是明确声明的,因此调度器知道哪些数据在哪里是必需的,因此它可以进行本地优化
        • 将运算符之间的中间状态保存在内存中或写入本地磁盘通常就足够了
        • 运算符可以在输入准备就绪后立即开始执行,在下一个开始之前不需要等待前个阶段全部完成。
        • 现有的 Java 虚拟机进程可以被重用来运行新的运算符,从而减少启动开销。
      • 容错
        • 将中间状态完全实体化到分布式文件系统的一个优点是持久化,这使得在 MapReduce 中实现容错变得相当容易
        • Spark, Flink 和 Tez 避免将中间状态写入 HDFS,所以它们采用不同的方法来容忍错误: 如果机器发生故障,并且该机器上的中间状态、丢失,则利用其他可用的数据重新计算
        • 为了实现重新计算,框架必须追踪给定数据是如何计算的,使用了哪个输入分区,以及应用了哪个运算符。
      • 关于实体化的讨论
        • 数据流对 MapReduce 的改进是,不需要自己将所有中间状态写入文件系统。
    • 图与迭代处理
      • 在批处理环境中查看图也很有趣,其目标是在整个图上执行某种离线处理或分析。这种需求经常出现在机器学习应用程序或排名系统中
      • Pregel 处理模型
        • 作为对图数据的批处理优化,计算的批量同步并行模型
        • 一个顶点可以“发送消息”到另一个顶点,通常这些消息沿着图的边被发送。
        • 顶点状态和顶点之间的消息具有容错性和持久性,并且通信以固定的方式进行:每一轮迭代中,框架会将前一次迭代中的所有消息都发送出去。 Actor model 通常没有这样的时序保证。
      • 容错
        • 由于 Pregel 集型保证在一次迭代中发送的所有消息都会在下一次迭代中被发送,所以先前的迭代必须全部完成,而且所有的消息必须在下一次迭代 开始之前复制到网络中。
        • 即使底层网络可能会丢弃、重复或任意延迟消息,但 Pregel 的实现可以保证在后续迭代中消息在目标顶点只会被处理一次。像 MapReduce 一样,该框架透明地从故障中恢复,以简化 Pregel 顶层算陆的编程模型。
        • 这种容错方式是通过在迭代结束时定期快照所有顶点的状态来实现的,即将其全部状态写入持久存储。如果某个节点发生故障并且其内存中状态丢失,则最简单的解决方也是将整个图计算回攘到上一个检查点,然后重新开始计算。如果算法是确定性的, 并且记录了消息,那么也可以选择性地只恢复丢失的分区
      • 并行执行
        • 顶点不需要知道它运行在哪台物理机器上。当它发送消息到其他顶点时,只需要将消息发送至一个顶点 ID。框架对图进行分区,即确定哪个顶点运行在哪个机器上,以及如何通过网络路由消息,以便它们都能到达正确的位置。
  • 高级 API 与语言
    • 由于手工编写 MapReduc 巳作业太过耗时费力,因此 Hive、Pig、Cascading 和 Crunch 等高级语言和 API 变得非常流行。随着 Tez 的出现, 这些高级语言还能够移植到新的数据流执行引擎,而无需重写作业代码。Spark 和 Flink 也包含他们自己的高级数据流 API
    • 这些高级接口不仅使提高了系统利用率,而且提高了机器级别的作业执行效率。
    • 转向声明式查询语言
      • 通过将声明式特征与高级 API 结合,使查询优化器在执行期间可以利用这些优化方法,批处理框架看起来就更像 MPP 数据库了,井且能够实现性能相当。同时,通过具有运行任意代码和读取任意格式数据的可扩展性,它们依然保持了灵活性的优势。
    • 不同领域的专业化
      • 要实现可重用的通用构建模块
      • 可重复使用统计和数值算法

流处理系统

  • 我们将把事件流视为一种数据管理机制: 一种无界的、持续增量处理的方式
  • 发送事件流
    • 在流处理的上下文中,记录通常被称为事件
    • 本质上是一回事:一个小的、独立的、不可变的对象,该对象包含某个时间点发生的事情的细节。每个事件通常包含一个时间戳,用于指示事件发生的墙上时间
      • 案例
        • 服务器的每一行日志
        • 用户的下单或者浏览
      • 格式
        • 文本
        • JSON
        • 二进制
    • 生成一次,读取多次
    • 在流系统中,相关的时间通常被组合成主题或流
    • 传统数据库无法支持实时的消息通知
    • 消息系统
      • 向消费者通知新事件的常见方法是使用消息系统:生产者发送包含事件的消息,然后该消息被推送给一个或多个消费者
      • 当生产者发送消息比消费者处理快
        • 系统丢弃消息
        • 将消息存储在队列中
        • 激活背压,也称流量控制,组织生产者发送过多消息
      • 如果节点崩溃或者离线,是否会有消息丢失
        • 持久化需要写入磁盘,需要成本,在可以接收消息丢失的系统上,在同样的硬件上可以将获得更高的吞吐量和更低的延迟
        • 是否接收消息丢失取决于应用程序
      • 生产者与消费者之间的直接消息传递
        • UDP 组播广泛应用与金融行业,例如股票市场等低延迟场景
        • 无代理的消息库,过 TCP 或 IP 多播实现发布/订阅消息传递。
        • 使用不可靠的 UDP 消息传递
        • 消费者在网络上公开服务,则生产者可以直接发出 HTTP 或 RPC 请求以将消息推送给消费者
        • 缺点
          • 有限容错,消息丢失
          • 生产者和消费者都需要保持在线
      • 消息代理
        • 它作为服务器运行,生产者和消费者作为客户端连接到它。
        • 生产者将消息写入代理,消费者通过从消息代理那里读取消息来接收消息
      • 消息代理与数据库对比
        • 而大多数消息代理在消息成功传递给消费者时就自动删除消息。这样的消息代理不适合长期的数据存储。
        • 消息代理的工作集很小,如果消费慢了就会占用很多内存,整体吞吐降低
        • 数据库支持二级索引和各种搜索数据的方式,消息代理采用订阅匹配特定模式的主题
        • 数据库查询基于数据快照,消息代理不支持查询
      • 多个消费者
        • 负载均衡式
          • 每一条消息都只被传递给其中一个消费者,所以消费者可以共享主题中处理消息的工作。代理可以任意分配消息给消费者。
        • 扇出式
          • 每条消息都被传递给所有的消费者。扇出允许几个独立的消费者各自“收听”相同的消息广播,而不会相互影响,流相当于多个读取相同输入文件的不同批处理作业
      • 确认和重新传递
        • 消费者随时可能崩溃,消费者消费完事件后需要显式告知消息代理,消息代理才会从队列中删除
        • 消息代理重试时间导致事件发生顺序改变,如果消息之间存在因果关系会导致问题。
    • 分区日志
      • 将数据库的持久存储方居与消息传递的低延迟功能相结合
      • 基于日志的消息存储
        • 日志是磁盘上一个仅支持追加式修改记录的序列,我们可以使用相同的结构来实现消息代理:生产者通过将消息追加到日志的末尾来发消息,消费者通过依次读取日志来接收消息。如果消费者读到日志的末尾,它就开始等待新消息被追加的通知。
        • 为了突破单个磁盘所能提供的带宽吞吐的上限,可以对日志进行分区。不同的节点负责不同的分区,使每个分区成为一个单独的日志,并且可以独立于其他分区读取和写入。然后可以将主题定义为一组分区,他们都携带相同类型的消息
        • 在每个分区中,代理为每个消息分配一个单调递增的序列号或偏移量。这样的序列号是非常有意义,因为分区只能追加,所以分区内的消息是完全有序的。不同分区之间则没有顺序保证。
      • 对比日志与传统消息系统
        • 都支持扇出式消息传递
        • 因为同一分区内的消息将被传递到同一节点,所以消费一个主题的节点数最多等于该主题中的日志分区数
        • 如果单个消息处理缓慢,则会阻碍该分区中的后续消息的处理
      • 消费者偏移量
        • 顺序读取一个分区可以很容易地判断哪些消息已经被处理,代理不需要跟踪每条消息的确认,只需要定期记录消费者的偏移量
      • 磁盘空间使用
        • 如果持续不断地追加日志,磁盘空间最终将被耗尽。为了回收磁盘空间,日志实际上是被分割成段,并且不时地将旧段删除或归档保存。
        • 如果一个消费者的速度慢到难以跟上消息产生的速度,并且远远落后以至于消费者偏移量指向了已经被删除的片段,那么消费者将会错过一些消息。实际上,日志实现了一个有限大小的缓冲区,当缓冲区变搞时,旧的消息就被丢弃,该缓冲区也被称为循环缓冲区或环形 缓冲区。由于该缓冲区在磁盘上,因此它可以非常大。
      • 当消费者跟不上生产者时
        • 基于日志的方法是一种缓冲形式,它具有比较大的缓冲区
        • 当消费者明显落后消息时发出警报,让操作员有时间修复
      • 重新处理信息
        • 可以用旧的偏移量重新开启一个消费队列,并将输出写到不同的位置,以便重新处理最后一段时间的消息,通过改变处理代码可以多次重复此操作。
  • 数据库与流
    • 保持数据同步
      • 多数据系统数据一致性
    • 变更数据捕获
      • 捕获数据库中的更改并不断将相同的更改应用于搜索索引。如果以相同顺序应用于更改日志,那么可以预期搜索索引中的数据与数据库中的数据匹配。搜索索引和任何其他派生的数据系统只是变更流的消费者
      • 实现变更数据捕获
        • 我们可以调用日志消费者的派生数据,变更数据捕获机制可以确保对记录系统所做的所有更改都反映在派生数据系统中,以便派生系统具有数据的准确副本,从本质上讲,变更数据捕获使得一个数据库成为主节点,井将其他变成从节点。由于基于日志的消息代理保留了消息的排序,因此它非常适合从原数据库传输更改事件
      • 原始快照
        • 如果有了数据库所有更改的日志,就可以通过 replay 日志来重建数据库的整个状态。构建新的全文索引需要整个数据库的完整副本,仅仅应用最近更改的日志还不够,因为它会丢失最近未更新的项目。因此,如果没有完整的日志历史记录,则需要从一致的快照开始,数据库的快照必须与更改日志中的已知位置或偏移量相对应,以便在快照处理完成后,知道在哪一点开始应用更改。
      • 日志压缩
        • 存储引擎定期查找具有相同 key 的日志记录,丢弃所有的重复项,井且只保留每个 key 的最新的更新。这个压缩和合并的过程是在后台运行的。
      • 对变更流的 API 支持
        • 数据库将关系数据模型中的输出流表示为表,该表支持事务插入元组,但不支持查询。输出流包含了向该特殊表提交写事务的元组日志,并严格按照事务提交顺序排序。外部消费者可以异步使用此日志并使用它来更新派生数据系统。
    • 时间溯源
      • 在变更捕获中,CDC 记录了操作发生的顺序
      • 时间溯源中,应用程序逻辑基于写入事件日志的不可变事件构成。
      • 从事件日志导出当前状态
        • 使用事件溯源的应用程序需要记录事件的日志,井将其转换为适合向用户显示的状态 。这种转换可以使用任意的逻辑,但它应该是确定性的,以便可以再次运行它并从事件日志中派生相同的应用程序状态。
        • 用于更新记录的 CDC 事件通常包含记录的全部新版本,因此 key 的当前值完全由该 key 的最近事件确定,井且日志压缩可以丢弃相同 key 之前的事件
        • 使用事件溯源在更高的层次上对事件建模: 事件通常用来表达用户行为的意图,而不是一种对行为结果进行相应状态更新的机制
      • 命令和事件
        • 事件溯源的哲学是小心的区分事件和命令。当来自用户的请求第一次到达时,它最初是一个命令:此时它可能仍然会失败,例如因为违反了某些完整性条件。应用程序必须首先验证它是否可以执行该命令。如果验证成功并且命令被接受,它将变成一个持久且不可变的事件。
  • 状态,流与不可变
    • 事务日志记录了对数据库所做的所有更改。高速追加是更改日志的唯一方位。从这个角度来看,数据库的内容保存了日志中最新记录值的缓存。日志是事实。数据库是日志子集的缓存。该缓存子集恰好是来自日志的每个记录和索引值的最新值。
    • 日志压缩则是链接日志与数据库区别的一种方式。它仅保留每条记录的最新版本,井丢弃被覆盖的版本。
    • 不变事件的优势
      • 恢复历史数据
      • 记录历史操作
    • 相同的事件日志中派生出多个视图
      • 通过从不变事件日志中分离可变状态,可以从相同的事件日志派生出多个面向读取的表示方式
    • 并发控制
      • 事件捕获和变更数据捕获的最大缺点是事件日志的消费者通常是异步的,所以用户可能会写入日志,然后从日志派生的视图中读取,却发现这些写操作还没有反映在读取视图中
      • 一种解决方案是同步执行读取视图的更新,并将事件追加到日志中。这需要一个事务来将写入操作合并到一个原子单元中,所以要么需要将事件日志和读取视图保存在同一个存储系统中,要么需要跨不同系统的分布式事务。
      • 另一方面,从事件日志导出当前状态也简化了并发控制。对于多对象事务的大部分需求源自单个用户需要在不同地方改变数据的操作。通过事件溯源,可以设计一个事件,使其成为用户操作的独立描述。用户操作只需要在一个地方进行一次写操作,即将事件追加到日志中,这很容易使其原子化。
    • 不变形的限制
      • 隐私,彻底删除数据
  • 流处理
    • 三种处理
      • 可以将事件中的数据写入数据库、缓存、搜索索引或者类似的存储系统,然后被其他客户端查询
      • 可以通过某种方式将事件推送给用户
      • 可以处理一个或多个输入流以产生一个或多个输出流。数据流可能会先经过由几个这样的处理阶段组成的流水线,最终在输出端结束
    • 流处理的适用场景
      • 监控目的的流应用
        • 信用风控
        • 金融交易监控
        • 机器状态监控
        • 军事情报系统报警
      • 复杂事件处理
        • 在流中搜索特定模式的事件
      • 流分析
        • 测量某种类型事件的速率
        • 计算一段时间内某个值的波动平均值
        • 将当前的统计数据与以前的时间间隔进行比较
      • 维护物化视图
        • 使用数据库更改流来保持派生数据系统与源数据库之间的同步,可以将这些示例视为一种维护物化视图的例子: 对某个数据集导出一个特定的试图以便高效查询,并在底层数据更改时自动更新该导出视图。
      • 在流上搜索
        • 需要基于一些复杂条件来搜索单个事件, 搜索流是把查询条件先保存下来,所有文档流过查询条件,筛选出结果。
      • 消息传递和 RPC
    • 流的时间问题
      • 流处理系统经常需要和时间打交道,尤其是在用于分析目的时,这些分析通常使用时间窗口
      • 事件延迟处理会引发流处理各种问题,需要有介入处理这种问题
      • 混淆事件时间与处理时间会导致错误的结果,重启流处理系统倒是的事件积压,出现处理高峰
      • 什么时候准备就绪
        • 无法确定是否完全收到特定窗口内所有的事件
          • 忽略滞后的事件,丢失大量数据时报警
          • 发布一个更新,针对滞后事件的一个更新值。可能还需要收回以前的输出
          • 用一个特殊值来触发窗口处理
      • 你用谁的钟
        • 为了调整不正确的设备时钟,一种是方法是记录三个时间戳
          • 根据设备的时钟,记录事件发生的时间。
          • 根据设备的时钟,记录将事件发送到服务器的时间。
          • 根据服务器时钟,记录服务器收到事件的时间。
      • 窗口类型
        • 轮转窗口
          • 翻滚窗口长度固定,每个事件都属于一个窗口
        • 跳跃窗口
          • 窗口长度固定,窗口之间有重叠以提供平滑过渡
        • 滑动窗口
          • 滑动窗口包含在彼此的某个间隔内发生的所有事件, 滑动窗口可以通过保留按时间排序的事件缓冲区井且在从窗口过期时移除旧事件来实现
        • 会话窗口
          • 与其他窗口类型不同,会话窗口没有固定的持续时间。相反,它是通过将同一用户在时间上紧密相关的所有事件分组在一起而定义的,一旦用户在一段时间内处于非活动状态,则窗口结束。会话分析是网站分析中常见的一种需求
      • 流式 join
        • 流和流 join
          • 搜索事件和点击事件
        • 流和表 join
          • 流处理提前加载表的内容,在流处理时匹配相关 ID
        • 表和表 join
          • 流视图和流视图的 join,每当视图更新刷新 join 结果
      • join 的时间依赖性
        • 不同流和分区之间的事件,顺序是如何确定的
        • 通常的做法是,当数据发生变化后赋予一个新的关联 ID
    • 流处理的容错
      • 批处理容错方法可以确保批处理作业的输出与没有出错时的最终结果相同
      • 微批处理和校验点
        • 将流分解成多个小块,并像小型批处理一样处理每个块。这种方法被称为微批处理
        • Apache Flink 中使用了该方法的一个变体,它定期生成状态滚动检查点并将其写入持久化存储。如果流操作发生崩溃,它可以从最近的检查点重新启动,并丢弃在上一个检查点和崩溃之间生成的所有输出
      • 重新审视原子提交
        • 在出现故障时,为了看起来实现恰好处理了一次,我们需要确保当且仅当处理成功时,所有输出和副作用才会生效.
      • 幕等性
        • 容等操作是可以多次执行的操作,并且它与只执行一次操作具有相同的效果
      • 故障后重建状态
        • 任何需要状态的流处理,比如基于窗口的聚合以及表和索引的 join 操作,都必须确保在故障发生后状态可以恢复
        • 一种选择是将状态保存在远程存储中井采取复制,然而为每个消息去查询远程数据库可能会很慢。另一种方也是将状态在本地保存,并定期进行复制。之后,当流处理器从故障中恢复时,新任务可以读取副本的状态、井且在不丢失数据的情况下恢复处理。
        • 在某些情况下,甚至可能不需要复制状态,而是从输入流开始重建

数据系统的未来

  • 每一个软件,即使是所谓的“通用”数据库,也都是针对特定的使用模式而设计的, 第一个挑战就是弄清楚软件产品与他们适合运行环境之间的对应关系
  • 在复杂的应用程序中,数据通常以多种不同的方式被使用。不太可能存在适用于所有不同环境的软件,因此你不可避免地要将几个不同的软件组合在一起,以提供应用程序的功能性。
  • 数据集成
    • 采用派生数据来组合工具
      • 许多应用程序需要结合两种或以上不同的工具来满足所有需求。
      • 数据集成的需求通常只有在缩小井考虑整个组织框架内数据流时才会变得更加凸显
      • 为何需要数据流
        • 通过单个系统来决定所有输入的写入顺序,那么以相同的顺序处理写操作就可以更容易地派生出数据的其他表示形式
        • 无论是使用变更数据捕获还是事件获取日志,都不如简化总体顺序的原则重要
        • 根据事件日志来更新一个派生数据系统通常会比较好实现,并且可以实现确定性和幂等性
      • 派生数据与分布式事务
        • 分布式事务通过使用锁机制进行互斥来决定写操作的顺序
        • CDC 和事件源使用日志进行排序
        • 分布式事务使用原子提交来确保更改只生效一次
        • 基于日志的系统通常基于确定性重试和幕等性
        • 事务系统通常提供线性化,保证读自己的写一致性
        • 派生数据系统通常是异步更新的,所以默认情况下它们无法提供类似级别保证。
        • 作者认为基于日志的派生数据是集成不同数据系统的最有前途的方法
      • 全局的局限
        • 完全有序的日志需要一个主节点来决定排序,随着系统变大,越来越复杂时,瓶颈就开始出现了
        • 事件吞吐量大于单台的可处理上限时,需要分区到多个节点,不同分区之间的事件顺序难以保证
        • 如果服务器在不在的数据中心,数据同步效率效率低,通常每个数据中心都有自己的主节点,两个不同数据中心的事件顺序不确定。
        • 无状态的微服务之间不共享状态,两个事件来自不同的服务时,这些事件没有清楚的顺序。
        • 网络延迟甚至离线导致的数据不一致问题。
        • 设计突破单节点吞吐量甚至在广域地理环境分布的共识算能仍然是一个有待研究的开放性问题
      • 排序事件以捕获因果关系
        • 逻辑时间戳可以在无协调者情况下提供的全序关系,所以当全序关系广播不可行时可以用得上,但是,它们仍然需要接收者 去处理那些乱序事件,井且需要额外的元数据
        • 如果可以记录一条事件来标记用户在做决定以前所看到系统状态,并给该事件一个唯一的标识符,那么任何后续的事件都可以通过引用该事件标识符来记录因果关系
        • 冲突解决算法,可以处理异常顺序的事件。
    • 批处理和流处理集成
      • 数据整合的目标是确保数据在所有正确的地方以正确的形式结束
      • 批处理的数据是已知的有限大小
      • 流处理运行在无界的数据集上
      • 一种类型的处理可以通过另一种类型来模拟,尽管性能特征有所不同。
      • 保持派生状态
        • 批处理
          • 倡导确定性
          • 纯函数操作,输出仅依赖输入
          • 输出不可变
          • 追加式输出结果
        • 流处理
          • 除了批处理的特征
          • 扩展了操作来支持可管理的,容错的状态
        • 拥有良好定义的输入和输出的确定性函数原理上不仅有利用容错,还简化了组织中数据流的推理
        • 从数据管道的角度来看,对于从一个事物派生出另一个事物,通过功能应用程序代码推动一个系统中的状态更改以及将这种效果应用到派生系统,都是有帮助的。
        • 为应用程序演化而重新处理数据
          • 为维护系统提供了一个良好的机制,平滑支持新功能以及多变的需求
          • 通过重新处理,可以将数据集重组为一个完全不同的模型,以便更好地满足新要求。
          • 派生视图可以逐步演变,知道所有用户迁移到新视图,如果有风险,总有一个工作系统可以回退
        • Lambda 架构
          • Lambda 体系结构的核心思想是进来的数据以不可变事件形式追加写到不断增长的数据集,类似于事件源。基于这些总事件,可以派生出读优化的视图。
        • 统一批处理和流处理
          • 支持以相同的处理引擎来处理最新事件和处理历史回放事件。
          • 支持只处理一次语义
          • 支持依据事件发生时间而不是处理时间进行窗口化
  • 分拆数据库
    • 编排多种数据存储技术
      • 创建一个索引
        • 数据库必须扫描表的一致性快照,挑选出所有被索引的字段值,对它们进行排序,然后得到索引。接下来,必须处理从一致性快照创建以来所累计的写入操作,完成后,只要有事务写入表中,数据库就必须持续保持索引处于最新状态。
        • 索引是现有数据的一个视图
      • 元数据库
        • 联合数据库:统一端读
          • 可以为各种各样的底层存储引擎和处理方法提供一个统一的查询接口:一种称为联合数据库或聚合存储的方法
        • 分离式数据库:统一写端
          • 在构建跨多个存储系统的数据库时,我们同样需要确保所有数据更改都会体现在所有正确的位置上,即使中间发生了某些故障。多个存储系统可以可靠地连接在一起
      • 分离式如何工作
        • 基于日志的集成的一大优势是各个组件之间的松耦合,这体现在两个方面
          • 在系统级别,异步事件流使整个系统在应对各个组件的中断或性能下降时表现更加稳健,日志可以慢慢消费且不会丢失。
          • 在人员角度看,分离式数据系统使得不同的团队可以独立的开发、改进和维护不同的软件组件和服务。专业化使得每个团队都可以专注于做好一件事情,且与其他系统维护清晰明确的接口,事件日志提供了一个足够强大的接口,不但能捕获相当强的一致性,同时也普遍适用于几乎任何类型的数据。
      • 分离式与集成式系统
        • 目前形式的数据库不会被取代
          • 维护流处理器中的状态仍然需要数据库
          • 专门的查询引擎对于特定的工作负载仍然很重要
        • 运行多个不同的基础架构所带来的复杂性可能确是一个问题
          • 不同的学习曲线
          • 不同的配置
          • 不同的操作习惯
        • 单个集成的软件产品有可能确实在其针对的负载上表现更好,性能更可预测
        • 分离的目标是让你可以将多个不同的数据库组合起来,以便在更广泛的工作负载范围内实现比单一软件更好的性能
        • 当没有单一的软件能满足所有需求时,分离和组合的优势才会显现出来
    • 围绕数据流设计应用系统
      • 应用程序代码作为派生函数
        • 当某个数据集从另一个数据集派生而来时,它一定会经历某种转换函数
          • 二级索引是一种派生的数据集,它具有一个简单的转换函数:对于主表中的每一行或者一个文档,挑选那些索引到的列或者字段值,并且按照值进行排序
          • 通过各种自然语言处理函数创建全文搜索索引,然后构建用于高效查找的数据结构
          • 在机器学习系统中,可以考虑通过应用各种特征提取和统计分析功能从训练数据中导出模型。
          • 缓存通常包含那些即将显式在用户界面的聚合数据
      • 应用程序代码与状态分离
        • 作者认为系统的某部分专注于持久性数据存储,同时有另外一部分专门负责运行应用程序代码是有道理的。这两部会有交互,但是各自仍保持独立运行。
        • 数据库充当一种可以通过网络同步访问的可变共享变量。应用程序可以读取或更新变量,数据库负责持久性,提供一些井发控制和容错功能。
      • 数据流: 状态变化和应用程序代码之间的相互影晌
        • 当维护报生数据时,状态更改的顺序通常很重要,如果从事件日志中派生出了多个视图,每个试图都需要按照相同的顺序来处理这些事件,以使它们互相保持一致。
        • 容错性是派生数据的关键: 丢失哪怕单个消息都会导致派生数据集永远无法与数据源同步。消息传递和派生状态更新都必须可靠。
      • 流式处理与服务
        • 面向服务的结构优于单体应用程序之处在于松相合所带来的组织伸缩性: 不同的团队可以在不同的服务上工作,这减少了团队之间的协调工作
        • 数据流系统与微服务理念有很多相似的特征。但是,底层的通信机制差异很大:前者是单向、异步的消息流,而不是同步的请求/响应交互
        • 最快和最可靠的网络请求就是根本没有网络请求
        • 订阅变化的流,而不是在需要时去查询状态,使我们更接近类似电子表格那样的计算模型: 当某些数据发生更改时,依赖于此的所有派生数据都可以快速更新
    • 观察派生状态
      • 写路径和读路径涵盖了数据的整个过程,从数据收集到数据使用
      • 写路径可以看作是预计算的一部分,即一旦数据进入,即刻完成,无论是否有人要求访问它。
      • 过程中的读路径则只有当明确有人要求访问时才会发生。
      • 实体化视图与缓存
        • 全文索引:写路径构建了索引,读路径不需要扫描全部文档
        • 缓存:对常见查询进行预计算,使这部分查询可以快速响应,其他的依然依靠索引,当添加新的数据时,视图也要随之更新
        • 它们主要是调整读、写路径之间的边界。通过预先计算结果,写路径上承担了更多的工作,而读路径则可以简化加速
      • 有状态,可离线客户端
        • SPA 应用支持很多有状态的功能,移动 app 也可以在本地保存很多状态,很多交互不需要和服务器通信。
        • 我们可以将设备上的状态视为服务器上的状态缓存。屏幕上的呈现是一种客户端对象模型的实体化视图;而客户端的对象模型则是远程数据中心在本地的状态副本
      • 状态更改推送至客户端
        • 更新的 HTTP 协议支持从服务端推送事件到客户端,从而缩小两者之间状态的滞后程度
      • 端到端的事件流
        • 状态变化可以通过端到端的写路径流动:某个设备上交互行为触发了状态变化,通过事件日志、派生数据系统和流式处理等,一直到另一台设备上用户观察到状态。这些状态变化传播的延迟可以做到很低的水平,例如端到端只需一秒。
        • 为了将写路径扩展到最终用户,我们需要从根本上重新思考构建这些系统的方式:从请求/响应交互转向发布/订阅数据流
        • 更具晌应性的用户界面和更好的离线支持
      • 读也是事件
        • 当写入和读取都被表示为事件,并且被路由到相同的 stream operator 统一处理时,我们实际上是在查询流和数据库之间执行 stream-table join 操作。读事件需要发送到保存数据的数据库分区节点上
        • 以日志方式记录读事件可能还可以帮助跟踪系统级别的事件因果关系和数据源: 它可以重建用户在做出某个决定之前看到的内容
      • 多数据分区处理
        • 对于仅涉及单个分区的查询,通过流来发送查询并收集响应事件流可能显得有些大材小用 。然而 ,这种方怯却开启了 一种分布式执行复杂查询的可能性,这需要合并来自多个分区的数据,并很好地借助底层流处理系统所提供的消息路由、分区和 join 功能。
  • 端到端的正确性
    • 数据库的端到端争论
      • 仅仅因为应用程序使用了具有较强安全属性的数据系统,并不能意味着应用程序一定保证没有数据丢失或损坏,应用程序的 bug 或从数据库删除数据
      • Exactly-once 执行操作
        • 使操作满足幂等性
        • 维护额外的元数据确保节点丢失和切换中必要的 fencing 措施。
      • 重复消除
        • 许多模式都需要重复消除
        • TCP 采用序列号检测包丢失或重复,井最终确保数据包以正确的顺序接受,丢失的数据包都会被重新发送,并且在将数据交给应用程序之前,TCP 堆栈将负责删除重复的数据包。
        • 网络不佳的情况下,客户端和服务端之间的事件可能会丢失,客户端上再次操作会导致事件重复提交
      • 标识操作符
        • 可以为每个事件生成一个唯一的标识符号,确保在服务端每个事件只被执行一次
      • 端到端的争论
        • 底层的可靠性功能本身不足以确保端到端的正确性。
      • 在数据系统中采用端到端的思路
        • 即使应用程序所使用的数据系统提供了比较强的安全属性,也并不意味着应用程序就一定没有数据丢失或损坏,应用程序本身也需要采取端到端的措施,例如重复消除。
        • 事务处理的代价很高,特别是在楼及异构存储技术时
        • 大多数的应用程序级别的容错机制无法正常工作导致了数据丢失或者损坏
        • 探索更好的容错抽象是很有必要的
    • 强制约束
      • 唯一性约束需要达成共识
        • 基于主节点做出所有的决策,就能够达成共识
        • 无法支持异步的多主节点复制,发生写冲突,无法保证值的唯一性
        • 单点失败,扩展性问题
      • 基于日志的消息传递唯一性
        • 日志机制可以确保所有消费者以相同的顺序查看消息,这种保证在形式上被称为全序关系广播,它等价于共识问题
        • 在基于日志的消息传递的分离式数据库系统中,我们可以采用非常类似的方法来保证唯一性约束
        • 任何可能冲突的写人都被路由到特定的分区并按顺序处理, 在每个分区内事件有唯一顺序
      • 多分区请求处理
        • 通过将多分区事务划分为两个不同分区的处理阶段,并使用端到端的请求 ID,实现了同样的正确性
    • 实效性与完整性
      • 实效性
        • 时效性意味着确保用户观察到系统的最新状态
      • 完整性
        • 完整性意味着避免数据损坏,即没有数据丢失,也没有互相矛盾或错误的数据。尤其是,如果将某些派生数据集作为基础数据的视图来进行维护,派生必须做到正确, 如果完整性受到破坏, 这种不一致将是永久性的
      • 数据流系统的正确性
        • 可靠的流处理系统可以在不需要分布式事务和原子提交协议的情况下保持完整性
          • 将写入操作的内容表示为单条消息,可以轻松地采用原子方式编写,这种方法非常适合事件源
          • 使用确定性派生函数从该条消息报生所有其他状态的更新操作
          • 通过所有这些级别的处理来传递客户端生成的请求 ID,实现端到端重复消除和容等性。
          • 消息不可变,并支持多次重新处理派生数据,从而使错误恢复变得更容易
      • 宽松的约束
        • 传统的需要达成唯一性约束需要通过单节点汇聚所有分区事件实现
        • 很多应用程序采取了弱一致性
          • 如果万一两个人同时注册了相同的用户名或预订了同一个座位,则可以向其中一个发送道歉消息,并要求他们选择另一个,这种纠正错误的措施被称为补偿性事务
          • 如果客户订购的商品超出当前库存,则可以追加补充库存,但需要为延误发货向客户道歉,并为他们提供折扣
        • 很多场景中,实际上可以接受违反约束,通过后续的事务补偿来恢复最终的完整性
      • 无需协调的数据系统
        • 两种观察
          • 数据流系统可以保证派生数据的完整性,无需原子提交,线性化或跨分区的同步协调。
          • 唯一性约束要求时效性和协调性,但是只要整体上保证完整性,即使发生暂时约束破坏,可以事后进行修复,因此许多应用实际上采用宽松式的约束并没有问题。
        • 数据流系统可以为应用提供数据管理服务而不需要协调,同时仍然提供强大的完整性保证。这种避免协调的数据系统具有很大的吸引力:与需要执行同步协调的系统相比,可以实现更好的性能和容错能力
        • 跨数据中心多主节点异步复制系统,任何一个节点都可以独立运行,实效性弱,完整性强
        • 同步协调只在有必要的时候使用
        • 另一种理解协调和约束的方毡是:它们减少了由于不一致而引发的道歉数量,但是也可能降低系统的性能与可用性,并由此可能增加由于业务中断而引发的道歉数量 。你不能将道歉减少到零,但是你可以根据自己的需求找到最佳的折中方案:选择一个合适点使得既不能有太多不一致,也不能出现太多可用性问题。
    • 信任,但要确认
      • 总会有一些违反假设的事情发生,硬件和软件,我们不能假设依赖的基础设施不会出错
      • 软件缺陷时的完整性
        • 软件存在的 bug,无法通过校验和来捕获,一旦发现会造成数据的破坏,需要在设计,测试,代码检查方面经过大量的努力。
      • 不要盲目信任承诺
        • 硬件和软件并不能总是处于理想状态,数据损坏迟似乎只是迟早的事情而无也避免。因此,我们至少需要有办桂来查明数据是否已经损坏,以便之后修复这些数据,并试图找出错误的根掘。检查数据的完整性也被称为审计 。
        • 成熟的系统同样会考虑不太可能的事情出错的可能性,并且主动管理这种风险。
        • 如果想确保你的数据仍然在那,只能不断地去读取和检查。大多数情况下,情况一切正常,但万一发现异常,则越早发现问题越好。基于此,今早尝试从备份来恢复数据,否则当你丢失数据 ,你会发现连备份也已经破坏,那时将为时已晚。千万不要盲目地相信系统总是正常工作。
      • 验证的文化
        • 许多人认为正确性的保证是绝对的,而没有为少见但可能的数据损坏而有所准备,作者希望将来会有更多的自我验证或自我审计系统,不断的检查自身的完整性,而不是依赖盲目的信任。
      • 可审计性的设计
        • 基于事件的系统可以提供更好的可审计性。在事件源方法中,用户对系统中的输入都被表示为一个单一的不可变事件,并且任何结果状态的更新都是依据该事件派生而来。派生可以很确定性的执行并且是可重复的,所以通过相同版本的派生代码来处理相同的事件日志将产生相同的状态更新。
        • 清楚地控制数据流可以使数据的来源管理更加清晰,从而使完整性检查更加可行
        • 对于事件日志,我们可以使用哈希校验来检查存储层是否发生数据破坏
        • 对于派生状态,我们可以重新运行对相同的事件日志执行的批处理和流处理,以检查是否得到相同的结果,甚至是并行运行一个冗余派生系统。
        • 确定的和定义清晰数据流也有助于系统调试和跟踪系统的操作,从而确定为什么发生了某些事情。如果中间发生了意外事件,可以提供诊断能力来重现导致意外事件的相同环境,这种精准复现历史时刻的调试能力将非常有价值。
      • 端到端论点的再讨论
        • 检查数据系统的完整性最好以端到端的方式进行:在完整性检查中所包含的系统部件越多,则过程中某些阶段发生无告警的数据破坏的概率就越少。如果我们可以检查整个派生系统流水线是端到端正确的,那么路径中的任何磁盘、网络、服务和算法已经全部囊括在内了。
        • 持续的端到端完整性检查可以提高你对系统正确性的信心,从而使你的发展速度更快
      • 审计数据系统的工具
        • 目前,将可审计性列为高优先级别关注的数据系统井不多。有些应用程序实现了内部的审计机制,例如将所有更改记录到单独的审计表中,但是保证审计日志的完整性和数据库状态仍然有些困难
  • 做正确的事
    • 每个系统的都有其构建目的,我们所采取的每一个行动都会产生有意或无意的后果。目的可能像赚钱一样简单,但对世界带来的影响可能远远超出我们的初衷。建立这些系统的工程师有责任仔细考虑这些后果,井有意识地决定我们想要生活在什么样的世界。
    • 许多数据集都是关于人的: 他们的行为、他们的兴趣和他们的身份。我们必须以人性和尊重来对待这些数据。用户也是人,人的尊严是最重要的。
    • 作者认为软件工程师如果只专注于技术而忽视其后果是不够的,道德责任也是我们要担起的责任。评判道德总是困难的,但它太重要了以至无论如何不能被忽视。
    • 预测性分析
      • 通过大数据算法预测一个人的犯罪倾向,自动化的系统则有可能系统地、任意地排除某个人参与社会活动,而且是在这个人没有任何犯罪证据的情况下,井且对他/她来说几乎没有上诉的机会。
      • 偏见和歧视
        • 算法做出的决定不一定比人类做得更好或更糟
        • 如果在算法的输入中存在系统性偏见,那么系统很可能吸收并在最终输出中放大这种偏见
        • 预测分析系统只是基于过去而推断,如果过去是有偏见的,它们就会把这种偏见编码下来。如果我们希望未来比过去更好,那么就需要道德想象力,而这只有人类才具备。数据和模型只应该是我们的工具,而不是我们的主人。
      • 责任与问责
        • 基于机器学习的评分算法通常使用更广泛的输入范围,而且更加不透明,更难理解某个特定决策是如何发生的,以及是否有人受到不公正的对待
        • 盲目地相信数据至高无上不仅是误解的,而且是非常危险的 。随着数据驱动的决策变得越来越普遍,我们需要弄清楚如何使算陆更负责任和透明,如何避免强化现有的偏见,以及如何在错误不可避免时加以修复。
        • 我们还需要弄清楚如何防止数据被滥用,井努力发挥数据的正面作用
      • 反馈环路
        • 当预测分析影响人们的生活时,特别是由于自我强化反馈环路而出现一些有害问题。由于不合适的假设,产生了这样一个隐藏在数学严谨性和数据的伪装背后的下降旋涡。
        • 我们不是总能预测什么时候发生这样的反馈环路。然而,通过思考整个系统可以预测许多后果,这是一种被称为系统思维 的方法。我们可以尝试理解一个数据分析系统是如何响应不同的行为、结构和特征。系统是否强化和扩大了人们之间存在的差异?还是试图打击不公平性? 即使有最好的意图,我们也必须小心意外的后果
    • 数据隐私与追踪
      • 跟踪行为数据对于许多面向用户的在线服务变得越来越重要, 这些功能需要一定量的用户行为跟踪,井且用户也可以从中受益。但是,根据公司的商业模式,追踪往往不止于此。如果服务是通过广告获得资助的,那么广告主就是实际的客户,而用户的利益则是次要的。跟踪数据会更加详细,分析变得更加深入,数据也会被保留很长时间,以便为营销目去建立每个人的详细资 料。现在,公司和被收集数据的用户之间的关系开始变得和以往大不一样了。用户得到免费的服务,并尽可能地被引诱参与到服务中。对用户的追踪不再是服务与个人,而是服务于资助广告客户的需求。我认为这种关系可以用一个更阴暗的词来描述 : 监视。
      • 监视
        • 当监控被用来确定生活中重要的事情,例如保险或就业等方面的东西时,它就开始变得不那么亲切了。此外,数据分析可以揭示出令人惊讶的侵入性的事情
      • 赞成与选择的自由
        • 用户几乎不知道什么样的个人数据会进入到数据库,或者数据是如何保留和处理的,大多数隐私政策的条款也极尽所能地搞得含混不清。不清楚他们的数据会发生什么,用户就不能给予任何有意义的认同。通常,来自用户的数据还被用到了不是该服务的用户身上,并且该用户根本就没有同意数据收集的任何条款。
        • 数据是通过单向过程从用户提取而来,而不是通过真正的互惠关系,也不是公平的价值交换。没有对话,用户无战选择提供多少数据以及他们会收到什么样的服务 :服务与用户之间的关系是非常不对称的 :这些条款是由服务提供商所设置,而不是由用户
        • 由于担心服务跟踪用户而决定拒绝使用,这只对极少数拥有足够的时间和知识来充分了解隐私政策的人群可以称得上是一种选择,并且他们可以不需要担心由此可能会失去某些机会而被迫参与这些服务。然而,对于处境较差的人来说,选择自由没有意义:对他们来说,被监视变得不可避免。
      • 数据隐私和使用
        • 拥有隐私并不意味着一切事情都要保密:它意味着你可以自由选择向谁展示,并展示哪些东西,要公开什么,以及要保密什么。隐私权是一个决定权:每个人都能够决定在各种情况下如何在保密和透明之间取舍。这事关个人的自由和自主。
        • 这些公司最终选择对大部分数据继续保持私密,因为泄露数据会引起可怕的后果,井且会损害它们的商业模式。关于用户的隐私信息通常是间接地被泄露,例如借助数据分析,将广告投放给特定人群
        • 互联网服务使得在没有用户同意的情况下积累大量敏感信息更加容易,并且在用户不知情相关后果的前提下大规模地使用它。
      • 数据作为资产和权力
        • 数据中介公司的存在也印证了个人数据是宝贵资产的说法,这个数据中间商是一个秘密行业,从事采购、汇总、分析、推 断和兜售侵入性个人数据,主要是为了营销目的。很多初创公司主要靠它们的用户量来估价。
        • 收集数据时,一定要综合考量
        • 此外,审视他人但避免自我审查是最重要的权力形式之一。尽管今天的科技公司并没有公开地寻求某些权力,但是它们所积累的数据和知识给了它们很大的控制权力,而且很多是在私下进行,不在公众监督之内。
      • 记住工业革命
        • 数据是信息时代的关键性特征。互联网,数据存储,处理器和软件驱动的自动化正在对全球经济和人类社会产生重大影响。由此不由得联想到工业革命, 但工业革命也带来了注入环境污染,工人处境恶劣等一系列问题。
        • 正如工业革命存在需要被管理的黑暗面一样,向信息时代的过搜也有需要面对和解决的重大问题。作者相信收集和使用数据就是其中一个
      • 立法与自律
        • 数据保护法可能有助于维护个人的权利
        • 从根本上说,我认为需要对针对个人数据的技术领域有观念上转变。我们应该停止过度以用户为衡量指标,牢记用户值得尊重。我们应该主动调整数据收集和处理流程,建立和维持与那些依赖我们软件的人们之间的信任关系。我们应该主动向用户介绍他们的数据如何使用,而不是让他们蒙在鼓里全然不知
        • 我们应该允许每个人维护自己的隐私,即控制自己的数据而不是通过监视来窃取他们的控制权
        • 我们不应该永远保留数据,一且不再需要,就尽快清除它们
        • 一个很有前途的方陆是通过加密协议来实施访问控制,而不仅仅是通过策略
        • 总的来说,观念与态度的变化都是必要的。