1. 读写分离原理
概念: 将数据库的读操作和写操作分散到集群中的不同节点上,从而提升性能 。
架构: 包含一个主数据库服务器和一个或多个从数据库服务器 。
主服务器负责读写操作 。
从服务器只负责读操作 。
主服务器通过复制机制将数据同步到从服务器,每台数据库服务器都存储了所有的业务数据 。
“主从集群”与“主备集群”的区别: “从机”意为“仆从”,需要提供读数据功能;而“备机”通常被认为仅提供备份功能,不提供访问功能 。
2. 读写分离引入的设计复杂度
复制延迟:
问题: 主从复制可能存在延迟(例如MySQL可能达到1秒,数据量大时可能达到1分钟) 。
影响: 如果业务服务器在数据写入主服务器后立即(1秒内)进行读取,读操作访问从机时可能读不到最新数据,导致业务问题(例如用户刚注册后立刻登录却被提示“你还没有注册”) 。
解决方案:
写操作后的读操作指定发给数据库主服务器: 例如,注册完成后的登录操作也访问主服务器。这种方式与业务强绑定,对业务侵入大,容易因开发人员不熟悉而引入bug 。
读从机失败后再读一次主机(二次读取): 这种方式与业务无绑定,实现代价小,但如果二次读取量大,会增加主机的读操作压力,甚至可能导致主机崩溃(例如黑客暴力破解账号) 。
关键业务读写操作全部指向主机,非关键业务采用读写分离: 例如,用户管理系统中,注册+登录业务全部访问主机;用户的介绍、爱好、等级等业务可以采用读写分离,即使查询时看到的是旧数据,业务影响也较小 。
分配机制: 区分读写操作并将其路由到不同数据库服务器的方式 。
程序代码封装(数据访问层/中间层封装):
方法: 在代码中抽象一个数据访问层,实现读写分离和数据库服务器连接管理 。
特点:
实现简单,可根据业务进行定制化功能 。
每种编程语言都需要单独实现,无法通用,如果业务包含多种编程语言的子系统,重复开发工作量大 。
在主从发生切换的故障情况下,可能需要所有系统修改配置并重启 。
示例:淘宝的TDDL (Taobao Distributed Data Layer) 是一个开源的通用数据访问层,以jar包形式提供,基于集中式配置的JDBC datasource 实现,具有主备、读写分离、动态数据库配置等功能。
中间件封装:
方法:独立一套系统(数据库中间件),实现读写操作分离和数据库服务器连接管理。中间件对业务服务器提供SQL兼容协议,业务服务器无需自行进行读写分离,业务服务器视中间件为数据库服务器 。
特点:
能够支持多种编程语言,因为数据库中间件对业务服务器提供标准SQL接口 。
实现复杂,需要支持完整的SQL语法和数据库服务器协议,细节多,容易出现bug,需要较长时间才能稳定 。
中间件自身不执行真正的读写操作,但所有数据库请求都经过中间件,对中间件的性能要求很高 。
数据库主从切换对业务服务器无感知,数据库中间件可以探测数据库服务器的主从状态(例如通过向测试表写入数据判断主从) 。
建议: 一般情况下建议采用程序语言封装方式或使用成熟的开源数据库中间件,大型公司可以投入人力实现数据库中间件,因为其一旦做好,接入业务系统越多,节省的开发投入越大 。
示例: MySQL官方推荐的MySQL Router,主要功能有读写分离、故障自动切换、负载均衡、连接池等 。奇虎360公司也开源了基于MySQL Proxy实现的数据库中间件Atlas 。
3. 读写分离的适用场景与业务规模
适用场景: 通常适用于单机并发量无法支撑且读请求远多于写的场景 。例如微博、博客、中小型朋友圈等读多写少的业务类型 。
最大规模: 最终瓶颈在于承担写操作的主机的IOPS等能力 。读的规模可以横向扩展,但也不是无限的,因为复制工具的复制频率受业务决定 。
何时引入: 并非所有系统都需要读写分离。首先应确认系统业务量是否出现数据库性能问题,如果是,应先通过优化SQL语句、调整不合理业务逻辑、引入缓存等方式进行优化,确定没有优化空间后才考虑读写分离或集群 。
不适用情况: 如果并发写入特别高,单机写入无法支撑,则不适合读写分离模式 。交易型业务缓存应用不多,缓存一般用在查询类业务上 。

