读写分离是数据库架构中常见的扩展手段。基本思路是把写操作发往主库,读操作分散到从库,通过增加从库数量来提升读能力。这个方案听起来简单,实际配置起来有不少门道。复制延迟怎么处理、读请求路由怎么做、事务中的一致性怎么保证、故障切换怎么搞——每一个问题都可能成为生产事故的导火索。这篇文章记录了在天翼云环境下配置TeleDB读写分离的实践过程,包括架构设计、配置方法和踩过的坑。
架构设计
读写分离的架构并不复杂:一个主库负责写入,两个从库负责读取。主从之间通过异步复制同步数据。应用通过代理层访问数据库,代理层根据SQL类型(读或写)将请求路由到主库或从库。
设计阶段讨论了一个关键问题:用什么做读写分离的代理层?有两种选择。一是使用TeleDB自带的读写分离功能,在数据库内部实现请求路由。二是使用外部的数据库代理中间件。两种方案各有优劣。
TeleDB自带的读写分离功能配置简单,不需要额外部署组件,与数据库的兼容性最好。但灵活性有限,路由策略相对固定。外部代理中间件功能丰富,支持复杂的路由规则和负载均衡策略,但增加了一层组件,运维复杂度上升。
考虑到团队规模和运维能力,最终选择了TeleDB自带的读写分离功能。理由是:减少架构层次就是减少故障点,对于当前规模的业务来说,自带功能够用就行,没必要引入额外复杂度。
配置过程
配置过程大致分为几步。
第一步是搭建主从复制。在天翼云上创建了三个TeleDB实例,一个作为主库,两个作为从库。在管理控制台中配置复制关系,从库自动从主库同步数据。配置完成后检查复制状态,确认复制通道正常,数据开始同步。
第二步是配置读写分离规则。TeleDB的读写分离通过设置读权重来实现。在配置中指定主库处理写请求和强一致读请求,从库处理普通读请求。两个从库各配置50%的读权重,实现负载均衡。同时设置一个参数:写操作后的一段时间内(默认为3秒),同一会话的读请求也发往主库,避免读到旧数据。这个机制叫"读一致性窗口",是解决读写分离一致性问题的重要手段。
第三步是应用适配。应用的数据库连接配置从直连主库改为通过TeleDB的读写分离入口连接。大部分应用代码不需要修改,因为路由是在数据库层面做的,对应用透明。但有一类情况需要特别注意:应用中如果有先写后读的逻辑(比如插入一条记录后立即查询),需要确保读请求走主库,否则可能读不到刚写入的数据。
踩坑记录
坑一:复制延迟导致的读取不一致。异步复制必然存在延迟,正常情况下延迟在毫秒到秒级。但在大事务或批量写入时,延迟可能达到分钟级。有一次业务侧做了大批量数据更新,复制延迟飙升到两分钟。在此期间从库读到的都是旧数据,导致业务逻辑出错。解决方案有两个:一是对一致性要求高的读请求,显式指定走主库;二是在应用层增加重试机制,当检测到数据可能不一致时自动重试。
坑二:长事务阻塞复制。从库在应用复制日志时,如果遇到一个长事务(执行时间很长的写事务),必须等这个事务在从库上也执行完才能继续应用后续日志。有一次一个报表查询在从库上跑了十几分钟,期间复制延迟持续增长。解决方案是限制从库上查询的执行时间,通过设置超时参数,超过一定时间的查询自动终止。
坑三:连接池配置不当。读写分离后,应用实际上连接的是两个库(主库和从库),但连接池是统一的。如果连接池大小不够,高峰期可能出现连接等待。初始配置的连接池大小是按单库计算的,没有考虑读写分离的因素。调整为按总连接数计算后,问题解决。
坑四:从库故障导致读流量集中。两个从库中有一个挂了,所有读流量都压到了另一个从库上,导致该从库负载飙升。解决方案是配置健康检查,当从库不可用时自动从读权重中剔除,同时设置告警及时通知运维人员。另外,在极端情况下可以把部分读流量回切到主库。
坑五:统计信息不一致。主库和从库的统计信息是独立收集的,可能存在差异。如果优化器在主库和从库上选择了不同的执行计划,同样的SQL在主库和从库上性能差异很大。解决方案是确保主从库的统计信息收集策略一致,或者在低峰期手动同步统计信息。
最佳实践
经过这些踩坑,总结了几条读写分离的最佳实践。第一,明确哪些读可以走从库、哪些必须走主库。对一致性要求高的读操作(如写入后立即读取、事务内读取)走主库,对一致性要求不高的读操作(如报表查询、历史数据查询)走从库。第二,持续监控复制延迟,设置合理的告警阈值。延迟超过阈值时自动把读流量切回主库,避免读到旧数据。第三,从库的配置不要低于主库太多。如果从库性能差,读请求的响应时间会变长,甚至成为瓶颈。第四,定期做故障演练,验证从库故障和主库故障时的切换流程是否正常。
读写分离是一个成熟且实用的数据库扩展方案,但"成熟"不意味着"简单"。每个配置项和边界条件都需要认真对待,否则看似简单的架构也可能引发复杂的问题。