读写分离
读写分离原理
读写分离的基本原理是将数据库读写操作分散到不同的节点上。

读写分离方式
将读写操作区分开来,然后访问不同的数据库服务器,一般有两种方式:程序代码封装和中间件代理层。
程序代码封装
程序代码封装的方式具备几个特点:
- 实现简单,而且可以根据业务做较多定制化的功能。
- 每个编程语言都需要自己实现一次,无法通用,如果一个业务包含多个编程语言写的多个子系统,则重复开发的工作量比较大。
- 故障情况下,如果主从发生切换,则可能需要所有系统都修改配置并重启。
目前开源的实现方案中,淘宝的 TDDL(Taobao Distributed Data Layer,外号:头都大了)是比较有名的。它是一个通用数据访问层,所有功能封装在 jar 包中提供给业务代码调用。其基本原理是一个基于集中式配置的 jdbc datasource 实现,具有主备、读写分离、动态数据库配置等功能,基本架构是:
现在,一般通过引入第三方组件来帮助我们读写请求。
这种方式目前在各种互联网公司中用的最多的,相关的实际的案例也非常多。如果你要采用这种方式的话,推荐使用 sharding-jdbc ,直接引入 jar 包即可使用,非常方便。同时,也节省了很多运维的成本。
你可以在 shardingsphere 官方找到 sharding-jdbc 关于读写分离的操作。
中间件代理层
- 能够支持多种编程语言,因为数据库中间件对业务服务器提供的是标准 SQL 接口。
- 数据库中间件要支持完整的 SQL 语法和数据库服务器的协议(例如,MySQL 客户端和服务器的连接协议),实现比较复杂,细节特别多,很容易出现 bug,需要较长的时间才能稳定。
- 数据库中间件自己不执行真正的读写操作,但所有的数据库操作请求都要经过中间件,中间件的性能要求也很高。
- 数据库主从切换对业务服务器无感知,数据库中间件可以探测数据库服务器的主从状态。例如,向某个测试表写入一条数据,成功的就是主机,失败的就是从机。
提供类似功能的中间件有 MySQL Router(官方, MySQL Proxy 的替代方案)、Atlas(基于 MySQL Proxy)、MaxScale、MyCat。
MySQL Router 的主要功能有读写分离、故障自动切换、负载均衡、连接池等,其基本架构如下。关于 MySQL Router 多提一点:在 MySQL 8.2 的版本中,MySQL Router 能自动分辨对数据库读写/操作并把这些操作路由到正确的实例上。这是一项有价值的功能,可以优化数据库性能和可扩展性,而无需在应用程序中进行任何更改。具体介绍可以参考官方博客:MySQL 8.2 – transparent read/write splitting。
奇虎 360 公司也开源了自己的数据库中间件 Atlas,Atlas 是基于 MySQL Proxy 实现的,基本架构如下:
来自于:14 | 高性能数据库集群:读写分离 - 极客时间已完结课程限时免费阅读
避免复制延迟
- 写操作后的读操作指定发给数据库主服务器
- 🌰:注册账号后,登陆时的读账号操作也在 DB Master 读取。
- 缺点:和业务强绑定,对业务的侵入和影响较大,后续可能忘掉导致 BUG。
- 比如
Sharding-JDBC就是采用的这种方案。通过使用 Sharding-JDBC 的HintManager分片键值管理器,我们可以强制使用主库。hintManager.setMasterRouteOnly();
- 读从机失败后再读一次主机
- 优点:和业务无绑定,实现代价小。
- 缺点:如果很多次二次读取,将大大增加主机的读操作压力。甚至需要对黑客暴力破解进行预防。
- 关键业务读写操作全部指向主机,非关键业务采用读写分离
- 例如,对于一个用户管理系统来说,注册+登录的业务读写操作全部访问主机,用户的介绍、爱好、等级等业务,可以采用读写分离,因为即使用户改了自己的自我介绍,在查询时却看到了自我介绍还是旧的,业务影响与不能登录相比就小很多,还可以忍受。
- 延迟读取
- 通过前端的一些交互界面,将读取操作向后延迟。
- 对于一些对数据比较敏感的场景,你可以在完成写请求之后,避免立即进行请求操作。比如你支付成功之后,跳转到一个支付成功的页面,当你点击返回之后才返回自己的账户。
- 敏感数据/关键业务读写主库
- 在开发中有部分业务需要写库后实时读数据,这一类操作通常可以通过强制读主库来解决。
主从延迟的原因
与主从同步有关的时间点主要有 3 个:
- 主库执行完一个事务,写入 binlog,将这个时刻记为 T1;
- 从库 I/O 线程接收到 binlog 并写入 relay log 的时刻记为 T2;
- 从库 SQL 线程读取 relay log 同步数据本地的时刻记为 T3。
结合我们上面讲到的主从复制原理,可以得出:
- T2 和 T1 的差值反映了从库 I/O 线程的性能和网络传输的效率,这个差值越小说明从库 I/O 线程的性能和网络传输效率越高。
- T3 和 T2 的差值反映了从库 SQL 线程执行的速度,这个差值越小,说明从库 SQL 线程执行速度越快。
那什么情况下会出现出从延迟呢?这里列举几种常见的情况:
- 从库机器性能比主库差。相同或更高规格的机器,对从库性能优化,比如调整参数、增加缓存、使用 SSD 等。
- 从库处理的读请求过多。引入缓存、一主多从、使用其他系统来提供查询的能力,比如将 binlog 接入到 Hadoop、Elasticsearch 等系统中。
- 大事务:大事务本身执行时间长,且从库执行会花费更多时间和资源。避免大批量修改数据,尽量分批进行。
- 从库太多:主库 binlog 生成与传输,会增加同步的时间和开销。减少从库数量,分层传递。
- 网络延迟。优化网络环境,比如提升带宽、降低延迟、增加稳定性等。
- 单线程复制:MySQL5.5 及之前,只支持单线程复制。为了优化复制性能,MySQL 5.6 引入了 多线程复制,MySQL 5.7 还进一步完善了多线程复制。
- 复制模式:MySQL 默认的复制是异步的,必然会存在延迟问题。全同步复制不存在延迟问题,但性能太差了。半同步复制是一种折中方案,相对于异步复制,半同步复制提高了数据的安全性,减少了主从延迟(还是有一定程度的延迟)。MySQL 5.5 开始,MySQL 以插件的形式支持 semi-sync 半同步复制。并且,MySQL 5.7 引入了 增强半同步复制 。
- ……
其他参考:MySQL 主备延迟及原因
分库分表
背景
数据数目过多后,单台数据库服务器成为瓶颈:
- 数据量太大,读写的性能会下降,即使有索引,索引也会变得很大,性能同样会下降。
- 数据文件会变得很大,数据库备份和恢复需要耗费很长时间。
- 数据文件越大,极端情况下丢失数据的风险越高(例如,机房火灾导致数据库主备机都发生故障)。
分库概念
业务分库指的是按照业务模块将数据分散到不同的数据库服务器。例如,一个简单的电商网站,包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上,而不是将所有数据都放在一台数据库服务器上。

新问题:
JOIN 操作问题。业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 JOIN 查询。事务问题。虽然数据库厂商提供了一些分布式事务的解决方案(例如,MySQL 的 XA),但性能实在太低,与高性能存储的目标是相违背的。先扣商品库存,扣成功后生成订单,如果因为订单数据库异常导致生成订单失败,业务程序又需要将商品库存加上;而如果因为业务程序自己异常导致生成订单失败,则商品库存就无法恢复了,需要人工通过日志等方式来手工修复库存异常。成本问题。业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台。
分表概念
如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
单表数据拆分有两种方式:垂直分表和水平分表。从上往下切就是垂直切分,从左往右切就是水平切分。

垂直分表的复杂性:
- 垂直分表引入的复杂性主要体现在表操作的数量要增加。
水平分表的复杂性:
- 路由问题 —— 数据放到哪个表中。
- 范围路由。每个范围放在不同的表中。
- 优点:数据量增大时,可以平滑的扩展表。
- 缺点:范围的选择是复杂问题,分段小则表多,分段大则性能差;并且可能存在分布不均匀问题,0~1KW 中可能只有 10W 有效数据。
- Hash 路由。
- 优点:分布一般相对均匀。
- 缺点:最初需要确定 Hash 表数目,选择难题。并且数据增大后的扩展麻烦。
- 配置路由。用独立的表来记录路由,user_id & table_id。
- 优点:实现简单;扩充时,只需要修改路由表。
- 缺点:查询时需要多一次查表;路由表可能行数也会爆炸,成为瓶颈。
- 范围路由。每个范围放在不同的表中。
- JOIN 操作
- Count 操作
- ORDER BY 操作
常见分片算法
分片算法主要解决了数据被水平分片之后,数据究竟该存放在哪个表的问题。
- 哈希分片:求指定分片键的哈希,然后根据哈希值确定数据应被放置在哪个表中。哈希分片比较适合随机读写的场景,不太适合经常需要范围查询的场景。哈希分片可以使每个表的数据分布相对均匀,但对动态伸缩(例如新增一个表或者库)不友好。
- 范围分片:按照特定的范围区间(比如时间区间、ID 区间)来分配数据,比如将
id为1~299999的记录分到第一个表,300000~599999的分到第二个表。范围分片适合需要经常进行范围查找且数据分布均匀的场景,不太适合随机读写的场景(数据未被分散,容易出现热点数据的问题)。 - 映射表分片:使用一个单独的表(称为映射表)来存储分片键和分片位置的对应关系。映射表分片策略可以支持任何类型的分片算法,如哈希分片、范围分片等。映射表分片策略是可以灵活地调整分片规则,不需要修改应用程序代码或重新分布数据。不过,这种方式需要维护额外的表,还增加了查询的开销和复杂度。
- 一致性哈希分片:将哈希空间组织成一个环形结构,将分片键和节点(数据库或表)都映射到这个环上,然后根据顺时针的规则确定数据或请求应该分配到哪个节点上,解决了传统哈希对动态伸缩不友好的问题。
- 地理位置分片:很多 NewSQL 数据库都支持地理位置分片算法,也就是根据地理位置(如城市、地域)来分配数据。
- 融合算法分片:灵活组合多种分片算法,比如将哈希分片和范围分片组合。
- ……
分片键如何选择
分片键(Sharding Key)是数据分片的关键字段。分片键的选择非常重要,它关系着数据的分布和查询效率。一般来说,分片键应该具备以下特点:
- 具有共性,即能够覆盖绝大多数的查询场景,尽量减少单次查询所涉及的分片数量,降低数据库压力;
- 具有离散性,即能够将数据均匀地分散到各个分片上,避免数据倾斜和热点问题;
- 具有稳定性,即分片键的值不会发生变化,避免数据迁移和一致性问题;
- 具有扩展性,即能够支持分片的动态增加和减少,避免数据重新分片的开销。
实际项目中,分片键很难满足上面提到的所有特点,需要权衡一下。并且,分片键可以是表中多个字段的组合,例如取用户 ID 后四位作为订单 ID 后缀。
推荐的方案
Apache ShardingSphere 是一款分布式的数据库生态系统,可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。
ShardingSphere 项目(包括 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar)是当当捐入 Apache 的,目前主要由京东数科的一些巨佬维护。
ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理、影子库、数据加密和脱敏等功能。
ShardingSphere 提供的功能如下:
ShardingSphere 的优势如下(摘自 ShardingSphere 官方文档:https://shardingsphere.apache.org/document/current/cn/overview/):
- 极致性能:驱动程序端历经长年打磨,效率接近原生 JDBC,性能极致。
- 生态兼容:代理端支持任何通过 MySQL/PostgreSQL 协议的应用访问,驱动程序端可对接任意实现 JDBC 规范的数据库。
- 业务零侵入:面对数据库替换场景,ShardingSphere 可满足业务无需改造,实现平滑业务迁移。
- 运维低成本:在保留原技术栈不变前提下,对 DBA 学习、管理成本低,交互友好。
- 安全稳定:基于成熟数据库底座之上提供增量能力,兼顾安全性及稳定性。
- 弹性扩展:具备计算、存储平滑在线扩展能力,可满足业务多变的需求。
- 开放生态:通过多层次(内核、功能、生态)插件化能力,为用户提供可定制满足自身特殊需求的独有系统。
另外,ShardingSphere 的生态体系完善,社区活跃,文档完善,更新和发布比较频繁。
不过,还是要多提一句:现在很多公司都是用的类似于 TiDB 这种分布式关系型数据库,不需要我们手动进行分库分表(数据库层面已经帮我们做了),也不需要解决手动分库分表引入的各种问题,直接一步到位,内置很多实用的功能(如无感扩容和缩容、冷热存储分离)!如果公司条件允许的话,个人也是比较推荐这种方式!
数据迁移方式
- 常用方案 —— 停机迁移。凌晨脚本将数据迁移到新系统。
- 不停机迁移 —— 双写方案。如何不停机的更换数据库?、数据迁移应该怎么做?
总结
- 读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。这样的话,就能够小幅提升写性能,大幅提升读性能。
- 读写分离基于主从复制,MySQL 主从复制是依赖于 binlog 。
- 分库 就是将数据库中的数据分散到不同的数据库上。分表 就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。
- 引入分库分表之后,需要系统解决事务、分布式 id、无法 join 操作问题。
- 现在很多公司都是用的类似于 TiDB 这种分布式关系型数据库,不需要我们手动进行分库分表(数据库层面已经帮我们做了),也不需要解决手动分库分表引入的各种问题,直接一步到位,内置很多实用的功能(如无感扩容和缩容、冷热存储分离)!如果公司条件允许的话,个人也是比较推荐这种方式!
- 如果必须要手动分库分表的话,ShardingSphere 是首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理等功能。另外,ShardingSphere 的生态体系完善,社区活跃,文档完善,更新和发布比较频繁。