当前位置: 首页 > 科技观察

这四种情况,是时候考虑分库分表了!

时间:2023-03-22 12:07:56 科技观察

数据库瓶颈无论是IO瓶颈还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,逼近甚至达到数据库能够承载的活跃连接数的阈值.从业务服务的角度来看,可用的数据库连接很少或者没有,然后就可以想象了(并发、吞吐量、崩溃)。第一种IO瓶颈:磁盘读IO瓶颈,热数据太多,数据库缓存放不下,每次查询都会产生大量IO,降低查询速度->分库垂直分表第二种:网络IO瓶颈,请求数据过多,网络带宽不足->分库CPU瓶颈第一种:SQl问题:比如SQL包含join,groupby,orderby,非索引字段条件查询等,增加CPU运算->SQL优化,在业务Service层建立相应的索引并进行业务计算。第二种:单表数据量过大,查询时扫描的行过多,SQl效率低下,增加CPU运算量。->水平表。水平分库1、概念:以字段为基础,按照一定的策略(hash、range等),将一个数据库中的数据拆分到多个数据库中。2.结果:每个库的结构都是一样的。每个图书馆的资料都不一样。没有交集。并且没有明显的业务归属来垂直划分数据库。4、分析:数据库多了,io和cpu的压力自然成倍增加。层次分表1、概念:以字段为基础,按照一定的策略(hash、range等),将一个表中的数据拆分成多个表中的数据。2、结果:每个表的结构相同,每个表的数据不同,没有交集,所有表的并集就是全量数据。3、场景:系统绝对并发没有增加,但是单表数据量过大,影响SQL效率,增加CPU负担,从而成为瓶颈。可以考虑水平分表。4、分析:单表数据量减少,单条SQL执行效率高,自然减轻CPU负担。垂直分库1、概念:以表为基础,根据不同的业务归属,将不同的表拆分成不同的分库。2.结果:每个库的结构都不一样。每个图书馆的数据也不一样。没有交集。所有库的联合就是全量数据。3.场景:系统的绝对并发量增加,可以抽象出单独的业务模块的情况下。4、分析:至此,基本可以面向服务了。比如:随着业务的发展,公共配置表、字典表等越来越多,这时候可以把这些表拆分成一个单独的库,甚至可以面向服务。此外,随着业务的发展,已经孵化出了一套商业模式。这时候可以把相关的表拆解成一个单独的库,甚至可以面向服务。垂直分表1、概念:以字段为基础,将表中的字段按照字段的活跃度拆分成不同的表(主表和扩展表)。2、结果:每个表的结构都不一样。每个表的数据都不一样。一般来说,每个表的字段至少有一个交集列,通常是主键,用来关联数据。所有表的并集就是全量数据。3.场景:系统的绝对并发没有增加。表中记录不多,但是字段很多,热点数据和非热点数据在一起。单行数据需要的存储空间大,使得缓存在数据库中的数据行减少,查询时回头读取磁盘数据产生大量随机读IO,造成IO瓶颈。4.分析:可以用列表页和详情页帮助理解。垂直分表的原理是将热点数据(可能被频繁查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表,这样可以缓存更多的热点数据,从而减少随机读IO.拆解后,如果要获取所有的数据,需要关联两个表来获取数据。但是切记不要使用join,因为Join不仅会增加CPU负担,还会把两个表耦合在一起(必须在一个数据库实例上)。关联数据应该在服务层进行,分别获取主表和扩展表的数据,然后使用关联字段关联所有数据。Sharding-jdbc(当当网)TSharding(蘑菇街)Atlas(奇虎360)Cobar(阿里巴巴)MyCAT(基于Cobar)Oceanus(58同城)Vitess(谷歌)自查各种工具优劣带来的问题通过分库分表分库分表可以有效缓解单机单表带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈,同时也带来一些问题,下面将介绍问题及解决方法。事务一致性问题分布式事务当更新内容同时存在于不同数据库中时,必然会导致跨库事务问题。跨分片交易也是分布式交易,没有简单的解决方案。一般可以使用“XA协议”和“两阶段提交”进行处理。分布式事务可以最大化数据库操作的原子性。但是,在提交事务时需要协调多个节点,延误了提交事务的时间点,延长了事务的执行时间,增加了事务访问共享资源时发生冲突或死锁的概率。随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面横向扩展的桎梏。最终一致性对于那些对性能要求高但对一致性要求不高的系统,往往不需要系统的实时一致性。只要在允许的时间段内达到最终一致性,就可以使用事务补偿。不同于交易执行过程中出现错误立即回滚的方式,交易补偿是一种事后检查和修复的措施。一些常见的实现方式包括:数据的对账校验、基于日志的比对、定期与标准数据源同步比对等。通过join,但是经过segmentation之后,数据可能会分布在不同的节点上。这时候join带来的问题就比较麻烦了,考虑到性能,尽量避免使用Join查询。一些解决方案:全局表全局表也可以看作是“数据字典表”,是系统中所有模块都可能依赖的一些表。为了避免libraryjoin查询,这样的表可以在每个数据库中保存一份。这些数据通常很少被修改,因此无需担心一致性问题。字段冗余是典型的反范式设计,以空间换时间,避免连接查询以求性能。比如order表在保存userId的时候,同时保存了一份userName的冗余副本,这样用户名userName顺表查询订单明细就可以找到了,就不用去查询买家的user表了.但是,这种方法适用的场景有限。比较适用于依赖字段比较少,冗余字段的一致性难以保证的情况。数据在系统的服务层面进行组装,查询分为两次。第一次查询的结果集找出关联的数据id,然后根据id发起者的第二次请求获取关联数据,最后对获取的结果进行字段组装。这是一种比较常用的方法。在ERsharding关系型数据库中,如果表(比如订单表和订单明细表)之间的关系已经确定,并且将那些有关系的表记录存储在同一个分片上,那么可以更好的避免跨分片加入的问题,您可以在分片内加入。在1:1或1:n的情况下,主键通常根据主表的ID进行拆分。跨节点分页、排序、函数问题跨节点、跨数据库查询时,会出现限制分页、排序排序等问题。分页需要根据指定的字段进行排序。当排序字段为分页字段时,更容易通过分片规则定位到指定的分片;当排序字段不是分片字段时,它变得更复杂。需要在不同的分片节点中对数据进行排序返回,然后将不同分片返回的结果集再次汇总排序,最终返回给用户如下图所示:上图只取第一页的数据,对性能影响不大。但是如果获取的page数量很大,情况就复杂很多,因为每个shard节点的数据可能是随机的。为了排序的准确性,需要对所有节点的前N页数据进行排序,进行合并。最后进行整体排序。这样的操作会消耗CPU和内存资源,所以页数越大,系统性能就越差。在使用Max、Min、Sum、Count等函数进行计算时,也需要先在每个分片上执行相应的函数,然后再汇总每个分片的结果集再进行计算。全局主键规避问题在分库分表环境下,由于表中的数据同时存在于不同的数据库中,通常使用的主键值自增长将无用,而分区数据库自生成的ID不能保证全局唯一。因此需要单独设计全局主键,避免跨数据库主键重复。下面是一些策略:UUIDUUID的标准形式是32位十六进制数字,分成5段,形式是8-4-4-4-12的36个字符。UUID是最简单的方案,本地生成,性能高,无网络耗时,但缺点明显,占用存储空间大,另外,建索引为主键,根据索引查询存在性能问题,尤其是在InnoDb引擎,UUID无序会导致索引位置频繁变化,导致分页。在数据库中创建序列表结合数据库维护主键ID表:CREATETABLE`sequence`(`id`bigint(20)unsignedNOTNULLauto_increment,`stub`char(1)NOTNULLdefault'',PRIMARYKEY(`id`),UNIQUEKEY`stub`(`stub`))ENGINE=MyISAM;存根字段设置为唯一索引,同一个存根值在序列表中只有一条记录,可以同时为多个表的全局ID。使用MyISAM引擎而不是InnoDb已经实现了更高的性能。MyISAM使用表锁,表的读写是串行的,不用担心并发时读取同一个ID两次。当需要全局唯一ID时,执行:REPLACEINTOsequence(stub)VALUES('a');选择1561439;这种方案比较简单,但缺点也很明显:存在单点问题,对DB的依赖性强。当DB异常时,整个系统无法使用。配置主从可以提高可用性。另外,性能瓶颈仅限于单个Mysql的读写性能。另一种主键生成策略,类似于序列表方案,更好的解决了单点和性能瓶颈问题。本方案的总体思路是:建立2台以上的服务器进行全局ID的生成,每台服务器上只部署一个数据库,每个数据库都有一个序列表记录当前的全局ID。表中增加的步长为数据库个数,起始值依次错开,这样生成的ID就可以hash到每个数据库。该方案将生成ID的压力平均分配到两台机器上,同时提供系统容错,如果第一台机器出错,可以自动切换到第二台机器获取ID。但是也有几个缺点:系统增加机器,横向扩展比较复杂;每次获取ID都要读一次DB,对DB的压力还是很大的,只能通过堆机来提升性能。Snowflake分布式自增ID算法Twitter的snowfalke算法解决了分布式系统生成全局ID的需求,生成64位Long数。组成部分:第一个数字不用,后面41位是毫秒级时间,41位长度可以代表69年,有5个datacenterId和5个workerId。10位长度支持最多部署1024个节点。最后12位在毫秒内计数。12位的计数序列号支持每个节点每毫秒产生4096个ID序列。数据迁移和扩容当业务快速发展,面临性能和存储瓶颈时,就会考虑分片设计。这时候就不可避免地要考虑历史数据的迁移。一般的做法是先读取历史数据,然后按照指定的分片规则将数据写入各个分片节点。另外需要根据当前数据量QPS和业务发展速度进行容量规划,计算大概需要的分片数量(一般建议单个分片中单表的数据量不应超过1000W)。什么时候考虑分库分表?并不是所有的表都需要拆分,但数据的增长速度主要取决于它。分割在一定程度上增加了业务的复杂性。除非万不得已,否则不要使用分库分表的“大招”,避免“过度设计”和“过早优化”。分库分表前,尽量优化:升级硬件、升级网络、读写分离、索引优化等,当数据量达到单表瓶颈时,考虑分库分表-桌子。数据量过大,正常运维影响业务接入。这里的运维指的是:对于数据库备份,如果单表太大,备份需要大量的磁盘IO和网络IO。在对大表做DDL时,MYSQL会锁住整张表,时间会很长,而且这段时间业务不能访问这张表,影响很大。大表被频繁访问和更新,更容易出现锁等待。随着业务的发展,有些领域需要垂直拆分,这里就不举例了。实际业务中可能会遇到一些不经常访问或不经常更新的字段要从大表中分离出来。数据量快速增长随着业务的快速发展,单表的数据量会不断增长。当性能接近瓶颈时,就要考虑水平切分,做分库分表。