个人随笔
目录
存储高性能方案
2022-06-13 21:42:39

1、关系数据库

1.1、读写分离

读写分离的基本实现如下:

(1)、数据库服务器搭建主从集群, 一主一从,一主多从都可以。
(2)、数据库主机负责读写操作,从机只负责读操作。
(3)、数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
(4)、业务服务器将写操作发给数据库主机,将读操作发给数据库从机。

复杂性

读写分离的实现逻辑并不复杂,但在实际应用过程中需要应对复制延迟带来的复杂性。

解决方法

(1)、写操作后的读操作指定发给数据库主服务器。

例如,注册账号完成后, 登录时读取账号的读操作也发给数据库主服务器。这种方式和业
务强绑定,对业务的侵入和影响较大,如果哪个新来的程序员不知道这样写代码,就会导致一个bug

(2)、读从机失败后再读一次主机。

这就是通常所说的“二次读取”,二次读取和业务无绑定,只需要对底层数据库访问的 API进行封装即可 实现代价较小, 不足之处在于如果有很多二次读取,将大大增加主机的读操作压力,例如,黑客暴力破解账号,会导致大量的二次读取操作,主机可能顶不住读操作的压力

(3)、关键业务读写操作全部指向主机 ,非关键业务采用读写分离。

1.2、分库分表

读写分离分散了数据库读写操作的压力,但没有分散存储压力,当数据量达到千万甚至上亿条的时候,单台数据库服务器的存储能力会成为系统的瓶颈,主要体现在以下几个方面:

(1)、数据量太大,读写的性能会下降,即使有索引 ,索引也会变得很大,性能同样会下降
(2)、数据文件会变得很大 数据库备份 恢复需要耗费很长时间
(3)、数据文件越大,极端情况下丢失数据的风险越高(例如,机房火灾导致数据库主备,都发生故障〉。

基于上述原因,单个数据库服务器存储的数据量不能太大,需要控制在一定的范围内 。为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上。

常见的分散存储 方法有“分库”和“分表”两大类,接下来将详细介绍这些方案。

分库

分库指的是按照业务模块将数据分散到不同的数据库服务器。

虽然分库能够分散存储和访问压力,但同时也带来了新的问题,接下来我们进行详细分析。

【join操作问题】

业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL的join查询。

【事务问题】

原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。虽然数据库厂商提供了一些分布式事务的解决方案(例如,MySQL的XA ),但性能实在太低,与高性能存储的目标是相违背的。

【成本问题】

业务分库同时也带来了成本的代价,本来1台服务器搞定的事情,现在要3台,如果考虑备份,那就是2台变成了6台。
基于上述原因,对于初创业务,并不建议一开始就这样拆分,主要有几个原因:

(1)、初创业务存在很大的不确定性,业务不一定能发展起来,业务开始的时候并没有真正的存储和访问压力,业务分库并不能为业务带来价值。
(2)、业务分库后,表之间的 join 查询、数据库事务无法简单实现了
(3)、业务分库后,因为不同的数据要读写不同的数据库,代码中需要增加根据数据类型映射到不同数据库的逻辑,增加了工作量 而业务初创期间最重要的是快速实现、快速验证,业务分库会拖慢业务节奏。

分表

将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。

单表数据拆分有两种方式:垂直分表和水平分表

垂直分表

把一个表按列拆分为多个表,每个表所含有的列不同

水平切分

表的列相同,但是包含不同的行

分表后的表不一定非得分散到不同的库中,就算在同一个库也会有很大的性能提升,除非分表后单台服务器还是无法满足性能要求,就不得不再按业务进行分库设计了。

分表导致的复杂性

垂直分表引入的复杂性主要体现在表操作的数量增加

水平分表复杂性
【路由】

水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算。
范围路由:选取有序的数据列(整型,时间戳等)作为路由的条件,不同分段分散到不同的数据库表中。
Hash路由:选取某个或者某几个列组合进行Hash运算,然后根据Hash结果分散到不同的数据库表中。
配置路由:用一张独立的表来记录路由信息。

【join操作】

水平分表后,数据如果分散在多个表中,如果需要与其他表进行join查询,需要在业务代码或者数据库中间件中进行多次的join查询,然后将结果合并。

【count()操作】

水平分表后,就不能一个count()搞定了,我们可以采用所有表count()后相加,也可以用一张表记录每个表的数据量,但是这个又会增加复杂度,可能会导致数据不一致等,不过也可以采取最终一致性,比如定时count()相加后维护到这张记录数表。

【order by操作】

水平分表后,数据分散到多个子表中,排序操作无法在数据库中完成,只能由业务代码或者数据库中间件分别查询每个子表中的数据,然后汇总进行排序。

1.3、实现方法

读写分离需要将读/写操作区分开来,然后访问不同的数据库服务器;分库分表需要根据不同的数据访问不同的数据库服务器,两者本质上都是一种分配机制,即将不同的SQL语句发送到不同的数据库服务器。

常见的分配方式有两种:程序代码封装和中间件封装

程序代码封装

程序代码封装指在代码中抽象一个数据访问层来实现读写分离、分库分表。比如sharding jdbc。

有如下几个特点
(1)、实现简单,而且可以根据业务做较多定制化的功能。
(2)、每个编程语言都需要自己实现一次,无法通用,如果一个业务包含多个编程语言写的多个子系统,则重复开发的工作量比较大。
(3)、故障情况下,如果主从发生切换,则可能需要所有系统都修改配置并重启。

中间件封装

中间件封装指的是独立一套系统出来,实现读写分离和分表分库操作。中间件对业务服务器提供SQL兼容的协议,对于业务服务器来说,访问中间件和访问数据库没有区别,事实上在业务服务器看来,中间件就是一个数据库服务器。比如MyCat。

有如下几个特点:
(1)、能够支持多种编程语言,因为数据库中间件对业务服务器提供的是标准SQL接口。
(2)、数据库中间件要支持完整的SQL语法和数据库服务器的协议,比较复炸,容易出bug.
(3)、数据库中间件自己不执行真正的读写操作,但所有数据库操作请求都要经过中间件,对中间件的性能要求很高。
(4)、数据库主从切换对业务服务器无感知,数据库中间件可以探测数据库服务器的主从状态。

实现复杂度

读写分离实现时只要识别SQL操作是读操作还是写操作即可,通过简单地判断SELECT、UPDATE、INSERT、DELETE几个关键字就可以实现,而分库分表的实现除了要判断操作类型,还要判断SQL中的具体需要操作的表、操作函数(例如:count())、order by 、group by 操作等,然后根据不同的操作进行不同的处理。所以相比下来,分库分表实现的要复杂得多。

2、NoSQL

2.1、关系数据库的缺点

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

(2)、关系数据库的chema扩展很不方便。

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

(3)、关系数据库在大数据场景下 I/O 较高

例如,对一些大量数据的表进行统计之类的运算,关系数据库的I/O会很高,因为即使只针对其中某一列进行运算,关系数据库也会将整行数据读取。

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

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

针对上述问题,分别诞生了不同的NoSQL解决方案,这些方案与关系数据库相比,在某些应用场景下表现更好。但世上没有免费的午餐, NoSQL 方案带来的优势,本质上是牺牲 ACID特性中的某个或某几个特性,因此我们不能盲目地迷NoSQL是银弹,应该将 NoSQL作为SQL的一个有力补充, NoSQL !=No SQL ,而是 NoSQL = Not Only SQL.

2.2、NoSQL方案

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

K-V存储

K-V存储的全称是 Key-Value 存储,其中 Key 是数据的标识,和关系数据库中的主键含义一样, Value就是具体的数据。

Redis是K-V存储的典型代表,它是一款开源(基于BSD许可的高性能K-V缓存和存储系统。Redids的Value是具体的数据结构,包括string、hash、list、set、sorted、bitmap、hyperloglog所以常常被称为数据结构服务器。

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

Redis事务不支持原子性,Redis不支持回滚操作,事务中间一条命令执行失败,既不会导致前面己经执行的命令被回滚,也不会中断后面的命令的执行。

Redis提供两种持久化的方式,即 RDB和AOF。
RDB 持久化只备份当前内存中的数据集,事务执行完毕时,其数据还在内存中,并未立即写入到磁盘,所以RDB持久化不能保证 Redis事务的持久性。AOF持久化是先执行命令,执行成功后再将命令追加到日志件中,即使AOF每次执行命令后立刻将日志文件刷盘,也可能丢失1条命令数据,因此 AOF 也不能严格保证 Redis事务的持久性。

虽然Redis并没有严格遵循ACID原则,但实际上大部分业务也不需要严格遵循ACID原则。比如Redis用来做缓存存储数据。

文档数据库

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

文档数据库带来了如下的优点:
(1)、新增字段简单
业务上新增字段,无须再像关系型数据库一样先执行DDL语句修改表结构,程序代码直接读写即可。
(2)、历史数据不会出错。
对于历史数据,即使没有新增的字段,也不会导致错误,只会返回空置,此时代码进行兼容处理即可。
(3)、可以很容易存储复杂数据。
JSON是一种强大的描述语言,能够描述复杂的数据结构。

文档数据库no-schema缺点
(1)、不支持事务
(2)、无法实现关系数据库的join操作。

因此文档数据库并不能完全取代关系数据库,更多时候是作为关系数据库的一种补充。例如,在常见的电商网站设计中可以使用关系数据库存储商品库存信息、订单基础信息(例如MySQL),而使用文档数据库来存储商品详细信息(例如MongoDB)。

列式数据库

顾名思义,列式数据库就是按照列来存储数据的数据库,与之对应的传统关系数据库被称为“行式数据库”,因为关系数据库是按照行来存储数据的。这里的按照列存储并不是值表面上的,而是存储到硬盘的结构是按列的。比如有如下数据:

  1. 小王 18 60
  2. 小猪 20 50
  3. 行式数据库存储
  4. 小王1860分小猪2050
  5. 列式数据存储
  6. 小王小猪18206050

关系数据库按照行式来存储数据,主要有如下几个优势:
(1)、业务同时读取多个列时效率高,因为这些列都是按行存储在一起的,一次磁盘操作就能够把一行数据中的各个列都读取到内存中。
(2)、能够一次性完成对一行中的多个列的写操作,保证了针对行数据写操作的原子性和一致性,否则如果采用列存储,可能 出现某次写操作,有的列成功了,有的列失败了,导致数据不一致。

行式存储的优势是在特定的业务场景下才能体现,如果不存在这样的业务,那么行式存储的优势也将不存在甚至成为劣势,典型的场景就是海量数据进行统计。例如,计算某个城市体重超重的人员数据,实际上只需要读取每个人的体重这一列并进行统计即可,而行式存储即使最终只使用一列,也会将所有行数据都读取出来如,而如果采用列式存储,每个用户只需要读取体重这一列的数据。

除了节省I/O,列式存储还具备更高的存储压缩比,能够节省更多的存储空间,普通的行式数据库一般压缩率在3:1到5:1左右,而列式数据库的压缩率一般在8:1到30:1左右,因为单个列的数据相似度相比行来说更高,能够达到更高的压缩率。同样,如果场景发生变化,列式存储的优势又会变成劣势。典型的场景是需要频繁地更新多个列。因为列式存储将不同列存储在磁盘上不连续的空间,导致更新多个列时磁盘是随机写操作,而行式存储时同一行多个列都存储在连续的空间,一次磁盘写操作就可以完成,列式存储的随机写效率要远远低于行式存储的写效率。此外,列式存储高压缩率在更新场景下也会成为劣势,因为更新时需要将存储数据解压后更新,然后再压缩,最后写入磁盘。

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

全文搜索引擎

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

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

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

基本原理

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

3、缓存

虽然我们可以通过各种手段来提升存储系统的性能,但在某些复杂的业务场景下,单纯依靠存储系统的性能提升不够的,典型的场景如下。

(1)需要经过复杂运算后得出的数据,存储系统无能为力。
(2)读多写少的数据,存储系统有心无力。

缓存就是为了弥补存储系统在这些复杂业务场景下的不足,缓存的基本原理就是将可能重复使用的数据放到内存中,一次生成,多次使用,避免每次使用都去访问存储系统。缓存能够带来性能的大幅提升。

缓存虽然能够大大减轻存储系统的压力,但同时也给架构引入了更多复杂性。架构设计时如果没有针对缓存的复杂性进行处理,某些场景下甚至会导致整个系统崩溃。比如可能遇到缓存穿透、缓存雪崩等问题(参考:缓存穿透、缓存雪崩、缓存击穿)

 224

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2