读写分离作为常见的数据库优化手段,在日常的开发中是疆场用到的,本文主要关注适用场景和一致性问题。
一、适用场景
读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上,这样做能小幅度提升写性能,大幅度提升读性能。
一般情况下,选择一主多从,也就是一台主数据库负责写,其他的从数据库负责读,主库与从库之间会进行数据同步,以保证从库中数据的准确性。这样的架构·实现起来相对简单,并且也符合系统的写少读多的特点。
普通的java生态下,可以在应用层使用Shardingsphere,也可以使用数据库中间件Mycat在数据库层面去做读写分离的工作。
二、一致性问题
读写分离对于提升数据库的并发非常有效, 但是, 同时也会引来一个问题: 主库和从库的数据同步往往是存在延迟, 比如写完主库之后, 主库的数据同步到从库是需要时间的, 这个时间差就导致了主库和从库的数据不一致性问题。 这也就是我们经常说的主从同步延迟。这里讲解三种方式:
2.1、半同步复制
在半同步复制模式下,主库(Master)在提交一个事务后,会等待至少一个从库(slave)接收并写入该事务的二进制日志(binlog)到其本地中继日志(relay log)中。只有当从库发送确认信息(ACK)给主库后,主库才正式提交事务并返回给客户端。这种机制确保了主从数据的一致性,但这是以牺牲主库的性能为代价的。在Mysql中半同步有两种模式,有主库先提交,也有主库后提交的,AFTER_SYNC和AFTER_COMMIT,这里看半同步如何配置,具体情况具体分析即可。
Mysql的Replication默认是一个异步复制的过程,从mysql 5.5开始,mysql以插件的形式支持半同步复制。半同步复制的优点是利用数据库原生功能,比较简单缺点是主库的写请求时延会增长,吞吐量会降低。
-
事务在主库写完二进制后需要从库返回一个已接受才返回给客户端。
-
确保事务提交后二进制日志至少传输到一个主库。
-
不保证从库应用完成这个事务的二进制日志。
2.2、数据库中间件
在这种模式下, 所有的读写都走数据库中间件, 通常情况下, 写请求路由到主库,读请求路由到从库。 记录所有路由到写库的 key, 在主从同步时间窗口内( 假设是 500ms) , 如果有读请求访问中间件, 此时有可能从库还是旧数据, 就把这个 **key **上的读请求路由到主库。 在主从同步时间过完后, 对应 **key **的读请求继续路由到从库。 相关的中间件有 Canal, Otter。 优点是能保证绝对一致, 缺点是数据库中间件的成本较高。
2.3、缓存记录(应用层)
写流程:如果key要发生写操作,记录在cache里,并设置“经验主从同步时间”的cache超时时间,例如500ms,然后修改主数据库,相当于你自己做一个中间件。
读流程:先到缓存里查看,对应key有没有相关数据,如果有相关数据,说明缓存命中,这个key刚发生过写操作,此时需要将请求路由到主库读最新的数据。如果缓存没有命中,说明这个key上近期没有发生过写操作,此时将请求路由到从库,继续读写分离。
这种方式的优点是数据库相对数据库中间件,成本较低,缺点是为了保证“一致性”,引入了一个cache组件,并且读写数据库时都多了缓存操作。