分库分表。这是企业高并发、大数据量场景常用的技术优化方案。这也是一个非常常见的面试问题。但是,由于很多人在分库分表方面的经验都不是很丰富,所以能很好回答这个问题的人其实很少。所以,本文将尝试一次搞清楚分库分表的事情。分库分表分库分表首先我们要知道,所谓的“分库分表”根本就不是一回事,而是三件事,而他们要解决的问题也不同。这三个东西就是“只分库不分表”、“只分表不分库”、“既分库又分表”。什么时候拆分数据库?其实分库主要是解决大并发的问题。因为一旦并发量增加,数据库就可能成为瓶颈,因为数据库的连接数是有限的。虽然可以调节,但不是无级调节的。因此,当你的数据库读写QPS过高,导致数据库连接数不足时,你需要考虑分库,通过增加数据库实例来提供更多可用的数据库链接,从而提高系统的并发性。分库比较典型的场景是,我们在拆分微服务的时候,会将各个业务的数据按照业务边界从一个数据库中拆分出来,将订单、物流、商品、会员等拆分成一个单独的数据库。此外,有时可能需要将历史订单移动到历史库中。这也是分库的具体方法。什么时候分表?分库主要解决大并发的问题,分表其实主要解决大数据量的问题。如果你的单表数据量很大,因为并发不高,可能数据量连接够了,但是存储和查询的性能遇到了瓶颈,做完还是不能提高效率很多优化,需要考虑做分了。通过将数据拆分到多个表中,减少单个表中的数据量,从而提高查询速度。一般我们认为,当单表行数超过500万行或者单表容量超过2GB时,就要考虑分库分表了。如果数据量小于这个,建议您先通过其他优化解决性能问题。什么时候分库分表?那么什么时候分库分表,也就是需要解决大并发的问题,大数据量的问题的时候。通常高并发和大数据量的问题是同时出现的,所以我们经常会遇到需要同时分库分表的情况。所以,当你的数据库链接不够多,单表数据量也很大,导致查询慢的时候,就需要分库分表。水平拆分和垂直拆分说到分库分表,就涉及到如何拆分的问题。通常,拆分的方式有两种,分别是水平拆分(horizo??ntalsplitting)和垂直拆分(verticalsplitting)。如果我们有一个表,如果把这个表中一条记录的多个字段拆分成多个表,这就是垂直拆分。那么如果把一个表中不同的记录放到不同的表中,这就是水平拆分。水平拆分的结果是数据库表中的数据会分散到多个子表中,这样每个单表的数据量就会减少。比如我们可以将不同用户的订单分表拆分到不同的表中。垂直拆分的结果是数据库表中的数据字段数量会减少,从而减少了每个单表中数据的存储。比如我可以把商品详情信息、价格信息、库存信息等拆分到不同的表中。还有就是我们讲的拆分成不同业务的多个数据库的情况,其实就是一种垂直拆分。分表字段的选择在分库分表的过程中,我们需要一个分表的字段,比如按用户分表、按时间分表、按地区分表等。用户、时间、地域就是所谓的分表字段。那么在选择这个分表字段的时候一定要注意,根据实际业务情况慎重选择。比如我们要划分交易订单的时候,我们可以选择很多信息,比如买家ID、卖家ID、订单号、时间、地区等等,我们应该怎么选择呢?通常情况下,如果有特殊需求,比如月度汇总、区域汇总等,我们通常会建议您按照买家ID分表。因为这样可以避免一个关键问题就是——数据倾斜(hotdata)。买家还是卖家?首先我们来说说为什么我们不按照卖家来划分榜单?因为我们知道电商网站上有很多买家和卖家,但是一个大卖家可能会产生很多订单,比如苏宁易购,当当网等店铺,他每天在天猫上产生的订单量是非常大的.如果按照sellerId分表,那么同一个seller的很多订单都会分到同一个表中。这样就会造成有的表数据量非常大,有的表数据量很小,这就产生了数据倾斜。卖家的数据成为热点数据,随着时间的增长,卖家的所有操作都会变得异常缓慢。但是如果用买家ID作为分表字段就不会出现这种问题,因为买家不容易对数据进行倾斜。不过需要注意的是,当我们说按买家Id分表时,是保证了同一个买家的所有订单都在同一张表中,没必要给每个买家单独分配一张表.我们在做分表路由的时候,可以设置一定的规则。比如我们要分1024张表,那么我们可以用买家ID或者买家ID的hashcode对1024取模,结果是0000-1023,然后存入子表中即可相应的编号。卖家查询怎么办?如果按照买家ID分表,那么卖家的查询呢?这不是说跨表查询吗?首先,我们需要结合业务来讨论业务问题。电商网站订单查询有几种场景?买家查看自己的订单,卖家查看自己的订单。平台上的二级检查员检查用户的订单。首先,我们用买家ID做一个分表,所以买家来查询的时候,一定要能带上买家ID,我们直接去对应的表去查询就可以了。如果是卖家查询怎么办?如果卖家询价,你也可以带上卖家的id。那么,我们就可以有一个基于binlog、flink等准实时同步的卖家维度的分表。该表仅用于查询。解决卖家咨询。本质上是用空间换取时间的做法。不知道大家看到这里会不会有这样的疑问:同步一个卖家的表,这不是给大卖家带来了一个热点问题吗?首先我们说同步一个卖家的维度表,但实际上所有的写操作还是需要写入买家表,只是需要通过准实时同步的方案同步到卖家表。也就是说,我们的seller表理论上是没有业务写操作的,只有读操作。所以,这个卖家的图书馆只要有高性能的阅读就可以了。这种情况下有很多选择,比如部署到一些没有那么高配置的机器上,或者其实可以干脆不使用MYSQL,而是使用HBASE,PolarDB,Lindorm等数据库就可以了。这些数据库可以处理海量数据并提供高性能查询。更重要的是,大卖家通常是可以识别的。提前锁定大卖家,将他们的订单按照一定的规则拆分到多个表中。因为只有读操作,没有写操作,所以分表时不需要考虑事务问题。按顺序查询怎么办?上面说的是有买家ID,没有买家ID怎么办?直接用订单号查询怎么办?这个问题的解决方法是在生成订单号的时候,我们一般是分表解编码到订单号里面,因为生成订单的时候必须知道买家ID,那么我们把买家的路由结果ID,比如1023,作为固定值写入订单号??即可。这就是所谓的“遗传法”。根据订单号查询时,解析出这个号码直接查询对应的分表即可。至于其他的询盘,如果没有买家ID或者订单号,其实是低频询盘或者非核心功能的询盘,可以使用ES等搜索引擎解决方案来解决。我不会详细介绍。分表算法选择分表字段后,如何根据这个分表字段将数据准确分到某个表中呢?这就是分表算法要做的事情,但是不管是什么算法,我们都需要保证,一个前提是,同一个分表字段,经过这个算法处理后,结果一定是一致的,不可变的.通常我们在对order表进行分表的时候,比如我们要分128张表,那么得到的128张表应该是:order_0000,order_0001,order_0002....order_0126,order_0127通常的分表算法有以下类型:直接取模。分库分表的时候,我们可以提前知道要分多少库,分多少表。因此,更简单的方法是取模方式。比如我们要分128张表,用一个整数对128取模就可以了。如果结果是0002,那么就把数据放到order_0002表中。哈希取模,如果分表字段不是数字类型,而是字符串类型怎么办?一种方式是hash取模,就是先取分表字段的Hash,再取模。但是需要注意的是,Java中hash方法得到的结果可能是负数,需要考虑这个负数。ConsistentHash前两种取模方式比较好,可以让我们的数据更均匀的分布到多个子表中。但仍有不足之处。即如果需要扩容二级分表,当总表数发生变化时,需要重新计算哈希值,需要涉及到数据迁移。为了解决扩容问题,我们可以使用一致性哈希来分表。一致性哈希可以将对应的key按照常用的哈希算法哈希到一个2^32个节点的空间中,形成一个顺时针首尾相连的闭环。所以在添加新的数据库服务器时,只会影响逆时针方向上添加的服务器位置与第一个服务器之间的键。全局ID的生成涉及到分库分表,这会导致在分布式系统中生成唯一主键ID,因为我们可以使用数据库主键作为单表的唯一ID,但是如果分库分表-建表后,多个单表的自增主键肯定会冲突。那么就没有全局唯一性。那么,如何生成一个全球唯一的ID呢?有几种方式:UUID很多人都熟悉UUID,它可以是全局唯一的,生成方法也很简单,但是我们通常不建议使用它作为唯一ID,首先UUID太长,其次,string的查询效率比较慢,而且没有业务意义,根本看不懂。基于单表的自增主键多个单表生成的自增主键会冲突,但是如果所有表中的主键都是从同一张表生成的,可以吗?当所有的表都需要一个主键时,就去这张表中获取一个自增ID。这样就可以做到唯一自增,但是问题是这张单表成为了整个系统的瓶颈,同时也存在单点问题。一旦挂了,整个数据库就写不出来了。基于多张单表+步长的自增主键为了解决单库作为自增主键的瓶颈和单点故障,我们可以引入多张表一起生成。但是如何保证多张表生成的ID不重复呢?如果我们可以实现如下生成方式:实例1生成的ID从1000开始到1999结束,实例2生成的ID从2000开始到2999结束,实例3生成的ID从3000开始到3999结束.实例4生成的ID是从4000开始到4999结束的,这样可以避免ID重复,那如果第一个实例的ID已经用到1999了怎么办?然后生成一个新的起始值:实例1生成的ID从5000开始到5999结束。实例2生成的ID从6000开始到6999结束。实例3生成的ID从7000开始到7999结束。实例4生成的ID从8000开始,到8999结束。我们设置步长为1000,保证每个单表的主键初始值不同,与当前最大值相差1000。雪花算法雪花算法也是一种常用的分布式ID生成方法,具有全局唯一、增量、高可用等特点。雪花算法生成的主键主要由4部分组成,1bit符号位,41bit时间戳位,10bit工作过程位,12bit序号位。时间戳占41位,精确到毫秒,一共可以放69年左右。工作进程位占用10位,其中高位为数据中心ID,低位为工作节点ID,最多可容纳1024个节点。序号占12位,每个节点每毫秒从0开始累加,最多可以累加4095个,一共可以生成4096个ID。因此,雪花算法可以在同一毫秒内生成多达1024X4096=4194304个唯一ID。选择好分表字段和分表算法后,如何实现这些功能,我们需要做什么呢?如何像单表一样处理分库分表的数据呢?这就需要使用分库分表的工具。目前市面上比较好的分库分表开源框架有3个,分别是sharding-jdbc、TDDL和Mycat。作品)。它定位为一个轻量级的Java框架,在Java的JDBC层提供额外的服务。它使用客户端直接连接数据库,以jar包的形式提供服务,无需额外部署和依赖。可以理解为JDBC驱动的增强版,完全兼容JDBC和各种ORM框架。开源地址:https://shardingsphere.apache.orgTDDLTDDL是一个开源的淘宝访问数据库的中间件。集成了分库分表、读写分离、权重分配、动态数据源配置等功能。封装了jdbc的DataSource为用户提供统一的基于客户端的使用。开源地址:https://github.com/alibaba/tb_tddlMycatMycat是一个分布式关系型数据库中间件。支持分布式SQL查询,兼容MySQL通信协议,与Java生态支持多种后端数据库,通过数据分片提高数据查询处理能力。开源地址:https://github.com/MyCATApache/Mycat2分库分表带来的问题分库分表后,会带来很多问题。首先,分库分表后,所有的读写操作都需要有分表字段,这样我们才能知道在哪个数据库和表中查询数据,如果没有,就得支持全表扫描。但是单表的时候扫描全表比较容易,但是分库分表之后就没办法扫描表了。如果要扫描表,则必须扫描所有物理表。还有,一旦我们想查询或写入多个数据库的数据,有很多事情是我们做不到的,比如不支持跨库事务。所以分库分表后会带来不支持事务导致的数据一致性问题。其次,分库分表后,以前单表中很方便的逐页查询、排序等操作都失效了。因为我们不能跨多个表进行分页排序。总之,分库分表虽然可以解决一些大数据量、高并发的问题,但是也会带来一些新的问题。所以在做数据库优化的时候,建议大家先选择其他的优化方式,最后再考虑分库分表。综上所述,本文介绍了分库分表的一些原因,以及如何做分库分表,并对关键的分表字段和分表算法进行了探讨。还介绍了几个比较好的分库分表的相关框架。最后,还有一些需要注意的地方。分库分表会引入一些新的问题,解决这些问题的成本并不低,所以在做技术选型的时候一定要做好这方面的评估。
