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

当数据库扼住了系统性能的咽喉,直接分库分表就能解决问题吗?

时间:2023-03-16 21:14:09 科技观察

众所周知,数据库很容易成为应用系统的瓶颈。单机数据库的资源和处理能力是有限的。在高并发的分布式系统中,可以通过分库分表来突破单机数据库的限制。本文总结了分库分表的相关概念、全局ID生成策略、分片策略、平滑扩容方案和流行方案。一、分库分表概述当业务量不大时,单库单表即可支撑。当数据量太大无法存储,或者并发量太大无法加载时,就需要考虑分库分表。1、分库分表相关术语读写分离:不同的数据库同步相同的数据,只负责读写数据;Partition:指定分区列表达式,将记录拆分到不同的区域(必须是同一个服务器,可以是不同的硬盘),应用看起来还是同一张表,没有变化;分库:一个系统中的多个数据表,存储在多个数据库实例中;分表:针对一张表有多行(记录)多列(字段)的二维数据表,可以分为两种情况:不常用或大容量,或不同业务外出;②水平拆分表(最复杂):水平拆分,按照特定的分区算法,不同的表存储不同的记录。2、真的要分库分表吗?需要注意的是,分库分表会给数据库维护和业务逻辑带来一系列的复杂度和性能损失。除非万不得已估计业务量这么大,否则不要过火。设计,过早的优化。对于规划期间的数据量和性能问题,尝试通过以下方式解决:当前数据量:如果没有达到几百万,通常不需要分库分表;数据量问题:增加磁盘,增加数据库(不同的业务功能表,整张表拆分到不同的数据库);性能问题:升级CPU/内存,读写分离,优化数据库系统配置,优化数据表/索引,优化SQL,分区,数据表垂直拆分;if仍然是最复杂的解决方案,直到它不起作用时才考虑:数据表的水平切分。二、全局ID生成策略1、自动增长列优点:数据库功能各异,有序,性能好。缺点:单库单表还好。如果分库分表时没有计划,可能会出现ID重复的情况。解决方法:设置自增偏移量和步长:##假设总共有10个子表##级别可选:SESSION(会话级别)、GLOBAL(全局)SET@@SESSION.auto_increment_offset=1;##起始值,取值为1~10SET@@SESSION.auto_increment_increment=10;##Stepincrement如果使用该方案,扩容时需要将已有数据迁移到新的shard。全局ID映射表:为全局Redis中的每一张数据表创建一个IDkey,记录该表当前的ID;每次申请一个ID,都会加1,返回给application;Redis应该持久化到全局定时数据库。2、UUID(128位)是在一台机器上产生的一个数字,保证在同一时间和空间内的所有机器都是唯一的。通常平台会提供API来生成UUID。UUID是一个32字节长的字符串,用4个连字符(-)分隔生成的字符串,总长度为36字节。形状是这样的:550e8400-e29b-41d4-a716-446655440000。UUID的计算因素包括:以太网卡地址、纳秒时间、芯片ID码和许多可能的数字。UUID是一个标准,其实有好几个,最常用的是微软的GUID(GlobalsUniqueIdentifiers)。优点:简单且全球唯一。缺点:存储和传输空间大,无序,性能差。3、COMB(组合)将GUID(10字节)和时间(6字节)结合起来,达到有序的效果,提高索引性能。4.雪花(Snowflake)算法Snowflake是Twitter开源的分布式ID生成算法,结果是一个long(64bit)值。它的特点是各个节点不需要协调,按时间大致排序,整个集群中各个节点不重复。该值的默认组成如下(符号位以外的三部分允许个性化调整):1bit:符号位,始终为0(为了保证值为正数);41bit:毫秒(69年可用);10bit:节点ID(5bit数据中心+5bit节点ID,支持32*32=1024个节点);12bit:序列号(每个节点每毫秒支持4096个ID,相当于409万个QPS,如果同一时间内ID倒序,等到下一毫秒)。三、分片策略1、连续分片,根据特定字段(如用户ID、下单时间)的范围和该范围内的值,划分为特定的节点。优点:集群扩容后,指定新范围落在新节点上即可,无需数据迁移。缺点:如果按时间划分,数据热点分布不均(历史数据冷,当前数据热),导致节点负载不均。2.IDmodulosharding缺点:扩容后需要迁移数据。3、一致性哈希算法优点:扩容后无需迁移数据。4、雪花分片优点:扩容后无需迁移数据。四、分库分表引入的问题1、由于分布式事务中两阶段/三阶段提交带来的性能损失较大,可以改用事务补偿机制。2、跨节点JOIN对于单库JOIN,MySQL原生支持;对于多数据库,出于性能考虑,不建议使用MySQL自带的JOIN,可以采用以下方案来避免跨节点JOIN:全局表:一些稳定的共享数据每个数据库保存一张表;字段冗余:在每个数据表中保存一些常用的公共字段;应用程序组装:应用程序获取数据,然后进行组装;另外:某个ID的用户信息在哪个节点,哪个节点是它的关联数据(比如一个订单),可以避免分布式查询。3、跨节点聚合只能在应用端做。但是对于分页查询,每次进行大量的聚合然后分页,性能并不好。4.节点扩容节点扩容后,新的分片规则导致数据所属分片发生变化,需要进行数据迁移。五、节点扩容计划1.常规计划如果没有计划增加节点数和扩容操作,那么大部分数据所属的分片会发生变化,需要在分片之间迁移:估计耗时迁移并发布服务停止公告;停止服务(用户无法使用服务),使用事先准备好的迁移脚本进行数据迁移;修改为新的分片规则;启动服务器。2、免迁移扩容采用双扩容策略,避免数据迁移。扩容前,需要将每个节点的一半数据迁移到新节点上,对应关系比较简单。具体操作如下(假设已经有2个节点A/B,需要扩容到4个节点A/A2/B/B2):不需要停止应用服务器;添加两个数据库A2/B2作为从库,设置主从同步关系为:A=>A2,B=>B2,直到主从数据同步完成(早期数据可以手动同步);调整分片规则,使其生效:原来的ID%2=0=>A改为ID%4=0=>A,ID%4=2=>A2;原来的ID%2=1=>B改为ID%4=1=>B,ID%4=3=>B2。释放数据库实例的主从同步关系并使其生效;此时四个节点的数据是完整的,但是存在冗余(存储了与自己配对的节点的部分数据),可以适时清空(可以进行之后的任何时间都不会影响业务)。六、分库分表方案1、以代理层方式部署一个伪装成MySQL服务器的代理服务器。代理服务器负责与真正的MySQL节点连接,应用程序只与代理服务器连接。它对应用程序是透明的。比如MyCAT,官网,源码。MyCAT后端可支持MySQL、SQLServer、Oracle、DB2、PostgreSQL等主流数据库,以及新型NoSQL存储MongoDB,未来还将支持更多类型的存储。MyCAT不仅可以用于读写分离、分表分库、容灾管理,还可以用于多租户应用开发和云平台基础架构,让您的架构具有高度的适应性和灵活性。2、应用层位于业务层和JDBC层之间,以JAR包的形式提供给应用程序调用,对代码有侵入性。主要解决方案有:淘宝的TDDL:2012年关闭维护通道,建议不要使用;当当网的Sharding-JDBC:仍在积极维护中:当当网的应用框架ddframe,来自于关系数据库模块dd-rdb的分库水平分片框架,实现了透明的数据库分库分表访问,实现了Snowflake分片算法。Sharding-JDBC定位为轻量级Java框架,使用客户端直接连接数据库,无需额外部署,无其他依赖,DBA无需改变原有运维方式。Sharding-JDBC具有灵活的分片策略,可以支持等号、between、in等多维分片,支持多个shardingkey。SQL解析功能完善,支持聚合、分组、排序、limit、or等查询,支持BindingTable和笛卡尔积表查询。Sharding-JDBC直接封装了JDBCAPI,可以理解为JDBC驱动的增强版,旧代码迁移成本几乎为零:可以应用于任何基于Java的ORM框架,如JPA、Hibernate、Mybatis、SpringJDBCTemplate或直接使用JDBC。可以基于任意第三方数据库连接池,如DBCP、C3P0、BoneCP、Druid等,理论上只要实现了JDBC规范的数据库都可以支持。虽然目前只支持MySQL,但有计划支持Oracle、SQLServer等数据库。参考申建:《数据库秒级平滑扩容架构方案》分布式事务解决方案:https://kefeng.wang/2018/03/01/distributed-transaction/《The Cost of GUIDs as Primary Keys》:http://www.informit.com/articles/article。aspx?p=25862::https://github.com/twitter-archive/snowflake/tree/snowflake-2010https://www.lanindex.com/twitter-snowflake%EF%BC%8C64%E4%BD%8D%E8%87%AA%E5%A2%9Eid%E7%AE%97%E6%B3%95%E8%AF%A6%E8%A7%A3/