内存缓存之HashMap、EHCache、Guava Cache对比
2018年02月11日


缓存不应该被过度使用,在不同场景下,选用不同的方式缓存数据。


简单场景,存入一些占用内存不多的数据,而且这些数据不会主动发生变化,服务器启动后就永久存储,修改和删除都是全手动执行。而且服务器重启时不需要再自动恢复到之前的状态。这种数据直接用Map等放到内存中即可。


而一个标准Cache的主要特征有:

  • 过期时间

  • 容量规划(重要)

  • 清除策略(重要)

  • 命中率统计


其中,常见的清除策略、淘汰机制(Eviction policy)有:

  • FIFO(First In First Out):先进先出算法,即先放入缓存的先被移除;

  • LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除;

  • LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除;


其中,过期时间包括:

  • TTL(Time To Live):存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)

  • TTI(Time To Idle):空闲期,即一个数据多久没被访问将从缓存中移除的时间。


基于以上特征,使用HashMap作为本地cache似乎很不适当。某些场景下面,倒是可以用JDK自带的WeakHashMap。


通过简单的包装,就可以为你的HashMap增加过期时间和容量规划。而且比其他cache更高效。openfire的DefaultCache就是一个很好的例子:

DefaultCache.java

DefaultCache的核心是HashMap,另外增加了一层很薄的包装来实现过期和LRU。DefaultCache包括两个LinkedList,一个用于存储插入顺序,另一个用于存储插入时间。当cache size达到临界值时,从最尾部删除。有朋友测试过,比ehcache快5倍。


如果本地cache不能满足你的要求,ehcache是个很好的选择。不仅仅作为分布式的cache,甚至作为状态同步,ehcache都有非常优秀的案例。也可以实现多机copy。分享一个数据:某生产系统中ehcache每天处理17,466,415次replication。


不过,也听说ehcache代码写得很复杂,设计有些问题,相比之下 Google的Guava Cache就要轻量得多,但是只支持内存缓存。

Guava Cache与ConcurrentMap很相似。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。


通常来说,Guava Cache适用于:

  • 你愿意消耗一些内存空间来提升速度。

  • 你预料到某些键会被查询一次以上。

  • 缓存中存放的数据总量不大,不会超出内存容量。(否则,请尝试EHCache、Memcached这类工具)

如果你的场景符合上述的每一条,Guava Cache就适合你。


Ehcache使用的注意点:

1、比较少的更新数据表的情况

2、对并发要求不是很严格的情况

3、多台应用服务器中的缓存是不能进行实时同步的。

4、对一致性要求不高的情况下


ehcache支持集群,但因为Ehcache本地缓存的特性(属于分散式缓存),目前无法很好的解决不同服务器间缓存同步的一致性问题,所以我们在一致性要求非常高的场合下,尽量使用Redis、Memcached等集中式缓存


Ehcache has a lot more features than a Map:
  • limit the maximum number of elements in memory

  • overflow to disk (if the above number is exceeded)

  • set a time-to-live and time-to-idle for elements

  • allows replication within a cluster

If you don't need any of those, you can safely use a Map - it will be easier to configure.