https://mp.weixin.qq.com/s?__biz=MzIwMzY1OTU1NQ==&mid=2247486210&idx=1&sn=77fb08edcaeaa07eb55b029f24f76ac1&chksm=96cd4b4ea1bac2584a471468e89fa6f5ad4103b21f14cd726e8b3d47ddcc55261befdf319fc6&mpshare=1&scene=1&srcid=05157y1N1aj5Gv2Y118LPJNj&key=259ddc97107c0c69acb90391a5e7354d2de386bc05b14c366352634e5a3e1d4698537d8a65bd7bbd08fd91d8bcf075d3b4cba14b647d69f3700e605449546964900f0d589f8ca3fa581373df52b60f9f&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=bYp8tOH0gLQNlxdvvvx7dfnhmidIEkms7qrgkKSic6LsIYkoC0g1IX3prIh0P6kU
面试官:我看你简历提到xxx项目使用了redis
小弱鸡:嗯,因为xxxx的性能问题,经过排查之后,发现性能瓶颈在数据库上面,所以引入了redis
面试官:行,那你了解redis的过期策略吗?
小弱鸡:有了解过,因为redis是基于内存来进行高性能、高并发的读写操作的,既然是内存,那肯定有空间的限制,如果只有10g内存,一直往里面写数据,那肯定不行,所以采用一些过期策略把不需要的数据删除、或者是淘汰掉。
面试官:那都有哪些过期策略?
小弱鸡:我了解的有 定期删除、惰性删除两种
面试官:你先讲讲定期删除怎么实现?
小弱鸡好像有点兴奋:所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
面试官:为什么是随机抽取?
小弱鸡:假如在redis 里插入10w个key,并且都设置了过期时间,如果每次都检查所有key,那cpu基本上都消耗在过期key的检查上了,redis对外的性能也会大大降低,简直就是一场灾难。
面试官:随机检查会存在什么问题?
小弱鸡:可能导致本已经过期的key没有被扫描到,而继续留在内存中,并占用空间,等待被删除。
面试官:这种情况怎么解决?
小弱鸡又兴奋了:这时候就需要第二种过期策略了,惰性删除,就是在获取某个 key 的时候,redis 会检查一下 ,如果这个 key 设置了过期时间,并且已经过期了,那么就直接删除,返回空。
面试官面带一丝笑意:嗯,那再考虑一种情况,如果大量的key没有被扫描到,且已过期,也没有被再次访问,即没有走惰性删除,这些大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,这种情况下,怎么办?
小菜鸡想了会,抓了抓脑袋:redis内部提供了内存淘汰机制,应该有好几种策略,但我只知道LRU算法。
面试官:嗯,那你手写一个LRU算法?
小菜鸡*花一紧,这不是给自己挖坑么!!! 如果从头开始写一个完整的LRU算法,那会要了命,幸好小菜鸡还记得 LinkedHashMap
,可以基于 LinkedHashMap
实现一个简单版本的LRU算法。
-
class LRUCache<K, V> extends LinkedHashMap<K, V> {
-
private final int CACHE_SIZE;
-
* @param cacheSize 缓存大小
-
*/
-
// true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
-
public LRUCache(int cacheSize) {
-
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
-
CACHE_SIZE = cacheSize;
-
}
-
@Override
-
// 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
-
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
-
return size() > CACHE_SIZE;
-
}
-
}
小菜鸡写完之后,轻轻舒了一口气。