后台服务代码架构:项目实际应用中 redis 缓存与数据库一致性问题解决
一、需求起因
在项目中,出现了 redis 缓存与数据库一致性问题。假设先写数据库,再淘汰缓存:如果第一步写数据库操作成功,第二步淘汰缓存失败,则会出现 DB 中是新数据,Cache 中是旧数据,数据不一致。如果先淘汰缓存,再写数据库:如果第一步淘汰缓存成功,第二步写数据库失败,则会出现 cache 中无数据,db 中是旧数据。因此,结论是:先淘汰缓存,再写数据库。
二、数据不一致原因
数据不一致的原因是:先操作缓存,在写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致。
写流程:(1) 先淘汰cache(2) 再写db
读流程:(1) 先读cache,如果数据命中hit 则返回(2) 如果数据未命中miss 则读 db(3) 将 db 中读取出来的数据入缓存
在分布式环境下,数据的读写都是并发的,上游有多个应用,通过一个服务的多个部署(为了保证可用性,一定是部署多份的),对同一个数据进行读写,在数据库层面并发的读写并不能保证完成顺序,也就是说后发出的读请求很可能先完成(读出脏数据)。
三、问题解决思路
能否做到先发出的请求一定先执行完成呢?常见的思路是“串行化”。但是,在服务层面,任务队列不能保证任务不并发执行,单服务多数据库连接不能保证任务不并发执行,多服务单数据库连接不能保证任务不并发执行,单服务单数据库连接可能保证任务不并发执行,但吞吐量级低,且不能保证服务的可用性。
退一步想,其实不需要让全局的请求串行化,而只需要“让同一个数据的访问能串行化”。在一个服务内,如何做到“让同一个数据的访问串行化”,只需要“让同一个数据的访问通过 同一条 DB 连接执行”就行。
如何做到“让同一个数据的访问通过同一条 DB 连接执行”,只需要“在 DB 连接池层面稍微修改,按数据取连接即可”。获取 DB 连接的 CPool.GetDBConnection()【返回任何一个可用 DB 连接】改为 CPool.GetDBConnection(long id)【返回 id 取模相对应的 DB 连接】。
因此,解决方案是:在 DB 连接池层面,根据数据 id 取模,返回相对应的 DB 连接,以保证同一个数据的访问串行化。