通过数据库锁及存储引擎的学习,我们知道数据库在数据操作过程中为了保证数据的一致性是会给表或行加锁的,在网站发展的初期,由于没有太多访问量,一般来讲只需要一台服务器就够了,这的操作也不会有什么问题。但随着业务系统的扩大,系统变得越来越复杂,越来越难以维护,开发效率变得越来越低,并且对资源的消耗也变得越来越大,通过硬件提高系统性能的方式带来的成本也越来越高。
因此,前期一个好的网站架构设计是后期系统进行扩展和维护的重要保障。随着业务系统的扩大,遇到的问题主要分为两大类:
虽然这些问题的具体解决方案要根据实际场景分析,但其思想基本都是对数据库的扩展,数据库的扩展主要包括:业务拆分、读写分离,数据库分库与分表。
业务拆分是在架构设计层面上的解决方案,业务起步初始,为了加快应用上线和快速迭代,很多应用都采用集中式的架构。
例如:电商平台,包含了用户、商品、评价、订单等几大模块,最简单的做法就是在一个数据库中分别创建users、shops、comment、order四张表。
但是随着业务系统的扩大,系统变得越来越复杂,越来越难以维护,开发效率变得越来越低,并且对资源的消耗也变得越来越大,通过硬件提高系统性能的方式带来的成本也越来越高。我们不得不对业务进行拆分。每一个模块都使用单独的数据库来进行存储,不同的业务访问不同的数据库,如下图将原本对一个数据库的依赖拆分为对4个数据库的依赖,这样的话就变成了4个数据库同时承担压力,系统的吞吐量自然就提高了。
业务拆分要结合具体的实用常场景,尽早的作出合理的架构设计,以免后续的扩展难度过大。
在之前数据库锁的学习中我们知道,读操作是共享锁并不会阻塞其他读操作,但写操作会阻塞其他所有操作。因此显然读的效率一般是远高于写的效率的,特别是在业务读多写少的情况下,写操作会阻塞大量的读,数据量大甚至会造成数据库崩溃,因此采用数据库读写分离的策略解决这个问题。
读写分离,顾名思义就是读操作和写操作分离开来。让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。
数据库读写分离的基本结构:
MySQL支持的复制类型
复制过程
所以对于整个读写分离过程,就是在主服务器上修改,数据会同步到从服务器,从服务器只能提供读取数据,不能写入,实现备份的同时也实现了数据库性能的优化,以及提升了服务器安全。
较为常见的Mysql读写分离分为以下两种:基于程序代码内部实现和基于中间代理层实现。
基于程序代码内部实现
在代码中根据select 、insert进行路由分类,这类方法也是目前生产环境下应用最广泛的。优点是性能较好,因为程序在代码中实现,不需要增加额外的硬件开支,缺点是需要开发人员来实现,运维人员无从下手。
基于中间代理层实现
代理一般介于应用服务器和数据库服务器之间,代理数据库服务器接收到应用服务器的请求后根据判断后转发到后端数据库。
有以下代表性代理服务器:
一般选择mycat作为代理服务器来实现,基本实现原理为:
基本思路为:
一个服务器开放N个链接给客户端来连接的,这样有会有大并发的更新操作, 但是从服务器的里面读取binlog 的线程仅有一个, 当某个SQL在从服务器上执行的时间稍长 或者由于某个SQL要进行锁表就会导致,主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致, 也就是主从同步延迟。
主从同步原理及解决方案是视情况而定的,基本没有统一的解决办法,具体可参考这篇文章。
1.分区
分区就是把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的,分区实现比较简单,数据库mysql、oracle等很容易就可支持。对业务透明,分区只不过把存放数据的文件分成了许多小块,根据一定的规则把数据文件(MYD)和索引文件(MYI)进行了分割,分区后的表呢,还是一张表。
2.分表
分表就是把一张表按一定的规则分解成N个具有独立存储空间的实体表。系统读写时需要根据定义好的规则得到对应的字表明,然后操作它。当数据量大到一定程度的时候,都会导致处理性能的不足,这个时候就没有办法了,只能进行分表处理。也就是把数据库当中数据根据按照分库原则分到多个数据表当中,这样,就可以把大表变成多个小表,不同的分表中数据不重复,从而提高处理效率。
3.分库
分表和分区都是基于同一个数据库里的数据分离技巧,对数据库性能有一定提升,但是随着业务数据量的增加,原来所有的数据都是在一个数据库上的,网络IO及文件IO都集中在一个数据库上的,因此CPU、内存、文件IO、网络IO都可能会成为系统瓶颈。
单表的数据量是有限制的,当单表数据量到一定条数之后数据库性能会显著下降。数据多了之后,对数据库的读、写就会很多。分库减少单台数据库的压力。一般都是通过主键进行散列分库分表的。
而就主键来说,对于大部分数据库的设计和业务的操作基本都与用户的ID(主键)相关,因此使用用户ID是最常用的分库的路由策略。用户的ID可以作为贯穿整个系统用的重要字段。因此,使用用户的ID我们不仅可以方便我们的查询,还可以将数据平均的分配到不同的数据库中(当然,还可以根据类别等进行分表操作,分表的路由策略还有很多方式)。
以电商平台为例,订单表order存放用户的订单数据,有用户ID(user_id)、订单号(order_num)等字段列。当数据比较大的时候,对数据进行分表操作,首先要确定需要将数据平均分配到多少张表中,也就是:表的容量。
这里假设有100张表进行存储,则我们在进行存储数据的时候,首先对用户ID进行取模操作,根据 user_id %100
获取对应的表进行存储查询操作,示意图如下:
如图,先根据user_id % 100 定位所属表,再根据user_id具体找到查询的数据。
需要注意的是,另外,在实际的开发中,我们的用户ID更多的可能是通过UUID生成的,这样的话,我们可以首先将UUID进行hash获取到整数值,然后在进行取模操作。(UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,UUID是由一组32位数的16进制数字所构成,所以UUID理论上的总数为16^32=2^128,约等于3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。)
数据库分表能够解决单表数据量很大的时候数据查询的效率问题,但是无法给数据库的并发操作带来效率上的提高,因为分表的实质还是在一个数据库上进行的操作,很容易受数据库IO性能的限制。
因此,如何将数据库IO性能的问题平均分配出来,很显然将数据进行分库操作可以很好地解决单台数据库的性能问题。分库策略与分表策略的实现很相似,最简单的都是可以通过取模的方式进行路由。
依旧以上述电商为例,假设有100个数据库,先根据user_id % 100 定位所属库,再根据user_id具体找到查询的数据。
上述的配置中,数据库分表可以解决单表海量数据的查询性能问题,分库可以解决单台数据库的并发访问压力问题。
有时候,我们需要同时考虑这两个问题,因此,我们既需要对单表进行分表操作,还需要进行分库操作,以便同时扩展系统的并发处理能力和提升单表的查询性能,就是我们使用到的分库分表。
分库分表的策略相对于前边两种复杂一些,一种常见的路由策略如下:
- 中间变量 = user_id%(库数量*每个库的表数量);
- 库序号 = 取整(中间变量/每个库的表数量);
- 表序号 = 中间变量 % 每个库的表数量;
例如:数据库有256 个,每一个库中有1024个数据表,用户的user_id=262145,按照上述的路由策略,可得:
- 中间变量 = 262145%(256*1024)= 1;
- 库序号 = 取整(1/1024)= 0;
- 表序号 = 1%1024 = 1;
即,对于user_id=262145,将被路由到第0个数据库的第1个表中。
数据库的读写分离和分库分表都是数据库进行扩展的一种方法,除此之外,对于一个可扩展、高性能、高并发的网站使用到的技术远不止这些。使用缓存、页面静态化技术、数据库优化、分布式部署数据库等是实现一个好的网站所必须掌握的技术,但学习是一个漫长的过程,还需一步步脚踏实地。
参考文章:
https://blog.csdn.net/xlgen157387/article/details/53230138
https://blog.csdn.net/xlgen157387/article/details/53976153