存储服务一般从两方面改造:
- 提升读写性能,尤其读性能,朋友圈、微博和淘宝,都是读QPS>>写QPS(读写分离)
- 增强扩展能力,以满足大数据量存储需求(分库分表)
但之后仍有问题待解决:
比如朋友圈关系的数据量达到千亿,即使分成1024个库表,单表数据量也达到亿级,且关系数据量还在极速增加,即使你分成再多库表,数据量也会很快到达瓶颈。
传统DB难以彻底解决该问题,因为扩展性很弱。这时,就可以利用NoSQL,天生分布式,能提供优秀的读写性能,补充了传统关系型数据库短板。那么它是如何做到的呢?
NoSQL,不同于传统关系型数据库的其他数据库系统的统称,不使用SQL作为查询语言,提供优秀的横向扩展能力和读写性能,非常契合互联网项目高并发大数据的特点。
Redis、LevelDB这样的KV存储,相比于传统DB,有极高读写性能,对性能有比较高的要求的场景都会使用。
Hbase、Cassandra列式存储数据库,适于一些离线数据统计场景。
MongoDB、CouchDB这种文档型数据库,Schema Free(模式自由),表中字段可任意扩展,比如说电商系统中的商品有非常多的字段,并且不同品类的商品的字段也都不尽相同,使用关系型数据库就需要不断增加字段支持,而用文档型数据库就简单了。
NoSQL弥补了传统数据库在性能方面的不足;
数据库变更方便,不需要更改原先的数据结构;
适合互联网项目常见的大数据量的场景;
但在业务开发的场景下还是需要利用SQL查询及传统数据库事务和灵活的索引等功能,NoSQL只能作为一些场景的补充。
使用NoSQL提升写入性能
数据库系统大多使用机械磁盘,机械磁盘访问方式有两种
随机IO
随机IO就需花费时间做昂贵磁盘寻道,读写效率比顺序IO小两到三数量级,想要提升写入性能就要尽量减少随机IO。
顺序IO
MySQL更新binlog、redolog、undolog都是在做顺序IO。而更新datafile和索引文件则是在做随机IO,为减少随机IO,关系DB做了很多优化,比如写入时先写入内存,然后批量刷盘,但还是会产生随机IO。
索引在InnoDB引擎中是B+树,MySQL主键是聚簇索引(数据与索引数据在一起),在数据插入或更新时,需找到要插入位置,再把数据写到特定位置,这就产生了随机IO。一旦发生页分裂,就会做数据移动,极大损耗写性能。
而很多NoSQL使用基于LSM树的存储引擎,LSM树(Log-Structured Merge Tree)牺牲一定读性能换取写入数据的高性能,Hbase、Cassandra、LevelDB都是用这种算法作为存储的引擎。
数据首先会写入到MemTable内存结构,在MemTable中数据按写入的Key排序。为防止MemTable数据因为机器掉电或者重启而丢失,一般会写Write Ahead Log将数据备份在磁盘。
MemTable在累积到一定规模时,会被刷新生成一个新的文件,叫SSTable(Sorted String Table)。
当SSTable达到一定数量时,会将这些SSTable合并,减少文件数量,因为SSTable有序,所以合并快。
当从LSM树里面读数据时,我们首先从MemTable中查找数据,如果数据没有找到,再从SSTable中查找数据。因为存储的数据都是有序的,所以查找的效率是很高的,只是因为数据被拆分成多个SSTable,所以读效率低于B+树索引。
类似算法有很多,如TokuDB使用的名为Fractal tree的索引结构,它们的核心思想就是将随机IO变成顺序的IO,从而提升写入的性能。
适用场景
除了提升性能,NoSQL还可在某些场景下弥补传统关系型DB的不足,假设要设计商品搜索功能,需支持按照商品的名称模糊搜索到对应的商品。
这不就是:
使用了“name”上的索引。而一旦没有使用索引就会扫描全表数据。
于是发现Elasticsearch支持搜索,基于“倒排索引”来实现,将记录中的某些列做分词,然后形成的分词与记录ID之间的映射关系。
比如电商项目有以下记录: