业务飞速发展导致了数据规模的急速膨胀,单机数据库已经无法适应互联网业务的发展。
传统的将数据集中存储至单一数据节点的解决方案,在容量、性能、可用性和运维成本这三方面难于满足海量数据场景。在单库单表数据量超过一定容量水位的情况下,索引树层级增加,磁盘 IO 也很可能出现压力,会导致很多问题。
主从复制只能减轻读压力,但容量问题是无法解决的。
影响:
主从结构解决了高可用,读扩展,但单机容量不变,单机写性能无法解决。
提升容量 =》分库分表,分布式,多个数据库,作为数据分片的集群提供服务。 降低单个节点的写压力。提升整个系统的数据容量上限。
分库和分表是两码事,可能光分库不分表,也可能光分表不分库。 比如业务发展迅猛,注册用户数达到了2000万!每天活跃用户数100万!每天单表数据量10万条!高峰期每秒最大请求达到1000!感觉压力大。 因为每天多10万条数据,一个月就多300万条数据,现在咱们单表已经几百万数据了,马上就破千万了。但是勉强还能撑着。高峰期请求现在是1000,咱们线上部署了几台机器,负载均衡搞了一下,数据库撑1000 QPS也还行。但现在开始担心了,接下来咋整?
此时每天活跃用户数上千万,每天单表新增数据达50万,目前一个表总数据量都已经达到了两三千万了!扛不住!数据库磁盘容量不断消耗!高峰期并发达到5000~8000!你的系统绝对支撑不到现在,已经挂掉了!
所以分库分表实际上是跟着公司业务发展走的,你公司业务发展越好,用户就越多,数据量越大,请求量越大,那你单个数据库一定扛不住。
比如你单表都几千万数据了,你确定你能抗住么? 绝对不行,单表数据量太大,会极大影响你的 SQL执行性能,到了后面你的SQL可能就跑的很慢。经验来看,单表到几百万时性能就会相对差点,就得分表了。
把一个表的数据放到多个表中,然后查询时,就查一个表。
比如按用户id分表,将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内,比如每个表就固定在200万以内。
单库一般达到2000并发,亟需扩容,合适的单库并发值推荐在1000/s。可将一个库的数据拆分到多个库,访问时就访问一个库。
阿里b2b团队开发,proxy层方案,但已停止维护。
不支持读写分离、存储过程、跨库join和分页。
淘宝团队开发,client层方案
不支持join、多表查询等语法,支持读写分离。 使用的也不多,因为还依赖淘宝的diamond配置管理系统,而且已被阿里云商用,不再开源。
360开源的,proxy层方案,但六年前就不维护了。
最初由当当开源,client层方案。 SQL语法支持较多,支持分库分表、读写分离、分布式id生成、柔性事务(最大努力送达型事务、TCC事务)。被大量公司使用,我司也在用。现在已经升级为Apache组织的项目。
这种client层方案的优点: 不用部署,运维成本低,无需代理层的二次转发请求,性能很高 但遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合sharding-jdbc的依赖。
基于cobar改造,proxy层方案,支持的功能非常完善,社区活跃。但相比sharding jdbc年轻一些。
proxy层方案的缺点: 需要部署,自己及运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。
推荐使用sharding-jdbc和mycat:
把一个表的数据给弄到多个库的多个表里,但每个库的表结构都一样,只不过每个库中表放的数据不同,所有库表的数据加起来就是全部数据。
垂直分库分表=》分布式服务化=》微服务架构。
将一个数据库,拆分成多个提供不同业务数据处理能力的数据库。 例如拆分所有订单的数据和产品的数据,变成两个独立的库,数据结构发生了变化,SQL 和关联关系也必随之改变。 原来一个复杂 SQL 直接把一批订单和相关的产品都查了出来,现在得改写 SQL 和程序。先查询订单库数据,拿到这批订单对应的所有产品 id,再根据产品 id 集合去产品库查询所有的产品信息,最后再业务代码里进行组装把一个有很多字段的表给拆分成多个表或库。每个库表的结构都不一样,每个库表都包含部分字段。
一般将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表。 因为数据库有缓存,访问频率高的行字段越少,可在缓存里缓存更多行,性能就越好。这个一般在表这个层面做的较多。
现有的中间件都可实现分库分表后,根据你指定的某个字段值,比如userid,自动路由到对应库,然后再自动路由到对应表。
就是每个库一段连续的数据,一般按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了
均匀分散,最为常用。
把数据库横向扩展到多个物理节点的一种有效方式,主要是为了突破数据库单机服务器的 I/O 瓶颈,解决数据库扩展问题。
Sharding可简单定义为将大数据库分布到多个物理节点上的一个分区方案。每一个分区包含数据库的某一部分,称为一个shard,分区方式可以是任意的,并不局限于传统的水平分区和垂直分区。 一个shard可以包含多个表的内容甚至可以包含多个数据库实例中的内容。每个shard被放置在一个数据库服务器上。一个数据库服务器可以处理一个或多个shard的数据。系统中需要有服务器进行查询路由转发,负责将查询转发到包含该查询所访问数据的shard或shards节点上去执行。
表与表之间
的I/O竞争
e.g. 将原来的老订单库,切分为基础订单库和订单流程库。数据库之间的表结构不同
同个表
的数据分块,保存至不同的数据库
以解决单表中数据量增长压力。这些数据库中的表结构完全相同
分表:把一张表分成多个小表; 分区:把一张表的数据分成N多个区块,这些区块可以在同一个磁盘上,也可以在不同的磁盘上。
分表能解决单表数据量过大带来的查询效率下降
问题,但无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库主服务器无法承载写压力,不管如何扩展从服务器,都没有意义了。
换个思路,对数据库进行拆分,提高数据库写性能
,即分库。
一个MySQL实例中的多个数据库拆到不同MySQL实例中:
数据水平切分后我们希望是一劳永逸或者是易于水平扩展的,所以推荐采用mod 2^n这种一致性Hash。
比如一个订单库,分库分表方案是32*32,即通过UserId后四位mod 32分到32个库中,同时再将UserId后四位Div 32 Mod 32将每个库分为32个表,共计分为1024张表。
线上部署情况为8个集群(主从),每个集群4个库。
为什么说这易于水平扩展?分析如下场景:
或1024都无法满足。
假如单表都突破200G,200*1024=200T 没关系,32 * (32 * 2^n),这时分库规则不变,单库里的表再裂变,当然,在目前订单这种规则下(用userId后四位 mod)还是有极限的,因为只有四位,所以最多拆8192个表。
使用
auto_increment_increment
auto_increment_offset
系统变量让MySQL以期望的值和偏移量来增加auto_increment
列的值。
在一个全局数据库节点中创建一个包含auto_increment
列的表,应用通过该表生成唯一数字。
避免了MySQL性能低的问题。
为减少运营成本并减少额外风险,美团排除了所有需要独立集群的方案,采用了带有业务属性的方案: 时间戳+用户标识码+随机数
userID的后四位
,在查询场景,只需订单号即可匹配到相应库表而无需用户ID,只取四位是希望订单号尽可能短,评估后四位已足。分库分表后,由于数据存到了不同库,数据库事务管理出现困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
分库分表之后,难免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上,这时,表的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次询能够完成的业务,可能需要多次查询才能完成。
额外的数据管理负担,最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算。 例如,对于一个记录用户成绩的用户数据表userTable,业务要求查出成绩最好的100位,在进行分表前,只需一个order by即可。但分表后,将需要n个order by语句,分别查出每一个分表前100名用户数据,然后再对这些数据进行合并计算,才能得出结果。
并非所有表都需要水平拆分,要看增长的类型和速度,水平拆分是大招,拆分后会增加开发的复杂度,不到万不得已不使用。 拆分维度的选择很重要,要尽可能在解决拆分前问题的基础上,便于开发。
参考