天外客AI翻译机中数据库读写分离中间件选型与配置
在“天外客AI翻译机”上线初期,我们遇到了一个典型的高并发场景:每天有数十万用户通过设备进行实时语音翻译,后台需要处理海量的用户行为日志、个性化偏好同步和历史记录查询。很快,数据库主库的CPU使用率就飙到了95%以上 🚨,部分写入请求甚至开始超时。
问题出在哪?很简单—— 所有的读写操作都压在了同一个MySQL实例上 。当用户刚完成一次翻译并立刻查看历史记录时,系统既要写又要读,主库瞬间成为瓶颈。
于是我们决定引入 数据库读写分离 ,把“读”和“写”拆开跑。但这不是简单加个从库就能解决的事儿。真正的挑战在于:如何让应用层无感地将SQL自动路由到正确的数据库?怎么应对主从延迟?扩容时能不能不停机?
答案是: 选对中间件,配好策略,才能真正落地读写分离 。
我们调研了市面上主流方案,最终选择了 Apache ShardingSphere-JDBC 来作为核心中间件。为什么没选 MyCat 或 DBProxy 这类独立部署的代理模式?先卖个关子,后面揭晓 😏。
ShardingSphere-JDBC 是 Apache 开源的一套分布式数据库解决方案中的轻量级组件,它不像传统中间件那样需要额外部署服务节点,而是以 JAR 包形式直接嵌入到 Spring Boot 应用中。你可以把它理解为一个“聪明的 JDBC 增强器”,在不改变原有代码结构的前提下,悄无声息地完成 SQL 解析、路由决策和结果归并。
它的最大优势是什么? 零网络跳转、低延迟、易集成 。对于像“天外客”这样后端规模适中但对响应时间敏感的边缘计算类产品来说,简直是量身定制。
来看一段关键配置:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource shardingDataSource() throws SQLException {
// 主库数据源
HikariDataSource primaryDs = new HikariDataSource();
primaryDs.setJdbcUrl("jdbc:mysql://primary-host:3306/tianwaiker?useSSL=false&serverTimezone=UTC");
primaryDs.setUsername("root");
primaryDs.setPassword("password");
// 从库数据源(可多个)
HikariDataSource replicaDs1 = new HikariDataSource();
replicaDs1.setJdbcUrl("jdbc:mysql://replica1-host:3306/tianwaiker?useSSL=false&serverTimezone=UTC");
replicaDs1.setUsername("reader");
replicaDs1.setPassword("readonly");
// 构建读写分离规则
MasterSlaveRuleConfiguration masterSlaveRule = new MasterSlaveRuleConfiguration(
"ds_master_slave",
"primaryDs",
Arrays.asList("replicaDs1")
);
Properties props = new Properties();
props.setProperty("sql.show", "true"); // 开启SQL日志输出,调试神器!
return ShardingDataSourceFactory.createDataSource(
Collections.singletonMap("primaryDs", primaryDs),
Collections.singletonMap("replicaDs1", replicaDs1),
Collections.singletonList(masterSlaveRule),
props
);
}
}
这段代码干了啥?
👉 它创建了一个支持读写分离的数据源,所有
INSERT/UPDATE/DELETE
自动走主库,而
SELECT
默认轮询分发到从库。整个过程对 MyBatis 完全透明,业务代码一行都不用改 ✅。
但现实总是比理想复杂一点。比如,有个用户刚保存了一条翻译记录,马上点击“查看历史”,却发现查不出来!🤯
这其实是典型的
主从延迟问题
:写操作已经在主库完成,但从库还没来得及同步,读请求却被路由到了从库,结果就是“看不见自己刚写的数据”。
怎么办?两种解法:
- 全局降级 :一旦检测到主从延迟超过3秒,所有读请求临时切回主库;
- 精准控制 :只针对特定会话或事务强制走主库。
我们采用的是第二种,更灵活也更高效。借助 ShardingSphere 提供的
HintManager
,可以在关键路径上手动干预路由逻辑:
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly(); // 强制本次会话走主库
List<User> users = userMapper.selectAll(); // 即使是SELECT也会发往主库
比如在“保存+立即查询”的场景中,我们在事务提交后短暂开启“读主模式”,确保用户体验一致。等几秒后自动恢复默认策略,避免长期占用主库资源。
支撑这一切的背后,是 MySQL 原生的 主从复制机制 。它是整个读写分离架构的地基 💡。
原理其实不难:主库开启 binlog 记录所有变更,从库通过 I/O Thread 拉取这些日志写入 relay log,再由 SQL Thread 逐条重放,实现数据同步。整个过程是异步的,所以存在毫秒到秒级的延迟窗口。
为了让复制更稳定,我们调整了几项关键参数:
| 参数名 | 推荐值 | 说明 |
|---|---|---|
log-bin
| ON | 必须开启,否则无法作为主库 |
server-id
| 唯一整数 | 集群内每个实例必须不同 |
binlog-format
| ROW | 行格式更安全,避免误删数据 |
read_only
| ON(从库) | 防止人为误操作写入从库 |
sync_binlog
| 1 | 每次事务提交刷盘,提升持久性 |
innodb_flush_log_at_trx_commit
| 1 | 确保事务日志落盘 |
特别是
binlog-format=ROW
,强烈推荐!语句格式(STATEMENT)虽然节省空间,但在某些函数调用下可能导致主从数据不一致,踩过坑的人都懂 😓。
当然,光搭起来还不够,还得能“看得见”。我们通过 Prometheus + Grafana 对以下指标进行了持续监控:
-
Seconds_Behind_Master:实时观测主从延迟 - 中间件连接池状态:主库连接数 vs 从库负载
- SQL 执行耗时分布(P99 < 100ms)
- 故障切换成功率
一旦发现从库延迟突增,系统会自动触发告警,并结合业务流量判断是否启用“读主降级”。如果是短暂抖动,通常几分钟内就能追平;如果持续异常,则通知运维介入排查。
实际运行下来,这套组合拳带来了显著收益:
- 主库负载下降 60% ,高峰期 CPU 再也没冲过 70%
- 数据库平均响应时间降低 40%
- 新增从库只需修改配置文件,热加载生效,无需重启服务
- 支持灰度发布:可以通过标签路由让部分用户走新数据库逻辑
更重要的是,它让我们具备了 横向扩展的能力 。未来用户量翻倍?没问题,加两个从库就行,中间件自动接管流量分配。
也有需要注意的地方:
⚠️ DDL 操作要小心!比如
ALTER TABLE
可能阻塞复制线程,建议在低峰期执行。
⚠️ 禁止大分页查询(
LIMIT 1000000, 10
),这种操作在从库上极易引发性能雪崩。
⚠️ 多团队共用数据库时,务必通过中间件做逻辑隔离,避免相互干扰。
说到这儿,再回头看看当初那个选择题: ShardingSphere-JDBC vs MyCat/DBProxy ,到底差在哪?
| 维度 | ShardingSphere-JDBC | 代理类方案(如MyCat) |
|---|---|---|
| 部署复杂度 | ⭐⭐⭐⭐⭐(JAR包集成) | ⭐⭐(需独立部署Proxy) |
| 性能损耗 | ~5%-8% | ~10%-15%(多一次网络跳转) |
| 开发侵入性 | 中等(需配置规则) | 极低(完全透明) |
| 故障隔离 | 依赖应用重启 | Proxy可集中熔断降级 |
| 分片能力 | 强 | 一般 |
我们的结论很明确:对于中小型 AI 硬件产品,“轻量 + 高性能”远比“完全透明”更重要。ShardingSphere-JDBC 在维护成本和性能之间找到了完美平衡点。
如今,“天外客AI翻译机”已稳定服务超过一年,日均处理请求超 50 万次。这套基于 ShardingSphere-JDBC + MySQL 主从复制 的读写分离架构,不仅扛住了流量压力,还为后续功能迭代留足了空间。
技术选型从来不是追求“最先进”,而是找到“最合适”的那一款。有时候,一个简单的 JAR 包,就能解决让人头疼已久的性能瓶颈 🎯。
毕竟,在真实世界里, 最好的架构,往往是那些让你忘了它存在的架构 。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
998

被折叠的 条评论
为什么被折叠?



