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

为什么要分库分表?

时间:2023-04-02 00:36:03 Java

前言在高并发系统中,分库分表是必不可少的技术手段之一,也是BAT等大公司面试时经常考的热门试题。知道我们为什么要分库分表吗?这个问题从两行开始:垂直和水平。1垂直方向垂直方向主要针对业务。下面说说业务开发和分库分表的关系。1.1系统初期,单体数据库的业务功能比较简单,系统模块较少。为了快速满足迭代需求,减少一些不必要的依赖。更重要的是,为了降低系统的复杂度,保证开发速度,我们通常使用单个库来保存数据。系统初始数据库结构如下:此时使用的数据库方案为:一个数据库包含多个业务表。用户的读数据请求和写数据请求都操作同一个数据库。1.2分表系统上线后,随着业务的发展不断增加新的功能。这样一来,单表的字段越来越多,开始变得有点难维护了。一张user表包含几十个甚至上百个字段,管理起来有点混乱。这个时候怎么办?答:分表。将用户表拆分为:用户基本信息表和用户扩展表。用户基本信息表存储了用户最重要的信息,如:用户名、密码、别名、手机号、邮箱、年龄、性别等核心数据。这些信息与用户密切相关,查询频率很高。用户扩展表存储了用户的扩展信息,如所属单位、居住地、居住城市等,为非核心数据。这些信息只有在特定的业务场景下才需要查询,绝大部分业务场景都不需要查询。所以核心数据和非核心数据分表分离,让表的结构更清晰,职责更单一,也更容易维护。除了根据实际业务分表,我们还有一个常用的分表原则:调用频率高的放在一张表,调用频率低的放在另一张表。一个很经典的例子就是:订单表和订单明细表。1.3分馆不知不觉,系统已经上线一年多了。经过N次需求开发迭代,功能已经完善。系统的完善功能意味着系统的各种关联错综复杂。这时候如果不快速梳理业务逻辑,后面会带来很多隐患,害的是自己。这就需要根据业务功能划分不同的区域。同一个数据库中的同一个字段的表,另一个数据库中不同字段的表。具体拆分过程如下:将用户、产品、物流、订单相关的表从原来的数据库拆分成单独的用户数据库、产品数据库、物流数据库、订单数据库,共四个数据库。这里为了看起来更直观,每个库我只画了一张表,实际场景中可能会有多张表。按域拆分后,每个域只需要关注自己的相关表,职责更单一,维护一下子变得容易了。1.4分库分表有时根据业务,只分库或只分表是不够的。例如:一些财务系统需要按月和年汇总所有用户的资金情况。这就需要做到:分库分表。每年都有一个单独的数据库。每个数据库中有12张表,每张表存储一个月的用户资金数据。这样数据库分表后,每个月或者每年都可以非常高效的查询某个用户的资金情况。另外还有一些特殊的需求,比如需要按照地区划分数据库,比如:华中、华北、华南等地区,每个地区都有一个单独的数据库。甚至有一些游戏平台会根据连接的游戏厂商来划分数据库和表。2水平方向的水向主要针对数据。再说说数据和分库分表的关系。2.1系统初期,由于用户很少,系统的并发量很小。而且表中存储的数据量也很小。此时的数据库结构如下:此时使用的数据库方案也是:一个主库包含多个业务表。用户的读数据请求和写数据请求都操作同一个数据库。这种方案比较适合并发度很低的业务场景。2.2主从读写分离系统上线一段时间后,用户量增加。这时候你会发现,在用户的请求中,读取数据的请求占了大部分,真正写入数据的请求占了很小的比例。众所周知,数据库连接是有限的,是一种非常宝贵的资源。并且每次对数据库的读写请求都需要占用至少一个数据库连接。如果写数据请求需要的数据库连接被读数据请求用完了,那岂不是无法写数据了?这个问题很严重。为了解决这个问题,我们需要将读库和写库分开。于是就出现了主从读写分离的架构:考虑到一开始的用户量没有那么大,所以选择了一主一从的架构,也就是常说的一主一从奴隶。所有的写数据请求都指向主库。一旦主库写完数据,马上异步同步到从库。这样所有的读数据请求都可以及时从库中拿到数据(除非网络有延迟)。读写分离方案可以解决上面提到的单节点问题。相比单库方案,更能保证系统的稳定性。因为如果主库宕机了,可以把从库升级为主库,把所有的读写请求指向新的主库,系统又可以正常运行了。读写分离方案其实就是分库的一种。与备份数据相比,成为系统初期的首要方案。但是这里有个问题:如果用户量真的很大,如果master挂了,把slave升级为master,把所有的读写请求都指向新的master。但是这个时候,如果新的master根本无法处理所有的读写请求怎么办?这就需要一主多从的架构:我在上图中列出了一主两从。如果master挂了,可以选择从库1或者从库2中选择一个升级到新的master。如果我们这里把从库1升级为新的master,那么原来的从库2就会成为新master的slave。调整后的架构图如下:这样就解决了上面的问题。此外,如果查询请求量增加,我们还可以将架构升级为一主三从、一主四从……一主N从等。2.3子上的读写分离方案-library确实可以解决读请求大于写请求时master节点无法处理的问题。但是如果是某个字段,比如:用户库。如果注册用户的请求量非常大,也就是写请求本身的请求量非常大,一个主库承受不了这么大的压力。这个时候怎么办?答:创建多个用户库。用户库的拆分过程如下:这里我把用户库拆分成三个库(实际场景不一定是这样),每个库的表结构完全一样,只是存储的数据不同.2.4用户对分表请求的增加,必然导致数据量成本的增加。即使分库,也有可能单个数据库,比如用户数据库,有5000万条数据。根据经验,单表数据量尽量控制在1000万以内,性能最好。如果有上千万的数据量,使用单表存储会导致性能不佳。如果数据量太大,需要建立的索引也会很大。从小到大检索数据会非常耗时,并且会消耗cpu资源。这个时候怎么办?答:分表,这样可以控制每个表的数据量和索引大小。分表过程如下:这里我将用户库中的用户表拆分成四张表(实际场景中不一定如此),每张表的表结构完全一样,但是存储的数据是不同的。如果以后用户数据量越来越大,只需要再分几张用户表就可以了。2.5分库分表当系统发展到一定阶段,并发用户量很大,需要存储的数据量也很大。这个时候怎么办?答:需要做分库分表。如下图所示:图中,用户库被拆分为三个库,每个库包含四个用户表。如果有用户请求,会先根据用户id路由到其中一个用户数据库,然后定位到某个表。路由算法挺多的:根据id取模,比如:id=7,有4张表,那么7%4=3,取模3,路由到用户表3。指定一个id的区间范围,例如:如果id的值为0-100,000,则数据将存储在用户表0中;如果id的值为100,000-200,000,数据将存储在user表1中。本文不会过多介绍一致性哈希算法,后面会有文章专门介绍这些路由算法。3真实案例下面废话不多说,给大家分享一下我参与过的三个分库分表项目的经验,给有需要的朋友一个参考。3.1分库我以前在一家公司工作过。我们团队从事的是游戏运营。我们公司提供了一个平台,游戏厂商连接到我们的平台来推广他们的游戏。游戏玩家通过我们的平台登录,成功跳转到游戏厂商指定的游戏页面后,玩家即可正常进行游戏,也可以充值游戏币。这就需要在我们的账号系统和游戏厂商的账号之间建立一个映射关系。游戏玩家登录本平台游戏账号,成功后转换为游戏厂商自有平台账号。这里有两个问题:每个游戏厂商的接入方式可能不一样,账号系统的映射关系也不一样。用户从我们的平台登录,成功后跳转到游戏厂商的游戏页面。当时接入了N个游戏厂商,活跃的游戏玩家众多,并发登录界面不容小觑。为了解决这两个问题,我们当时采用的解决方案是:分库。即为每个游戏建立一个单独的数据库,允许数据库中的表结构不同。我们当时并没有进一步划分表格,因为考虑到当时每个游戏的用户数量,并没有大到离谱。不像王者荣耀这样拥有数亿玩家的现象级游戏。其中一个关键点是游戏id字段需要传入登录界面。通过这个字段,系统知道要操作哪个库,因为库名包含了游戏id信息。3.2亚米还在那个游戏平台公司,我们还有另外一个业务:金钻会员。说白了就是打造了一个与游戏相关的会员体系。为了让用户保持活跃,会员提供了很多福利,比如:免费游戏币、充值优惠、积分兑换、抽奖、专属客服等等。在这个会员体系中,有一个非常重要的功能:积分。用户获取积分的方式有很多,例如:签到、充值、玩游戏、抽奖、推广、参加活动等等。积分有什么用?游戏币抽奖兑换实物礼物说了这么多。其实我想说,一个用户一天可能会多次获得积分或者消费积分,所以一个用户一天可能会产生几十条记录。如果用户多了,积分相关的数据量其实是相当惊人的。我们当时考虑到横向的数据量可能很大,但是并发用户数并不大,不像登录界面。所以采用的解决方案是:分表。当时用信用数据库就够了,但是分了128张表。然后根据用户id,将hash除以128取模。特别要注意的是,分表的个数最好是2的幂,方便以后扩展。3.3分库分表后来去了一家从事餐饮软件开发的公司。这家公司的一个特点是每天中午和晚上的用餐高峰期,并发用户量非常大。用户在用餐前需要通过我们的系统点餐,然后下单,然后结账。那时候并发下单和下单的数量还是挺多的。一个餐厅里可能有很多人,每个人可能会下多个订单。这样会导致用户并发度高,数据量大。所以,经过综合考虑,我们当时采用的技术方案是:分库分表。经过研究,感觉使用的是当当网开源的基于jdbc的中间件框架:sharding-jdbc。当时分为4个图书馆,每个图书馆有32张桌子。4总结以上主要是从:纵向和横向两个方向介绍我们的系统为什么要分库分表。说实话,垂直方向(也就是业务方向)比较简单。在水平方向(也就是数据方向),分库和分表的作用其实是不一样的,不要混为一谈。分库:是解决数据库连接资源不足和磁盘IO性能瓶颈的问题。分表:解决单表数据量过大,即使使用索引用SQL语句查询数据也非常耗时的问题。另外,也可以解决消耗cpu资源的问题。分库分表:解决数据库连接资源不足、磁盘IO性能瓶颈、数据检索耗时、cpu资源消耗等问题。如果在某些业务场景中,并发用户数很大,但是需要保存的数据量很少,那么只能分库,不能分表。如果在某些业务场景中,并发用户数不大,但是需要保存的用户数很大,这时候只能分表,不能分库。如果在一些业务场景中,并发量很大,需要保存的数据也很多,可以分库分表。好了,今天的内容就到这里。是不是有点没完没了?没关系,其实分库分表相关的内容还挺多的。本文是分库分表系列的第一弹,作为开胃菜,分享给大家。文末顺便问几个问题:分库分表的具体实现方案是什么?分库分表后如何顺利扩容?分库分表后带来了哪些问题?项目中如何实现分库分表功能?