背景
前几天在流动工位听到一同事在电话面试候选人,听他问:“分库分表接触过吗,分别是为了解决什么问题”,让我联想到曾经有一个面试官问没接触过分库分表的我——如果由我来设计,如何实现分库分表?
是啊,分库分表原理是什么,具体又应该是怎么实现的,我当时的想法对不对呢?
介绍
单库表的问题
通常在业务发展初期,一系列相关数据通常存储在一张单表中,比如user表,article表等。随着业务的发展,单表存储的数据量可能十分巨大,达到几千万甚至上亿;同时一张表的字段可能也扩展到几十上百个,此时可能带来一系列问题:
- 资源不足
- 单机的IO、连接数都是有限的,一旦并发量过高DB就会成为整个系统的瓶颈
- 读写效率降低
- 写操作,锁冲突现象一定会更明显
- 读操作,由于数据量过大及单行数据过多,某些批量查询一定伴随着频繁IO,也难以命中缓存
- 冷热数据混杂
- 一部分字段实际上并不需要经常读写,导致单行数据也存在冷热之分
- 索引膨胀
- 数据量的增加伴随着索引量的增加,对索引的操作耗时也连带着提高
分库分表前的铺垫工作
切勿过渡设计,切勿过早优化。
千万不要为了追求引入新技术而直接分库分表,这会导致系统复杂性呈指数上升。在进行分库分表前,至少应该做好如下铺垫工作:
- SQL优化 减少慢查询,腾出连接资源
- 索引优化 减少非索引查询,新建索引的成本远低于架构改变
- 增加缓存 减少DB压力,改造成本低
- 主从分离 写操作以及事务中的读操作走主库,其他读操作走从库,需要注意主备延迟
对于大多数业务系统来说读远多于写,增加缓存、主从分离后问题应该能有很大缓解。
但是即便做了上述工作,还是没有解决根本性的单表数据量过大和字段过多的问题,在业务继续发展过程中问题一定还会再次出现。此时除了分库分表或修改业务逻辑使用NoSql数据库外,几乎没有其他太好的方法。
水平/垂直分库/分表是什么
- 分库 即将原本存储在单库的表分散存储在多个库中
- 分表 将一张大表分散存储在多个表中
垂直分表
根据关联度或业务属性按列将表拆分,如频繁展示的“核心信息”与对应的“详情信息”拆分。
拆分后的表之间结构不相同,数据存在相互关联,有小部分重叠,如user_id对应。
水平分表
根据一定的策略将表中的数据行分别存储在不同的表中,如按创建时间/id哈希等拆分。
拆分后的表结构相同,但存储的数据不重叠。
垂直分库
将业务关联度不高的表分离到不同库中,如拆分为“用户库”、“商品库”。
水平分库
将水平分表后的多张表再次分散到不同库中存储,减少单库压力。
分库分表解决的问题
分库的核心目的:
- 将原本单库的机器资源扩充为多库,包括IO、连接数等
- 冷热数据分离,将更多资源分配给频繁访问的数据
分表的核心目的:
- 减少单表列数,减小数据块大小,减少IO次数及提高缓存利用率,减少单表IO压力(垂直分表)
- 冷热数据分离,可以将数据分为不常变更的数据和常变更的数据,分配不同的访问策略(垂直分表)
- 减少单表行数,将数据分散存储到多张表中(水平分表)
- 冷热数据分离,在一些时效性强的场景下将当前数据和过时数据分开处理(水平分表)
- 减少单表索引量,提升读写效率(水平分表)
- 一些分库操作的前提条件,即先做好分表设计后可以将表分布在不同库中存储
分库分表我的思路
- 建立中间层,对应用层和数据库做到无侵入式对接,所有的配置和路由等问题全部在中间层处理
- 设计路由规则,配置好需要的各个库
- 接收并解析应用层SQL语句,提取表名、字段名等关键信息
- 根据路由规则计算目标库表名,如果查询目标分布在多个库表还需要将单条查询拆成多条查询
- 生成新的SQL语句或替换原有语句
- 在对应库表中进行查询
- 将返回结果根据查询语句类型进行融合,如COUNT、SUM、ORDER BY等
- 返回查询结果
当然在实际应用中还需要考虑更多系统层面的保障,包括降级熔断、读写分离、宕机、扩容等等。
分库分表如何实现
常见的分库分表中间件有:
-
ShardingSphere
前身为Sharding-JDBC,目前已成为Apache基金会顶级项目 -
TDDL
阿里开源(开源了吗?) -
MyCat
民间开源
分库分表中间件主要是处理水平分库分表的问题,而垂直分库分表本身处理逻辑比较简单,在应用层也容易实现和维护。
而分库分表最重要的能力则是SQL的解析、路由与转发,以及相关的支撑能力包括配置、扩容等。
以TDDL为例,处理流程为:
一般而言的业务演化改造流程:
- 单库单表
- 重要业务数据可考虑直接主从分离,此时考虑的更多是数据容灾问题
- 数据量千万以下完全撑得住
- SQL优化
- 避免慢查询占用连接资源
- 索引优化
- 优化索引相对成本低,投入产出比高
- 增加缓存/读写分离
- 针对大多数读多写少的业务帮助较大
- 垂直分库
- 增加机器资源,降低查询压力
- 开始深入优化,改造难度上升,维护也变得复杂,需要注意的包括但不限于:
- 数据迁移
- 分布式事务
- 全剧唯一id
- 冷热数据分离,可以单独给热区数据做分库分表
- 服务器资源规划
- 垂直分表
- 改造成本更高,应用层逻辑可能需要改造,如数据双写等
- 水平分表
- 当单表数据量过大时,需要借助中间件进行分表
- 水平分库
- 扩容,增加机器资源
- 如果还是撑不住,试试其他存储方式吧,比如NoSql数据库
分库分表带来的问题
自增主键
分表后各表无法同步自增id,需要依赖全局唯一id生成方案实现,如snowflake等。
数据更新
垂直分库分表后,一些单表更新变成多表更新。
数据库事务
由于跨库的问题,原本的数据库事务难以支持,需要借助分布式事务实现。
关联操作
由于跨库,join等操作无法实现,关联查询很难实现,大多数应用通过冗余存储或借助其他存储方式实现。
外键约束/全文索引
分库分表后无法支持全文索引或外键约束,正如一些参考资料所说,现今的业务本身最好就尽最大可能避免这样的操作发生,以本人从业经验来看也从未遇到应用场景。
性能开销
原本的一些单表查询在分库分表后可能变为多表查询,以及额外的数据拼接处理开销。但相比单表海量数据的读写压力而言这些额外开销是可以接受的。
运维管理
从单库单表到分库分表,维护难度呈指数增长,尤其是涉及到扩容、宕机等等。
总结
分库分表中间件本质上就是将应用层的多表查询和结果处理能力分离,进行抽象化和标准化。
虽然核心思路并不复杂,但整个服务的体系化,包括维护、容灾等才是最困难的部分。同时,当面试官问过分库分表的问题后,紧接着可以展开的内容也非常丰富,包括全局唯一id、数据库锁、分布式事务、主从同步、负载均衡、一致性哈希、限流、缓存......能够关联到很多的知识,是一个非常好的切入点。
参考资料
概览 :: ShardingSphere
分布式数据库中间件—TDDL的使用介绍 - 知乎
不要NoSQL/NewSQL,也不要分区,直接分库分表!_存储
Mycat1.6