非关系型的数据库 NOSQL (Not Only SQL)是对不同于传统的关系型数据库的数据库管理系统的统称,其中最重要的是 NOSQL 不使用 SQL 作为查询语言,其数据存储可以不需要固定的表格模式,也经常会避免使用 SQL 的 JOIN 操作,一般有水平可扩展性的特征。
1. NOSQL 概述
WEB2.0 引发传统数据库的问题:
(1)对数据库高并发读写的需求
Web 2.0 网站要根据用户个性化信息来实时生成动态页面和提供动态信息,所以基本上无法使用动态页面静态化技术,因此数据库并发负载非常高,往往要达到每秒上万次读写请求。关系数据库应付上万次 SQL 查询还勉强顶得住,但是应付上万次SQL写数据请求,硬盘 I/O 就已经无法承受了。对于普通的 SNS(Social Network Site) 社交网站,往往存在对高并发写请求的需求。
(2)对海量数据的高效率存储和访问的需求
对于大型的SNS网站,每天用户产生海量的用户动态,以Facebook为例,每天要处理27亿次Like按钮点击,3亿张图片上传,500TB数据接收。对于关系数据库来说,在上亿条记录的表里面进行SQL查询,效率是极其低下乃至不可忍受的。特别是大型Web网站的用户登录系统,例如腾讯,动辄数以亿计的账号,关系数据库也很难应付。
(3)对数据库的高可扩展性和高可用性的需求
在基于Web的架构当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,你的数据库却没有办法像 WebServer 和 AppServer 那样简单地通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展是非常痛苦的事情,往往需要停机维护和数据迁移,为什么数据库不能通过不断地添加服务器节点来实现扩展呢?
NOSQL 的缺点:
数据库事务一致性需求。很多Web实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求也不高。
数据库的写实时性和读实时性需求。对关系数据库来说,插入一条数据之后立刻查询, 是肯定可以读出来这条数据的,但是对于很多Web应用来说,并不要求这么高的实时性。
对复杂的SQL查询,特别是多表关联查询的需求。任何大数据量的Web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的复杂SQL报表查询,特别是SNS类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大地弱化了。
NOSQL 的主要优势:
打破了长久以来关系型数据库与ACID理论大一统的局面。
灵活的存储格式,NOSQL 数据存储不需要固定的表结构,通常也不存在连接操作。在大数据存取上具备关系型数据库无法比拟的性能优势。
应用体系结构数据存储在横向伸缩性上能够满足需求。
NOSQL应用案例:Google 的 BigTable 与 Amazon 的 Dynamo。一些开源的 NOSQL 体系,如 Facebook 的 Cassandra,Apache 的 HBase。
2. 相关理论基础
2.1 一致性
CAP 理论是对于一个分布式系统,一致性(Consistency)、可用性(Availablity)和分区容忍性(Partition tolerance)三个特点最多只能三选二。
一致性:意味着系统在执行了某些操作后仍处在一个一致的状态,这点在分布式的系统中尤其明显。比如某用户在一处对共享的数据进行了修改,那么所有有权使用这些数据的用户都可以看到这一改变。简言之,就是所有的结点在同一时刻有相同的数据。
可用性:指对数据的所有操作都应有成功的返回。高可用性则是在系统升级(软件或硬件) 或在网络系统中的某些结点发生故障的时候,仍可以正常返回。简言之,就是任何请求不管成功或失败都有响应。
分区容忍性:这一概念的前提是在网络发生故障的时候。在网络连接上,一些结点出现故障,使得原本连通的网络变成了一块 一块的分区,若允许系统继续工作,那么就是分区可容忍的。
在数据库系统中,事务的ACID属性保证了数据库的一致性。比如银行系统中,转账就是一个事务,从原账户扣除金额,以及向目标账户添加金额,这两个数据库操作的总和构成一个 完整的逻辑过程,具有原子的不可拆分特性,从而保证了整个系统中的总金额没有变化。
然而,这些ACID特性对于大型的分布式系统来说,是和高性能不兼容的。比如,你在网上书店买书,任何一个人买书这个过程都会锁住数据库直到买书行为彻底完成(否则书本库存数可能不一致),买书完成的那一瞬间,世界上所有的人都可以看到书的库存减少了一本(这也意味着两个人不能同时买书)。这在小的网上书城也许可以运行得很好,可是对Amazon这种网上书城却并不是很好。
而对于Amazon 这种系统,它也许会用Cache 系统,剩余的库存数也许是几秒甚至几个小时前的快照,而不是实时的库存数,这就舍弃了一致性。并且,Amazon 可能也舍弃了独立性,当只剩下最后一本书时,也许它会允许两个人同时下单,宁愿最后给那个下单成功却没货的人道歉,而不是整个系统性能的下降。
BASE理论是一个弱一致性的理论,只要求最终一致性。
基本可用 (Basically Available)
软状态 (Soft state):可以理解为“无连接”,相对应的就是“面向连接 (Hard state)”。
最终一致性 (Eventual consistency):最终整个系统(时间和系统的要求有关)看到的数据是一致性的。
对应的场景有Amazon的卖书系统,也许在卖的过程中,每个用户看到的库存数是不一样的,但最终卖完后,库存数都为0。再比如SNS网络中,C更新状态,A也许可以1分钟就看到,而B甚至5分钟后才看到,但最终大家都可以看到这个更新。
如果选择了CP (一致性和分区容忍性),那么就要考虑ACID理论(传统关系型数据库的基石,事务的四个特点)。如果选择了AP (可用性和分区容忍性),那么就要考虑BASE理论。如果选择了CA (一致性和可用性),如Google的bigtable,那么在网络发生分区的时候,将不能进行完整的操作。
2.2 分区
分区是为了解决随数据量的增加,单一存储在计算机系统中或者备份等,同时,为了一些规模性的操作或者动态因素的影响。主要方法如下:
- 内存缓存:缓存技术可以看成一种分区。
- 集群:数据库服务器集群在为用户提供服务时的透明性(用户感觉数据像是在同一个地方),是另外一个对数据进行分区的方法。
- 读写分离:指定一台或多台主服务器,所有或部分的写操作被送至此,同时再设一定数量的副本服务器用以满足读请求。
- 范围分割技术或分片:指对数据按照如下方式进行分区操作,即对数据的请求和更新在同一个结点上,并且对于分布在不同服务器上的数据存储和下载的量大致相同。
2.3 存储分布
存储分布是确定了如何访问磁盘,以及如何直接影响性能,主要分为基于行的存储布局、 列存储布局、带有局部性群组的列存储布局、LSM-Tree 四种。
(1)行存储将每条记录的所有字段的数据聚合存储。行存储主要适用于OLTP,或者更新操作,尤其是插入、删除操作频繁的场合;
(2)列存储将所有记录中相同字段的数据聚合存储。列存储主要适用于OLAP,数据仓库,数据挖掘等查询密集型应用。
列存储相对行存储的优点主要有两个:
每个字段的数据聚集存储,在查询只需要少数几个字段的时候能大大减少读取的数据 量。而查询密集型应用的特点之一就是查询一般只关心少数几个字段,而行存储每次必须读取整条记录。
既然一个字段的数据聚集存储,那就更容易为这种聚集存储设计更好的压缩/解压算法。
(3)带有局部性群组的列存储,它和列存储很相似,但是增加了局部性群组的特色。它是指根据需要将原来不存储在一起的数据,以列族为单位存储至单独的字表中。如用户对网站排名、语言等分析信息感兴趣,那么可以将这些列族放在单独的子表,减少无用信息读取,改善存取效率。
(4)LSM-Tree (Log Structured MergeTrees,日志结构合并树),前面的存储结构在描述如何序列化逻辑数据结构,而LSM-Tree描述的则是为了满足高效、 高性能、安全地读写的要求,如何有效地利用内存和磁盘存储。
2.4 查询模型
不同的NOSQL数据存储提供的查询功能有重大分歧。现在许多NOSQL数据库是基于DHT(Distributed Hash Table,分散哈希表)模型的。为了访问和修改对象数据,客户端要求提供对象的主键,然后数据库再根据提供的主键进行相等匹配。
常见的查询模型:
结合 SQL 数据库。一个最直接的方式是通过将NOSQL数据库拷贝到关系数据库或者文本数据库,来提供查询能力。当然,这就要求关系数据库要足够大以便能够存储每个对象的查询属性。
分散/集合本地搜索。一些NOSQL数据库提供本地数据库内的索引和查询处理机制。在这种情况下,我们可以让查询处理器将查询广播到DHT中的所有节点,在每个节点上将会执行查询,并将结果送回到查询处理器,然后查询处理器将结果聚集成一个单一响应。
分布式B+树。B+树是一种用在关系型数据库管理系统中常见的索引结构。B+树的分布式版本可以用在DHT环境中。其基本思路是为了定位B+树的根节点哈希要搜索的属性。根节点的“值”包含其孩子节点的ID。因此,客户端为了找到孩子节点可以发起另一个DHT 查找调用。继续这个过程,客户端最终向下导航到叶节点,从而与搜索条件匹配。紧接着,为了提取实际的对象,客户端将发起另一个DHT查找。
前缀哈希表/分布式Trie。前缀哈希表(Prefix Hash Table, PHT,又名分布式Trie)是一个树形数据结构。在这个树形结构中,从根节点到叶子的每一条路径上均包含了键值的前缀,并且每个Trie中的节点都包含了它是谁的前缀的所有数据。PHT主要包含 三个操作:lookup、rangequery和insert/delete。
3. NOSQL 数据库的种类
NOSQL数据库并没有统一的模型,而且是非关系型的。常见的NOSQL数据库通过存储方式划分为文档存储、键值存储、列存储和图存储。NOSQL数据库的分类和特定如下:
3.1 文档存储
文档存储一般用类似json的格式存储,存储的内容是文档型的。面向文档的数据库具有以下特征:即使不定义表结构,也可以像定义了表结构一样来使用。另外,面向文档的数据 库可以通过复杂的查询条件来获取数据。虽然不具备事务处理和join等关系型数据库所具有的处理能力,但其他的处理基本都可以实现。
在文档存储中,文档可以很长,很复杂,无结构,可以是任意结构的字段,并且数据具有物理和逻辑上的独立性,这就和具有高度结构化的表存储(关系型数据库的主要存储结构)有很大的不同,而最大的不同则在于它不提供对参数完整性和分布事物的支持;不过,它们之间也并不排斥,可以进行数据的交换。
1)文档数据库MongoDB
MongoDB 无表结构,在保存数据和数据结构时,会把数据和数据结构都完整地以BSON的形式保存起来(BSON是JSON的二进制编码格式,但比JSON支持更加复杂的格式,在空间利用上更加高效) 并把它作为值和特定的键进行关联。它拥有比关系型数据库更快的速度,而且可以像关系型数据库那样通过添加索引来进行高速处理。
MongoDB 的主要特征:
高性能:提供JSON、XML等可嵌入数据快速处理功能;提供文档的索引功能,相对传统数据库而言,大大提高查询速度。
丰富的查询语言:为数据聚合、结构文档、地理空间提供丰富的查询功能。
高可用性:提供数据冗余处理和故障自动转移的功能。
水平扩展能力:通过集群将数据分布到多台机器,而不是只提升单个节点的性能,具体处理分为主从和权衡两种处理模式。
MangoDB为C 、C# 、.NET、Java、PHP等各种开发语言提供了程序库。如 Java (Morphia) 、PHP (Mongo)、Ruby (Rmongo,mongo-ruby-dirver) 、Objective C (NuMongoDB)、Perl (MongoDB)、R (RMongo)、Lua (LuaMongo)。
2)其他文档存储产品
除了应用比较多的MangoDB文档数据库之外,另外还有如下一些在应用中使用比较多的文档型数据库:
BaseX 是一个非常轻巧和高性能的 XML 数据库系统和 Xpath/Xquery 处理。包含了对 W3C Update 和FullText 扩展的全面支持。一个可交互和友好的 GUI 前台操作界面,可以用 Xquery 查询相关数据库的 XML 文件,也可动态展示 XML 文件层次和结点关系的图。它具有高度的交互可视性,可实时执行和可进行 Xquery 编辑的特点。
CouchDB 也是一种面向文档的非关系型数据库,用 Erlang 编写,它主要致力于健壮性、 高并发性和容错性。它与其他 NOSQL 数据库最大的不同在于它的双流向增加副本。CouchDB 的文件也是基于 JSON,但同样也有二进制的设置,它的 API 基于 REST,用标准的动词 GET、 PUTPOST 和 DELETE。可以用 JavaScript 来操作 CouchDB,用户可以通过 Map/reduce 函数来生成自己的视图。
LotusNotes 也是目前较为流行的文档数据库系统之一。作群件系统,它利用自身强大的功能使其在企业、政府办公自动化方面的应用越来越广。它实现了业务流程化,并且在全文检索、复制、集成开发环境和7层安全机制等方面都有自己独特的定义。
3.2 键值存储
键值存储模型时最简单,最方便使用的数据模型,支持键值存储和提取。键值存储最大的好处是不用为值指定一个特定的数据类型,这样就能在值里存储任意类型的数据。另外具有极高的并发读写性能。
键值存储中的键是很灵活的,可以是图片名称、网页 URL 或者文件路径名,它们指向那 些图片、网页和对应的文档。
键可以用很多种格式来表示:
图片或者文件的路径名。
根据值的哈希值生成的字符串。
REST Web 服务调用。
SQL 查询。
值可以是任何 BLOB 数据,如字符串、图片、网页或视频。
键值存储示例如下:
图片名称:{'123.BMP':'Binary image file'} 网页URL:{'http:///www.google.com':'HTML of a web page'} 文件路径名:{'C:/usr/Zhang/filel.doc':'WORD document'} MD5哈希:{'808936d6iojfej20feif930890930r':'This is test'} REST Web 服务调用:{'View-person?person-id=123&format=xml':'<Person><id> 123</id></Person>'} SQL 查询:{'SELECT *FROM STUDENT':'*'}
键值存储数据操作方式,存在 put、get 和 delete。这三种操作规定了程序员与键值存储交互的基本方式。使用示例如下:
put(k e y a s x s : s t r i n g , key as xs:string,keyasxs:string,value as item())对表添加一个新的键值对,并且当键值存在时,更新键对应的值。
get($key as xs:string) as item())根据给出的任意键返回键对应的值,如果键值存储中没有该键,将返回一个错误信息。
delete($key as xs:string)将键和对应的值从表中删除,如果键值存储中没有该键,将返回一个错误信息。
键值存储的数据保存方式可以分为临时性、永久性和两者兼具三种。
临时性保存类型,特点有在内存中保存数据;可以快速进行读和写;数据有可能丢失。
永久性保存类型,特点有在硬盘上保存数据;可以进行较快速的读和写操作;数据不会丢失。
两者兼具型,特点有同时在内存和硬盘上保存数据;可以进行非常快速的读和写操作;保存在硬盘上的数据不会丢失。
键值存储产品
键值存储产品主要有亚马逊的 Memcached、Redis、Dynamo、ProjectVoldemort、TokyoTyrant、Riak、Scalaries 这几个数据库,
Memcached 属于前面分类中的临时性保存类型,是高性能的分布式内存对象缓存系统。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态 web 应用的速度、提高可扩展性。
Redis 是一种主要基于内存存储和运行,能够快速响应的键值数据库,属于临时和永久兼具类型,有点像 Memcached,整个数据库统统加载在内存当中进行操作,但是通过定期异步操作把数据库数据 flush 到硬盘上进行保存。因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过10万次读写操作。
3.3 列存储
列存储是以列作为单位来进行数据的存储的,对大量行少数列进行读取,对所有行的指定列进行同时更新。
面向列的数据库具有高扩展性,即使数据增加也不会降低相应的处理速度,所有它主要应用于需要处理大量数据的情况。另外,利用面向列的数据库的优势,把它作批处理程序的存储器来对大量数据进行更新也是非常有用的。
列存储数据库,主要产品有 Google 的 Bigtable、由 Bigtable 衍生的 Hypertable 和 HBase、Cassandra 这几个数据库。
Bigtable 是指根据需要将原来不存储在一起的数据,以列族为单位存储至单独的字表中,通过一些主键来组织海量数据,并实现高效的查询。
Hypertable 是一个开源、高性能、可伸缩的数据库,是Bigtable的一个开源实现,它采用与Google的Bigtable相似的模型。
HBase,即Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价 PC Server 上搭建起大规模结构化存储集群。
Cassandra 用于存储特别大的数据,是一套开源的分布式数据库, 结合了 Dynamo 的键值与 Bigtable 的面向列的特点。它是一个混合型的NoSQL数据库,其主要功能比键值存储 Dynamo 更丰富,但支持力度却并不如文档存储 MongoDB。它的主要特性是: 分布式、基于 column 的结构化、高扩展性。
3.3 图存储
图存储是一个包含一连串的节点和关系的系统,当它们结合在一起时,就构成了一个图。 图存储有三个字段:节点、关系和属性。图节点通常是现实世界中对象的表现,如人名、组织、电话号码、网页或计算机节点。而关系可以被认为是这些对象之间的联系,通常被表示为图中两个节点之间连接线。
图存储数据库,主要产品有 AllegroGraph、DEX、Neo4j、FlockDB。
其中,比较成熟的是 Twitter 的 FlockDB。FlockDB 是一个分布式的图数据库,但是它并没有优化遍历图的操作。它优化的操作包括:超大规模邻接矩阵查询,快速读写和可分页查询;它主要是要解决可伸缩性的问题,通俗点说就是通过增加服务器就能解决用户量上升造成的访问压力,而不需要在软件上做大的变动。
Neo4j 提供了大规模可扩展性,在一台机器上可以处理数十亿节点/关系/属性的图像,可以扩展到多台机器并行运行。重点解决了拥有大量连接的传统 RDBMS 在查询时出现的性能衰退问题。通过围绕图进行数据建模,Neo4j 会以相同的速度遍历节点与边,其遍历速度与构成图的数据量没有任何关系。此外,Neo4j 还提供了非常快的图算法、推荐系统和 OLAP 风格的分析,而这一切在目前的 RDBMS 系统中都是无法实现的。
4. NOSQL 应用案例和新技术
4.1 HBase 数据库
HBase 为列存储模式与键值 对存储模式相结合的 NoSQL 数据库,具有灵活的数据模型,不仅可以基于键进行快速查询,还可以实现基于值、列名等全文遍历和检索。还可以实现自动的数据分片,用户在不知道数据存储在哪个节点上时,只要说明检索要求,系统会自动进行数据的查询和反馈。
HBase数据逻辑结构主要包括行键(Rawkey)、列(Column)、时间戳(Timestamp)和单元格(Celll)。
行(Row):在 HBase 表中,Row key 是用来检索记录的主键,可以是任意字符串,它的最大长度是64KB,实际应用中长度一般为10~ 100bytes。
列(Column):列由列族(Column Family)和列标识(Column Qualifier)两部分组成。列族是列的集合,所有成员有着相同的前缀;列标识没有特定的数据类型,通常列族里面的数据通过列标识进行定位。
时间戳(Timestamp):每个单元格会保存同一数据的多个版本,通常通过时间戳进行索引。读取单元格时,如果时间戳没有被指定,则默认返回最新的数据;写入单元格时,如果时间戳没被指定,则返回当前时间。
单元格(Cell):单元格由行、列和时间戳共同确定,可以使用{Rowkey,Column(=<family>+<qualifier>),Timestamp}三元组进行访问。单元格的数据没有特定类型,以二进制字节进行存储。
HBase Shell 是 HBase 的命令行工具,可以使用 Shell 命令来查询 HBase 中数据的详细情况。
创建表
语法格式为:create<table>,{NAME=><family>,VERSION=><version>}
。 大括号内是对列族的定义,NAME、VERSION 是参数名,不需要单引号。需要注意的是, 在HBase Shell 语法中,所有字符串参数必须包含在单引号内,且区分大小写。Version=><version>指的是单元格内保存最近 version 个版本。
create 'student',{NAME=>'info',VERSION=>2}
插入数据
语法为:put<table>,<rowkey>,<family:column>,<value>,<timestamp>
。table 是表名;第二个参数是行键名称,它的类型为字符串类型;第三个参数是列族和列的名称,列族必须是已经创建的,列名临时定义,因此列是可以随意拓展的;第四个参数是单元格的值,所有的数据都是字符串的形式;最后一个是时间戳。
put 'student','7001','info:age','16',1
查询数据
语法为:scan<table>,{COLUMNS=[family:column,…]},LIMIT=>num
。使用scan 命令指定表名,即可查询全表的数据,还可以指定列族和列的名称,或者输出行数等。在指定条件进行输出的时候,需要使用大括号将参数括起来。指定列族和列名称时使用 COLUMNS 限定符,LIMIT 指的是扫描前几条数据。还可以添加 STARTROW 和 TIMERANGE 等高级功能,指定输出范围使用 STARTROW 和 ENDROW 限定符,此时输出行不包括 ENDROW;TIMERANGE 指定最大时间戳和最小时间戳,只有在此范围内的单元格才能被获取。
删除数据
删除行中某个列值的命令是:
delete <table>,<rowkey>,<family:column>,<timestamp>
删除行的命令是:
deleteall <table>,<rowkey>,<family:column>,<timestamp>
删除表中的所有数据的命令是:
truncate <table>
4.2 云数据库 GeminiDB
GeminiDB 是一款基于华为自主研发的计算存储分离架构的分布式非关系型数据库服务。
“云”技术的分层架构大致可分为三层:
- 基础设施层。如互联网数据中心 (Internet Data Center, IDC) 机房,服务器以及网络。
- 存储层。所有独立的存储服务器进行集中式统一管理。统一存储指分布式存储,如开源社区的 Ceph,Google 的 GFS, Hadoop 生态的 HDFS 等。
- 计算层。如中间件、应用、大数据计算(Max Compute),以及计算存储分离后的数据库等。