分布式的数据库保证数据一致性,如果有分布式事务是最好,或者XA这种分布式事务也挺好。
但是这里说的可以粗暴的理解成redis作为缓存使用的时候如何保证redis和mysql中数据的一致性。
一、读操作
读操作都没什么大的分歧,一般都是先读redis,redis没有就读mysql,然后把读出来的数据写到redis中。
二、先删除redis,再更新mysql
即使写mysql失败了,也没关系,大不了就是redis被删了,下次查询要读mysql而已。
这个操作有个问题需要解决。
问题场景:
比如说,有一条数据,在redis和mysql中存的都是id和age的对应。
现id=1 age=3。
线程A写操作,线程B读操作,并发执行。
线程A要把id=1的数据它的age=3改成age=4,它先删除了redis中id=1的数据
此时线程B并发执行,线程B查redis发现id=1的数据没有,线程B查询mysql(select普通查询查的是快照并不需要加锁, 事务隔离机制是RR)的到id=1的数据age=3,然后线程B把id=1的数据age=3写到redis中。
线程A给mysql中id=1的这条记录加了排他锁。此时线程A把mysql中id=1的数据改成age=4。
这就造成了数据不一致。一直要到该缓存到了失效的时间才有可能让redis和mysql的数据一直。
解决方案:(读/写请求串行化)
同一个数据唯一id每次都打在同一个service机器上,在service机器中同一个数据唯一id每次都用同一个数据库连接。
这样可以保证对同一个数据唯一id的数据每次在mysql中的读写都是串行的。
比如
1.在用哪个service的时候根据数据id取模来决定
获取Service连接的CPool.GetServiceConnection()【返回任何一个可用Service连接】
改为CPool.GetServiceConnection(longid)【返回id取模相关联的Service连接】
2.在service中获取数据库连接的时候也根据数据id取模来决定
获取DB连接的CPool.GetDBConnection()【返回任何一个可用DB连接】
改为CPool.GetDBConnection(longid)【返回id取模相关联的DB连接】
这个问题详细解释看这篇博客:https://www.cnblogs.com/duanxz/p/3783369.html
三、Cache Aside 先更新数据库,再删除redis(推荐)
这种操作需要把更新数据库mysql和删除redis放在同一个mysql的事务当中。
这样的好处是保证数据一致性,如果更新mysql成功,删除redis失败报错,mysql这个更新事务会回滚。
这里同样可能出现一个问题
问题场景:
比如说,有一条数据,在mysql中存的id和age对应。
现id=1 age=3。
线程A写操作,线程B读操作,并发执行。
线程A给mysql中id=1的数据加上了排他锁,更新age=4,删除redis,此时事务并没有结束。
此时线程B读redis发现没有,线程B去读mysql(隔离级别RR),这里是select快照读,不需要加锁,读到id=1的数据age=3,写到redis中。
这也造成了redis和mysql的数据不一致
解决方案:
读加共享锁,读和写必须互斥
比如不要用select快照读,用select lock in share mode读的时候加上共享锁。
四、Read/Write through(缓存代理)
Read/Write Through套路是把更新数据库(mysql)的操作由缓存自己代理了。
不用业务系统来维护数据库和缓存的数据一致性。
but~如果缓存是选主用redis,redis并不支持缓存代理,换句话说redis并不会帮业务系统维护redis和mysql数据的一致性
五、Write behind caching(写缓存异步刷库)
在更新数据的时候,只更新缓存,不更新数据库,另外有个线程或者进程异步的把缓存批量更新到数据库。
比如业务系统中另外写个线程定时把redis的数据刷新到mysql中
问题场景:
redis死了,数据丢了
解决方案:
redis做持久化。使用aof的always才能保证恢复数据的时候不会有数据丢失。