高性能架构设计-NOSQL基础

一、NOSQL简介

1.关系数据库存在如下缺点

(1)关系数据库存储的是行记录,无法存储数据结构

以微博的关注关系为例,“我关注的人”是一个用户 ID 列表,使用关系数据库存储只能将列表拆成多行,然后再查询出来组装,无法直接存储一个列表。

(2)关系数据库的 schema 扩展很不方便

关系数据库的表结构 schema 是强约束,操作不存在的列会报错,业务变化时扩充列也比较麻烦,需要执行 DDL(data definition language,如 CREATE、ALTER、DROP 等)语句修改,而且修改时可能会长时间锁表(例如,MySQL 可能将表锁住 1 个小时)。
 

(3)无法应对每秒上万次的读写请求

硬盘IO此时也将变为性能瓶颈(由于表之间关联关系导致的)。同时大数据场景下 I/O 较高如果对一些大量数据的表进行统计之类的运算,关系数据库的 I/O 会很高,因为即使只针对其中某一列进行运算,关系数据库也会将整行数据从存储设备读入内存,所以大数据查询SQL效率极低。

(4)关系数据库的全文搜索功能比较弱

关系数据库的全文搜索只能使用 like 进行整表扫描匹配,性能非常低,在互联网这种搜索复杂的场景下无法满足业务要求。
 

2.什么是NOSQL

NoSQL != No SQL,而是 NoSQL = Not Only SQL。非关系型数据库,存储的数据不需要固定的模式,无须多余操作就可以横向扩展,虽然NOSQL可以解决关系型数据库的问题,但是同时它也牺牲了ACID中的一点或者几点。
 

3.NoSQL数据库的优点

  • 海量数据下,读写性能优异,存储和访问的需求效率高
  • 数据模型灵活,什么样的数据类型都可以(不需要像sql一样在建表的时候定义字段的数据类型)
  • 数据间无关系,易于扩展,实时更改数据库


 

4.适用场景

高并发的操作是不建议有关联查询的,互联网公司用数据字段的冗余避免关联查询。分布式项目跨数据库、服务器进行表关联查询是十分不推荐的。

文件存储格式为BSON(一种JSON的扩展)BSON(Binary Serialized document Format)存储形式是指:存储在集合中的文档,被存储为键-值对的形式。键用于唯一标识一个文档,为字符串类型,而值则可以是各种复杂的文件类型。

SQL数据库适合那些需求确定和对数据完整性要求严格的项目。NoSQL数据库适用于那些对速度和可扩展性比较看重的那些不相关的,不确定和不断发展的需求。简单来说就是:

  • SQL是精确的。它最适合于具有精确标准的定义明确的项目。典型的使用场景是在线商店和银行系统。
  • NoSQL是多变的。它最适合于具有不确定需求的数据。典型的使用场景是社交网络,客户管理和网络分析系统。

很少有项目能够很好的适用于一种数据库。如果你对数据的需求比较小或是非标准化的数据任何一种数据库都是可以的。你比我更了解你的项目,我不建议你将SQL上的数据移植到NoSQL上反之亦然,除非它能够提供非常可观的收益。当然选择权在于你自己。在项目的一开始就要考虑好使用它们的利弊,这样才不会导致选择错误。


 

二、NoSql的五大分类

1.键值(Key-Value)存储数据库(解决关系数据库无法存储数据结构的问题)

这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。Key/value模型对于IT系统来说的优势在于简单、易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。例如:Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB。Redis 是 K-V 存储的典型代表,它是一款开源(基于 BSD 许可)的高性能 K-V 缓存和存储系统。Redis 的 Value 是具体的数据结构,包括 string、hash、list、set、sorted set、bitmap 和 hyperloglog,所以常常被称为数据结构服务器。

(1)不适用场景
  • 取代通过键查询,而是通过值来查询。Key-Value数据库中根本没有通过值查询的途径。
  • 需要储存数据之间的关系。在Key-Value数据库中不能通过两个或以上的键来关联数据。
  • 事务的支持。Redis 的事务只能保证隔离性和一致性(I 和 C),无法保证原子性和持久性(A 和 D)。虽然 Redis 并没有严格遵循 ACID 原则,但实际上大部分业务也不需要严格遵循 ACID 原则。以上面的微博关注操作为例,即使系统没有将 A 加入 B 的粉丝列表,其实业务影响也非常小,因此我们在设计方案时,需要根据业务特性和要求来确定是否可以用 Redis,而不能因为 Redis 不遵循 ACID 原则就直接放弃。

(2)适用的场景

储存用户信息,比如会话、配置文件、参数、购物车等等。这些信息一般都和ID(键)挂钩,这种情景下键值数据库是个很好的选择。

缓存:缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。

排行榜:很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

计数器:什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。

分布式会话:集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。

分布式锁:在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

社交网络:点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。

最新列表:Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。

消息系统:消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。


 

2.列存储数据库(解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表)

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

(1)适用的场景

业务同时读取多个列时效率高,因为这些列都是按行存储在一起的,一次磁盘操作就能够把一行数据中的各个列都读取到内存中。

能够一次性完成对一行中的多个列的写操作,保证了针对行数据写操作的原子性和一致性;否则如果采用列存储,可能会出现某次写操作,有的列成功了,有的列失败了,导致数据不一致。

行式存储的优势是在特定的业务场景下才能体现,如果不存在这样的业务场景,那么行式存储的优势也将不复存在,甚至成为劣势,典型的场景就是海量数据进行统计。例如,计算某个城市体重超重的人员数据,实际上只需要读取每个人的体重这一列并进行统计即可,而行式存储即使最终只使用一列,也会将所有行数据都读取出来。如果单行用户信息有 1KB,其中体重只有 4 个字节,行式存储还是会将整行 1KB 数据全部读取到内存中,这是明显的浪费。而如果采用列式存储,每个用户只需要读取 4 字节的体重数据即可,I/O 将大大减少。

除了节省 I/O,列式存储还具备更高的存储压缩比,能够节省更多的存储空间。普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右,因为单个列的数据相似度相比行来说更高,能够达到更高的压缩率。

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

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

  • 数据压缩比较有优势
  • 任何列都可以做索引
  • 查询时只有涉及到的列会被读取
(2)不适用场景
  • 如果需要ACID事务。Vassandra就不支持事务。
  • 原型设计。如果我们分析Cassandra的数据结构,我们就会发现结构是基于我们期望的数据查询方式而定。在模型设计之初,我们根本不可能去预测它的查询方式,而一旦查询方式改变,我们就必须重新设计列族。
  • 每次查询时,都需要对查询到的列进行数据重新组装
  • 插入/更新操作比较困难


 

(3)列式存储查询过程

  • 在列存储中,对于同样的核酸记录表,存储的物理结构如下:

在列式存储中,会把每一列存储到一起,如姓名列,是把所有记录中的姓名这列的值使用连续空间存放到一起

而对于各个列之间,是没有必要使用连续空间存放到一起的,所以很多列式数据库都使用了分布式存储的方式,存储各个列


 

①数据压缩

  • 很多列式数据库都是通过字典表的方式进行数据压缩,因为是把每一列存放到一起的,所以很容易通过对于每一列进行去重,来构建一个字典表,例如:对于姓名列,这列的所有数据如下:
彦祖|德华|路人甲|德华|彦祖

对这列值去重以后,构建一张姓名列字典表,构建算法忽略,就使用自增id的方式,如下:

| id | 姓名列 | |-----|-----| |1| 彦祖 | |2| 德华 | |3| 路人甲 |
  • 这样构建字典表,对于列存储的物理存储结构,就可以执行存储字典表中的id,而不用存储具体的值(而且重复值可以用相同的id替代),有了字典表以后姓名列存储如下:
1|2|3|2|1
  • 同样对于价格列,这列的所有数据如下:
35|20|8|23|20

对这列值去重以后,构建一张价格列字典表,构建算法忽略,就使用自增id的方式,如下:

| id | 价格列 | |-----|-----| |1| 35 | |2| 20 | |3| 8 | |4|23|
  • 有了字典表以后价格列存储如下:
1|2|3|4|2


 

②查询执行流程

select * from 核酸记录表 where 姓名=彦祖 and 价格=20

对于该sql,执行过程如下:

1.对于where 姓名=彦祖 首先查询姓名字典表,查询到彦祖的id=1

2.通过查询到彦祖的id,对于性名列进行对比,构建一个bitmap,把匹配的要的列的索引位设置为1,否则为0

3.对于where 价格=20 和上面一样的操作,先查询价格字段表,20的id=2

4.通过查询到价格20的id,对于价格列进行对比,构建一个bitmap,把匹配的要的列的索引位设置为1,否则为0

5.对于两个where条件的结果bitmap做与运算,bitmap中,位为1的索引就是要查询数据的所有列的索引,如该栗子中,两个结果bitmap与运算后的结果是00001,所以所有列的第5个值,拼接起来就是我们要查询的数据

6.所以我们把所有列的第五个值拿出来组装后就是我们需要的数据


 

3.文档型数据库(解决关系数据库强 schema 约束的问题,以 MongoDB 为代表)

每个文档都是自包含的数据单元,是一系列数据项的集合。每个数据项都有一个名称与对应的值,值既可以是简单的数据类型,如字符串、数字和日期等;也可以是复杂的类型,如有序列表和关联对象。数据存储的最小单位是文档,同一个表中存储的文档属性可以是不同的,数据可以使用XML、JSON或者JSONB等多种形式存储,因为 JSON 数据是自描述的,无须在使用前定义字段,读取一个 JSON 中不存在的字段也不会导致 SQL 那样的语法错误。。产品:MongoDB、CouchDB、RavenDB,当表结构经常变化并且数据量很大的时候推荐使用

(1)适用的场景

MongoDB 等文档型数据库,优点在于方便横向扩展,业务上增加新的字段,无须再像关系数据库一样要先执行 DDL 语句修改表结构,程序代码直接读写即可。对于历史数据,即使没有新增的字段,也不会导致错误,只会返回空值,此时代码进行兼容处理即可。

JSON 是一种强大的描述语言,能够描述复杂的数据结构。例如,我们设计一个用户管理系统,用户的信息有 ID、姓名、性别、爱好、邮箱、地址、学历信息。其中爱好是列表(因为可以有多个爱好);地址是一个结构,包括省市区楼盘地址;学历包括学校、专业、入学毕业年份信息等。如果我们用关系数据库来存储,需要设计多张表,包括基本信息(列:ID、姓名、性别、邮箱)、爱好(列:ID、爱好)、地址(列:省、市、区、详细地址)、学历(列:入学时间、毕业时间、学校名称、专业),而使用文档数据库,一个 JSON 就可以全部描述。

文档数据库的这个特点,特别适合电商和游戏这类的业务场景。以电商为例,不同商品的属性差异很大。即使是同类商品也有不同的属性。例如,LCD 和 LED 显示器,两者有不同的参数指标。这种业务场景如果使用关系数据库来存储数据,就会很麻烦,而使用文档数据库,会简单、方便许多,扩展新的属性也更加容易。
 

(2)不适用场景

对事务要求严格的业务场景是不能使用文档数据库。例如,使用 MongoDB 来存储商品库存,系统创建订单的时候首先需要减扣库存,然后再创建订单。这是一个事务操作,用关系数据库来实现就很简单,但如果用 MongoDB 来实现,就无法做到事务性。异常情况下可能出现库存被扣减了,但订单没有创建的情况。

无法实现关系数据库的 join 操作。例如,我们有一个用户信息表和一个订单表,订单表中有买家用户 id。如果要查询“购买了苹果笔记本用户中的女性用户”,用关系数据库来实现,一个简单的 join 操作就搞定了;而用文档数据库是无法进行 join 查询的,需要查两次:一次查询订单表中购买了苹果笔记本的用户,然后再查询这些用户哪些是女性用户。

4.图形(Graph)数据库

图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。它适用于关联关系十分复杂的业务场景。

5.全文搜索引擎(解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表)

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

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

使用Elasticsearch 后的数据存储方案

  • ES + MySQL。将要参与查询的字段信息加上 id,放入 ES,做好分词。将全量信息放入 MySQL,通过 id 快速检索。
  • ES + HBASE。如果要省去分库分表什么的,或许可以抛弃 MySQL ,选择分布式数据库,比如 HBASE , 对于这种 NOSQL 来说,存储能力海量,扩容 easy ,根据 rowkey 查询也很快。


 

6.时序列数据库TSDB

(1)什么是时序列数据库

时序列数据库用来存储时序列数据并以时间建立索引的软件,其中时序列数据可以定义如下:

  • 可以唯一标识的序列名/ID(比如cpu.load.1)及meta-data;
  • 一组数据点{timestamp, value}。timestamp是一个Unix时间戳,一般精度会比较高,比如influxdb里面是nano秒。一般来说这个精度都会在秒以上。

一般时序列数据都具备如下两个特点:

  • 数据结构简单:可以理解为某一度量指标在某一时间点只会有一个值,没有复杂的结构(嵌套、层次等)和关系(关联、主外键等)。
  • 数据量大:这是由于时序列数据由所监控的大量数据源来产生、收集和发送,比如主机、IoT设备、终端或App等。
     
(2)TSDB数据库特点
  • 数据写入:
    • 写多于读:95%-99%的操作都是写操作
    • 顺序写:由于是时间序列数据,因此数据多为追加式写入,而且几乎都是实时写入,很少会写入几天前的数据。
    • 很少更新:数据写入之后,不会更新
    • 区块(bulk)删除:基本没有随机删除,多数是从一个时间点开始到某一时间点结束的整段数据删除。比如删除上个月,或者7天前的数据。很少出现删除单独某个指标的数据,或者跳跃时间段的数据。区块删除很容易进行优化,比如可以按区块来分开存储到不同的文件,这样删除一个区块只需要删除一个文件就可以了,成本会比较低。
  • 数据读取(查询)
    • 顺序读,基本都是按照时间顺序读取一段时间内的数据。
    • 基数大,基本数据大,超过内存大小,要选取的只是其一小部分,且没有规律,缓存几乎不起任何作用。
  • 分布式(集群):TSDB应该天生就要考虑到分布式和分区等特性,将存储和查询分发到不同的服务器,以支撑大规模的数据采集和查询请求。
  • 基本数据分析支持:TSDB的数据是用来分析的,所以TSDB还会提供做数据分析所必须的各种运算、变换函数。比如可以方便的对时序列数据进行求和、求平均值等操作,就像传统的RDBMS一样。


 

(3)时序列数据库选型
  • 性能:主要就是读和写的性能,写入性能必须能跟得上、无延时,并且不能阻塞读操作,且读操作能快速返回最新的数据。
  • 存储方案(或引擎):存储方案主要会影响到读写性能、集群扩展容易程度、以及运维的复杂度。典型的存储方案有HDFS、HBase、Cassandra、LevelDB等。
  • 集群功能:一般来说,集群主要集中为存储和查询的集群功能,也代表其可扩展性,因为时序列数据库的数据量很可能很大,并且增长趋势不可预测,尤其是随着大数据和物联网的兴起,GB已经算入门,TB也是刚起步。
  • API(HTTP API和Client Library):如果你需要定制,或者只是使用TSDB做存储,自己写入数据并通过查询接口进行数据展示,那么API的完善程度将是一个很重要的评判因素。还好大部分TSDB都提供了HTTP API,除了简单的文本格式,有很多还支持JSON格式的输入、输出。


 

(4)应用场景

运维和监控系统。

例子:淘宝、天猫

商品基本信息:mysql

商品描述、详情、评价信息(多文字类)

多文字信息类,IO读写性能变差,所以我们选择存到文档数据库MongDb中

商品图片:分布式的文件系统

商品的关键字:solr之类的搜索引擎

商品波段性的热点高频信息(就是某一时间段热搜的数据):内存数据库 Redis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值