当前位置: 首页 > 后端技术 > Java

终于有人把分库分表写清楚了!!

时间:2023-04-01 14:22:42 Java

作者|老顾聊科技 友情链接|www.toutiao.com/i6677459303055491597在中大型项目中,一旦数据量比较大,大家应该都知道要对数据进行拆分。有立式和卧式两种。垂直拆分比较简单,就是一个数据库在数据量大之后,本来从业务角度拆分成多个数据库。如下图,订单库和用户库是独立拆分的。水平拆分的概念是在同一个业务有大量数据后进行水平拆分。上图中的订单数据已经达到了4000万。我们也知道mysql单表的推荐存储容量是百万。如果不处理,单个mysql表的数据太大,会降低性能。使用方案可以参考数据进行水平拆分。将4000万条数据拆分为4个或更多表。当然也可以先分库再分表;将压力与数据库级别分开。分库分表方案分库分表方案中有常用的方案,如hashmodulo和rangerange方案;分库分表方案中最重要的部分是路由算法,它根据指定的算法存储路由密钥。下面分别介绍这两种方案的特点。1.哈希取模方案在我们设计系统之前,我们可以预估过去几年的订单量,比如:4000万。我们可以每张表容纳1000万,也可以设计4张表来存储。那么如何路由存储呢?hash解决方案是取指定的routingkey(如:id),对分表总数取模。上图中,id=12的订单会取4的模,即取0,那么这个订单就会放在0表中。对于id=13的订单,如果取模值为1,则放入1表。为什么取4的模,因为分表总数是4个。优点:订单数据可以均匀的放在4个表中,这样订单操作的时候不会出现热点。热点的含义:热点是指对订单的操作集中在一个表中,对其他表的操作很少。订单的一个特点是时间属性。一般用户在操作订单数据时,都会集中在这段时间产生的订单。如果这段时间产生的订单都在同一个订单表中,就会形成一个热点,那张表的压力会比较大。缺点:未来的数据迁移和扩展会比较困难。比如:业务发展很好,订单量很大,超过4000万,那我们就需要增加分表的数量。如果我们增加4张表,一旦我们增加了子表的总数,模数的底数就会变成8,按照这个方案会在4张表中查询之前id=12的订单,但是之前的订单是at0表,导致找不到数据。这是因为模数的基数发生了变化。遇到这种情况,小伙伴们想到的解决办法就是做数据迁移,把之前的4000万数据重新做一个hashscheme,放到新的规划子表中。即我们需要做数据迁移。这是一件很痛苦的事情。一些小公司可以接受晚上停机迁移,但是大公司不允许停机进行数据迁移。当然你可以结合自己公司的业务做数据迁移,做一个工具,但是也会带来很大的工作量。每次扩容,都需要做数据迁移。有没有不需要数据迁移的解决方案?下面看方案2,范围方案范围方案就是按照范围对数据进行拆分。范围方案比较简单,就是将一定范围内的订单存储在一张表中;如上图,id=12放在0表,id=1300万放在1表。本方案设计时,前期设计表的范围。路由按id存储。优点让我们小伙伴们想一想,这个方案是不是有利于以后的扩展,不需要数据迁移。立即添加4个表。前面4个表的范围不需要改变。id=12的还在表0,id=1300万的还在表1,新加的4个表的范围必须大于4000万后,再分范围。缺点是热点问题。我们想一想,因为id的值会一直增加,变大。这段时间的订单会不会在某个表里,比如id=1000万到id=2000万,这段时间会不会所有产生的订单都集中在这个表里,会导致表过热压力过大1、而其他表没有压力。3、总结一下hash取模方案:没有热点问题,但是数据的扩容和迁移很痛苦。Range解决方案:不需要数据迁移,但存在热点问题。那么结合两者优点的解决方案是什么?,即不需要迁移数据,又能解决数据热点问题?其实还是有实际需要的。存储能否根据服务器的性能和存储的高低进行均衡调整?解决思路是hash可以解决数据统一性问题,range可以解决数据迁移问题。我们可以将两者结合起来吗?如何利用两者的特性?让我们考虑一下,数据的膨胀意味着路由键(比如id)的值变大了。这是肯定的。那么我们首先保证,当数据变大的时候,我们先使用范围方案,让数据落在一个范围内。这样以后id变大了,以前的数据就不用迁移了。但也需要考虑数据的统一性。数据能否在一定范围内统一?因为我们每次扩容的时候,肯定会提前设计好扩容范围的大小。我们只需要保证这个范围内的数据是偶数即可。对于方案设计,我们首先定义一个group组的概念,包括一些分库和分表,如上图,有几个关键点:1)id=0~4000万必须属于group01组2)group01group有3个DB,那个id怎么路由到哪个DB?3)根据哈希模数定位DB,什么是模数?模数应该是所有groupDB中的表数,上图中的表总数是10,为什么要到表总数?而不是DB总共3个?4)例如id=12,id%10=2;值为2,会落入哪个DB?这是一个预设的设计,那么如何设置呢?5)一旦你设计并定位到哪个DB,你需要确定哪个表落入该DB?核心主流程就是上面的流程,我们可以根据这个规则定位到一个id,看看能不能避开热点。我们看一下,如果id在[0,1000万]范围内,按照上面的流程设计,1000万以内的id均匀分布到DB_0、DB_1、DB_2三个数据库的Table_0表中。为什么它们可以均匀分布?因为我们使用了hashscheme取模10。上面我们也提出了问题,为什么取表中的总数10而不是DB中的总数3取模呢?我们看看为什么DB_0有4张表,另外两个DB_1有3张表?我们在安排服务器的时候,有的服务器性能高,存储量大,可以安排多存储数据,有的服务器性能低,可以少存储数据。如果我们按照3个DB的总数取模,就意味着[0,4000万]的数据平均分布在3个DB中,不能按照服务器容量合理分配。根据表10的总数,可以实现。看看上图中10的取模是如何实现的。如果值为[0,1,2,3],则路由到DB_0,[4,5,6]路由到DB_1,[7,8,9]路由到DB_2。现在大家明白了吗,这个设计可以把DB_0的数据多放一点,其他两个DB的数据可以少一点。DB_0承载了4/10的数据量,DB_1承载了3/10的数据量,DB_2也承载了3/10的数据量。整个Group01承载了[0,4000万]条数据量。注意:小伙伴们不要被DB_1或者DB_2中表的范围搞糊涂了,同样是0到4000万。这个就是范围区间,也就是这个id在哪个范围,就落在了哪个表上。上节的介绍解决了热点问题,可以根据服务器指标设计数据量的分配。如何扩容其实上面的设计思路都明白了,扩容就已经出来了;也就是在扩容的时候,设计一个group02组,定义好这个组的数据范围,就可以了。因为是新加入的group01,所以没有数据迁移的概念。是一个新的group组,这个group组还是防止热点的,即[4000万,5500万]数据均匀分布在三个DB的table_0表中,[5500万到7000万]数据均匀分布到table_1表。系统设计思路确定,设计比较简单,3张表就可以,组、DB、表建立关系即可。group与DB的关系表与db的关系上面的表关联其实比较简单,只要把原理和思路理顺了就ok了。小伙伴们,开发的时候不要每次都去查询关联的三个表,可以保存在缓存中(本地jvm缓存),不会影响性能。一旦需要扩容,是否要增加group02关联关系,是否需要重启应用服务?如果简单点,凌晨配置好,重启应用服务。但是如果是大公司就不行了,因为凌晨还有订单。那我们该怎么办呢?如何更新本地jvm缓存?其实解决办法有很多。您可以使用zookeeper或分布式配置。这里推荐使用分布式配置中心。您可以将这些数据配置到分布式配置中心。近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.别在满屏的if/else中,试试策略模式,真的很好吃!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!