为什么使用Redis做缓存
MySQL缺点
- 单机连接数目有限
- 对数据进行写速度慢
Redis优点
- 内存操作数据速度快
- IO复用,速度快
- 单线程模型,避免线程切换带来的开销,速度快
一致性问题
读数据的时候首先去Redis里读,没有读到再去MySQL里读,读回来之后更新到Redis里作为下一次的缓存。写数据的时候回产生数据不一致的问题,无论是先写到Redis里再写MySQL还是先写MySQL再写Redis,这两步写操作不能保证原子性,所以会出现Redis和MySQL里的数据不一致。无论采取何种方式都不能保证强一致性,如果对Redis里的数据设置了过期时间能够保证最终一致性,对架构做的优化只能降低不一致性发生的概率,不能从根本上避免不一致性。
根据写入的顺序不同分为四种。
先删缓存,再更新数据库
这种操作的问题?
一般考虑某种策略的问题都是考虑该种策略会不会导致被删除的脏数据由于时序混乱再次被读线程从MySQL中读出来。A线程写数据,B线程读数据,A线程删除了缓存,B线程读数据发现缓存没有命中从数据库中读数据,B线程把读出的旧数据写到Redis里,A线程把新数据写回去。和下面的先写数据库再删缓存相比,这种方式显著的缺点
- 先删缓存,所以当两个线程并发的时候很大几率会出现缓存不命中,一旦缓存不命中,在写线程修改MySQL完成之前读进来的永远是脏数据
- 在缓存到期之前Redis里一直是脏数据
解决策略
延迟双删,双删就是在更新完数据库后再删一次。不过延迟双删中更新数据库之前的删除还有什么意义?延迟的目的是为了删除在写MySQL期间读线程可能把脏数据再次读到Redis里,延迟的时间参照一次从MySQL读数据并写入Redis的时间
Cache Aside Pattern--先写数据库,再删缓存
为什么更新数据库后不更新而是删除缓存?
- 更新缓存的操作不是必须的。可能缓存里的数据并没有被读到,就会被下一次更新MySQL操作带来Redis更新操作覆盖,那么本次更新操作就是无意义的。
- 更新缓存代价大。如果缓存里的数据不是把MySQL里的数据直接存下来,而是需要经过某种复杂的运算,那么这种不必要的更新会带来更大的浪费。
这种操作的问题?
- 并发问题。A线程读数据但没有命中,B线程写数据。A线程读到了MySQL的旧数据,B线程写了新的数据进MySQL,B线程删除了Redis中旧数据的缓存,A用旧数据写到了Redis缓存里。此时Redis里就是旧的脏数据。但这种case出现的概率较低,需要Redis缓存失效同时出现线程写入操作,而且理论上A线程从MySQL中读数据应该更快的返回。
解决方案?
每个写MySQL线程写完后延时一定时间在去删Redis中的缓存。
Write Behind Caching Pattern--只更缓存,不更MySQL,MySQL由缓存异步的更新