背景
上一篇我写了为了处理高并发带来的数据库压力问题,引出了数据库读写分离技术。其思想总结为:一主多从、读写分离,冗余多个读库。然而存在一个明显的问题就是:从数据库同步数据的同时可能给业务方返回的是旧的数据,即所谓的脏读现象。如图所示:
【1】系统先对Master-DB进行了一个写操作,写主库;
【2】很短的时间内并发进行了一个读操作,读从库,此时主从同步没有完成,故读取到了一个旧数据;
【3】主从同步完成。
为了解决主从同步一致性导致读取旧数据的问题,本文主要提出四种解决方案,仅供参考。
解决方案
方案一、读写都在主库
显然,这是一种最简单的方法,读写全落在主库上,必然不会带来不一致问题。如图所示:方案二、semi-sync(半同步复制)
之所以会读取到旧数据,关键在于主从同步需要一个时间段,而读取请求可能刚好就发生在同步阶段。为了读取到最新的数据,需要等主从同步完成之后,主库上的写请求再返回。示意图如图所示:【1】系统先对DB-master进行了一个写操作,写主库;
【2】等主从同步完成,写主库的请求才返回;
【3】读从库,读到最新的数据(如果读请求先完成,写请求后完成,读取到的是“当时”最新的数据)。
显然带来的后果就是主库的写请求时延会增加,吞吐量会降低。
方案三、数据库中间件
借助中间件的路由作用,对服务层的读写请求进行分发,从而避免出现不一致问题。示意图如图所示:【1】所有的读写都走数据库中间件,通常情况下,写请求路由到主库,读请求路由到从库;
【2】记录所有路由到主库的key,在经验主从同步时间窗口内(假设是500ms),如果有读请求访问中间件,此时有可能从库还是旧数据,就把这个key上的读请求路由到主库;
【3】经验主从同步时间过完后,对应key的读请求继续路由到从库
中间件带来的好处就是能保证数据的绝对一致性,但同时也带来成本上升的问题。
方案四、利用缓存
原理同方案三类似,当写请求发生时,【1】将某个库上的某个key要发生写操作,记录在cache里,并设置“经验主从同步时间”的cache超时时间;
【2】修改数据库
从图中可以看出:
1)先到cache里查看,对应库的对应key有没有相关数据;
2)如果cache hit,有相关数据,说明这个key上刚发生过写操作,此时需要将请求路由到主库读最新的数据;
3)如果cache miss,说明这个key上近期没有发生过写操作,此时将请求路由到从库,继续读写分离。
显然,利用缓存,减少了中间件带来的成本问题,但多了一个Cache组件,并且读写数据库多了一步Cache操作,操作相对其他稍较繁琐。