本文转载自微信公众号《程序员的内幕》,作者程序员内幕。转载本文请联系程序员内电史公众号。之前有很多刚入坑的Java爱好者留言,想系统学习一下分库分表的相关技术,但是一直没有下定决心去做。现在赶上公司的项目,正在使用sharing-jdbc对现有的MySQL架构做分库分表的改造,所以借此机会发表了分库实践系列文章和分表,算是自己学习建筑的一个总结。网上也陆续看了一些分库分表的文章,但是发现网上同质化的资料很多,知识点比较零散,也没有详细的实战案例.为了更深入的学习,在一些平台上买了一些付费课程。看了几个课程后,发现有一定经验的人都能看懂,但是对于初学者来说,学习难度其实还是挺高的。为了让新手容易理解,我可能会用更多的篇幅来描述一些知识点。我希望你不要认为我啰嗦。等分库分表系列文章写完了,我会做成PDF文档开源出来,一一算出来!如果您发现文中有错误或不严谨的地方,欢迎交流指正。在分库分表的具体实践之前,先简单说几句,复习一下分库分表的基本概念。什么是分库分表?其实分库和分表是两个概念,只是通常对分库和分表的操作会同时进行,所以我们习惯性的称它们为分库分表。分库分表的目的是为了解决数据库和表数据量大导致数据库性能持续下降的问题。按照一定的规则,将数据量大的数据库拆分成多个独立的数据库,将数据量大的表拆分成若干个数据表,使单个数据库和表的性能达到最优效果(响应快),从而提高整体数据库性能。如何分库分表的核心概念是对数据进行分片(Sharding),以及分片后如何快速定位数据并整合查询结果。分库和分表都可以从两个纬度来划分:vertical(纵向)和horizo??ntal(水平)。分库分表下面我们以订单相关业务为例,看看如何做分库分表的纵横切分。垂直拆分垂直拆分包括垂直分库和垂直分表。1.垂直分库垂直分库比较容易理解。核心概念只有四个字:专用仓库。表按业务类型分类,订单、支付、优惠券、积分等对应的表放在对应的数据库中。开发者无法跨数据库直接连接其他业务数据库。如果他们想要其他业务数据,相应的业务方可以提供API接口。这是微服务的初始形式。垂直分片很大程度上取决于业务的划分,但有时业务之间的划分并不是那么明确,例如:订单数据的拆分要考虑到与其他业务的关系,而不是与订单直接相关。就像把你的图书馆里的桌子。垂直分片在一定程度上看似提升了一些数据库性能,但实际上并没有解决单表数据量大带来的性能问题,所以需要结合水平分片来解决。Verticalsharding2.Verticaltablesharding垂直分表是基于数据表的列(字段),是一种将大表拆分成小表的模式。例如:在一张订单表中,将订单金额、订单号等经常访问的字段拆分到一个单独的表中,将blob类型等大字段或不经常访问的字段拆分,单独创建一个扩展表work_extend,使得每个table只存储原表的一部分字段,然后分表分布到不同的库中。对于垂直分表,我们知道数据库是以行为单位加载数据到内存中的。拆分后,核心表大部分是访问频率高的字段,字段长度更短,可以将更多的数据加载到内存中。提高查询命中率,减少磁盘IO,提高数据库性能。垂直切分的优势:业务间数据解耦,不同业务数据独立维护、监控、扩容。在高并发场景下,一定程度上缓解了数据库的压力。垂直切分的缺点:增加了开发的复杂度。由于业务隔离,很多表无法直接访问,数据必须通过接口进行聚合。分布式事务管理的难度增加。数据库仍然存在单表数据过多的问题,没有从根本上解决,需要配合横向切分。Horizo??ntalsharding前面提到了verticalsharding仍然存在单库、表数据量大的问题。当我们的应用无法再进行细粒度的垂直分片时,单库读写和存储仍然存在性能瓶颈。水平分片必须配合使用,因为水平分片可以大大提高数据库性能。1.水平分库水平分库是将同一张表按照一定的规则拆分到不同的数据库中。每个数据库可以位于不同的服务器上,实现水平扩展。它是提高数据库性能的常用方法。这种方案往往可以解决单库存储和性能瓶颈的问题,但是由于同一张表分配在不同的数据库中,数据访问需要额外的路由工作,因此也增加了系统的复杂度。比如下图中,orderDB_1、orderDB_1、orderDB_3这三个数据库的tableorder是完全一样的。当我们访问订单时,我们可以对订单的订单号取模。订单号mod3(数据库实例数)来指定该订单应该在哪个数据库中运行。同一个数据库中按照一定的规则建立相同的结构,每个表只存储原表的一部分数据。例如:一张订单表有900万条数据,水平拆分后创建三张表,order_1、order_2、order_3,每张表有300万条数据,以此类推。水平拆分表虽然拆分了表,但是子表还在同一个数据库实例中。只是解决了单表数据量过大的问题,并没有把分表分布到不同的机器上,还是在争夺同一台物理机的CPU、内存、网络IO等。为了进一步提高性能,需要将分表分散到不同的数据库中,达到分布式的效果。分库分表水平切分的优点:解决高并发时单库数据量过大的问题,提高系统稳定性和负载能力。业务系统改造的工作量不是很大。水平分片的缺点:难以保证跨分片的事务一致性。跨库join关联查询性能较差。扩容难度和维护量都比较大(拆分成几千个分表想想都可怕)。什么是一定的规则?我们在上面多次提到了一定的规则。这个规则其实就是一个路由算法,就是决定一条数据应该存放在哪个数据库的哪个表中。常见的有取模算法和范围限制算法1.取模算法就是按字段取模(取哈希结果的余数(hash()modN),其中N为数据库实例或子表的个数)是最常见的切分方式。同样以order表为例,先对数据库进行编号从0到N-1,对order订单表中work_no订单号字段取模,得到余数i,i=0存入第一个库,而i=1存放的是第一个库二个库,i=2存放的是第三个库……以此类推。这样,同一个订单的数据就会存储在同一个库、同一个表中。查询时,使用相同的规则,以work_no订单号作为查询条件,可以快速定位到数据。优点:数据分片比较统一,不容易把所有的请求都发到一个库。缺点:该算法存在一些问题。当某台机器宕机时,本应落在数据库中的请求无法正确处理。此时,宕机实例将被踢出集群。这时算法变成了hash(userId)modN-1,用户信息可能已经不在同一个库中了。2.范围限制算法根据时间间隔或ID间隔进行分段。比如我们切分的是user表。我们可以定义每个库的User表只存储10000条数据,第一个库只存储userId从1到9999的数据,第二个库的userId为10000~20000,第一个库的userId第三个盘点是20001~30000……以此类推,时间范围也是如此。优点:单表数据量可控,横向扩展简单。只需要增加节点,不需要从其他分片迁移数据。可以快速定位到要查询的数据在哪个数据库中。缺点:由于连续分片,可能会出现数据热点。例如,按时间字段分片可能会导致某个时间段内的订单突然增加,可能会被频繁读写,而一些分片存储的历史数据很少被查询到。分库分表难点1.分布式事务由于表分布在不同的数据库中,必然会出现跨库事务问题。一般可以采用“三阶段提交”和“两阶段提交”进行处理,但是这种方式性能较差,代码开发量也比较大。通常的做法是实现最终的一致性方案。如果对系统的实时一致性要求不高,只要在允许的时间段内达到最终一致性,就采用事务补偿的方式。这里我使用阿里的分布式事务框架Seata来管理分布式事务,后面会结合实际案例。2、分页、排序、跨库联查询分页、排序、联查询是开发中使用频率很高的功能,但是分库分表后,这些看似普通的操作却非常麻烦。查询分散在不同图书馆的表格中的数据,然后汇总所有的结果提供给用户。3、分布式主键分库分表后,数据库的自增主键意义不大,因为我们不能依赖单个数据库实例上的自增主键来实现全局不同数据库之间的唯一主键。这时候,一个能够生成一个全球唯一ID的系统就非常有必要了,所以这个全球唯一ID就叫做分布式ID。4.读写分离。不难发现,大部分主流关系型数据库都提供了主从架构的高可用方案,但是我们需要实现读写分离+分库分表。表格处理,后面会有具体的实战案例。5、数据脱敏数据脱敏是指通过脱敏规则对某些敏感信息进行数据转换,从而实现对敏感隐私数据的可靠保护,如身份证号、手机号、卡号、账户密码等个人信息。这些都需要脱敏。分库分表工具我还是这么说。尽量不要自己造轮子,因为自己造的轮子不一定那么圆。业界已经有很多成熟的分库分表中间件。我们根据自己的业务需求来选择。更多地关注业务实施。sharding-jdbc(当当网)TSharding(蘑菇街)Atlas(奇虎)Cobar(阿里巴巴)MyCAT(基于Cobar)Oceanus(58同城)Vitess(Google)为什么选择sharding-jdbcsharding-jdbc是轻量级的是客户端不需要额外部署的端产品。它相当于JDBC驱动的增强版。相比之下,像Mycat这样需要单独部署服务的服务端产品只是稍微复杂了一点。另外,我想更专注于业务的变现,不想做额外的运维工作。sharding-jdbc的兼容性也非常强,适用于任何基于JDBC的ORM框架,如:JPA、Hibernate、Mybatis、SpringJDBCTemplate或直接使用的JDBC。完美兼容任何第三方数据库连接池,如:DBCP、C3P0、BoneCP、Druid、HikariCP等,支持几乎所有的关系型数据库。不难发现,它确实是一个比较强大的工具,而且对项目的侵入性很大。几乎不需要修改任何代码层,也不需要修改SQL语句。只需要将数据表配置成分库分表即可。.对分库分表的基础知识进行总结和简单回顾。下一篇文章将结合实战项目介绍sharding-jdbc在分库分表的各个功能点。
