一:JDK 自带的 HashMap 和 ConcurrentHashMap
ConcurrentHashMap 可以看作是线程安全版本的 HashMap ,两者都是存放 key/value 形式的键值对。但是,大部分场景来说不会使用这两者当做缓存,因为只提供了缓存的功能,并没有提供其他诸如过期时间之类的功能。一个稍微完善一点的缓存框架至少要提供:过期时间、淘汰机制、命中率统计这三点。
二: Ehcache 、 Guava([ˈɡwɑːvə]) Cache 、 Spring Cache 这三者是使用的比较多的本地缓存框架。
- Ehcache 的话相比于其他两者更加重量。不过,相比于 Guava Cache 、 Spring Cache 来说, Ehcache 支持可以嵌入到 hibernate 和 mybatis 作为多级缓存,并且可以将缓存的数据持久化到本地磁盘中、同时也提供了集群方案(比较鸡肋,可忽略)。
- Guava Cache 和 Spring Cache 两者的话比较像。Guava 相比于 Spring Cache 的话使用的更多一点,它提供了 API 非常方便我们使用,同时也提供了设置缓存有效时间等功能。它的内部实现也比较干净,很多地方都和 ConcurrentHashMap 的思想有异曲同工之妙。
- 使用 Spring Cache 的注解实现缓存的话,代码会看着很干净和优雅,但是很容易出现问题比如缓存穿透、内存溢出。
三 后起之秀 Caffeine
本地缓存固然好,但是缺陷也很明显,比如多个相同服务之间的本地缓存的数据无法共享。
四、本地缓存和分布式缓存
本地缓存
本地缓存有如下缺点:
- 本地缓存对分布式架构支持不友好,比如同一个服务部署在多台机器上的时候,各个机器之间的缓存是无法共享的,因为本地缓存只在当前机器上有。
- 本地缓存容量受服务部署所在的机器限制明显。 如果当前系统服务所耗费的内存多,那么本地缓存可用的容量就很少。
分布式缓存
相当于是部署了一个单独的缓存服务器,这个服务器专门用来提供缓存的服务。使用分布式缓存之后,缓存部署在一台单独的服务器上,不管该服务分布式部署在多少台机器上,使用的都是同一份缓存。所以,单独的分布式缓存服务的性能、容量和提供的功能都要更加强大。
分布式缓存的缺点:
- 系统复杂性增加 :引入缓存之后,你要维护缓存和数据库的数据一致性、维护热点缓存等等。
- 系统开发成本往往会增加 :引入缓存意味着系统需要一个单独的缓存服务,需要耗费宝贵的内存。
五、缓存读写模式/更新策略
下面介绍到的三种模式各有优劣,不存在最佳模式,根据具体的业务场景选择适合自己的缓存读写模式。
5.1 Cache Aside Pattern(旁路缓存模式)
- 读:从 cache 中读取数据,读取到就直接返回,读取不到的话,就从 DB 中取数据返回,然后再把数据放到 cache 中。
- 写:更新 DB,然后直接删除 cache 。
Cache Aside Pattern 中服务端需要同时维系 DB 和 cache,并且是以 DB 的结果为准。另外,Cache Aside Pattern 有首次请求数据一定不在 cache 的问题,对于热点数据需要提前放入缓存中。
Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。
5.2 Read/Write Through Pattern(读写穿透)
Read/Write Through 套路是:服务器端只和缓存交互,由缓存和数据库进行交互数据更新和同步。
- 读(Read Through): 从 cache 中读取数据,读取到就直接返回 。读取不到的话,由缓存负责先从 DB 加载,写入到 cache 后返回响应。
- 写(Write Through):先查 cache,cache 中不存在,直接更新 DB。 cache 中存在,则先更新 cache,然后 由cache 服务自己更新 DB(同步更新 cache 和 DB)。
Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装。在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。
和 Cache Aside Pattern 一样, Read-Through Pattern 也有首次请求数据一定不再 cache 的问题,对于热点数据需要提前放入缓存中。
5.3. Write Behind Pattern(异步缓存写入)
Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。
但是,两个又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。
Write Behind Pattern 下 DB 的写性能非常高,尤其适合一些数据经常变化的业务场景比如说一篇文章的点赞数量、阅读数量。 往常一篇文章被点赞 500 次的话,需要重复修改 500 次 DB,但是在 Write Behind Pattern 下可能只需要修改一次 DB 就可以了。
但是,这种模式同样也给 DB 和 Cache 一致性带来了新的考验,很多时候如果数据还没异步更新到 DB 的话,Cache 服务宕机就 gg 了。