无论是构建什么样的应用,大都离不开数据。而在应用的架构设计中,如何设计数据库,使用什么类型的数据库,就是一个架构师必须了解的。所有的数据库的共同点都是以某种方式存储数据,以某种接口来访问存储的数据。我们今天就来看看不同类型的数据库架构和它们的使用场景。
关系型数据库
关系型数据库以数据表Table为核心来存储数据。数据是一行一行的表记录Record。表之间通过关联关系相互关联。 关系模型是表(行,列)组成的二维结构。SQL是关系型数据库的统一查询接口。
关系型数据库的架构设计,主要是要解决存储和事务。存储是要解决数据的查询问题。而事务则包含了四个特性,A(原子性)C(一致性)I(隔离性)D(持久性)。
上图是一个关系型数据库的典型架构。索引的存在是为了提高数据查询的性能。
索引通常是B/B+树。在没有索引的情况下,通过ID来查找一条数据记录的时间复杂度是O(n),也就是说随着数据量的增加,查询的速度随线性增加。这个是用户不能接受的。在建立了索引之后,如上图所示,访问记录需要从树的根节点通过两次跳转,达到记录。也就是说访问的速度取决于树的深度。这样的时间复杂度为 O(log n) 。访问速度受数据量的影响非常小。(其实在海量数据的情况下,即使是O(log n)也是个问题)
日志是关系型数据库的另一个核心架构设计概念。因为每一个数据库的操作都会以日志的形式记录。系统可以方便的进行事务的回滚。利用日志。系统可以通过日志回放的方式,构建任何一个时间点的系统状态。
优点:
- 通过事务处理保持数据的一致性
- 数据更新的开销很小
- 可以进行Join等复杂查询
- 20多年的技术历程,技术成熟
缺点:
- 数据读写必须经过sql解析,大量数据、高并发下读写性能不足
- 为保证数据一致性,需要加锁,影响并发操作
- 无法适应非结构化的存储
- 数据库中存储的对象与实际的对象实体有一定的差别
- 水平扩展困难,很难提供高扩展性和高可用性。
- 数据库庞大,价格昂贵
比较流行的关系型数据库有Oracle,MySQL,PostgresSQL等等
键值数据库
为了解决关系型数据库的各种限制和问题,各种NoSQL的数据库出现了。键值数据库就是其中一种。
键-值数据库,或键-值存储,是设计用来存储、检索和管理关联数组的数据存储范式,关联数组是现今更常称为“字典”或散列表的一种数据结构。字典包含对对象或记录的一个收集,依次、记录内有多个不同的“域”或称字段,再次、每个字段都包含数据。这些记录使用唯一标识这个记录的“键”来存储和检索,键还用来在数据库中快速的找到数据。
来自wikipedia的定义
键值数据库大大的简化了关系型数据库的表模型。所有的数据存在一个hash中,只包含key和value。
用户可以通过Key来查找记录。而记录的内容可以是任何东西,他们不需要遵守相同的表结构。这样就类似面向对象的封装和多态。 不同于多数的关系数据库,由于不使用占位符或输入参数来表示可选值,键-值数据库经常比同等的关系数据库使用更少的内存。
键值数据库由于其简单的数据模型,而非常适用于水平扩展。我们之前提到了关系型数据库很难提供高并发的大数据量的读取操作。因为关系的存在,数据表的水平分割是非常困难(虽然可以做到)。而键值数据库就很容易经行水平扩展,从而得到非常好的大规模读写的性能。
优点:
- 容易水平扩展Scalability,提供大数据量的读写性能。
缺点:
- 由于key-value数据库中没有schema,所以它是不提供数据之间的关系和数据的完备性的,所有的这些东西都落到了应用程序一端,其实也就是开发人员的头上。这无疑加重了开发人员的负担。
- 易用性,这里的易用性主要是由于SQL的支持。因为大部分程序员都非常数据SQL,但是对于键值数据库,大部分没有SQL的支持,使用起来就不如SQL那么方便。
流行的键值数据库有Redis,DynamoDB,Memcached等。
键值数据库牺牲了复杂性而打来了性能的显著提升。键值数据库由于其简单和高效性,常常被用作系统的缓存。
这里略微提一下DynamoDB,DynamoDB虽然是以Key-Value为核心构建的数据库,但是它也支持了一些扩展。如下图,DynamoDB有Primary Key和Secondary Key,Primary Key中又包含了Partition Key和Sort Key。用户可以通过设计对应的Key来实现相应的业务查询。
文档数据库
文档数据库是另一种类型的NoSQL数据库,他主要是为了解决关系型数据库的表结构固定,不灵活,不支持非结构化数据的问题而诞生的。
文档型数据库存贮的对象是文档对象,而非表。文档通常用JSON或者BSON来表示,例如如下的文档对象。
[
{
"id": 1,
"content": "test a"
},
{
"id": 2,
"content": "test b"
}
]
相比较关系型数据库,它的主要优点是:
- 灵活的Schema. 没有固定的schema,插入数据行灵活,对时常变更的应用友好,免去了关系数据库DDL之苦。
- 相关的数据都是存在一起的,比如一个人曾在多家公司任职这样的信息,可以存在一行里,查询时只访问这一行就行了,但在关系数据库里,这样一对多的关系,往往涉及join。
- 更加接近于应用端组织数据的方式,开发者用起来更加简单、易学。
- 大部分的文档数据库也拥有比较好的水平扩展性 Scalability
当然没有免费的午餐,文档型数据库也会有缺点:
- 跨多个文档的查询比较困难
- 事务的支持比较差,或者不支持
- 文档型数据库占用的存贮空间较大
比较流行的文档数据库有MongoDB,Couchbase, DynamoDB (是的,DynamoDB也是一种文档引擎,它支持文档型的内容多为key value的value)
文档型数据库很适合那些表结构经常改变,数据的逻辑结构没又没那么复杂不需要多表查询操作,数据量又比较大的应用场景。
搜索引擎数据库
搜索是大数据场景最常见的需求,就是要找到符合某个搜索条件的数据是否存在,并返回。
基于搜索架构的数据库通常的核心技术是倒排表索引和布隆过滤器。
倒排表索引对于每一个需要搜索的关键词建立一个他所在文档的引用记录。这样就可以快速找到对应的需要搜索的文档。
而布隆过滤器是一种类似Hash的技术,对于每一个文档,生成一个类似bitmap的对象,如果一个关键词在该文档中出现,该关键词的hash对应的bit位就会被置为1。这样当一个新的词如果他的hash中有一位对应的bit位的布隆过滤器的值为0,则可以肯定该关键词没有出现在该文档中。当然,如果所有的hash位都为1,也不能保证该词一定出现在该文档中。利用布隆过滤器,可以快速排除不包含搜索内容的文档,这个也是搜索引擎广泛采用的技术。
搜索型数据库的优点:
- 搜索是大数据最常见的场景,对于海量数据的搜索,基于搜索引擎的数据库提供了非常好的功能支持。
缺点:
- 由于需要搜索的数据量比较大,基于搜索的数据库往往是比较消耗资源的。
流行的基于搜索的数据库有Elastic Search,Splunk,Solr。 其中Elastic和Solr都是构建在Apache Lucene上的分布式搜索引擎。
搜索引擎数据库常常被用于系统的运维和监控。我们通常会把一些日志信息导入到基于搜索引擎的数据库中来进行分析和监控。
列式/宽列存储数据库
我这里把列存储数据库和宽列数据库放在一起,它们其实是有些差异的。
传统 OLTP 数据库通常采用行式存储。所有的列依次排列构成一行,以行为单位存储,再配合以 B+ 树或 SS-Table 作为索引,就能快速通过主键找到相应的行数据。行式存储对于 OLTP 场景是很自然的:大多数操作都以实体为单位,即大多为增删改查一整行记录,显然把一行数据存在物理上相邻的位置是个很好的选择。
然而,对于 OLAP 场景,一个典型的查询需要遍历整个表,进行分组、排序、聚合等操作,这样一来按行存储的优势就不复存在了。更糟糕的是,分析型 SQL 常常不会用到所有的列,而仅仅对其中某些感兴趣的列做运算,那一行中那些无关的列也不得不参与扫描。
列式存储就是为这样的需求设计的。同一列的数据被一个接一个紧挨着存放在一起,表的每列构成一个长数组。
显然,列式存储对于 OLTP 不友好,一行数据的写入需要同时修改多个列。但对 OLAP 场景有着很大的优势:
- 当查询语句只涉及部分列时,只需要扫描相关的列
- 每一列的数据都是相同类型的,彼此间相关性更大,对列数据压缩的效率较高
列式存储是为了分析而优化的,它的优点是分析查询的速度快。但不支持事务,一般对写操作不友好。
面向列存储的数据库有:Google Dremel,Apache Parquet
在关系型数据库中,不能将宽列存储与面向列存储相混淆。这是一个内部概念,用于提高RDBMS针对OLAP工作负载的性能,并存储表中的数据,而不是逐条记录,而是逐列存储。
宽列存储(也称为可扩展记录存储)将数据存储在记录中,并且能够容纳大量动态列。由于列名和记录键不是固定的,并且一条记录可以包含数十亿列,因此宽列存储可以看作是二维键值存储。
宽列存储与文档存储共享Shema-free的特性,但是实现却大不相同。
宽列存储的优点:
- 高可靠性
- 高效性
- 面向列
- 可伸缩
- 可在廉价PC Server搭建大规模结构化存储集群
宽列存储的缺点:
- 由于数据表之间是分离的,不支持join
- 读的性能明显不如写入的性能
- 支持的查询比较简单
宽列数据库比较流行的有:Cassandra,HBase, Google bigtable
时序数据库
时间序列DBMS是为处理时间序列数据而优化的数据库管理系统:每个条目都与时间戳关联。
例如,时间序列数据可以由所谓的物联网中的传感器,智能电表或RFID生成,或者可以描述高频股票交易系统的股票报价器。
时间序列DBMS旨在有效地收集,存储和查询具有高交易量的各种时间序列。尽管可以使用其他类别的DBMS(从键值存储到关系系统)来管理时间序列数据,但是特定的挑战通常需要专门的系统。例如。像“ SELECT SENSOR1_CPU_FREQUENCY / SENSOR2_HEAT”之类的查询会根据每个时间的重叠区域将两个时间序列合并在一起,并输出单个复合时间序列。
时序数据库的特点有一点类似我们之前的OLAP,它们都是写多读少。要按照指定的维度进行读取和聚合。
流行的时序数据库有: InfluxDB, Prometheus, Kdb+ 等等
图数据库
图数据库,也称为面向图的数据库,将图结构中的数据表示为节点和边,即节点之间的关系。它们允许轻松处理该形式的数据,并且可以简单地计算图形的特定属性,例如从一个节点到另一节点所需的步骤数。
图数据库通常不会在所有节点上提供索引,在这种情况下,无法基于属性值直接访问节点。
通常,在图计算中,基本的数据结构表达就是:
G=(V, E)V=vertex(节点)E=edge(边)
- 使用图(或者网)的方式来表达现实世界的关系很直接、自然,易于建模。比如某人喜欢看某电影,就可以建立一条边连接这个人和这部电影,这条边就叫做“喜欢”边,同时这个人还可以有其它边,比如“朋友”边、“同学”边等,同样这个电影也可以有其它边,比如“导演”边、“主演”边等,这样就构建了自然的关系网。
- 图数据库可以很高效的插入大量数据。图数据库面向的应用领域数据量可能都比较大,比如知识图谱、社交关系、风控关系等,总数据量级别一般在亿或十亿以上,有的甚至达到百亿边。mysql不做分表分库的情况下插入百万数据基本就慢到不行,图数据库基本能胜任亿级以上的数据,比如neo4j、titan(janus)、hugegraph等图数据库,持续插入十亿级的数据基本还能保持在一个较高的速度。
- 图数据库可以很高效的查询关联数据。传统关系型数据库不擅长做关联查询,特别是多层关联(比如查我的好友的好友有哪些人),因为一般来说都需要做表连接,表连接是一个很昂贵的操作,涉及到大量的IO操作及内存消耗。图数据库对关联查询一般都进行针对性的优化,比如存储模型上、数据结构、查询算法等,防止局部数据的查询引发全部数据的读取。
- 图数据库提供了针对图检索的查询语言,比如Gremlin、Cypher等图数据库语言。图查询语言大大方便了关联分析业务的持续开发,传统方案在需求变更时往往要修改数据存储模型、修改复杂的查询脚本,图数据库已经把业务表达抽象好了,比如上面的2层好友查询,Gremlin实现为g.V(me).out('friend').out('friend'),如果需要改为2层同学查询,那调整一下把好友换为同学即可g.V(me).out('classmate').out('classmate')。
- 图数据库提供了专业的分析算法、工具。比如ShortestPath、PageRank、PersonalRank、Louvain等等,不少图数据库还提供了数据批量导入工具,提供了可视化的图显示界面,使得数据的分析结果更加直观展示出来。
流行的图数据库有Neo4J,Dgraph,TigerGraph
除了上述提到的数据库类型,还有其它的一些例如对象数据库,事件数据库,内容数据库等,我这里就不列出了,大家可以在用到的时候其查找相应的内容。
希望上述内容能够帮助架构师在选择架构中的数据存储的时候,有所帮助!
相关链接:
- 刚哥谈架构 (一) 软件架构的定义
- 刚哥谈架构 (二) 我眼中的架构师
- 刚哥谈架构 (三)软件架构的道与术
- 刚哥谈架构(四)- 质量属性对软件架构的影响
- 刚哥谈架构 (五)- 架构师应该读些什么书?