zoukankan      html  css  js  c++  java
  • [读书笔记]数据密集型应用系统设计 第一部分 数据系统基础

    第1章 可靠、可扩展与可维护的应用系统(Reliable, Scalable, and Maintainable Applications)

    可靠性(Reliability)

    即使发生了某些错误,系统仍可以继续正常工作。
    • 硬件故障:硬件冗余方案,软件容错。
    • 软件错误
    • 人为失误
    可靠性的重要性:错误会导致效率下降,损失营收和声誉。

    可扩展性(Scalability)

    描述负载
    描述性能
    延迟与晌应时间
    延迟(latency)和响应时间(response time)容易说淆使用,但它们并不完全一样。通常响应时间是客户端看到的:除了处理请求时间(服务时间,service time )外,还包括来回网络延迟和各种排队延迟。延迟则是请求花费在处理上的时间。
     
    为了弄清楚异常值有多糟糕,需要关注更大的百分位数如常见的第95、99和99.9(缩写为p95、p99和p999)值。作为典型的响应时间阔值,它们分别表示有95% 、99% 或99.9%的请求响应时间快于阈值。例如,如果95百分位数响应时间为l.5s ,这意味着100 个请求中的95个请求快于1.5s,而5个请求则需要1.5s或更长时间。
     
    采用较高的响应时间百分位数(tail latencies,尾部延迟或长尾效应)很重要,因为它们直接影响用户的总体服务体验。例如,亚马逊采用99.9百分位数来定义其内部服务的响应时间标准,或许它仅影响1000个请求中的1个。
     
    百分位数通常用于描述、定义服务质量目标(Service Level Objectives, SLO)和服务质量协议(Service Level Agreements,SLA),这些是规定服务预期质量和可用性的合同。例如一份SLA合约通常会声明,响应时间中位数小于200ms,99%请求的响应时间小于1s,且要求至少99.9%的时间都要达到上述服务指标。这些指标明确了服务质量预期,井允许客户在不符合SLA的情况下进行赔偿。
     
    -应对负载增加的方法
    -垂直扩展、水平扩展

    可维护性(Maintainability)

    我们将特别关注软件系统的三个设计原则:
    • 可运维性:运维更轻松
    • 简单性:简化复杂度。简化系统设计并不意味着减少系统功能,而主要意味着消除意外方面的复杂性。消除意外复杂性最好手段之一是抽象。
    • 可演化性:易于改变

    第2章 数据模型与查询语言(Data Models and Query Languages)

    关系模型与文档模型(Relational Model Versus Document Model)

    现在最著名的数据模型可能是SQL,关系数据库的核心在于商业数据处理。
    • NoSQL的诞生
    • 对象-关系不匹配
    • 多对一与多对多的关系
    文档数据库是否在重演历史?
    • 网络模型,又称CODASYL模型
    • 关系模型
    • 文档数据库的比较
    关系数据库与文档数据库现状
    • 哪种数据模型的应用代码更简单?
    • 文档模型中的模式灵活性
      • 文档数据库应该是读时模式(数据的结构是隐式的,只有在读取时才解释),与写时模式(关系数据库的一种传统方法,模式是显式的,并且数据库确保数据写入时都必须遵循)相对应。
    • 查询的数据局部性
    • 文档数据库与关系数据库的整合
    常见的文档数据库有MongoDB, Amazon DynamoDB, Couchbase, Azure Cosmos DB, CouchDB等。
    常见的关系数据库有Oracle, MySQL, SQL Server, PostgreSQL, Db2等
    数据库排名参考:https://db-engines.com/en/ranking

    数据查询语言(Query Languages for Data)

    SQL是一种声明式查询语言,而很多常用的编程语言都是命令式,如Java,Python,JavaScript等。
    声明式查询语言很有吸引力,它比命令式API更加简洁和容易使用。但更重要的是,它对外隐藏了数据库引擎的很多实现细节,这样数据库系统能够在不改变查询语句的情况下提高性能。
    声明式语言通常适合于并行执行。
     
    Web上的声明式查询
    如声明式CSS样式表li.selected > p {...}
     
    MapReduce查询
    MapReduce既不是声明式查询语言,也不是一个完全命令式的查询API,而是介于两者之间:查询的逻辑用代码片段来表示,这些代码片段可以被处理框架重复地调用。它主要基于许多函数式编程语言中的map(也称为collect)和reduce(也称为fold或inject)函数。
     
    MapReduce是一个相当底层的编程模型,用于在计算集群上分布执行。而SQL这样的更高层次的查询语言可以通过一些MapReduce操作pipeline来实现,当然也有很多SQL的分布式实现并不借助MapReduce。

    图状数据模型(Graph-Like Data Models)

    1. 属性图,包括顶点和边。
    2. Cypher查询语言:一种用于属性图的声明式查询语言。
    3. SQL中的图查询
    4. 三元存储与SPARQL,三元存储包含三部分,主体,谓语,客体。
      1. 语义网
      2. RDF数据模型
      3. SPARQL查询语言
    5. Datalog基础

    小结

    新的非关系“NoSQL”数据存储在两个主要方向上存在分歧:
    1. 文档数据库的目标用例是数据来自于自包含文挡,且一个文档与其他文档之间的关联很少。
    2. 图数据库则针对相反的场景,目标用例是所有数据都可能会互相关联。
    所有这三种模型(文档模型、关系模型和图模型),如今都有广泛使用,并且在各自的目标领域都足够优秀。
     
    文档数据库和图数据库有一个共同点,那就是它们通常不会对存储的数据强加某个模式,这可以使应用程序更容易适应不断变化的需求。但是,应用程序很可能仍然假定数据具有一定的结构,只不过是模式是显式(写时强制)还是隐式(读时处理)的问题。

    第3章 数据存储与检索(Storage and Retrieval)

    数据库核心:数据结构(Data Structures That Power Your Database)

    为了高效地查找数据库中特定键的值,需要新的数据结构:索引。
    存储系统中重要的权衡设计:适当的索引可以加速读取查询,但每个索引都会减慢写速度。
     
    哈希索引(Hash Indexes)
    追加式日志的设计非常不错,主要原因有以下几个:
    • 追加和分段合并主要是顺序写,它通常比随机写入快得多,特别是在旋转式磁性硬盘上。
    • 如果段文件是追加的或不可变的,则并发和崩溃恢复要简单得多。
    • 合并旧段可以避免随着时间的推移数据文件出现碎片化的问题。
    哈希表索引也有其局限性:
    • 哈希表必须全部放入内存,如果有大量的键,就没那么幸运了。
    • 区间查询效率不高。 
    SSTables和LSM-Tree
    排序字符串表,简称为SSTable。它要求每个键在每个合并的段文件中只能出现一次(压缩过程已经确保了)。SSTable相比哈希索引的日志段,具有以下优点:
    1. 合并段更加简单高效,即使文件大于可用内存。
    2. 在文件中查找特定的键时,不再需要在内存中保存所有键的索引。
    3. 由于读请求往往需要扫描请求范围内的多个key-value对,可以考虑将这些记录保存到一个块中并在写磁盘之前将其压缩。
     
    构建和维护SSTables
    内存排序有很多广为人知的树状数据结构,例如红黑树或AVL树。使用这些数据结构,可以按任意顺序插入键并以排序后的顺序读取它们。
     
    从SSTables到LSM-Tree
    日志结构的合并树(Log-Structured Merge Tree,或LSM-Tree)
    基于合并和压缩排序文件原理的存储引擎通常都被称为LSM存储引擎。
     
    性能优化
    存储引擎通常使用额外的布隆过滤器来优化键不存在的访问。
    最常见的SSTables压缩和合并的策略是大小分级和分层压缩。LevelDB和RocksDB使用分层压缩,HBase使用大小压缩。
     
    B-trees
    它是几乎所有关系数据库中的标准索引实现。
    B-tree保留按键排序的key-value对,这样可以实现高效的key-value查找和区间查询。B-tree将数据库分解成固定大小的块或页,传统上大小为4KB,页是内部读/写的最小单元。
    如果要更新B-tree中现有键的值,首先搜索包含该键的叶子页,更改该页的值,并将页写回到磁盘。
     
    使B-tree可靠
    为了使数据库能从崩溃中恢复,常见B-tree的实现需要支持磁盘上的额外的数据结构:预写日志(write-ahead log, WAL),也称为重做日志。
     
    优化B-tree
    • 使用写时复制方案进行崩溃恢复。
    • 保存键的缩略信息,而不是完整的键。
    • 。。。
     
    对比B-tree和LSM-tree
    通过经验,LSM-tree通常对于写入更快,而B-tree被认为对于读取更快。
     
    LSM-tree的优点
    B-tree索引必须至少写两次数据:一次写入预写日志,一次写入树的页本身。
    LSM-Tree通常能够承受比B-tree更高的写入吞吐量,部分是因为它们有时具有较低的写放大,部分原因是它们以顺序方式写入紧凑的SSTable文件,而不必重写树中的多个页。
     
    LSM-tree的缺点
    日志结构存储的缺点是压缩过程有时会干扰正在进行的读写操作。
    高写入吞吐量时,压缩的另一个问题就会冒出来:磁盘的有限写入带宽需要在初始写入(记录并刷新内存表到磁盘)和后台运行的压缩线程之间所共享。
     
    其它索引结构
    • 在索引中存储值。将索引行直接存储在索引中,被称为聚集索引。例如,MySQL InnoDB存储引擎中,表的主键始终是聚集索引,二级索引引用主键(而不是堆文件位置)。
    • 多列索引。
      • 最常见的多列索引类型称为级联索引,它通过将一列追加到另一列,将几个字段简单地组合成一个键(索引的定义指定字段连接的顺序)。
      • 多维索引是更普遍的一次查询多列的方法,这对地理空间数据尤为重要。
    • 全文索引和模糊索引。如Lucene。
    • 在内存中保存所有内容。如Redis。

    事务处理与分析处理(Transaction Processing or Analytics)

    在线事务处理(online transaction processing,OLTP),在线分析处理(online analytic processing, OLAP)
     
    数据仓库
    数据仓库是单独的数据库,分析人员可以在不影响OLTP操作的情况下尽情的使用。数据仓库包含公司所有各种OLTP系统的只读副本。
     
    OLTP数据库和数据仓库之间的差异
    数据仓库的数据模型最常见的是关系型,因为SQL通常适合分析查询。
     
    星型和雪花型分析模式
    许多数据仓库都相当公式化的使用了星型模型,也称为维度建模。
    事实表中的列是属性,其他列可能会引用其他表的外键,称为维度表。
    星型模型的一个变体称为雪花模型,其中维度进一步细分为子空间。
    雪花模型比星型模型更规范化,但是星型模型通常是首选,主要是因为对于分析人员,星型模型使用起来更简单。

    列式存储(Column-Oriented Storage)

    面向列存储的想法很简单:不要将一行中的所有值存储在一起,而是将每列中的所有值存储在一起。
    面向列的存储布局依赖一组列文件,每个文件以相同顺序保存着数据行。
     
    列压缩,一种技术是位图编码(Bitmap)。
     
    内存带宽和矢量化处理
     
    列存储中的排序
    基于第一个排序键的压缩效果通常最好。
     
    列存储的写操作
    一个很好的解决方案是LSM-tree。
     
    聚合:数据立方体与物化视图
    创建这种缓存的一种方式是物化视图。物化视图常见的一种特殊情况称为数据立方体或OLAP立方体。
    物化数据立方体的优点是某些查询会非常快,主要是它们已被预先计算出来。
    缺点则是,数据立方体缺乏像查询原始数据那样的灵活性。

    小结(Summary)

    概括来说,存储引擎分为两大类:针对事务处理(OLTP)优化的架构,以及针对分析型(OLAP)的优化架构。
    • OLTP系统通常面向用户,这意味着它们可能收到大量的请求。
    • 由于不是直接面对最终用户,数据仓库和类似的分析型系统相对并不太广为人知,它们主要由业务分析师使用。 
    在OLTP方面,由两个主流流派的存储引擎:
    • 日志结构流派,它只允许追加式更新文件和删除过时的文件,但不会修改已写入的文件。BitCask, SSTables、LSM-tree、LevleDB、Cassandra、HBase、Lucene等属于此类。
    • 原地更新流派,将磁盘视为可以覆盖的一组固定大小的页。B-tree是这一哲学的最典型代表,它已用于所有主要的关系数据库,以及大量的非关系数据库。 

    第4章 数据编码与演化(Encoding and Evolution)

    向后兼容:较新的代码可以读取由旧代码编写的数据。
    向前兼容:较旧的代码可以读取由新代码编写的数据。
    向后兼容通常不难实现:作为新代码的作者,清楚旧代码所编写的数据格式,因此可以比较明确地处理这些旧数据。向前兼容可能会比较棘手,它需要旧代码忽略新版本的代码所做的添加。

    数据编码格式(Formats for Encoding Data)

    从内存中的表示(如对象、结构体、列表、数组、哈希表等)到字节序列(如JSON文档)的转化称为编码(或序列化等),相反的过程称为解码(或解析,反序列化)。
     
    语言特定的格式
    如Java有java.io.Serializable,Python有pickle等
     
    JSON、XML与二进制变体
    尽管存在一些缺陷,但JSON、XML和CSV已经可用于很多应用。特别是作为数据交换格式(即将数据从一个组织发送到另一个组织)。
     
    Thrift和Protocol Buffers
    Apache Thrift和Protocol Buffers是基于相同原理的二进制编码库。
    Thrift和PB都需要模式来编码任意的数据。
    字段标签和模式演化
    数据类型和模式演化
     
    Avro
    Apache Avro是另一种二进制编码格式,也使用模式来指定编码的数据结构。
    写模式和读模式:Avro的关键思想是,写模式和读模式不必是完全一模一样,它们只需保持兼容。
    模式演化规则:为了保持兼容性,只能添加或删除具有默认值的字段。
    动态生成的模式:Avro的一个优点是不包含任何标签号。关键之处在于Avro对动态生成的模式更友好。
    代码生成和动态类型语言:Avro为静态类型编程语言提供了可选的代码生成,但是它也可以在不生成代码的情况下直接使用。
     
    模式的优点
    通过演化支持与无模式/读时模式的JSON数据库相同的灵活性,同时还提供了有关数据和工具方面更好的保障。 

    数据流模式(Modes of Dataflow)

    基于数据库的数据流(Dataflow Through Databases)
    在数据库中,写入数据库的进程对数据进行编码,而读取数据库的进程对数据进行解码。
     
    不同的时间写入不同的值
    数据比代码更长久。五年前的数据还在,但代码可能废弃了。
     
    归档存储
     
    基于服务的数据流:REST和RPC(Dataflow Through Services: REST and RPC)
    面向服务/微服务体系结构的一个关键设计目标是,通过使服务可独立部署和演化,让应用程序更易于更改和维护。
     
    网络服务
    有两种流行的Web服务方法:REST和SOAP。与SOAP相比,REST已经越来越受欢迎,至少在跨组织服务集成的背景下,并经常与微服务相关联。
     
    远程过程调用(RPC)的问题
    尝试使远程服务看起来像编程语言中的本地对象一样毫无意义,因为它们是根本不同的事情。REST的部分吸引力在于,它并不试图隐藏它是网络协议的事实。
     
    RPC的发展方向
    REST似乎是公共API的主流风格。RPC框架主要侧重于同一组织内多项服务之间的请求,通常发生在同一数据中心内。
     
    RPC的数据编码和演化
    RPC方案的向后和向前兼容性属性取决于它所使用的具体编码技术。
     
    基于消息传递的数据流
    消息代理
    如Kafka、RabbitMQ等。主题只提供单向数据流。
     
    分布式Actor框架
    Actor模型是用于单个进程中并发的编程模型。流行的如Akka、Orleans、Erlang OTP等。

    小结(Summary)

    本章,我们研究了将内存数据结构转换为网络或磁盘上字节流的多种方法。我们看到这些编码的细节不仅影响其效率,更重要的是还影响应用程序的体系结构和部署时的支持选项。
     
    特别地,许多服务需要支持滚动升级,即每次将新版本的服务逐步部署到几个节点,而不是同时部署到所有节点。滚动升级允许在不停机的情况下发布新版本的服务(因此鼓励频繁地发布小版本而不是大版本),并降低部署风险(允许错误版本在影响大量用户之前检测井回滚)。这些特性非常有利于应用程序的演化和更改。
     
    在滚动升级期间,或者由于各种其原因,必须假设不同的节点正在运行应用代码的不同版本。因此,在系统内流动的所有数据都以提供向后兼容性(新代码可以读取旧数据)和向前兼容性(旧代码可以读取新数据)的方式进行编码显得非常重要。
     
    本章还讨论了多种数据编码格式及其兼容性情况:
    • 编程语言特定的编码仅限于某种编程语言,往往无法提供向前和向后兼容性。
    • JSON、XML和csv等文本格式非常普遍,其兼容性取决于你如何使用他们。它们有可选的模式语言,这有时是有用的,有时却是障碍。这些格式对某些数据类型的支持有些模糊,必须小心处理数字和二进制字符串等问题。
    • Thrift、Protocol Buffers和Avro这样的二进制的模式驱动格式,支持使用清晰定义的向前和向后兼容性语义进行紧凑、高效的编码。这些模式对于静态类型语言中的文档和代码生成非常有用。然而,它们有个缺点,即只有在数据解码后才是人类可读的。
    我们还讨论了数据流的几种模型,说明了数据编码在不同场景下非常重要:
    • 数据库,其中写入数据库的进程对数据进行编码,而读取数据库的进程对数据进行解码。
    • RPC、REST API,其中客户端对请求进行编码,服务器对请求进行解码井对响应进行编码,客户端最终对应进行解码。
    • 异步消息传递(使用消息代理或Actor),节点之间通过互相发送消息进行通信,消息由发送者编码并由接收者解码。
    我们可以得出这样的结论,只要稍加小心,向后/向前兼容性和滚动升级是完全可以实现的。

  • 相关阅读:
    HttpModule和在Global.asax区别
    SQL Server中视图的特点与优化
    SQL中int类型与varchar类型的隐式转换
    利用SQL语句查询SQL中所有正在执行的命令
    jquery子窗体操作父窗体中的元素
    js 连接数据库
    典型的列变行,用动态语句来做
    js中with、this的用法
    SQL SERVER数据库状态(脱机,联机,可疑)及SQL设置语句详解
    UVA 10465 Homer Simpson
  • 原文地址:https://www.cnblogs.com/sxpujs/p/12041385.html
Copyright © 2011-2022 走看看