**
结构化数据、非
结构化数据与半
结构化数据**
文章的开始,聊一下
结构化数据、非
结构化数据与半
结构化数据,因为数据特点的不同,将在技术上直接影响存储引擎的选型。
首先是
结构化数据,根据定义**
结构化数据指的是由二维表结构来逻辑表达和实现的数据,严格遵循数据格式与长度规范,也称作为行数据**,特点为:数据以行为单位,一行数据表示
一个实体的信息,每一行数据的
属性是相同的。例如:

因此关系型
数据库完美契合
结构化数据的特点,关系型
数据库也是关系型数据最主要的存储与管理引擎。
非
结构化数据,指的是**数据结构不规则或不完整,没有任何预定义的数据模型,不方便用二维逻辑表来表现的数据**,例如办公文档(Word)、文本、
图片、HTML、各类报表、视频音频等。
介于
结构化与非
结构化数据之
间的数据就是半
结构化数据了,它是
结构化数据的一种形式,虽然**不符合二维逻辑这种数据
模型结构,但是包含相关
标记,用来分割语义元素以及对记录和字段进行分层**。常见的半
结构化数据有XML和JSON,例如:
```
张三
18
12345
```
这种结构也被成为自描述的结构。
**以关系型
数据库的方式做存储的架构演进**
首先,我们看一下使用关系型
数据库的方式,企业
一个系统发展的几个阶段的架构演进(由于本文写的是
sql与No
sql,因此只以存储方式作为切入点,不会涉及类似MQ、ZK这些中间件
内容):

阶段一:企业刚发展的阶段,最简单,
一个应用服务器配
一个关系型
数据库,每次读写
数据库。
阶段二:无论是使用
MysqL还是Oracle还是别的关系型
数据库,
数据库通常不会先成为
性能瓶颈,通常随着企业规模的扩大,一台应用服务器扛不住上游过来的流量且一台应用服务器会产生单点故障的问题,因此**加应用服务器并且在流量入口使用
Nginx做一层
负载均衡**,保证把流量均匀打到应用服务器上。
阶段三:随着企业规模的继续扩大,此时由于读写都在同
一个数据库上,
数据库性能出现一定的瓶颈,此时简单地做一层**读写分离**,每次写主库,读备库,主备库之间通过
binlog同步数据,就能很大程度上
解决这个阶段的
数据库性能问题
阶段四:企业发展越来越好了,业务越来越大了,做了读写分离
数据库压力还是越来越大,这时候怎么办呢,一台
数据库扛不住,那我们就分几台吧,做**分库分表**,对表做垂直拆分,对库做水平拆分。以扩
数据库为例,扩出两台
数据库,以一定的单号(例如交易单号),以一定的规则(例如取模),交易单号对2取模为0的丢到
数据库1去,交易单号对2取模为1的丢到
数据库2去,通过这样的方式将写
数据库的流量均分到两台
数据库上。一般分库分表会使用Shard的方式,通过
一个中间件,便于连接管理、数据监控且客户端无需感知
数据库ip
**关系型
数据库的优点**
上面的方式,看似可以
解决问题(实际上确实也能
解决很多问题),正常对关系型
数据库做一下读写分离 + 分库分表,支撑个1W+的读写QPS还是问题不大的。但是受限于关系型
数据库本身,这套架构方案依然有着明显的不足,下面对利用关系型
数据库方式做存储的方案的优点先进行一下分析,后一部分再分析一下缺点,对某个技术的优缺点的充分理解是技术选型的前提。
* **易理解**
因为行 + 列的二维表逻辑是非常贴近逻辑世界的
一个概念,关系模型相对网状、层次等其他模型更加容易被理解
* **操作方便**
通用的
sql语言使得操作关系型
数据库非常方便,
支持join等复杂
查询
* **数据一致性**
支持ACID特性,可以维护数据之
间的一致性,这是使用
数据库非常重要的
一个理由之一,例如同银行转账,张三转给李四100元钱,张三扣100元,李四加100元,而且必须同时成功或者同时失败,否则就会造成
用户的资损
* **数据稳定**
数据持久化到磁盘,没有丢失数据风险,
支持海量数据存储
* **服务稳定**
最常用的关系型
数据库产品
MysqL、Oracle服务器
性能卓越,服务稳定,通常很少出现宕机异常
**关系型
数据库的缺点**
紧接着的,我们看一下关系型
数据库的缺点,也是比较明显的。
* **高并发下IO压力大**
数据按行存储,即使只针对其中某一列进行运算,也会将整行数据从存储设备中读入内存,导致IO较高
* **为维护索引付出的代价大**
为了提供丰富的
查询能力,通常热点表都会有多个二级索引,一旦有了二级索引,数据的新增必然伴随着所有二级索引的新增,数据的更新也必然伴随着所有二级索引的更新,这不可避免地降低了关系型
数据库的读写能力,且索引越多读写能力越差。有机会的话可以看一下自己公司的
数据库,除了数据
文件不可避免地占空间外,索引占的空间其实也并不少
* **为维护数据一致性付出的代价大**
数据一致性是关系型
数据库的核心,但是同样为了维护数据一致性的代价也是非常大的。我们都知道
sql标准为事务定义了不同的隔离级别,从低到高依次是读未提交、读已提交、可重复度、串行化,事务隔离级别月底,可能出现的并发异常越多,但是通常而言能提供的并发能力越强。那么为了保证事务一致性,
数据库就需要提供并发控制与故障恢复两种技术,前者用于减少并发异常,后者可以在系统异常的时候保证事务与
数据库状态不会被破坏。对于并发控制,其核心思想就是加锁,无论是乐观锁还是悲观锁,只要提供的隔离级别越高,那么读写
性能必然越差
* **水平扩展后带来的种种问题难处理**
前文提过,随着企业规模扩大,一种方式是对
数据库做分库,做了分库之后,数据迁移(1个库的数据按照一定规则打到2个库中)、跨库join(订单数据里有
用户数据,两条数据不在同
一个库中)、分布式事务处理都是需要考虑的问题,尤其是分布式事务处理,业界当前都没有特别好的
解决方案
* **表结构扩展不方便**
由于
数据库存储的是
结构化数据,因此表结构schema是固定的,扩展不方便,如果需要
修改表结构,需要执行DDL(data de
Finition language)语句
修改,
修改期间会导致锁表,部分服务不可用
* **全文
搜索功能弱**
例如like "%中国真伟大%",只能
搜索到"2019年中国真伟大,爱祖国",无法
搜索到"中国真是太伟大了"这样的文本,即不具备分词能力,且like
查询在"%中国真伟大"这样的
搜索条件下,无法命中索引,将会导致
查询效率大大降低
写了这么多,我的理解核心还是前三点,它反映出的
一个问题是**关系型
数据库在高并发下的能力是有瓶颈的**,尤其是写入/更新频繁的情况下,出现瓶颈的结果就是
数据库cpu高、
sql执行慢、客户端报
数据库连接池不够等
错误,因此例如万人秒杀这种场景,我们绝对不可能通过
数据库直接去扣减库存。
可能有朋友说,
数据库在高并发下的能力有瓶颈,我公司有钱,加
cpu、换固态硬盘、继续买服务器加
数据库做分库不就好了,问题是这是一种性价比非常低的方式,花1000万达到的
效果,换其他方式可能100万就达到了,不考虑人员、服务器投入产出比的
leader就是个不合格的
leader,且关系型
数据库的方式,受限于它本身的特点,可能花了钱都未必能达到想要的
效果。至于什么是花100万就能达到花1000万
效果的方式呢?可以继续往下看,这就是我们要说的No
sql。
**结合No
sql的方式做存储的架构演进**
像上文分析的,
数据库作为一种关系型数据的存储引擎,存储的是关系型数据,它有优点,同时也有明显的缺点,因此通常在企业规模不断扩大的情况下,不会一味指望通过增强
数据库的能力来
解决数据存储问题,而是会引入其他存储,也就是我们说的No
sql。
No
sql的全称为Not Only
sql,泛指非关系型
数据库,是对关系型
数据库的一种**补充**,特别注意补充这两个字,这意味着No
sql与关系型
数据库并不是对立关系,二者各有优劣,取长补短,在合适的场景下选择合适的存储引擎才是正确的做法。
比较简单的No
sql就是缓存:

针对那些读远多于写的数据,引入一层缓存,每次读从缓存中读取,缓存中读取不到,再去
数据库中取,取完之后再写入到缓存,对数据做好失效机制通常就没有大问题了。通常来说,缓存是
性能优化的第一选择也是见效最明显的方案。
但是,缓存通常都是KV型存储且容量有限(基于内存),无法
解决所有问题,于是再进一步的优化,我们继续引入其他No
sql:

数据库、缓存与其他No
sql并行工作,充分发挥每种No
sql的特点。当然No
sql在
性能方面大大优于关系挺
数据库的同时,往往也伴随着一些特性的缺失,比较常见的就是事务
功能的缺失。
下面看一下常用的No
sql及他们的代表产品,并对每种No
sql的优缺点和适用场景做一下分析,便于熟悉每种No
sql的特点,方便技术选型。
**KV型No
sql(代表----Re
dis)**
KV型No
sql顾名思义就是以键值对形式存储的非关系型
数据库,是最简单、最容易理解也是大家最熟悉的一种No
sql,因此比较快地带过。Re
dis、MemCache是其中的代表,Re
dis又是KV型No
sql中应用最广泛的No
sql,KV型
数据库以Re
dis为例,最大的优点我总结下来就两点:
* 数据基于内存,读写效率高
* KV型数据,时间复杂度为O(1),
查询速度快
因此,KV型No
sql最大的优点就是**高
性能**,利用Re
dis自带的BenchMark做基准测试,TPS可达到10万的级别,
性能非常强劲。同样的Re
dis也有所有KV型No
sql都有的比较明显的缺点:
* 只能根据K查V,无法根据V查K
*
查询方式单一,只有KV的方式,
不支持条件
查询,多条件
查询唯一的做法就是数据冗余,但这会极大的浪费存储空间
* 内存是有限的,无法
支持海量数据存储
* 同样的,由于KV型No
sql的存储是基于内存的,会有丢失数据的风险
综上所述,KV型No
sql最合适的场景就是**缓存**的场景:
* 读远多于写
* 读取能力强
* 没有持久化的需求,可以容忍数据丢失,反正丢了再
查询一把写入就是了
例如根据
用户id
查询用户信息,每次根据
用户id去缓存中
查询一把,查到数据直接返回,查不到去关系型
数据库里面根据id
查询一把数据写到缓存中去。
**
搜索型No
sql(代表----ElasticSearch)**
传统关系型
数据库主要通过索引来达到
快速查询的目的,但是在全文
搜索的场景下,索引是无能为力的,like
查询一来无法满足所有模糊匹配需求,二来使用限制太大且使用不当容易造成慢
查询,**
搜索型No
sql的诞生正是为了
解决关系型
数据库全文
搜索能力较弱的问题**,ElasticSearch是
搜索型No
sql的代表产品。
全文
搜索的原理是**倒排索引**,我们看一下什么是倒排索引。要说倒排索引我们先看下什么是正排索引,传统的正排索引是**文档-->关键字**的映射,例如"Tom is my friend"这句话,会将其切分为"Tom"、"is"、"my"、"friend"四个单词,在
搜索的时候对文档进行扫描,符合条件的查出来。这种方式原理非常简单,但是由于其检索效率太低,基本没什么实用价值。
倒排索引则完全相反,它是**关键字-->文档**的映射,我用张表格展示一下就比较清楚了:

意思是我现
在这里有"Tom is Tom"、"Tom is my friend"、"Thank you, Betty"、"Tom is Betty's husband"四句话,
搜索引擎会根据一定的切分规则将这句话切成N个关键字,并以关键字的维度维护关键字在每个文本中的出现
次数。这样下次
搜索"Tom"的时候,由于Tom这个词语在"Tom is Tom"、"Tom is my friend"、"Tom is Betty's husband"三句话中都有出现,因此这三条记录都会被检索出来,且由于"Tom is Tom"这句话中"Tom"出现了2次,因此这条记录对"Tom"这个单词的匹配度最高,最先展示。这就是
搜索引擎倒排索引的基本原理,假设某个关键字在某个文档中出现,那么倒排索引中有两部分
内容:
* 文档ID
* 在该文档中出现的位置情况
可以举一反三,我们
搜索"Betty Tom"这两个词语也是一样,
搜索引擎将"Betty Tom"切分为"Tom"、"Betty"两个单词,根据开发者指定的满足率,比如满足率=50%,那么只要记录中出现了两个单词之一的记录都会被检索出来,再按照匹配度进行展示。
搜索型No
sql以ElasticSearch为例,它的优点为:
*
支持分词场景、全文
搜索,这是区别于关系型
数据库最大特点
*
支持条件
查询,
支持聚合操作,类似关系型
数据库的Group By,但是
功能更加强大,适合做数据分析
* 数据写
文件无丢失风险,在集群环境下可以方便横向扩展,可承载PB级别的数据
* 高可用,
自动发现新的或者失败的节点,重组和重新平衡数据,确保数据是安全和可访问的
同样,ElasticSearch也有比较明显的缺点:
*
性能全靠内存来顶,也是使用的时候最需要注意的点,非常吃硬件资源、吃内存,大数据量下64G + SSD基本是标配,算得上是
数据库中的爱马仕了。为什么要专门提一下内存呢,因为内存这个东西是很值钱的,相同的配置多一倍内存,
一个月差不多就要多花几百块钱,至于ElasticSearch内存用在什么地方,大概有如下这些:
* Indexing Buffer----ElasticSearch基于Luence,Lucene的倒排索引是先在内存里
生成,然后定期以Segment File的方式刷磁盘的,每个Segment File实际就是
一个完整的倒排索引
* Segment Memory----倒排索引前面说过是基于关键字的,Lucene在4.0后会将所有关键字以FST这种数据结构的方式将所有关键字在启动的时候全量加载到内存,加快
查询速度,官方建议至少留系统一半内存给Lucene
* 各类缓存----Filter Cache、Field Cache、Indexing Cache等,用于提升
查询分析
性能,例如Filter Cache用于缓存使用过的Filter的结果集
* Cluter State Buffer----ElasticSearch被设计为每个Node都可以响应
用户请求,因此每个Node的内存中都包含有一份集群状态的拷贝,
一个规模很大的集群这个状态信息可能会非常大
* 读写之间有延迟,写入的数据差不多1s样子会被读取到,这也正常,写入的时候
自动加入这么多索引肯定影响
性能
* 数据结构灵活性不高,ElasticSearch这个东西,字段一旦建立就没法
修改类型了,假如建立的数据表某个字段没有加全文索引,想
加上,那么只能把整个表删了再重建
因此,
搜索型No
sql最适用的场景就是**有条件
搜索尤其是全文
搜索的场景**,作为关系型
数据库的一种替代方案。
另外,
搜索型
数据库还有一种特别重要的应用场景。我们可以想,一旦对
数据库做了分库分表后,原来可以在单表中做的聚合操作、
统计操作是否统统失效?例如我把订单表分16个库,1024张表,那么订单数据就散落在1024张表中,我想要
统计昨天浙江省单笔成交金额最高的订单是哪笔如何做?我想要把昨天的所有订单按照时间排序
分页展示如何做?**这就是文档型No
sql的另一大作用了,我们可以把分表之后的数据统一打在文档型No
sql中,利用文档型No
sql的
搜索与聚合能力完成对全量数据的
查询**。
至于为什么把它放在KV型No
sql后面作为第二个写呢,因为通常
搜索型No
sql也会作为一层前置缓存,来对关系型
数据库进行保护。
**列式No
sql(代表----HBase)**
列式No
sql,大数据时代最具代表性的技术之一了,以HBase为代表。
列式No
sql是基于列式存储的,那么什么是列式存储呢,列式No
sql和关系型
数据库一样都有主键的概念,区别在于关系型
数据库是按照行组织的数据:

看到每行有name、phone、address三个字段,这是行式存储的方式,且可以观察id = 2的这条数据,即使phone字段没有,它也是占空
间的。
列式存储完全是另一种方式,它是按每一列进行组织的数据:


这么做有什么好处呢?大致有以下几点:
*
查询时只有指定的列会被读取,不会读取所有列
* 存储上节约空间,Null值不会被存储,一列中有时候会有很多重复数据(尤其是枚举数据,性别、状态等),这类数据可压缩,行式
数据库压缩率通常在3:1~5:1之间,列式
数据库的压缩率一般在8:1~30:1左右
* 列数据被组织到一起,一次磁盘IO可以将一列数据一次性读取到内存中
第二点说到了数据压缩,什么意思呢,以比较常见的字典表压缩方式举例:

自己看图理解一下,应该就懂了。?
接着继续讲讲优缺点,列式No
sql,以HBase为代表的,优点为:
* 海量数据无限存储,PB级别数据随便存,底层基于HDFS(Hadoop
文件系统),数据持久化
* 读写
性能好,只要没有滥用造成数据热点,读写基本随便玩
* 横向扩展在关系型
数据库及非关系型
数据库中都是最方便的之一,只需要
添加新机器就可以实现数据容量的线性增长,且可用在廉价服务器上,节省成本
* 本身没有单点故障,可用性高
* 可存储
结构化或者半
结构化的数据
* 列数理论上无限,HBase本身只对列族
数量有要求,建议1~3个
说了这么多HBase的优点,又到了说HBase缺点的时候了:
* HBase是Hadoop生态的一部分,因此它本身是一款比较重的产品,依赖很多Hadoop组件,数据规模不大没必要用,运维还是有点复杂的
* KV式,
不支持条件
查询,或者说条件
查询非常非常弱吧,HBase在Scan扫描一批数据的情况下还是提供了前缀匹配这种API的,条件
查询除非定义多个RowKey做数据冗余
*
不支持分页查询,因为
统计不了数据总数
因此**HBase比较适用于那种KV型的且未来无法预估数据增长量的场景**,另外HBase使用还是需要一定的经验,主要体现在RowKey的设计上。
**文档型No
sql(代表----MongoDB)**
坦白讲,根据我的工作经历,文档型No
sql我只有比较浅的使用经验,因此这部分只能结合之前的使用与网上的
文章大致给大家介绍一下。
什么是文档型No
sql呢,文档型No
sql指的是将半
结构化数据存储为文档的一种No
sql,文档型No
sql通常以JSON或者XML格式存储数据,因此文档型No
sql是没有Schema的,由于没有Schema的特性,我们可以随意地存储与读取数据,因此文档型No
sql的出现是**
解决关系型
数据库表结构扩展不方便的问题的**。
MongoDB是文档型No
sql的代表产品,同时也是所有No
sql产品中的明星产品之一,因此这里以MongoDB为例。按我的理解,作为文档型No
sql,MongoDB是一款完全和关系型
数据库对标的产品,就我们从存储上来看:

看到,关系型
数据库是按部就班地每个字段一列存,在MongDB里面就是
一个JSON字符串存储。关系型数据可以为name、phone建立索引,MongoDB使用createIndex命令一样可以为列建立索引,建立索引之后可以大大提升
查询效率。其他方面而言,就大的基本概念,二者之间基本也是类似的:

因此,对于MongDB,我们只要理解成
一个Free-Schema的关系型
数据库就完事了,它的优缺点比较一目了然,优点:
* 没有预定义的字段,扩展字段容易
* 相较于关系型
数据库,读写
性能优越,命中二级索引的
查询不会比关系型
数据库慢,对于非索引字段的
查询则是全面胜出
缺点在于:
*
不支持事务操作,虽然Mongodb4.0之后宣称
支持事务,但是
效果待观测
* 多表之
间的关联
查询不支持(虽然有嵌入文档的方式),join
查询还是需要多次操作
* 空间占用较大,这个是MongDB的设计问题,空间预分配机制 +
删除数据后空间不释放,只有用db.repairDatabase()去修复才能释放
* 目前没发现MongoDB有关系型
数据库例如
MysqL的Navicat这种成熟的运维工具
总而言之,MongDB的使用场景很大程度上可以对标关系型
数据库,但是比较适合处理那些没有join、没有强一致性要求且表Schema会常变化的数据。
**总结:
数据库与No
sql及各种No
sql间的对比**
最后一部分,做
一个总结,本文归根到底是两个话题:
* 何时选用关系型
数据库,何时选用非关系型
数据库
* 选用非关系型
数据库,使用哪种非关系型
数据库
首先是第
一个话题,关系型
数据库与非关系型
数据库的选择,在我理解里面无非就是两点考虑:

第一点,不多解释应该都理解,非关系型
数据库都是通过牺牲了ACID特性来
获取更高的
性能的,假设两张表之间有比较强的一致性需求,那么这类数据是不适合放在非关系型
数据库中的。
第二点,核心数据不走非关系型
数据库,例如
用户表、订单表,但是这有
一个前提,就是这一类核心数据会有多种
查询模式,例如
用户表有ABCD四个字段,可能根据AB查,可能根据AC查,可能根据D查,假设核心数据,但是就是个KV形式,比如
用户的聊天记录,那么HBase一存就完事了。
这几年的工作经验来看,非核心数据尤其是日志、流水一类中间数据千万不要写在关系型
数据库中,这一类数据通常有两个特点:
* 写远高于读
* 写入量巨大
一旦使用关系型
数据库作为存储引擎,将大大降低关系型
数据库的能力,正常读写QPS不高的核心服务会受这一类
数据读写的拖累。
接着是第二个问题,如果我们使用非关系型
数据库作为存储引擎,那么如何选型?其实上面的
文章基本都写了,这里只是做
一个总结(所有的缺点都不会体现事务这个点,因为这是所有No
sql相比关系型
数据库共有的
一个问题):

但是这里特别说明,**选型一定要结合实际情况而不是照本宣科**,比如:
* 企业发展之初,明明
一个关系型
数据库就能搞定且支撑一年的架构,搞一套大而全的技术方案出来
* 有一些数据条件
查询多,更适合使用ElasticSearch做存储降低关系型
数据库压力,但是公司成本有限,这种情况下这类数据可以尝试继续使用关系型
数据库做存储
* 有一类数据格式简单,就是个KV类型且增长量大,但是公司没有HBase这方面的人才,运维上可能会有一定难度,出于实际情况考虑,可先用关系型
数据库顶一阵子
所以,如果不考虑实际情况,虽然合适有些存储引擎更加合适,但是强行使用反而适得其反,总而言之,适合自己的才是最好的。
## 最后
这次要给大家
分享总结的东西就是这些了
资料全都放在——***[我的学习笔记:大厂面试真题+微服务+
MysqL+Java+Re
dis+算法+网络+Linux+Spring全家桶+JVM+学习笔记图](https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB)***
最后再
分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+
性能优化

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。