# 缓存 ## 缓存有3大问题,以及如何解决? ### 缓存雪崩 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。 解决办法: - 缓存过期时间设置成随机 - 热点数据考虑永不过期(定时刷新) - 使用分布式缓存,防止单点故障缓存全部丢失 ### 缓存穿透 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。 解决办法: - 空对象 - 布隆过滤器 ### 缓存击穿 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力 解决办法: - 热点数据永不过期(后台进程定时刷新) - 加互斥锁 ## 缓存有哪些淘汰策略? - 先进先出策略 FIFO(First In,First Out) 如果一个数据最先进入缓存中,则应该最早淘汰掉 - 最近最少使用策略 LRU(Least Recently Used) 如果数据最近被访问过,那么将来被访问的几率也更高。对于循环出现的数据,缓存命中不高。实际实现时候一般可采用双向链表,将最近访问过得缓存key放在链表首部,删除尾部的缓存key,再加上hash表来记录key-value,实现快速访问缓存 - 最少使用策略 LFU(Least Frequently Used) 如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。对于交替出现的数据,缓存命中不高 ## 读写屏障是怎么回事? ![](https://static.cyub.vip/images/202103/memory_arch.gif) 内存屏障分为读屏障(rmb)与写屏障(wmb)。写屏障主要保证在写屏障之前的在Store buffer中的指令都真正的写入了缓存。读屏障主要保证了在读屏障之前所有Invalidate queue中所有的无效化指令都执行。有了读写屏障的配合,那么在不同的核心上,缓存可以得到强同步。 ## 缓存一致性 **对于读是不存在缓存与数据库不一致的的情况**。读的流程: - 如果我们的数据在缓存里边有,那么就直接取缓存的。 - 如果缓存里没有我们想要的数据,我们会先去查询数据库,然后将数据库查出来的数据写到缓存中。 - 最后将数据返回给请求 对于数据库更新操作, 执行操作时候,两种选择: - 先操作数据库,再操作缓存 - 先操作缓存,再操作数据库 操作缓存,两种方案选择: - 更新缓存 - 删除缓存 一般我们都是采取删除缓存缓存策略的,原因: - 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就更加容易导致数据库与缓存数据不一致问题。(删除缓存直接和简单很多) - 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边 (体现懒加载) **先删缓存,再更新数据库** 该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形: ![](https://static.cyub.vip/images/202107/cache-except1.jpg) **先更新数据,再删除缓存:** 如果在高并发的场景下,出现数据库与缓存数据不一致的概率特别低,也不是没有: ![](https://static.cyub.vip/images/202107/cache-except2.jpg) 解决办法: - Cache Aside Pattern ![](https://a-tour-of-golang.cyub.vip/_images/cache_aside.jpg) - binlog模式 ![](https://a-tour-of-golang.cyub.vip/_images/cache_binlog.jpg)