《Designing Data-Intensive Applications》笔记1:数据模型与存储

1.数据模型

    从数据库的数据模型上,可以将数据库分为关系型、文档型以及图型数据库等。这三类数据库在形式和使用的场景上各有不同。

1.1.关系型

    关系型模型基于集合论中的关系理论,将数据视为Tuple的集合。关系型在1970年被提出,人们一开始质疑这类技术是否能高效地实现,但在1980年左右出现了目前的SQL和RDBMS系统,从而开始了关系型数据库的时代。

关系型的缺点

    关系型数据库的主要缺点之一,是应用和数据库之间的模型失配问题。由于在应用层中,数据是以数据对象的形式存储的,但在数据库中却需要转换为关系型模型,两者之间存在差异。因此出现了极端复杂的ORM框架,来处理对象到关系型的映射问题,但是仍然无法完全屏蔽模型失配带来的细节问题。

关系型的优点

    关系型的优点在于背后的理论支撑,使他可以在有完整方法指导的条件下进行schema设计,并消除冗余数据。并且相比文档型能更好地处理多对多映射关系。

1.2.文档型

    文档型是对关系型的一种反范式妥协,将一对多,一对一的关系直接保存在同一文档内部。与关系型数据库不同,虽然文档型数据库一般情况下对schema不做要求,但应用程序在使用数据时,仍然会对数据的结构有隐式的要求。因此,文档型数据库中其实仍然存在schema。对于关系型而言,schema是写时敏感的,对于文档型而言则是读时敏感的,两者其实并没有明显的优势和劣势。

文档型的优点

    通过反范式的方法,文档型消除了在一对一、一对多时的join等操作;同时还能提高数据的聚集性,同一文档中的数据可以快速地一次性读取,不再需要join反复读取硬盘,从而提高了性能。并且由于json等文档格式与应用层的数据模型有相当好的兼容性,因此在开发时也更省时省力。

文档型的缺点

    但是传统的文档型数据库对join的支持比较弱,而且对于多对多的关系,文档型数据库需要采用join,从而导致性能降低;或是采用数据冗余的方式复制到每个文档中,但会在更新时带来极大的性能消耗。(但目前RethinkDB、MongoDB等均开始支持join)。而且文档型在更新时常常需要更新一个完整的文档,因此如果一个大型对象中的一小部分被修改了,对于文档型而言可能仍然需要写入一个大对象。

文档型与关系型的融合

    一般来说,当数据模型中存在大量多对多关系时,倾向于使用关系型数据库;而较少多对多时可采用文档型来帮助开发。

    但目前,由于文档型数据库也开始支持join,而关系型数据库也开始支持json字段的操作与查询,两者之间的界线正逐渐模糊。

文档型与DDD的思考

    其实对于DDD而言,文档型数据库能有很好的兼容性。每个A的实例可以作为一个文档存储,由于A内部的所有实体和数据均由聚合根自己管理,并且聚合也是事务数据一致性的最小单位,因此更适合作为一个完整的文档进行存储。而跨A之间的访问,则默认应通过id的方式进行引用,此时本身就重新需要使用repo进行findById的操作,对于文档数据库而言也没有问题。对于普通的ui展现,随着Angular等前端框架的兴起,完全可以将组件和A之间时行一一映射,因此前端也大多只发现findById的查询。唯一需要join的地方是在一些列表页或复杂页面,此时可能发起复杂查询,那么仍然需要关联所有A之间的信息,但由于各类文档数据库对join的良好支持,此类设计或许可以在日后逐步尝试。

1.3.图型

    图数据库则从模型上通过图的形式保存数据,认为所有节点之间天然就能产生关系。因此有很好的schema上的扩展性,可灵活地添加实体之间的关联关系并进行查询。

    对于图数据库而言,每个节点可包含id、出度和入度的边,以及若干属性;对于边而言,也有独立的id、边的类型以及边的属性。可以通过关系型或文档型数据库来模拟图数据库,但图数据库在查询时,可以使用多条边的动态匹配,这对于sql等语句来说需要额外的递归或循环处理,才能实现类似的功能。

    常用的图查询语言有Cypher。

2.存储结构

    数据库在存储和查找数据时,通常会基于一些索引或数据结构,本书主要以SST和B+为主,但为了引入SST先过渡性地介绍了Log+Hash的结构。

2.1.Log+Hash索引

    由简单而有效的结构,可能就是对所有写操作都直接记到日志中,然后在内存中维护一个key-日志文件offset的hash表。在查询时直接查询内存获得offset,并直接读取文件的相应位置即可。对于删除也只追加删除的标记,不进行物理删除。

    但由于日志会一直变大,因此需要对日志进行合并。可以将日志分割为若干segment,每个segment都有自身的hash索引,写始终只写最新的segment。查询时由新至老遍历segment的hash,找到第一个命中的值即可。然后可以后台进行segment的合并,将新老segment合并为一个segment,并以新segment中的value值为准。由于合并时不影响旧segment,因此在合并完成前仍使用原来的segment和hash表,不影响读写。

hash的优点

    简单易行。无论读写都性能极高,读时只读一次硬盘,写时也顺序写硬盘。且日志合并时仍然能提供服务。

hash的缺点

    hash表必须比内存小,key值很多时会无法放入内存。日志合并的效率较低。hash索引难以处理范围类的查询。

2.2.SST/LSM

    SST在hash的基础上进行了优化。要求每个segment都按key进行排序。从而不需要对每个segment中的key都进行索引,只需要选择部分key进行索引即可,在查询时找到最接近的key的offset,再对segment进行顺序扫描即可。在写时,需要先写内存store,内存store是一个按key排序的结构,当内存store写满时将此结构刷至硬盘形成一个segment。查询时先查询内存store,再对segment进行从新到老的遍历。写操作时,除了写内存还需要写WAL,以防止内存数据在宕机后遗失。

SST的优点

    解决了hash表必须比内存小的限制,通过部分key的索引就能满足大部分读的性能要求。同时增加了日志合并的速度,可采用merge sort的方式合并多个segment,全部操作均为顺序写和顺序读。

    拥有极高的写性能,因为只写了内存和WAL。

SST的缺点

    读性能一般,因为key是采样的,因此有更大概率需要扫描更多无用的segment,而且在扫描时需要读取更多的数据。而且在key不存在时,性能最低,需要遍历所有segment,但可以通过bloom filter,记录所有出现过的key来解决这个特殊的情况。

    SST需要经常性的合并segment,因此合并时会占用硬盘的带宽,导致写性能下限,也会影响读性能,会表现出性能的不稳定。并且一次写入可能会经过多次日志合并,每次合并时都重复写了一遍硬盘,所以会产生“写放大“的现象。

2.3.B+树

    B树是常用的RDB的索引结构。

B+树的缺点

    一次写可能产生多次写,同样有”写放大“的现象。而且一次写若超过一个page,可能造成存储位置移动的情况,同样需要修改索引。

    B+树可能产生并发问题,若多个线程同时修改,需要上锁,会影响效率。

    B+树由于有多次写,若在此过程中宕机,有可能造成数据损坏。

B+树的优点

    读的性能更好,并且更稳定。由于整个key只可能出现在一个地方,因此只要读有限次的硬盘就一定能找到相应的数据,而LSM则不是确定的,有一些读可能需要搜索多个segment。

3.常见数据库分类总结

*数据收集于各组件官网或其它网站信息

数据库数据模型索引/存储类型OLTP/APReplicaPartitionRe-partition
HBase宽列LSMTP Range自动
RedisKV全内存TP主从HASH人工变更节点数量
Vertica关系型列式AP HASH/Value人工
Mongodb文档型 TP主从HASH & Range+local二级索引 
Hive关系型列式APHDFSHASH/Value 
Riak-KVKV全内存 / Bitcask(全内存Key) / LevelDb(LSM)TP无主HASH+local二级索引人工确认
ES文档型LSM全文索引TP local二级索引 
TiDBKV based 关系型RocksDb(LSM)TP & 部分AP主从  
CouchbaseKV & 文档型Couchstore(B+Tree with append only) & ForestDB(B+trie) with 内存缓存TP主从HASH+global二级索引 
Couchdb文档型 TP主从 & 多主  
Cassandra宽列LSMTP无主HASH+local二级索引 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值