高并发服务设计——缓存

1 缓存回收策略

1.1 基于空间

即设置缓存的存储空间,如设置为10MB,当达到存储空间时,按照一定的策略移除数据。

1.2 基于容量

基于容量指缓存设置了最大大小,当缓存的条目超过最大大小,则按照一定的策略将旧数据移除。

1.3 基于时间

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

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

1.4 基于Java对象引用

软引用:如果一个对象是软引用,那么当JVM堆内存不足时,垃圾回收器可以回收这些对象。软引用适合用来做缓存,从而当JVM堆内存不足时,可以回收这些对象腾出一些空间供强引用对象使用,从而避免OOM。

弱引用:当垃圾回收器回收内存时,如果发现弱引用,则将立即回收它。相对于软引用有更短的生命周期。

注意:弱引用/软引用对象只有当没有其他强引用对象引用它时,垃圾回收时才回收该引用。
即如果有一个对象(不是弱引用/软引用)引用了弱引用/软引用对象,那么垃圾回收是不会回收该引用对象。

1.5 回收算法

使用基于空间和基于容量的缓存会使用一定的策略移除旧数据,常见的如下:

  • FIFO(Fisrt In Fisrt Out):先进先出算法,即先进入缓存的先被移除。
  • LRU(Least Recently Used):最近最少使用算法,使用时间距离现在最久的数据被移除。
  • LFU(Least Frequently Used):最不常用算法,一定时间段内使用次数(频率)最少的数据被移除。

实际应用中基于LRU的缓存较多,如Guava Cache、EhCache支持LRU。

2 Java缓存类型

2.1 堆缓存

使用Java堆内存来存储对象。可以使用Guava Cache、Ehcache 3.x、MapDB实现。

  • 优点:使用堆缓存的好处是没有序列化/反序列化,是最快的缓存;
  • 缺点:很明显,当缓存的数据量很大时, GC暂停时间会变长,存储容量受限于堆空间大小;一般通过软引用/弱引用来存储缓存对象,即当堆内存不足时,可以强制回收这部分内存释放堆内存空间。一般使用堆缓存存储较热的数据。

2.2 堆外缓存

即缓存数据存储在堆外内存。可以使用Ehcache 3.x、MapDB实现。

  • 优点:可以减少GC暂停时间(堆对象转移到堆外,GC扫描和移动的对象变少了),可以支持更大的缓存空间(只受机器内存大小限制,不受堆空间的影响)。
  • 缺点:读取数据时需要序列化/反序列化,会比堆缓存慢很多。

2.3 磁盘缓存

即缓存数据的存储在磁盘上。当JVM重启时数据还是在的。而堆缓存/堆外缓存重启时数据会丢失,需要重新加载。可以使用Ehcache 3.x、MapDB实现。

2.4 分布式缓存

在多JVM实例的情况时,进程内缓存和磁盘缓存会存在两个问题:1.单机容量问题; 2.数据一致性问题(既然数据允许缓存,则表示允许一定时间内的不一致,因此可以设置缓存数据的过期时间来定期更新数据); 3.缓存不命中时,需要回源到DB/服务查询变多:每个实例在缓存不命中情况下都会回源到DB加载数据,因此,多实例后DB整体的访问量就变多了。解决办法可以使用如一致性哈希分片算法来解决。因此,这些情况可以考虑使用分布式缓存来解决。可以使用ehcache-clustered(配合Terracotta server)实现Java进程间分布式缓存。当然也可以使用如Redis实现分布式缓存。

两种模式如下:

  • 单机时:存储最热的数据到堆缓存,相对热的数据到堆外缓存,不热的数据存到磁盘缓存。
  • 集群时:存储最热的数据到堆缓存,相对热的数据到堆外缓存,全量数据存到分布式缓存。

3 Java缓存实现

3.1 堆缓存

3.1.1 Guava Cache实现

Guava Cache只提供堆缓存,小巧灵活,性能最好,如果只使用堆缓存,那么使用它就够了。

Cache<String, String> myCache=
        CacheBuilder.newBuilder()
        .concurrencyLevel(4)
        .expireAfterWrite(10, TimeUnit.SECONDS)
        .maximumSize(10000)
        .build();

然后可以通过put、getIfPresent 来读写缓存。CacheBuilder有几类参数:缓存回收策略、并发设置等。

3.1.1.1 缓存回收策略/基于容量

maximumSize:设置缓存的容量,当超出maximumSize时,按照LRU进行缓存回收。

3.1.1.2 缓存回收策略/基于时间
  • expireAfterWrite:设置TTL,缓存数据在给定的时间内没有写(创建/覆盖)时,则被回收,即定期的会回收缓存数据。
  • expireAfterAccess:设置TTI,缓存数据在给定的时间内没有读/写时,则被回收。每次访问时,都会更新它的TTI,从而如果该缓存是非常热的数据,则将一直不过期,可能会导致脏数据存在很长时间(因此,建议设置expireAfterWrite)。
3.1.1.3 缓存回收策略/基于Java对象引用

weakKeys/weakValues:设置弱引用缓存。
softValues:设置软引用缓存。

3.1.1.4 缓存回收策略/主动失效

invalidate(Object key)/invalidateAll(Iterablekeys)/invalidateAll():主动失效某些缓存数据。

什么时候触发失效呢? Guava Cache不会在缓存数据失效时立即触发回收操作(如果要这么做,则需要有额外的线程来进行清理),是在PUT时会主动进行一次清理缓存,当然读者也可以根据实际业务通过自己设计线程来调用cleanUp方法进行清理。

3.1.1.5 并发级别

concurrencyLevel:Guava Cache重写了ConcurrentHashMap,concurrencyLevel用来设置Segment数量,concurrencyLevel越大并发能力越强。

3.1.1.6 统计命中率

recordStats:启动记录统计信息,比如命中率等

3.1.2 EhCache 3.x实现

CacheManager cacheManager = CacheManagerBuilder. newCacheManagerBuilder(). build(true);
CacheConfigurationBuilder<String, String> cacheConfig= CacheConfigurationBuilder.newCacheConfigurationBuilder(
       String.class,
       String.class,
       ResourcePoolsBuilder.newRes
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值