存储高性能[NoSQL]

NoSQL=Not Only SQL,虽然关系型数据库凭借强大的SQL和ACID的属性得到广泛应用,但并不意味着没有缺憾,比如关系型数据库无法存储数据结构存的是行记录,比如关系型数据库的schema扩展很不方便,比如关系型数据库在大数据场景下I/O较高,比如关系型数据库的全文搜索功能比较弱,因此诞生了不同的NoSQL解决方案,这些方案与关系型数据库比在某些应用场景下表现更好,但NoSQL方案带来的优势本质上是牺牲了ACID特性的某个或者某几个,因此NoSQL方案更适合作为SQL的一个有力补充

常见的 NoSQL 方案

  • K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表
  • 文档数据库 :解决关系数据库强schema约束的问题,以 MongoDB 为代表
  • 列式数据库 : 解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表
  • 全文搜索 引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表

K-V存储

K-V 存储的全称是 Key-Value 存储,其中 Key 是数据的标识,和关系数据库中的主键含义 一样,Value 就是具体的数据,Redis是K-V存储的典型代表,它是一款开源(基于 BSD 许可)的高性能K-V缓存和存储系统,Redis的Value是具体的数据结构,包括 string、hash、list、set、sorted set、bitmap和hyperloglog ,所以常常被称为数据结构服务器

以 List 数据结构为例,Redis提供了如下的典型的操作

  • LPOP key,从队列的左边出队一个元素
  • LINDEX key index,通过其索引列表获取一个元素
  • LLEN key,获得队列(List)的长度
  • RPOP key,从队列的右边出队一个元素

如果用关系数据库来实现以上这些功能,就会变得很复杂。 例如, LPOP 操作是移除井返回 key 对应的 list 的第一个元素。 如果用关系数据库来存储,为了达到同样目的,则需要进行 如下操作:

  1. 每条数据除了数据编号(例如,行ID),还要有位置编号,否则没有办法判断哪条数据是第一条。注意这里不能用行 ID作为位置编号,因为我们会往列表头部插入数据
  2. 查询出第一条数据
  3. 删除第一条数据
  4. 更新从第二条开始的所有数据的位置编号

可以看出关系数据库的实现很麻烦,而且需要进行多次 SQL 操作,性能很低

Redis的缺点主要体现在并不支持完整的 ACID 事务,Redis虽然提供事务功能,但 Redis的事务和关系数据库的事务不可同日而语, Redis的事务只能保证隔离性和一致性(I和C),无法保证原子性和持久性(A和D)
具体实现原理如下 :

  • 原子性:Redis事务不支持原子性,Redis 不支持回滚操作,事务中间一条命令执行失败,既不会导致前面己经执行的命令被回滚,也不会中断后面的命令的执行。
  • 一致性:Redis事务能够保证事务开始之前和事务结束以后,数据库的完整性没有被破坏
  • 隔离性:Redis不存在多个事务的问题,因为 Redis是单进程单线程的工作模式。这种隔离性的方式也带来一个隐含的问题:如果某个客户端通过事务提交了大量的命令,那么会阻塞其他客户端进行任何操作
  • 持久性:Redis提供两种持久化的方式,即RDB和AOF
  • RDB持久化只备份当前内存中的数据集,事务执行完毕时,其数据还在内存中,并未立即写入到磁盘,所以RDB持久化不能保证Redis事务的持久性。
  • AOF持久化是先执行命令,执行成功后再将命令追加到日志文件中 。 即使 AOF 每次执行命令后立刻将日志文件刷盘,也可能丢失1条命令数据,因此AOF也不能严格保证Redis事务的持久性

举一个例子来说明Redis事务和数据库事务的差别:
例如,用户A关注了用户B, 实际上产生了两条数据操作:A 的“ 关注” 列表要增加B, B的“粉丝”列表要增加 A, 如果用数据库来存储关系数据,通过事务可以保证这两个操作要么同时成功,要么同时失败,而使用 Redis事务来处理,可能将B加入了A的关注列表 ,但没有将A加入B的粉丝列表。

虽然Redis并没有严格遵循 ACID 原则,但实际上大部分业务也不需要严格遵循 ACID原则,以上述的微博关注操作为例,即使系统没有将A加入B的粉丝列表,其实业务影响也非常小,因此我们在设计方案时,需要根据业务特性和要求来确定是否可以用 Redis,而不能因为Redis不遵循ACID原则就直接放弃

文档数据库

为了解决关系数据库 schema 带来的问题,文档数据库应运而生,文档数据库最大的特点就是 no-schema,可以存储和读取任意的数据,目前绝大部分文档数据库存储的数据格式是JSON(或者BSON),因为 JSON数据是自描述的,无须在使用前定义宇段,读取一个JSON中不存在的字段也不会导致SQL那样的语法错误

文档数据库的no-schema特性,给业务开发带来如下几个明显的优势:

  • 新增字段简单:业务上增加新的字段,无须再像关系数据库一样要先执行 DDL 语句修改表结构,程序代码 直接读写即可
  • 历史数据不会出错:对于历史数据,即使没有新增的字段,也不会导致错误,只会返回空值,此时代码进行兼容处理即可
  • 可以很容易存储复杂数据
  • JSON是一种强大的描述语言 ,能够描述复杂的数据结构,例如,我们设计一个用户管理系统,用户的信息有ID 、 姓名、性别、爱好、邮箱、地址、学历信息。其中爱好是列表(因为可以有多个爱好),地址是一个结构,包括省、市、区楼盘地址,学历包括学校、专业、入学毕业年份信息等
  • 如果我们用关系数据库来存储,需要设计多张表,包括基本信息(列 : D、姓 名、性别、邮箱)、爱好(列:ID、爱好)、地址(列:省、市、区、详细地址)、学历(列:入 学时间、毕业时间、学校名称、专业),而使用文档数据库,一个 JSON 就可以全部描述
{
     
	"id":1000,
	"name":"Davieyang",
	"sex":"male",
	"hobbies":[
		"football",
		"playing",
		"singing"
	],
	"email":"[email protected]",
	"address":[
		"province":"Beijing",
		"city":"Beijing",
		"district":"Yizhuang",
		"detail":"Beijing Road 10000"
	],
	"education":[
		{
     
			"begin":"2001-09-01",
			"end":"2004-07-31",
			"school":"BIM",
			"major":"Computer Science&Technology"
		},
		{
     
			"begin":"2004-09-01",
			"end":"2007-07-01",
			"school":"SCUT",
			"major":"Computer Science & Technology"
		}
	]
}

通过这个样例我们看到,使用 JSON 来描述数据,比使用关系型数据库表来描述数据方便和容易得多而且更加容易理解
文档数据库的这个特点,特别适合电商和游戏这类的业务场景。以电商为例,不同商品的属性差异很大,例如,冰箱的属性和笔记本电脑的属性差异非常大,即使是同类商品也有不同的属性。例如,LCD和LED显示器,两者有不同的参数指标
这种业务场景如果使用关系数据库来存储数据,就会很麻烦,而使用文档数据库,简单、方便, 扩展新的属性也更加容易

  • 文档数据库no-schema的特性带来的这些优势也是有代价的, 最主要的代价就是不支持事务。 例如使用 MongoDB来存储商品库存,系统创建订单的时候首先需要扣减库存,然后再创建订单,这是一个事务操作,用关系数据库来实现就很简单,但如果用 MongoDB 来实现,就无法做到事务性。异常情况下可能出现库存被扣减了,但订单没有创建的情况 。因此某些对事务要求严格的业务场景是不能使用文档数据库的
  • 文档数据库另外一个缺点就是无法实现关系数据库的join操作。例如,我们有一个用户信 息表, 一个订单表,订单表中有买家用户id 。如果要查询“购买了苹果笔记本用户中的女性用户”,用关系数据库来实现,一个简单的join操作就搞定了,而用文档数据库是无法进行join查询的,需要查两次,一次查询订单表中购买了苹果笔记本的用户,然后再查询这些用户哪些 是女性用户
  • 综合上述的介绍和分析,文档数据库并不能完全取代关系数据库,更多时候是作为关系数据库的一种补充。例如,在常见的电商网站设计中,可以使用关系数据库存储商品库存信息、订单基础信息,而使用文档数据库来存储商品详细信息

列式数据库

顾名思义,列式数据库就是按照列来存储数据的数据库,与之对应的传统关系数据库被称为“行式数据库”,因为关系型数据库是按照行来存储数据的

关系型数据库按照行式来存储数据,主要有如下几个优势:

  • 业务同时读取多个列时效率高,因为这些列都是按行存储在一起的,一次磁盘操作就能够把一行数据中的各个列都读取到内存中
  • 能够一次性完成对一行中的多个列的写操作,保证了针对行数据写操作的原子性和一致性,否则如果采用列存储,可能会出现某次写操作,有的列成功了,有的列失败了,导致数据不一致

列式存储的优势

  • 行式存储的优势是在特定的业务场景下才能体现,如果不存在这样的业务 场景 ,那么行式存储的优势也将不复存在,甚至成为劣势,典型的场景就是海量数据进行统计。例如,计算某个城市体重超重的人员数据,实际上只需要读取每个人的体重这一列并进行统计即可,而行式存储即使最终只使用一列,也会将所有行数据都读取出来 。 如果单行用户信息有1KB,其中体重只有4个字节,行式存储还是会将整行1KB数据全部读取到内存中,这是明显的浪费。而如果采用列式存储,每个用户只需要读取4字节的体重数据即可,I/O将大大减少
  • 除了节省I/O,列式存储还具备更高的存储压缩比,能够节省更多的存储空间,普通的行式数据库一般压缩率在3:1到5:1左右而列式数据库的压缩率一般在8:1到30:1左右,因为单个列的数据相似度相比行来说更高,能够达到更高的压缩率

列式存储的劣势

  • 如果场景发生变化,例如需要频繁地更新多个列。因为列式存储将不同列存储在磁盘上不连续的空间,导致更新多个列时磁盘是随机写操作,而行式存储时同一行多个列都存储在连续的空间,一次磁盘写操作就可以完成,列式存储的随机写效率要远远低于行式存储的写效率
  • 列式存储高压缩率在更新场景下也会成为劣势,因为更新时需要将存储数据解压后更新,然后再压缩,最后写入磁盘

基于上述列式存储的优缺点,一般将列式存储应用在离线的大数据分析和统计场景中,因为这种场景主要是针对部分列进行操作,且数据写入后就无须再更新删除

全文搜索引擎

数据库的缺陷

传统的关系型数据库通过索引来达到快速查询的目的,但是在全文搜索的业务场景下,索引也无能为力,主要体现在如下几点:

  • 全文搜索的条件可以随意排列组合,如果通过索引来满足,则索引的数量会非常多
  • 全文搜索的模糊匹配方式,索引无法满足,只能用like查询,而like查询是整表扫描,效率非常低

需要考虑Not Only SQL通过引入全文搜索引擎来弥补关系型数据库的缺陷

基本原理

全文搜索引擎的技术原理被称为“倒排索引”(Inverted index),也常被称为反向索引 、置入档案或反向档案,是一种索引方法,其基本原理是建立单词到文档的索引。之所以被称为“倒排"索引,是和“正排"索引相对的,“正排索引”的基本原理是建立文档到单词的索引

  • 正排索引适用于根据文档名称来查询文档内容。例如,用户在网站上单击了“面向对象葵 花宝典是什么”,网站根据文章标题查询文章的内容展示给用户
  • 倒排索引适用于根据关键词来查询文档内容。例如,用户只是想看“设计”相关的文章, 网站需要将文章内容中包含“设计”一词的文章都搜索出来展示给用户

倒排索引和正排索引虽然是相反的两个索引技术,但实际应用的时候并不是非此即彼,而是将两者结合起来,用到倒排索引的地方几乎肯定会用到正排索引,例如,用户搜索文章时用的是倒排索引,系统根据搜索关键词搜索到文档 ID ,然后系 统根据文档 ID 去查询文档名称展示给用户:当用户单击具体的某篇文档时,系统根据文档 ID 查询文档 内容井展示给用户,此时用的是正排索引

与数据库结合

全文搜索引擎的索引对象是单词和文档,而关系数据库的索引对象是键和行,两者的术语差异很大,不能简单地等同起来。因 此,为了让全文搜索引擎支持关系型数据的全文搜索,需要做一些转换操作,即将关系型数据转换为文档数据,目前常用的转换方式是将关系型数据按照对象的形式转换为JSON文挡,然后将JSON文档输入全文搜索引擎进行索引

全文搜索引擎能够基于JSON文档建立全文索引,然后快速进行全文搜索,以 Elasticsearch为例,其索引基本原理如下:

  • Elasticseach是分布式的文挡存储方式,它能以实时的方式存储和检索复杂的数据结构并序列化成为JSON文挡
  • 在Elasticsearch中,每个字段的所有数据都是默认被索引的,即每个字段都有为了快速检索设置的专用倒排索引。 而且不像其他多数的数据库,它能在相同的查询中使用所有倒排索引,并以惊人的速度返回结果

需要注意的是,实际应用中的转换,并不限定为只能单表到文档的转换,而可以根据搜索需要,灵活地从表转换到文档,可以单表转换到文挡,也可以多表联合起来转换为单一文档。例如, 一个学生管理系统 , 数据库表 可以设计为基础信息表(包含学号、姓名、性别、年龄、籍贯等)、专业信息表(学院、专业等)、成绩信息表(学科、成绩等)、社团信息表(是否为学生会干部、学生团体等)等多个表格,但 最终转换为 JSON 文档时, 可以一个学生有一个 JSON 文档,文档中包含学生的所有信息。

你可能感兴趣的:(架构师养成记,NoSQL方案,K-V存储,文档数据库,列式数据库,全文搜索引擎)