2.3.1 数据冗余
可在发MQ时,不止发送朋友圈ID,而是发给Consumer需要的所有朋友圈信息,避免从DB重新查询数据。
推荐该方案,因为足够简单,不过可能造成单条消息较大,从而增加消息发送的带宽和时间。
2.3.2 使用Cache
在同步写DB的同时,把朋友圈数据写Cache,这样Consumer在获取朋友圈信息时,优先查询Cache,这也能保证数据一致性。
该方案适合新增数据的场景。若是在更新数据场景下,先更新Cache可能导致数据不一致。比如两个线程同时更新数据:
- 线程A把Cache数据更新为1
- 另一个线程B把Cache数据更新为2
- 然后线程B又更新DB数据为2
- 线程A再更新DB数据为1
最终DB值(1)和Cache值(2)不一致!
2.3.3 查询主库
可以在Consumer中不查询从库,而改为查询主库。
使用要慎重,要明确查询的量级不会很大,是在主库的可承受范围之内,否则会对主库造成较大压力。
若非万不得已,不要使用该方案。因为要提供一个查询主库的接口,很难保证其他人不滥用该方法。
主从同步延迟也是排查问题时容易忽略。
有时会遇到从DB获取不到信息的诡异问题,会纠结代码中是否有一些逻辑把之前写入内容删除了,但发现过段时间再去查询时又能读到数据,这基本就是主从延迟问题。
所以,一般把从库落后的时间作为一个重点DB指标,做监控和报警,正常时间在ms级,达到s级就要告警。
主从的延迟时间预警,那如何通过哪个数据库中的哪个指标来判别? 在从从库中,通过监控show slave
status\G命令输出的Seconds_Behind_Master参数的值判断,是否有发生主从延时。
这个参数值是通过比较sql_thread执行的event的timestamp和io_thread复制好的
event的timestamp(简写为ts)进行比较,而得到的这么一个差值。
但如果复制同步主库bin_log日志的io_thread线程负载过高,则Seconds_Behind_Master一直为0,即无法预警,通过Seconds_Behind_Master这个值来判断延迟是不够准确。其实还可以通过比对master和slave的binlog位置。
3 如何访问DB
使用主从复制将数据复制到多个节点,也实现了DB的读写分离,这时,对DB的使用也发生了变化:
- 以前只需使用一个DB地址
- 现在需使用一个主库地址,多个从库地址,且需区分写入操作和查询操作,再结合“分库分表”,复杂度大大提升。
为降低实现的复杂度,业界涌现了很多DB中间件解决DB的访问问题,大致分为:
3.1 应用程序内部
如TDDL( Taobao Distributed Data Layer),以代码形式内嵌运行在应用程序内部。可看成是一种数据源代理,它的配置管理多个数据源,每个数据源对应一个DB,可能是主库或从库。
当有一个DB请求时,中间件将SQL语句发给某个指定数据源,然后返回处理结果。
优点
简单易用,部署成本低,因为植入应用程序内部,与程序一同运行,适合运维较弱的小团队。
缺点
缺乏多语言支持,都是Java语言开发的,无法支持其他的语言。版本升级也依赖使用方的更新。
3.2 独立部署的代理层方案
如Mycat、Atlas、DBProxy。
这类中间件部署在独立服务器,业务代码如同在使用单一DB,实际上它内部管理着很多的数据源,当有DB请求时,它会对SQL语句做必要的改写,然后发往指定数据源。
优点
- 一般使用标准MySQL通信协议,所以可支持多种语言
- 独立部署,所以方便维护升级,适合有运维能力的大中型团队
缺点
所有的SQL语句都需要跨两次网络:从应用到代理层和从代理层到数据源,所以在性能上会有一些损耗。
4 总结
可以把主从复制引申为存储节点之间互相复制存储数据的技术,可以实现数据冗余,以达到备份和提升横向扩展能力。
使用主从复制时,需考虑:
- 主从的一致性和写入性能的权衡
若保证所有从节点都写入成功,则写性能一定受影响;若只写主节点就返回成功,则从节点就可能出现数据同步失败,导致主从不一致。互联网项目,一般优先考虑性能而非数据的强一致性 - 主从的延迟
会导致很多诡异的读取不到数据的问题
很多实际案例:
- Redis通过主从复制实现读写分离
- Elasticsearch中存储的索引分片也可被复制到多个节点
- 写入到HDFS中,文件也会被复制到多个DataNode中
不同组件对于复制的一致性、延迟要求不同,采用的方案也不同,但设计思想是相通的。
FAQ
若大量订单,通过userId hash到不同库,对前台用户订单查询有利,但后台系统页面需查看全部订单且排序,SQL执行就很慢。这该怎么办呢?
由于后台系统不能直接查询分库分表的数据,可考虑将数据同步至一个单独的后台库或同步至ES。