数据密集型应用系统设计 笔记一

一 数据系统基础:

      一个应用必须完成预期的多种需求,主要包括功能性需求(即应该做什么,比如各种存储、检索、搜索和处理数据)和一些非功能性需求(即常规特性、例如安全性 、可靠性、合规性、可伸缩性 、兼容性和可维护性)。然后在谈应用的可靠性、可扩展性和可维护性。
可靠性:
    1.应用程序执行用户所期望的功能。
    2.可以容忍用户出现错误或者不正确的软件使用方法。
    3.性能可以应对典型场景、合理负载压力和数据量。
    4.系统可防止任何未经授权的访问和滥用。
    5.容错。容错总是指特定类型的故障 ,这样的系统才更有实际意义。
    可靠性意味着即使发生故障,系统也可以正常工作。故障包括硬件(通常是随机的, 不相关的)、软件(缺陷通常是系统的,更加难以处理)以及人为(总是很难避免时不时会出错)方面。容错技术可以很好地隐藏某种类型故障,避免影响最终用户。
可扩展性:
可扩展性是指负载增加时, 有效保持系统性能的相关技术策略。为了讨论可扩展性, 我们首先探讨了如何定量描述负载和性能。简单地以Twitter浏览时间线为例描述负载,并将响应时间百分位数作为衡量性能的有效方式。对于可扩展的系统,增加处理能力的同时,还可以在高负载情况下持续保持系统的高可靠性。
可维护性:
可维护性则意味着许多方面,但究其本质是为了让工程和运维团队更为轻松。良好抽象可以帮助降低复杂性,并使系统更易于修改和适配新场景。良好的可操作性意味着对系统健康状况有良好的可观测性和有效的管理方法。

二 数据模型与查询语言:

    声明式查询语言:只需指定所需的数据模式,结果需要满足什么条件,以及如何转换数据(例如,排序、分组和聚合),而不需指明如何实现这一目标。如:Sql 只需要关心结果不需要关心具体查询细节
命令式查询语言:命令式语言告诉计算机以特定顺序执行某些操作。需要关心所有的细节。
数据模型:
文档模型:以Json或xml储存  读模式
关系模型:文档模型(读模式)、关系模型(写模式)和图模型(读模式)
图模型:Neo4j

三 数据存储与检索:

数据库核心:数据结构 
Bitcask(Riak 中的默认存储引擎):以key-value储存
哈希索引:
        利用HashMap建立索引。如果数据以key-value的形式只写入一个文件会导致磁盘空间用尽。所以采用分段的方式在文件达到一定大小时就关闭文件,并将后续数据写入到新的文件中。同时对老的段文件进行压缩(压缩一个段文件或者合并压缩多个段文件),同一个Key只需要保留最新的value(数据是顺序写入的最后的数据最新)。新的文件产生了必然需要在内存中建立新的HashMap来映射。这些都完成后老的段文件就可以删除了。在合并文件时读写操作是正常的,仍然可以用旧的段文件继续正常读取和写请求。当合并过程完成后,将读取请求切换到新的合并段上,而旧的段文件可以安全删除。
有些细节还是需要的关注的:
1.删除数据
如果要删除键和它关联的值,则必须在数据文件中追加一个特殊的删除记录(有时候称为墓碑)。当合并日志段时,一旦发现墓碑标记,则会丢弃这个己删除键的所有值。
2.崩溃恢复
如果数据库重新启动,则内存中的hashmap将丢失 。原则上,可以通过从头到尾读取整个段文件,然后记录每个键的最新值的偏移量,来恢复每个段的hashmap。但是,如果分段文件很大,可能扫描需要很长时间,这将使服务器重启变得缓慢。Bitcask通过将每个段的hash map 的快照存储在磁盘上,可以更快地加载到内存中,以此加快恢复速度。
3.部分写入的记录
数据库随时可能崩愤,包括将记录追加到日志的过程中。Bitcask文件包括校验值,这样可以发现损坏部分井丢弃。
4.并发控制
由于写入以严格的先后顺序追加到日志中,通常的实现选择是只有一个写线程。 数据文件段是追加的,并且是不可变的, 所以他们可以被多个线程同时读取。
Hash表局限性:
1.哈希表必须全部放入内存,所以如果有大量的键,就没那么幸运了。原则上,可以在磁盘上维护hash map ,但不幸的是,很难使磁盘上的HashMap表现良好。它需要大量的随机访问 I/0 ,当哈希变满时,继续增长代价昂贵,井且哈希冲突时需要复杂的处理逻辑。
2.区间查询效率不高。只能逐个查询每一个键。

SSTables:
简单的修改文件的格式:要求key-value对的顺序按键排序。这种格式称为排序字符串表,或简称为SSTable 。
SSTable 相比哈希索引的日志段,具有以下优点:
1.合并段更加简单高效,即使文件大于可用内存。并发读取多个输入段文件,比较每个文件的第一个键, 把最小的键(根据排序顺序)拷贝到输出文件,并重复这个过程。这会产生一个新的按键排序的合并段文件(多路归并排序)。一个键在多个段中出现,那么通过段的时间来找到最新的值,每一个段中包含了一段时间内写入数据库的所有值。所以从段的角度来说段是有序的。
2.在文件中查找特定的键时,不再需要在内存中保存所有键的索引。因为key是有序的,如果知道A的地址,D的地址。那么B的地址就可以很简单的推断出来。所以,仍然需要一个内存索引来记录某些键的偏移,但它可以是稀疏的,由于可以很快扫描几千字节,对于段文件中每几千字节,只需要一个键就足够了。
3.由于读请求往往需要扫描请求范围内的多个key-value对,可以考虑将这些记录保存到一个块并在写磁盘之前将其压缩。然后稀 疏内存索引的每个条目指向压缩块的开头。除了节省磁盘空间,压缩还减少了I/O带宽的占用。

构建和维护SSTables
目前本质是最大的问题就是数据写入时是随机的,如何让数据按键排序。利用B-trees在磁盘上维护是可行的但是在内存中维护是比较简单的,如红黑树或AVL树。
储存引擎的基本工作流程如下:
1.写入数据时,将数据添加到内存中的平衡数据结构(红黑树)。这个内存树有时被称为内存表。
2.当内存数据大于某个阈值时,将其作为SSTable文件写入磁盘。此时写入磁盘的文件已经默认是排序完成的。新的SSTable文件成为数据库的最新部分。当SSTable写磁盘的同时,新的写入数据可以继续添加到一个新的内存表实例。
3.读请求,首先尝试在内存表中查找键,然后是最新的磁盘段文件,接下来是次新的磁盘段文件,以此类推,直到找到目标(或为空)。
4.后台进程周期性地执行段合并与压缩过程,以合并多个段文件,并丢弃那些已被 覆盖或删除的值。
问题:
如果数据库崩愤,最近的写入(在内存表中但尚未写入磁盘)将会丢失。为了避免该问题,可以在磁盘上保留单独的日志,每个写入都会立即追加到该日志,该日志不需要按键排序。日志的唯一目的是在崩溃后恢复内存表。每当将内存表写入SSTable时,相应的日志可以被丢弃。

从SSTable到LSM-Tree
RocksDB就使用的SSTable。
最初这个索引结构由Patrick O'Nei 等人以日志结构的合井树( Log-Structured MergeTree或LSM-Tree) 命名,它建立在更早期的日志结构文件系统之上 。因此,基于合并和压缩排序文件原理的存储引擎通常都被称为LSM存储引擎。

性能优化
1.当找数据库中某个不存在的键时, LSM-Tree算法可能很慢:在确定键不存在之前, 必须先检查内存表,然后将段一直回溯访问到最旧的段文件(可能必须从磁盘多次读取)。为了优化这种访问,存储引擎通常使用额外的布隆过滤器(布隆过滤器会有误差)。
2.还有不同的策略会影响甚至决定 SSTables 压缩和合并时的具体顺序和时机 。最常见的就是大小分级和分层压缩。Rocks DB 使用分层压缩,Hbase使用大小分级。在大小分级的压缩中,较新的和较小的SSTables 被连续合并到较旧和较大的SSTables 。在分层压缩中,键的范围分裂成多个更小的SSTables ,旧数据被移动到单独的“层级”,这样压缩可以逐步进行并节省磁盘空间。

B-trees 这个如果没有配图不太好理解 这里不再说明

对比B-tree和LSM-tree
LSM-tree的优点
B-tree索引必须至少写两次数据:一次写入预写日志,一次次写入树的页本身(还可能发生页分裂)。即使该页中只有几个字节更改,也必须承受写整个页的开销。 存储引擎甚至覆盖相同的页两次,以避免在电源故障的情况下最终出现部分更新的页。
由于反复压缩和SSTable的合并, 日志结构索引也会重写数据多次。这种影响( 在数据库内,由于一次数据库写入请求导致的多次磁盘写)称为写放大。对于SSD, 由于只能承受有限次地擦除覆盖,因此尤为关注写放大指标。
对于大量写密集的应用程序,性能瓶颈很可能在于数据库写入磁盘的速率。在这种情况下,写放大具有直接的性能成本:储存引擎写入磁盘的次数越多,可用磁盘带宽中每秒可以处理的写入越少。
此外, LSM-tree通常能够承受比B-tree更高的写入吞吐量,部分是因为它们有时具有较低的写放大(尽管这取决于存储引擎的配置和工作负载),部分原因是它们以顺序方式写入紧凑的SSTable文件,而不必重写树中的多个页。这种差异对于磁盘驱动器尤为重要 ,原因是磁盘的顺序写比随机写要快得多。
LSM-tree 可以支持更好地压缩,因此通常磁盘上的文件比B-tree小很多。由于碎片,B-tree 存储引擎使某些磁盘空间无法使用:当页被分裂或当一行的内容不能适合现有页时,页中的某些空间无能使用。由于LSM-tree不是面向页的,并且定期重写SSTablesl以消除碎片化,所以它们具有较低的存储开销,特别是在使用分层压缩时。
在许多SSD上,固件内部使用日志结构化算位将随机写入转换为底层存储芯片上的顺序写入,所以存储引擎写入模式的影响不那么明显。然而,更低的写放大和碎片减少对于SSD上仍然有益,以更紧凑的方式表示数据,从而在可用的1/0带宽中支持更多的读写请求。
1.LSM-tree在配置好的情况下 具有较低的写放大
2.LSM-tree 可以支持更好地压缩。
3.LSM-tree写入时是按照磁盘顺序写入的。

LSM-tree的缺点
日志结构存储的缺点是压缩过程有时会干扰正在进行的读写操作。即使存储引擎尝试增量地执行压缩,并且不影响并发访问,但由于磁盘的并发资源有限,所以当磁盘执行昂贵的压缩操作时,很容易发生读写请求等待的情况。这对吞吐量和平均响应时间的影响通常很小,但是如果观察较高的百分位数。日志结构化存储引擎的查询晌应时间有时会相当高,而B-tree的响应延迟则更具确定性。
高写入吞吐量时, 压缩的另一个问题就会冒出来:磁盘的有限写入带宽需要在初始写入(记录并刷新内存表到磁盘)和后台运行的压缩线程之间所共享。写入空数据库时,全部的磁盘带宽可用于初始写入,但数据库的数据量越大,压缩所需的磁盘带宽就越多。
如果写入吞吐量很高并且压缩没有仔细配置,那么就会发生压缩无法匹配新数据写入速率的情况。在这种情况下,磁盘上未合并段的数量不断增加,直到磁盘空间不足, 由于它们需要检查更多的段文件,因此读取速度也会降低。通常,即使压缩不能跟 上,基于SSTable 的存储引擎也不会限制到来的写入速率,因此需要额外的监控措施来及时发现这种情况。

列式存储
列压缩

列存储中的排序
单独排序每列是没有意义的,如果这样的话就无法知道列中的某一项属于哪一行。因为知道某列中的第K项和另一列的第K定属于同一行,基于这种约定我们可以重建一行。
排序的另一个优点是它可以帮助进一步压缩列。如果主排序列(选定一列作为第一排序列)上没有很多不同的值, 那么在排序之后,它将出现一个非常长的序列, 其中相同的值在一行中重复多次。一 个简单的游程编码,位图(存在为1不存在则为0的位图)那样,即使该表可能拥有数十亿行,也可以将其压缩到几千字节。
聚合:数据立方体与物化视图
物化视图本质就是查询缓存。在关系数据模型中,它通常被定义为标准(虚拟)视图:一个类似表的对象,其内容是查询的结果。不同的是,物化视图是查询结果的实际副本,并被写到磁盘 ,而虚拟视图只是用于编写查询的快捷方式 从虚拟视图中读取时, SQL引擎将其动态扩展到视图的底层查询,然后处理扩展查询。
当底层数据发生变化时,物化视图也需要随之更新,因为它是数据的非规范化副本。 数据库可以自动执行,但这种更新方式会影响数据写入性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值