第一部分-数据系统基础

1 可靠、可拓展与可维护的应用系统

2 数据模型与查询语言

3 数据存储与检索

3.1 日志结构存储

  • 代表:Bitcask,LSM-Tree(Log Structed Merge Tree)
  • 使用哈希索引实现
  • 只允许追加更新的数据文件,以最后一次键值对为准
  • 在内存存放key,以及value在硬盘中的偏移量,读取时只需一次磁盘寻址
  • 如何防止耗尽磁盘空间?
    • 将日志分解为小段,以文件大小限制,查找时依次向前查找
    • 在多个段上实行压缩,只保留最新的键值对,对于相同的key
  • 崩溃恢复:Bitcask将hashmap快照存放于磁盘,来加快恢复速度
  • 删除记录: 墓碑机制,压缩时直接忽略
  • 并发控制:单个线程负责追加,允许多个线程读取
  • 缺点
    • 哈希表必须全部放入内存
    • 区间查询效率不高
  • 改进:SSTables
    • 名称:排序字符串表
    • 内存中存放的叫稀疏索引,因为是间隔存放key及偏移的
    • 要求段文件内按照key排序,且每个key在每个段文件只出现一次
    • 优点:
      • 合并更加简单高效
      • 不必在内存存储所有key及索引,可以根据已有key进行推测。eg:根据handbag和handsome,区间内查找handiword对应的value
    • 整体流程:
      • 写入时,在内存维护一个类似平衡树的数据结构(红黑树/AVL)
      • 当内存表超过阈值,作为SSTables写入磁盘
      • 后台周期性的执行段合并和压缩过程,丢弃已经被覆盖或者删除的值
      • 在磁盘单独保留日志,先写日志,再写内存表,崩溃后恢复内存表
      • 查询多个段文件可能会比较慢,可以使用布隆过滤器提前判断
      • LevelDB和RocksDB使用分层压缩(根据key范围分裂成更小的SSTables,旧数据移动到单独的层级),HBase使用大小分级(较新和较小的SSTables合并大较久和较大的SSTables),Cassandra同时支持两种方案

3.2 B-trees

  • 与SSTable相似:保留按key排序的kv对,实现高效kv查找,以及区间查询
  • 将数据库分解成固定大小的页(通常4KB),更接近底层硬件(硬盘)
  • 一个页包含的子页引用,称为分支因子
  • 分支因子为500的4KB页的四级树,存储高达256TB
  • 具有n个键的btree,深度为O(logn)
  • 缺点:
    • 写入时直接覆盖页,对ssd不友好
    • 如果需要覆盖多个页,比如插入导致页溢出,会进行分裂,如果写入部分页后崩溃,可能会出现索引损坏,以及孤儿页
    • WAL(Write Ahead Log)支持崩溃恢复,写入时先写WAL,再修改页
    • 多线程访问B-tree,需要使用锁存器(轻量级锁)保护树的数据结构
  • 改进:
    • 保存键的缩略信息,而不是完整的键,节省页空间(b+树)
    • 使用写时复制技术,替代覆盖页+WAL的方案,同时对并发控制也有帮助(如LMDB)
    • 尝试对树进行布局,使相邻叶子节点在磁盘相邻,数据量变大之后,这个过程会变得困难

3.3 对比B-tree和LSM-tree

LSM-tree优点:

  • LSM-tree能够承受比B-tree更高的写入吞吐量,部分因为有时写放大(由于反复压缩和SSTables和合并,一次数据写入,会导致多次磁盘写,对ssd影响较大),部分因为以顺序方式写入紧凑的SSTables文件,而不必重写树的多个页
  • LSM-tree不是面向页,并且定期重写SSTables以消除碎片化,故具有较低的存储开销,尤其是在使用分层压缩时;由于碎片,B-tree当页被分裂获当一行的内容不能适合现有页,页中的空间可能会无法使用。
  • 部分ssd的内部会使用日志结构化算法,将随机写入转换为顺序写入,一定程度上能减轻写放大的危害

LSM-tree缺点:

  • 硬盘并发的读写资源有限,有时压缩过程会干扰正在进行的读写操作
  • 高写入吞吐量时,磁盘的有限写入带宽,需要在初始写入(记录并刷新内存表到硬盘)和后台运行的压缩线程之间共享
  • B-tree每个键都唯一对应索引某个位置,对事务语义的实现有帮助,可以直接将锁定义到树中;而LSM-tree一个键则可能存在多个副本,对事务支持不好

3.4 其他索引结构

二级索引

  • 基于kv索引来构建,对于高效的联结操作至关重要(我理解就是对外键建立索引)
  • 键不唯一,两种方式解决
    • 使索引每个值成为匹配行标识符的列表
    • 追加行标识符来使键变得唯一

在索引中存储值

  • 索引中的值可以是实际行(聚集索引),也可以是指向行的引用(非聚集索引,存储行的位置被称为堆文件)
  • 当更新值不更改键时,堆文件实现会非常高效
  • 覆盖和聚集索引可以加快读取速度,但是需要额外存储,会增加写入的开销

多列索引

  • 将几个字段组合成一个键
  • 建立方法:
    • 可以使用空格填充曲线将二维位置转换为单个数字,然后使用B-tree索引(联合索引)
    • 使用专门的空间索引,如R树(HyperDex也使用了二级索引这种技术)

全文搜索和模糊索引

  • 支持对单词的同义词查询
  • Lecene支持在某个编辑距离内搜索文本
  • Lecene对词典使用类似SSTables的结构,需要小内存结构来告诉查询,为了找到一个键,需要排序文件的那个偏移量。在LevelDB中,这个内存索引是一些键的稀疏集合(未存储所有键),在Lecene中,这个内存索引是建中字符序列的有限状态自动机,类似于字典树

在内存中保存所有内容

  • 用于缓存,数据丢失可接受
  • 提供了一些基于磁盘索引难以实现的数据模型,比如redis的几种数据类型

3.5 事务处理与分析处理

OLTP

  • Online Transaction Processing,在线事务处理
  • 读数据基于键,每次查询返回很少数据量
  • 写数据随机访问,低延迟写入
  • 使用场景为终端用户,通过网络应用程序
  • 规模为GB-TB

OLAP

    • Online Analytic Processing,在线分析处理
  • 读数据特征,对大量数据进行汇总
  • 写数据批量导入(ETL)或者事件流
  • 使用场景为分析师,为决策提供支持
  • 规模为TB-PB

数据仓库

  • 包含公司所有OLTP系统的只读副本
  • 在不影响OLTP的前提下尽情使用
  • 可以针对分析访问模式进行优化

星型与雪花分析模式

星型模式:

  • 当表关系可视化时,事实表位于中间,被一系列维度表包围,表之间连接像星星的光芒
    雪花模式:
  • 维度进一步细分为子空间

列式存储

面向列存储:

  • 将每一列所有的值存储在一起
  • 每一行在所有列文件中的偏移量相同(重要)
  • 查询时只需要读取和解析在该查询中使用的列,节省大量工作

列压缩:

  • 通常,列中不同值的数量小于行数
  • 使用单独位图表示列值出现的情况
  • 排序之后进行压缩,a个1,b个0...可以极大减少空间占用
  • Cassandra和HBase(集成自BigTable)有一个列族的概念,将一行所有列和行主键一起保存,且不进行列压缩,仍然是面向行存储的

列存储的排序

  • 帮助进一步压缩列
  • 对前几列压缩会取得不错的收益
  • 为了防止写入更加困难,借鉴了LSM-tree,写入首先进入内存存储区,添加到已排序结构中,然后准备写入磁盘。当积累了足够写入时,他们与磁盘上的列文件合并,并批量写入新文件(Vertica做了这个事儿)

聚合:数据立方体与物化视图

  • 物化聚合(COUNT/SUM/AVG等操作),重复查询浪费性能
  • 物化视图:查询结果的十几副本,被写入到磁盘
  • 虚拟试图:编写查询的快捷方式(mysql视图,是一个虚表)
  • 缺点:数据立方体缺乏像查询原始数据的灵活性
  • 仅当数据立方体可以对特性查询显著提升性能时,才会采用多为数据聚合
posted @ 2021-09-30 19:28  朕蹲厕唱忐忑  阅读(61)  评论(0编辑  收藏  举报