将不一致分为三种状况:mysql
在讨论这三种状况以前,先说明一下我使用缓存的策略,也是大多数人使用的策略,叫作 Cache Aside Pattern。简而言之,就是redis
读的逻辑你们都很容易理解,谈谈更新。若是不采起我提到的这种更新方法,你还能想到什么更新方法呢?大概会是:先删除缓存,而后再更新数据库。这么作引起的问题是,若是A,B两个线程同时要更新数据,而且A,B已经都作完了删除缓存这一步,接下来,A先更新了数据库,C线程读取数据,因为缓存没有,则查数据库,并把A更新的数据,写入了缓存,最后B更新数据库。那么缓存和数据库的值就不一致了。另外有人会问,若是采用你提到的方法,为何最后是把缓存的数据删掉,而不是把更新的数据写到缓存里。这么作引起的问题是,若是A,B两个线程同时作数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。按照我提到的这种更新缓存的策略,理论上也是有不一致的风险的,以前在其余的博客文章有看到过,只不过几率很小,咱们暂时能够不考虑,后面咱们有其余手段来补救。讨论完使用缓存的策略,咱们再来看这三种不一致的状况。sql
所以,最终的结论是,须要解决的不一致,产生的缘由是更新数据库成功,可是删除缓存失败。
解决方案大概有如下几种:数据库
第三种方案能够说是一个大杀器,任何不一致,均可以靠失效期解决,失效期越短,数据一致性越高。可是失效期越短,查数据库就会越频繁。所以失效期应该根据业务来定。缓存
并发不高的状况:网络
并发高的状况:并发
为了保证数据的一致性,不论何种方式处理缓存,都应该给缓存设置过时时间,这个是缓存必须的要素,不然数据的最终一致性很可贵到保证。下述讨论的讨论方案中,是在没有设置缓存过时时间 的状况下的极端讨论,仅仅为了理清思路,实际开发过程当中,都应该给缓存加上过时时间。异步
更新缓存:先下结论:对于数据变化,不该该同步更新缓存。由于:只有被查询的数据的数据创建缓存,才有意义。一个数据只会被更新,长期或者永远不会被查询,创建缓存就是浪费资源。总结:创建缓存的操做应该是在数据被读取的时候。分布式
先更新缓存,再更新DB:在更新缓存须要的时候,数据变动时若是:先更新缓存,再更新DB会有什么问题?ide
缓存的来源是DB,若是先更新缓存,在还未更新DB的这段时间内,若是有查询操做读取了这个缓存,读取的数据都是脏数据。
先更新DB,再更新缓存:若是先更新DB,再更新缓存又会遇到什么问题了?在以下场景中,问题就会出现:
第一步:2个线程A和B同时更新DB的一条数据,A线程先更新DB,B后更新DB。(此时B的数据为最新的)。
第二步:接下来A和B须要更新缓存,由于网络缘由,B发送的缓存更新指令先于A到达。(彻底有可能,由于A和B是异步的)
第三步:缓存中间件中,缓存先被B更新(最新的数据),才被A更新(老的数据)。由于B的数据才最最新的,可是缓存最后被A更新,此时的缓存的数据是脏数据。
一样是脏数据的问题。
1:缓存一致性问题:缓存系统与底层数据的一致性。这点在底层系统是“可读可写”时,写得尤其重要
2:有继承关系的缓存之间的一致性。为了尽可能提升缓存命中率,缓存也是分层:全局缓存,二级缓存。他们是存在继承关系的。全局缓存能够有二级缓存来组成。
3:多个缓存副本之间的一致性。为了保证系统的高可用性,缓存系统背后每每会接两套存储系统(如memcache,redis等)
缓存数据的淘汰策略:
1. 预估失效时间 2. 版本号(必须单调递增,时间戳是最好的选择)3. 提供手动清理缓存的接口。
穿透:频繁查询一个不存在的数据,因为缓存不命中,每次都要查询持久层。从而失去缓存的意义。
解决: 持久层查询不到就缓存空结果,查询时先判断缓存中是否exists(key) ,若是有直接返回空,没有则查询后返回,注意insert时需清除查询的key,不然即使DB中有值也查询不到(固然也能够设置空缓存的过时时间)
雪崩:缓存大量失效的时候,引起大量查询数据库。
解决:用锁/分布式锁或者队列串行访问;缓存失效时间均匀分布;热点key(某个key访问很是频繁,当key失效的时候有大量线程来构建缓存,致使负载增长,系统崩溃。)
解决:使用锁,单机用synchronized,lock等,分布式用分布式锁;缓存过时时间不设置,而是设置在key对应的value里。若是检测到存的时间超过过时时间则异步更新缓存;在value设置一个比过时时间t0小的过时时间值t1,当t1过时的时候,延长t1并作更新缓存操做;设置标签缓存,标签缓存设置过时时间,标签缓存过时后,需异步地更新实际缓存 具体参照userServiceImpl4的处理方式。
在不设置过时时间的状况下,不论是先更新DB仍是更新缓存,都很是容易出现缓存脏数据的问题,且不容易进行处理。再加上数据在不须要的时候被缓存就是浪费时间,因此不该该在数据发生变动的时候更新缓存。既然不更新缓存,那么就删除缓存吧。查询redis缓存时,通常查询若是以非id方式查询,建议先由条件查询到id,再由id查询pojo;比较简单的redis缓存,推荐使用canal。