今天早上先啃了 Redis 的三个经典问题。
缓存穿透:请求打过来,Redis 里没有,就直接砸到数据库上。要是有人故意用大量不存在的数据来刷,数据库压力就很大,查询变慢,甚至可能崩掉。最简单的方案就是:先去 Redis 里查,有就返回;没有,再去数据库查,数据库有的话写回 Redis,没有就缓存一个空值,这样后续同样的恶意请求就不会再穿透到数据库了。第二种方案是布隆过滤器,底层是 bit 数组和哈希,通过 0/1 来判断数据存不存在。如果判断不存在,那就一定不存在,可以直接返回空;如果判断存在,实际可能误判,所以“没有就是没有,有不一定有”,这是要留意的点。
缓存雪崩:大量缓存的 key 在同一时间过期,请求一瞬间全涌到数据库。一个有效的办法是给每个 key 的过期时间加上随机值,让它们错开失效,避免集体过期。
缓存击穿:某个热点 key 突然过期,同时又来了海量请求,就会直接击穿到数据库。两个方案:第一个用互斥锁,第一个请求进来时加锁去查数据库,其他请求就排队等着,锁可以用 Redis 的 setnx(大概就是“不存在才能设成功”那类指令)来实现,存在锁的话就 sleep 一会儿再重试,没锁的抢到锁就去查库然后回写缓存。第二个是逻辑过期,这个今天还没完全搞明白,明天再继续琢磨。
课上复习了 SSM 和 SpringBoot。我现在差不多能分清它们了:SpringBoot 相当于把 Spring、SpringMVC 那套都给你自动配好了,MyBatis 下个 starter 就差不多能跑,自己写个 BootApplication 启动类就行。Spring 像个大容器,管着所有带注解的类和方法;SpringMVC 主要是和前端交互那层,底层还是 jsp、servlet 那一套,这两块的细节我还没来得及深入看。
下午学了个建造者模式,感觉就是链式调用的写法。不写一堆 set 方法了,用内部 Builder,外部类通过return new Builder(),链式设值最后 build。好处是对象一旦构建出来,里面的数据就定死了,不容易被乱改。
晚上刷了两道哈希表的题。两数之和就是建个 HashMap,一次遍历,用 target 减当前值看 map 里有没有,有就直接返回。第二道有点意思,是用Map<String, List<String>>,把每个字符串排序后作为 key,原始字符串加到对应的列表里。先排序再判断 map 里有没有这个 key,有就 put 进去,没有就 new 一个列表。这样就能把字母异位词归到一组了。