You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
14 KiB
Markdown

2 years ago
# 11 | NoSQL在高并发场景下数据库和NoSQL如何做到互补
你好,我是唐扬。
前几节课,我带你了解了在你的垂直电商项目中,如何将传统的关系型数据库改造成分布式存储服务,以抵抗高并发和大流量的冲击。
对于存储服务来说,我们一般会从两个方面对它做改造:
1.提升它的读写性能尤其是读性能因为我们面对的多是一些读多写少的产品。比方说你离不开的微信朋友圈、微博和淘宝都是查询QPS远远大于写入QPS。
2.增强它在存储上的扩展能力,从而应对大数据量的存储需求。
我之前带你学习的读写分离和分库分表就是从这两方面出发,改造传统的关系型数据库的,但仍有一些问题无法解决。
比如在微博项目中关系的数据量达到了千亿那么即使分隔成1024个库表每张表的数据量也达到了亿级别并且关系的数据量还在以极快的速度增加即使你分隔成再多的库表数据量也会很快增加到瓶颈。这个问题用传统数据库很难根本解决因为它在扩展性方面是很弱的这时就可以利用NoSQL因为它有着天生分布式的能力能够提供优秀的读写性能可以很好地补充传统关系型数据库的短板。那么它是如何做到的呢
这节课我就还是以你的垂直电商系统为例带你掌握如何用NoSQL数据库和关系型数据库互补共同承担高并发和大流量的冲击。
首先我们先来了解一下NoSQL数据库。
## NoSQLNo SQL
NoSQL想必你很熟悉它指的是不同于传统的关系型数据库的其他数据库系统的统称它不使用SQL作为查询语言提供优秀的横向扩展能力和读写性能非常契合互联网项目高并发大数据的特点。所以一些大厂比如小米、微博、陌陌都很倾向使用它来作为高并发大容量的数据存储服务。
NoSQL数据库发展到现在十几年间出现了多种类型我来给你举几个例子
* Redis、LevelDB这样的KV存储。这类存储相比于传统的数据库的优势是极高的读写性能一般对性能有比较高的要求的场景会使用。
* Hbase、Cassandra这样的列式存储数据库。这种数据库的特点是数据不像传统数据库以行为单位来存储而是以列来存储适用于一些离线数据统计的场景。
* 像MongoDB、CouchDB这样的文档型数据库。这种数据库的特点是Schema Free模式自由数据表中的字段可以任意扩展比如说电商系统中的商品有非常多的字段并且不同品类的商品的字段也都不尽相同使用关系型数据库就需要不断增加字段支持而用文档型数据库就简单很多了。
在NoSQL数据库刚刚被应用时它被认为是可以替代关系型数据库的银弹在我看来也许因为以下几个方面的原因
* 弥补了传统数据库在性能方面的不足;
* 数据库变更方便,不需要更改原先的数据结构;
* 适合互联网项目常见的大数据量的场景;
不过这种看法是个误区因为慢慢地我们发现在业务开发的场景下还是需要利用SQL语句的强大的查询功能以及传统数据库事务和灵活的索引等功能NoSQL只能作为一些场景的补充。
那么接下来,我就带你了解**NoSQL数据库是如何做到与关系数据库互补的。**了解这部分内容你可以在实际项目中更好地使用NoSQL数据库补充传统数据库的不足。
首先,我们来关注一下数据库的写入性能。
## 使用NoSQL提升写入性能
数据库系统大多使用的是传统的机械磁盘对于机械磁盘的访问方式有两种一种是随机IO另一种是顺序IO。随机IO就需要花费时间做昂贵的磁盘寻道一般来说它的读写效率要比顺序IO小两到三个数量级所以我们想要提升写入的性能就要尽量减少随机IO。
以MySQL的InnoDB存储引擎来说更新binlog、redolog、undolog都是在做顺序IO而更新datafile和索引文件则是在做随机IO而为了减少随机IO的发生关系数据库已经做了很多的优化比如说写入时先写入内存然后批量刷新到磁盘上但是随机IO还是会发生。
索引在InnoDB引擎中是以B+树([上一节课](https://time.geekbang.org/column/article/146454)提到了B+树你可以回顾一下方式来组织的而MySQL主键是聚簇索引一种索引类型数据与索引数据放在一起既然数据和索引数据放在一起那么在数据插入或者更新的时候我们需要找到要插入的位置再把数据写到特定的位置上这就产生了随机的IO。而且一旦发生了页分裂就不可避免会做数据的移动也会极大地损耗写入性能。
**NoSQL数据库是怎么解决这个问题的呢**
它们有多种的解决方式这里我给你讲一种最常见的方案就是很多NoSQL数据库都在使用的**基于LSM树的存储引擎**这种算法使用最多,所以在这里着重剖析一下。
LSM树Log-Structured Merge Tree牺牲了一定的读性能来换取写入数据的高性能Hbase、Cassandra、LevelDB都是用这种算法作为存储的引擎。
它的思想很简单数据首先会写入到一个叫做MemTable的内存结构中在MemTable中数据是按照写入的Key来排序的。为了防止MemTable里面的数据因为机器掉电或者重启而丢失一般会通过写Write Ahead Log的方式将数据备份在磁盘上。
MemTable在累积到一定规模时它会被刷新生成一个新的文件我们把这个文件叫做SSTableSorted String Table。当SSTable达到一定数量时我们会将这些SSTable合并减少文件的数量因为SSTable都是有序的所以合并的速度也很快。
当从LSM树里面读数据时我们首先从MemTable中查找数据如果数据没有找到再从SSTable中查找数据。因为存储的数据都是有序的所以查找的效率是很高的只是因为数据被拆分成多个SSTable所以读取的效率会低于B+树索引。
![](https://static001.geekbang.org/resource/image/b4/eb/b4c9c93f22edae091740fa1606d109eb.jpg)
和LSM树类似的算法有很多比如说TokuDB使用的名为Fractal tree的索引结构它们的核心思想就是将随机IO变成顺序的IO从而提升写入的性能。
在后面的缓存篇中我也将给你着重介绍我们是如何使用KV型NoSQL存储来提升读性能的。所以你看NoSQL数据库补充关系型数据库的第一种方式就是提升读写性能。
## 场景补充
除了可以提升性能之外NoSQL数据库还可以在某些场景下作为传统关系型数据库的补充来看一个具体的例子。
假设某一天CEO找到你并且告诉你他正在为你的垂直电商项目规划搜索的功能需要支持按照商品的名称模糊搜索到对应的商品希望你尽快调研出解决方案。
一开始你认为这非常的简单不就是在数据库里面执行一条类似“select \* from product where name like %\*\*\*%’”的语句吗?可是在实际执行的过程中,却发现了问题。
你发现这类语句并不是都能使用到索引只有后模糊匹配的语句才能使用索引。比如语句“select \* from product where name like %电冰箱”就没有使用到字段“name”上的索引而“select \* from product where name like ‘索尼%”就使用了“name”上的索引。而一旦没有使用索引就会扫描全表的数据在性能上是无法接受的。
于是你在谷歌上搜索了一下解决方案发现大家都在使用开源组件Elasticsearch来支持搜索的请求它本身是基于“倒排索引”来实现的**那么什么是倒排索引呢?**
倒排索引是指将记录中的某些列做分词然后形成的分词与记录ID之间的映射关系。比如说你的垂直电商项目里面有以下记录
![](https://static001.geekbang.org/resource/image/20/57/201ffbb6da51e04894d8dee7eaeb5d57.jpg)
那么我们将商品名称做简单的分词然后建立起分词和商品ID的对应关系就像下面展示的这样
![](https://static001.geekbang.org/resource/image/c9/2f/c919944bcdfd1f1ce576790fc496a62f.jpg)
这样如果用户搜索电冰箱就可以给他展示商品ID为1和3的两件商品了。
而Elasticsearch作为一种常见的NoSQL数据库**就以倒排索引作为核心技术原理为你提供了分布式的全文搜索服务这在传统的关系型数据库中使用SQL语句是很难实现的。**所以你看NoSQL可以在某些业务场景下代替传统数据库提供数据存储服务。
## 提升扩展性
另外在扩展性方面很多NoSQL数据库也有着先天的优势。还是以你的垂直电商系统为例你已经为你的电商系统增加了评论系统开始你的评估比较乐观觉得电商系统的评论量级不会增长很快所以就为它分了8个库每个库拆分成16张表。
但是评论系统上线之后,存储量级增长的异常迅猛,你不得不将数据库拆分成更多的库表,而数据也要重新迁移到新的库表中,过程非常痛苦,而且数据迁移的过程也非常容易出错。
这时你考虑是否可以考虑使用NoSQL数据库来彻底解决扩展性的问题经过调研你发现它们在设计之初就考虑到了分布式和大数据存储的场景**比如像MongoDB就有三个扩展性方面的特性。**
* 其一是Replica也叫做副本集你可以理解为主从分离也就是通过将数据拷贝成多份来保证当主挂掉后数据不会丢失。同时呢Replica还可以分担读请求。Replica中有主节点来承担写请求并且把数据变动记录到oplog里类似于binlog从节点接收到oplog后就会修改自身的数据以保持和主节点的一致。一旦主节点挂掉MongoDB会从从节点中选取一个节点成为主节点可以继续提供写数据服务。
* 其二是Shard也叫做分片你可以理解为分库分表即将数据按照某种规则拆分成多份存储在不同的机器上。MongoDB的Sharding特性一般需要三个角色来支持一个是Shard Server它是实际存储数据的节点是一个独立的Mongod进程二是Config Server也是一组Mongod进程主要存储一些元信息比如说哪些分片存储了哪些数据等最后是Route Server它不实际存储数据仅仅作为路由使用它从Config Server中获取元信息后将请求路由到正确的Shard Server中。
![](https://static001.geekbang.org/resource/image/e8/80/e8cb47c8cc556fce058f7c5cf06d4780.jpg)
* 其三是负载均衡就是当MongoDB发现Shard之间数据分布不均匀会启动Balancer进程对数据做重新的分配最终让不同Shard Server的数据可以尽量的均衡。当我们的Shard Server存储空间不足需要扩容时数据会自动被移动到新的Shard Server上减少了数据迁移和验证的成本。
你可以看到NoSQL数据库中内置的扩展性方面的特性可以让我们不再需要对数据库做分库分表和主从分离也是对传统数据库一个良好的补充。
你可能会觉得NoSQL已经成熟到可以代替关系型数据库了但是就目前来看NoSQL只能作为传统关系型数据库的补充而存在弥补关系型数据库在性能、扩展性和某些场景下的不足所以你在使用或者选择时要结合自身的场景灵活地运用。
## 课程小结
本节课我带你了解了NoSQL数据库在性能、扩展性上的优势以及它的一些特殊功能特性主要有以下几点
1.在性能方面NoSQL数据库使用一些算法将对磁盘的随机写转换成顺序写提升了写的性能
2.在某些场景下比如全文搜索功能关系型数据库并不能高效地支持需要NoSQL数据库的支持
3.在扩展性方面NoSQL数据库天生支持分布式支持数据冗余和数据分片的特性。
这些都让它成为传统关系型数据库的良好的补充,你需要了解的是,**NoSQL可供选型的种类很多每一个组件都有各自的特点。你在做选型的时候需要对它的实现原理有比较深入的了解最好在运维方面对它有一定的熟悉这样在出现问题时才能及时找到解决方案。**否则盲目跟从地上了一个新的NoSQL数据库最终可能导致会出了故障无法解决反而成为整体系统的拖累。
我在之前的项目中曾经使用Elasticsearch作为持久存储支撑社区的feed流功能初期开发的时候确实很爽你可以针对feed中的任何字段做灵活高效地查询业务功能迭代迅速代码也简单易懂。可是到了后期流量上来之后由于缺少对于Elasticsearch成熟的运维能力造成故障频出尤其到了高峰期就会出现节点不可用的问题而由于业务上的巨大压力又无法分出人力和精力对Elasticsearch深入的学习和了解最后不得不做大的改造切回熟悉的MySQL。**所以对于开源组件的使用不能只停留在只会“hello world”的阶段而应该对它有足够的运维上的把控能力。**
## 一课一思
NoSQL数据库是可以与传统的关系型数据库配合一起解决数据存储问题的那么在日常工作中你用到了哪些NoSQL数据库呢在选型的时候是基于什么样的考虑呢欢迎在留言区与我分享你的经验。
最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。