分库分表:什么时候?怎么划分?
1.数据分割
关系数据库本身更容易成为系统瓶颈,因为单台计算机的存储容量、连接数和处理能力都是有限的。当单表数据量达到1000W或100G时,由于查询维度较多,即使添加子库、优化索引,执行较多操作时性能仍然会明显下降。这时就需要考虑它的细分。分段的目的是减少数据库负载并减少查询时间。
数据库分布的主要内容无非是数据共享(Sharding),以及Sharding后数据的定位和整合。数据分割的目的是将数据分散存储在多个数据库中,从而减少单个数据库的数据量。通过扩展主机数量,缓解单个数据库的性能问题,从而达到提高数据库运行性能的目的。
数据分割根据分割类型可以分为两种方式:垂直(vertical)分割和水平(horizontal)分割
1.垂直(Vertical)分段
Vertical 常见的分段有两种类型:垂直数据库共享和垂直表共享。
垂直数据库共享是指根据业务联动,将相关性较低的不同表存储在不同的数据库中。这种做法类似于将一个大系统划分为多个按业务分类独立划分的小系统。与“微服务管理”方法类似,每个微服务都使用单独的数据库。如图:
表格的垂直划分是基于数据库中的“列”。如果表字段较多,可以新建扩展表,将不常用或字段长度较大的字段划分到扩展表中。 。如果字段较多(例如一张大表有100多个字段),“将大表拆成小表”更容易开发和维护,也可以防止页面之间出现问题。 MySQL底层是通过数据页来存储的。占用过多空间的记录会导致页面交叉,造成额外的性能开销。另外,数据库以行为单位将数据加载到内存中,因此表中的字段长度较短,访问频率较高。内存可以加载更多的数据,访问速度更高,减少磁盘IO,提高数据库性能。
" data-src="https://user-gold-cdn.xitu.io/2018/12/28/167f2b2d69dc0787?imageView2/0/w/1280/h/960/format/webp/ignore-error /1" height="20" data-type="png" data-width="856" data-height="412" />
垂直细分的优势:
- 解决业务系统级别联动,业务清晰
- 与微服务管理类似,可以对各个企业的数据进行管理、维护、监控、扩展等。
- 有些表无法Join,只能通过接口聚合来解决,增加了开发复杂度
- 分布式事务处理困难
- 一张表数据仍然太多 大问题(需要水平切分)
2. Horizontal(横向)切分
当应用对精细化要求较高时分片,或者分片后的数据行数巨大,目前只需要水平分片的数据库的读、写、存储存在瓶颈。
水平切分分为数据库内部子表和数据库下子表。基于表中数据的自然逻辑关系,将同一张表根据不同的条件划分为多个数据库或多张表,每个表只包含部分数据,从而减少一张表的数据量。并达到分布式的效果。 如图展示:
数据库内分表 只解决一张表数据量过大的问题,但并没有将表分布到不同计算机上的库。因此,对于减轻MySQL数据库的压力并没有太大帮助。他们都还在争夺同一台物理机的CPU和内存。 ,网络IO,最好用分库分表来处理。
水平分片的优点:
- 不存在单库数据量过多、高并发等性能限制,提高系统稳定性和负载能力
- 应用端改造小,无需拆分业务模块
缺点:
- 难以保证分片之间事务的一致性
- 数据库之间连接查询性能较差
- 数据多次扩展困难且维护量巨大
水平切分后,同一张表出现在多个库/表中,且每个库/表的内容不同。一些典型的数据共享规则是:
1。根据编号范围
按时间间隔或ID间隔划分。例如:将不同月份甚至几天的数据按日期分发到不同的库;将用户 ID 为 1 到 9999 的记录分配给第一个库,将用户 ID 为 10,000 到 20,000 的记录分配到第二个库,以此类推。类似的做法,从某种意义上来说,就是一些系统中采用的“冷热数据分离”,将一些较少使用的历史数据迁移到其他库,只服务于业务功能中的热数据查询。
优点是:
- 一张桌子的大小可控
- 横向扩展自然方便。如果后续想要扩展整个分片集群,只需添加节点即可,无需修改其他分片的数据。迁移
- 使用分片数组进行范围查找时,连续分片可以快速找到分片进行快速查询,有效避免交叉查询问题。
缺点:
- 热点数据成为性能瓶颈。持续共享可以有数据热点,比如按时间字段共享。有些分片存储的是最近一段时间的数据,可以频繁读写,而有些分片存储的是很少被访问的历史数据
2。根据数值
取模一般情况下采用取模哈希分布方法。例如:Customer表根据cusno字段分为4个库,余数为0的则放在第一个栈中。剩下的1放在第二库,以此类推。这样,同一用户的数据就会分散到同一个数据库中。如果查询条件包含cusno字段,则可以明确的放在查询的相关数据库中。
优点:
- 数据分布比较均匀,不易出现热点和并发瓶颈)
- 容易面临交叉检验的难题。比如上面的例子,如果经常使用的查询条件中不包含cusno,那么就会查不到数据库。因此,需要同时对四个库运行查询,然后合并内存中的数据,取出最小集合并将其返回给应用程序。相反,图书馆成了一个累赘。
2。分库分表带来的问题
分库分表可以有效消除单个机器、单个数据库带来的性能限制和压力,突破网络IO、硬件资源和数据传输瓶颈连接数的增加,同时也带来了一些问题。下面介绍这些技术挑战和相应的解决方案。
1。事务一致性问题
分布式事务
当更新的内容同时分布在不同的库中时,不可避免地会出现跨数据库事务问题。跨分片交易也是分布式交易,没有简单的解决方案。一般来说,可以使用“XA协议”和“两阶段提交”来处理。
分布式事务可以最大程度保证数据库操作的原子性。但在进入一笔交易时,需要多个节点进行协调,这就延迟了交易发送的时间点,增加了交易的执行时间。当事务访问共享资源时,这会导致冲突或死锁的可能性增加。随着数据库节点数量的增加,这种趋势会越来越严重,成为数据库层面系统横向扩展的桎梏。
最终一致性
对于性能要求高但一致性要求不高的系统,往往不需要系统实时一致性。只要在允许的时间内达到最终一致性,就可以应用交易补偿。与执行过程中发生错误后立即回滚事务的方法不同,事务补偿是一种事后检查和纠正措施。一些常见的实现方法包括:数据一致性检查、基于日志的比较以及与标准数据源的定期比较。同步等等。还应考虑与交易系统相关的交易补偿。
2。节点之间分配的Join查询问题
在分片之前,系统中很多列表和详情页所需的数据都可以使用sql join来完成。分割后,数据可以分布在不同的节点上。这个时候,连接问题就会更加受到关注。出于性能原因,尽量避免使用连接查询。
解决这个问题的一些方法:
1)全局表
全局表也可以认为是“数据字典表”,它是系统中所有模块都可以依赖的一些表。为了避免跨数据库查询,您可以在每个数据库中存储此类表的副本。这些数据通常很少被修改,因此无需担心一致性问题。
2) 数组冗余
典型的反范式设计,以空间换时间,避免连接查询以提高性能。例如:orders表存储userId时,还存储了userName的冗余副本,这样查询订单明细时就不需要再查询“buyers用户表”了。
不过该方法的适用场景也有限,比较适合依赖字段比较少的情况。冗余字段的数据一致性也很难保证。就像上面的订单表例子一样,当买家编辑用户名时,是否必须在历史订单中同步更新?这还要结合实际业务场景来考虑。
3)数据编译
在系统层面,有两个查询。第一次查询的结果旨在找到关联的数据ID,然后根据该ID发起第二次请求以检索关联的数据。最后,将获得的数据编译成字段。
4) ER 分片
在关系数据库中,如果能够先确定表之间的关系,并将这些相关的表记录存储在同一个分片上,那么可以更好地避免交叉连接问题。在1:1或1:n的情况下,通常是通过主表ID的主键来分割。如下图所示:
这样Data Node1上的订单表和订单明细表就可以通过orderId部分链接到查询,Data Node2上也是如此。
3。跨节点分页、排序、功能问题
跨节点、多数据库查询时,可能会出现分页限制、排序顺序等问题。分页必须按指定字段排序。当排序数组为分片数组时,利用分片规则更容易找到指定的分片;当排序数组不是分片数组时,就变得更加复杂。首先必须在分数的不同节点对数据进行排序和返回,然后对不同分数返回的结果集进行汇总和重新排序,最后返回给用户。 如图表示:
上图只取第一页的数据,对性能没有太大影响。然而,如果检索的页面数量很大,情况就会变得更加复杂,因为每个断点处的数据可能是随机的。为了排序的准确性,需要对所有节点的前N个数据页进行排序并合并。最后进行总体排序。这样的操作会消耗CPU和内存资源,因此页面数量越大,系统性能就越差。
使用Max、Min、Sum、Count等函数进行计算时,也必须先对每个分数执行相应的函数,然后将每个分数的结果集相加并再次计算,最后结果为回。如图表示:
4。全局主键规避问题
在分库分表环境下,由于表中的数据同时存在于不同的数据库中,因此主键值的自动增加将毫无用处。不保证分区数据库生成的 ID 是全局唯一的。因此,需要单独设计全局主键,避免跨数据库主键重复。常见的主键生成策略有以下几种:
1)UUID
UUID的标准形式包含32个十六进制数字,分为5段,36个字符,形式为8-4-4-4-12,例如: 550e8400-e29b-41d4 -a716-446655440000
UUID 是作为主键的最简单的解决方案。本地生成,性能高,不需要网络时间。但缺陷也很明显。由于UUID很长,因此占用了大量的存储空间。此外,创建索引作为主键并针对该索引进行查询会导致性能问题。在InnoDB中,UUID故障会导致数据位置频繁变化。 ,导致分页。? ID。序列表的内容如下:
+-------------------+------+ | id | stub | +-------------------+------+ | 72157623227190423 | a | +-------------------+------+
为了获得更高的性能,请使用MyISAM存储模块而不是InnoDB。 MyISAM使用表级锁,表的读写都是序列化的,所以不用担心并发时两次读取同一个ID值。
当需要全局唯一的64位ID时,执行:
REPLACE INTO sequence (stub) VALUES ('a'); SELECT LAST_INSERT_ID();
这两条命令处于连接级别。 select last_insert_id() 必须与replace to 位于同一数据库连接下才能检索刚刚插入的新ID。
使用replace do代替insert do的好处是表行数不会太大,也不需要额外的定期清理。
这个方案比较简单,但缺点也很明显:问题点单一,对DB依赖严重。当DB异常时,整个系统将不可用。主从配置可以提高可用性,但当主库故障、主从切换时,特殊情况下很难保证数据一致性。而且性能瓶颈仅限于单个MySQL的读写性能。
flickr 团队使用的主键生成策略与上面的序列表解决方案类似,但更好地解决了单点和性能瓶颈问题。
该方案的总体思路是创建2台以上的服务器来生成全局ID,每台服务器上只部署一个数据库,每个数据库都有一个序列表来记录当前的全局ID。表中ID增长的步长是库的数量,将初始值按顺序展开,以便ID生成可以散列到每个数据库。如下图所示:
由两台数据库服务器生成ID,并设置不同的auto_increment值。第一个序列的初始值为 1,每一步加 2。下一个序列的初始值为 2,每一步加 2。这样一来,第一个站点生成的所有 ID 都是奇数( 1 、3、5、7……),第二站生成的ID均为偶数(2、4、6、8……)。
该解决方案将 ID 生成压力均匀地分配到两台机器之间。它还提供了对系统故障的恢复能力。如果第一台机器出现错误,可以自动切换到第二台机器获取ID。但它有以下缺点:系统添加机器时,横向扩展比较复杂;每次获取ID时都必须读写DB。 DB压力还是很大,只能依靠堆机来提升性能。
可以在flickr方案的基础上继续优化,使用批处理的方式降低数据库写入压力。每次获取ID号段的范围,使用后再去数据库获取,这样可以大大减轻数据库的压力。如下图所示:
始终使用两个DB以确保可用性。数据库中仅存储当前最大标识符。 ID生成服务每次批量下载6个ID,首先将max_id更改为5。应用程序访问ID生成服务时,不需要访问数据库,从号段缓存中依次发送ID 0到5 。 。当发送这些ID时,将max_id更改为11,下次可以分发ID 6~11。这样一来,数据库的压力就会减少到原来的1/6。
3)分布式自增ID雪花算法
Twitter雪花算法解决了分布式系统生成全局ID的需求,生成一个64位长的数字。组成部分是:
- 第一个数字没有使用
- 接下来的41位是毫秒级的时间,41位的长度可以代表69年
- 5位数据中心ID,5位工人数字 。 10位长度支持最多1024个节点的部署
- 最后12位是毫秒内的数字,12位计数序列号支持每个节点每毫秒生成4096个ID序列
优点是:毫秒数较高,生成的ID按照整体时间趋势增加;不依赖第三方系统,稳定性和效率高。理论上QPS约为409.6w/s(1000*2^12),整个分布式系统没有变化。发生ID冲突;可以根据自己的业务灵活分配比特。
缺点是它严重依赖机器时钟。如果时钟向后移动,可能会导致重复的 ID 生成。
5. 数据迁移和扩展问题
当业务快速增长,遇到性能和存储问题时,可以考虑分片方案。当前需要考虑历史数据的迁移问题。一般的做法是先读取历史数据,然后按照指定的分片规则将数据写入分片的各个节点。另外,还需要根据当前的数据量和QPS以及业务发展的速度进行容量规划,计算出大概需要的分片数量(一般建议一张表的数据量单个分片不超过1000W)
如果使用数量范围进行分片,只需添加节点扩容即可,无需迁移分片数据。如果采用数值模分片的话,考虑后期的扩展问题就相当成问题了。
3。何时考虑分段
让我们谈谈何时考虑数据分段。
1。尽量不要拆分
并不是所有的表都需要拆分,主要看数据增长的速度。细分会在一定程度上增加业务的复杂性。除了数据存储和查询之外,数据库也是其重要角色之一,可以帮助业务更好地实现需求。
不要使用大型子数据库和子表技巧,除非绝对必要以避免“过度设计”和“过早优化”。在对数据库和表进行分区之前,不要为了分区而分区。首先,尽量做一些力所能及的事情,比如硬件升级、网络升级、读写分离、索引优化等。当数据量达到单表瓶颈时,可以考虑分库分表。
2。数据量太大,正常运维影响业务处理
这里所说的运维涉及到:
1)如果一张表太大,如果一张表太大,需要大量的磁盘IO和押金。网络IO。例如,如果通过网络传输1T数据,需要50MB,则需要20,000秒才能完成。整个过程风险相当高
2)当在大表上修改DDL时,MySQL会锁定整个表。这个时间会很长。在此期间,公司无法访问该表,影响较大。如果使用pt-online-schema-change,使用过程中会创建触发器和影子表,这也需要很长的时间。在此操作期间,计为风险时间。拆分数据表并减少总数可以帮助降低这种风险。
3) 大表访问和更新频繁,更有可能遇到锁等待。拆分数据,以空间换时间,变相降低访问压力
3。随着业务的发展,有些领域需要进行纵向划分。随着业务的快速增长,用户数量从10万上升到10亿,用户非常活跃。每次登录都会更新last_login_name字段,导致用户表不断更新,压力很大。其他字段:id、name、personal_info 不变或很少更新。从业务角度来看,需要对last_login_time进行拆分,新建一张user_time表。
personal_info属性更新和查询频率较低,文本字段占用空间过多。此时就需要对user_ext表进行垂直拆分。
4。数据量快速增长
随着业务的快速发展,一张表的数据量会不断增长。当性能接近瓶颈时,考虑水平分片并创建单独的数据库和表。此时需要选择合适的切分规则,并提前预估数据容量
5。安全性和可及性
不要把鸡蛋放在同一个篮子里。垂直细分是在企业层面进行的,将不相关企业的数据库分开。由于每个企业的数据量和访问量都不同,数据库受到一个企业的影响,所以不能涉及到其他企业。当使用水平切片时数据库出现问题,不会影响100%的用户。每个数据库仅包含部分业务数据,以提高整体可访问性。 ?所有脱离商业的建筑设计都是小人。在共享数据库和表之前,需要解决业务场景的需求:
- 用户侧:访问量大的前端方式必须保证高可用性和高一致性。请求主要有两种类型:
- 用户登录:通过登录名/电话/邮箱请求用户信息,1%的请求属于此类
- 用户信息请求:登录后,通过uid请求用户信息,99%的请求属于该类型
- 操作页面:后台访问,支持操作需求,根据年龄、性别、登录时间、注册时间等进行分页查询。它是一个内部系统,访问量较小,可用性和一致性要求不高。 ?
“基于编号范围”:基于uid主键,将数据按照uid范围水平划分到多个数据库。例如:user-db1存储uid范围为0到的数据1000w和user-db2存储uid范围为1000w到2000wuid的数据。
- 优点是:扩容容易,如果容量不够就添加新的db即可。
- 缺点是:请求量大一般来说,新注册的用户会比较活跃,所以新的user-db2会比user-db1有更高的负载,这会导致服务器利用率不平衡
《基于值的模块》:primary的uid key也作为分区的依据,根据对uid取模的值将数据水平划分为多个数据库,例如:user-db1存储uid数据模1,user-db2存储uid数据模0。
- 优点是:数据量和请求量分布均匀
- 缺点是:扩展有问题,当容量不够时,添加新数据库需要修改。应考虑数据平滑迁移。
3。无uid查询方式
水平切分后,可以很好的满足通过uid查询,直接定向到具体数据库。对于基于非uid的查询,例如login_name,不知道应该访问哪个库。这样的话,就需要遍历所有的库,性能会明显下降。
在用户侧,可以接受“创建非uid属性到uid的映射关系”的方案;对于运营端,可以采用“前后端分离”的解决方案。 ?要存储的表或缓冲区。访问login_name时,首先通过映射表查找login_name对应的uid,然后通过uid查找具体的库。
映射表只有两列,可以包含大量数据。当数据量太大时,还可以对映射表进行水平分割。这类kv格式索引结构可以利用缓存来优化查询性能,而且映射关系不会频繁变化,缓存访问率会很高。
2)基因法
基因分割:如果按uid将文库分成8个文库,则按uid%8进行路由。此时uid的最后3位决定了该行的具体用户数据。属于哪个库,那么这3位就可以认为是子库基因。
上述映射关系方法需要额外存储映射表。通过非uid字段查询需要多一次访问数据库或者缓存。为了消除冗余的存储和查询,可以使用f函数,将login_name基因作为uid子库基因。生成uid时,参见上面描述的Distributed Unique ID生成方案加上最后3位值= f(login_name)。查询login_name时,只需计算f(login_name)%8即可找到具体的库。但这需要提前规划容量,预估未来几年需要将数据量划分为多少个数据库,并预留一定数量的数据库基因位。
3.2。前后端分离
在用户端,主要需求是专注于一行查询。需要创建一个从登录名/电话/邮箱到uid的映射关系,可以解决这些字段的查询问题。
流量侧,批量分页、各种条件的查询很多。此类查询需要大量计算,返回大量数据,消耗大量数据库性能。目前,如果与用户侧共享同一批服务或数据库,少量的后台请求就会消耗大量的数据库资源,导致用户侧访问性能下降或超时。
对于此类业务,最好采用“前后端分离”的解决方案。运营侧后端业务抽取独立的服务和数据库,解决与前端业务系统的接口。由于运营方对可用性和一致性要求不高,因此不需要实时访问库。相反,它可以通过访问binlog与操作库异步同步数据。当数据量较大时,还可以使用ES搜索引擎或Hive来完成复杂的后台查询方法。 ? TSharding(蘑菇街)
- Atlas(奇虎360)
- Cobar(阿里巴巴)
- )♸MyCAT♸‶Cobar。
- V itess(谷歌)
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。