1.业务拆分
业务发展的初期为了快速迭代一半采用集中式的架构,随着业务的发展,使系统变得越来越复杂和难以维护,开发效率越来越低,并且系统的资源消耗也越来越大,通过硬件提升性能的成本也越来越高,因此业务拆分难以避免。
在拆分后,每一块业务都使用不同的数据库来进行存储,前端的业务访问不同的数据库,这样原本依赖数据库的服务,变成了四个库一个承担压力,吞吐能力自然就提高了。
同时还有一个好处,原来一次简单修改,工程启动和部署需要很多时间,还要解决测试分支合并等等诸多问题,随着拆分后,单个系统的复杂度降低了,减轻了应用分支开发带来的合并冲突解决的麻烦,大大提高了开发测试的效率,也提升了系统稳定性。
2.复制策略
架构变化的同时,业务也在不断地发展,可能很快就会发现,随着访问量地不端增加,拆分后某个库地压力会越来越大,马上就要达到系统能力地瓶颈。这时候可以采用MYSQL地replication(复制策略)来对系统进行扩展。
也就是主从复制策略,将一台MYSQL数据库分为两台,一台专门作为写入,一台负责读。同时写入地数据库,需要将数据同步到读数据库中,于是复制策略地作用就显现出来了。前端应用通过MYSQL集群中地任意一台读服务器,每台数据库服务器地负载就会大大降低,从而提高整个系统地负载能力,达到扩展地目的。
如图所示:
实现如下:
要实现数据库的复制,就需要开启Master服务器端的Binary log,数据复制的过程实际上就是Slave从master获取binary log, 然后再在本地镜像的执行日志中记录的操作。由于复制过程是异步的,因此Master和Slave之间的数据有可能存在延迟的现象,此时只能够保证数据最终的一 致性。
MySQL的复制可以基于一 条语句 (statem entlevel), 也可以基千一 条记录 Crow level)。通过row level的复制,可以不记录执行的SQL语句相关联的上下文信息,只需要记录数据变更的内容即可。但由于每行的变更都会被记录,这样可能会产生大量的日志内容,而使用statement level则只是记录修改数据的SQL语句,减少了binary log的日志量,节约了I/0成本。但是,为了让SQL语句在Slave端也能够正确地执行,它还需要记录SQL执行的上下文信息,以保证所有语句在Slave端执行时能够得到在Master端执行时的相同结果。
一般而言,大多数站点的读数据库操作要比写数据库操作更为密集。如果读的压力较大,还可以通过新增Slave来进行系统的扩展,因此,Master-Slave的架构能够显著地减轻前面所提到的单库读的压力。
存在的问题
单点故障,当Master宥机时,系统将无法弓入,而在某些特定的场景下,也可能需要Master停机,以便进行系统维护、优化或者升级。同样的道理,Master停机将导致整个系统都无法写入,直到Master恢复,大部分情况下这显然是难以接受的。
解决方式:
使用Dual-Master架构,所谓的Dual Master, 实际上就是两台MySQL服务器互相将对方作为自己的Master, 自己作为对方的Slave, 这样任何一 台服务器上的数据变更,都会通过 M ySQ L 的复制机制同步到另一台服务器。
这样不会导致两台互为Master的MySQL之间循环复制吗?当然不会,这是由于MySQL在记录Binary log日志时,记录了当前的server-id, server-id 在我们配置MySQL复制时就已经设置好了。 一 旦有了server-id, MySQL就很容易判断最初的写入是在哪台服务器上发生的,MySQL不会将复制所产生的变更记录到Binary log, 这样就避免了服务器间数据的循环复制。
当然,我们搭建Dual-Master架构,并不是为了让两个Master能够同时提供写入服务,这样会导致很多问题。举例来说,假如MasterA与Master B几乎同时对一 条数据进行了更新,对Master A的更新比对Master B的更新早,当对MasterA的更新最终被同步到Master B时,老版本的数据将会把版本更新的数据覆盖,并且不会抛出任何异常,从而导致数据不一 致的现象发生。在通常情况下,我们仅开启一 台M aster的写入,另一 台M aster仅仅stand by或者作为读库开放,这样可以避免数据写入的冲突,防止数据不一 致的情况发生。
如果Msater需要进行停机维护,可以按照以下的步骤进行Mater的切换操作:
(1) 停止当前Master的所有写入操作。
(2) 在Master上执行set global read_ only= 1, 同时更新MySQL配置文件中相应的配置,避免重启时失效。
(3) 在Master上执行show Master status, 以记录Binary log坐标。
(4) 使用Master上的Binary log坐标,在stand by的Master上执行select Master _pos _ wa项),等待stand by Master的Binary log跟上Master的Binary log。
(5) 在stand by Master开启写入时,设置read_only=O。
(6) 修改应用程序的配置,使其写入到新的Master。
假如Master意外宅机,处理过程要稍微复杂一 点,因为此时M aster与stand by Master上的数据并不一 定同步,需要将 M aster上没有同步到stand by Master的Binary log复制到Master上进行replay, 直到stand by Master与原Master上的Binary log同步,才能够开启写入:否则,这一 部分不同步的数据就有可能导致数据不一 致。
分表与分库
对千大型的互联网应用来说,数据库单表的记录行数可能达到千万级别甚至是亿级,并且数据库面临着极高的并发访问。采用Master-Slave复制模式的MySQL架构,只能够对数据库的读进行扩展,而对数据的写入操作还是集中在Master上,并且单个Master挂载的Slave也不可能无限制多,Slave的数量受到Master能力和负载的限制。因此,需要对数据库的吞吐能力进行进一 步的扩展,以满足高并发访问与海摄数据存储的需要。
对于访问极为频繁且数据量巨大的单表来说,我们首先要做的就是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐,这就是所谓的分表。在分表之前,首先需要选择适当的分表策略,使得数据能够较为均衡地分布到多张表中,并且不影响正常的查询。
关于分表策略,对于互联网企业来说,大部分数据都是与用户关联的,因此用户id是最常用的分表手段。因为大部分查询都需要带上用户的id,这样既不影响查询,又能够是数据比较均衡的分布到各个表中,有时还能优化查询的效率。
假设有一张记录用户购买信息的订单表order,由于order表记录条数太多,将被拆分成256张表13。拆分的记录根据user_id%256取得对应的表进行存储,前台应用则根据对应的user_id%256, 找到对应订单存储的表进行访问。这样一来,userid便成为一个必需的查询条件,否则将会由于无法定位数据存储的表而无法对数据进行访问。
假设user表的结构如下:
create table order(
order_id bigint(20) primary key auto_increment, user_id bigint(20),
user_nick varchar(50),
auction_id bigint(20),
auction_title bigint(20), price bigint(20), auction_cat varchar(200), seller_id bigint(20), seller_nick varchar(SO)
) ;
那么分表以后,假设 user_id=257, 并且 auction_id= 100, 需要根据 auction_id 来查询对应的订单信息,则对应的SQL语句如下:
select* from order_l where user_id = 257 and action_id = 100;
其中, order_l根据 257% 256 计算得出,表示分表之后的第 1张order表。
分库
分表能够解决单表数据量过大带来的查询效率下降的问题,但是,却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库 M aster服务器无法承载写操作压力时,不管如何扩展 Slave 服务器,此时都没有意义了。因此,我们必须换一 种思路,对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库。
与分表策略相似,分库也可以采用通过一 个关键字段取模的方式,来对数据访问进行路由。还是之前的订单表,假设 user_id 字段的值为257, 将原有的单库分为 256 个库,那么应程序对数据库的访问请求将被路由到第 1个库 (257% 256=1)。
分库分表
有时数据库可能既面临着高并发访问的压力,又需要面对海量数据的存储问题,这时需要对数据库即采用分库策略,又采用分表策略,以便同时扩展系统的并发处理能力,以及提升单表的查询性能,这就是所谓的分库分表。
分库分表的策略比前面的仅分库或者仅分表的策略要更为复杂,一种分库分表的路由策略如下:
• 中间变量=user_id%(库数量*每个库的表数量);
• 库=取整(中间变量/每个库的表数量);
• 表=中间变量%每个库的表数量。
同样采用user_id作为路由字段,首先使用user_id对库数量X每个库表的数量取模,得到一个中间变量:然后使用中间变量除以每个库表的数量,取整,便得到对应的库;而中间变量对每个库表的数量取模,即得到对应的表。
假设将原来的单库单表order拆分成256个库,每个库包含1024个表,那么按照前面所提到的路由策略,对千user_id=262145的访问,路由的计算过程如下:
• 中间变量=262145%(256X 1024) =l;
• 库=取整C1/1024) =0:
表=1%1024=1。
这意味着,对于user_id=262 l 4 5的订单记录的查询和修改,将被路由到第0个库的第1个表中执行。
数据库经过业务拆分及分库分表之后,虽然查询性能和并发处理能力提高了,但也会带来一系列的问题。比如,原本跨表的事务上升为分布式事务;由于记录被切分到不同的库与不同的表当中,难以进行多表关联查询,并且不能不指定路由字段对数据进行查询。分库分表以后,如果需要对系统进行进一 步扩容(路由策略变更),将变得非常不方便,需要重新进行数据迁移。
整理不易,如果喜欢,帮我点个赞。