java中的本地缓存

java中的本地缓存,工作后陆续用到,一直想写,一直无从下手,最近又涉及到这方面的问题了,梳理了一下。自己构造单例、guava、ehcache基本上涵盖了目前的大多数行为了。

 

为什么要有本地缓存?

在系统中,有些数据,数据量小,但是访问十分频繁(例如国家标准行政区域数据),针对这种场景,需要将数据搞到应用的本地缓存中,以提升系统的访问效率,减少无谓的数据库访问(数据库访问占用数据库连接,同时网络消耗比较大),但是有一点需要注意,就是缓存的占用空间以及缓存的失效策略。

 

为什么是本地缓存,而不是分布式的集群缓存?

         目前的数据,大多是业务无关的小数据缓存,没有必要搞分布式的集群缓存,目前涉及到订单和商品的数据,会直接走DB进行请求,再加上分布式缓存的构建,集群维护成本比较高,不太适合紧急的业务项目。

         这里介绍一下缓存使用的三个阶段(摘自info架构师文档)

          

 

本地缓存在那个区域?

         目前考虑的是占用了JVM的heap区域,再细化一点的就是heap中的old区,目前的数据量来看,都是一些小数据,加起来没有几百兆,放在heap区域最快最方便。后期如果需要放置在本地缓存的数据大的时候,可以考虑在off-heap区域,但是off-heap区域的话,需要考虑对象的序列化(因为off-heap区域存储的是二进制的数据),另外一个的话就是off-heap的GC问题。其实,如果真的数据量比较大,那其实就可以考虑搞一个集中式的缓存系统,可以是单机,也可以是集群,来承担缓存的作用。

 

搞一个单例模式,里面有个Map的变量来放置数据

关于单例模式,一个既简单又复杂的模式(http://iamzhongyong.iteye.com/blog/1539642

非常典型的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public  class  SingletonMap {
     //一个本地的缓存Map
     private  Map<String,Object> localCacheStore =  new  HashMap<String,Object>(); 
 
     //一个私有的对象,非懒汉模式
     private  static  SingletonMap singletonMap =  new  SingletonMap(); 
 
     //私有构造方法,外部不可以new一个对象
     private  SingletonMap(){
     }  
 
     //静态方法,外部获得实例对象
     public  static  SingletonMap getInstance(){
         return  singletonMap;
     }
 
     //获得缓存中的数据
     public  Object getValueByKey(String key){
         return  localCacheStore.get(key);
     }
     //向缓存中添加数据
     public  void  putValue(String key , Object value){
         localCacheStore.put(key, value);
     }
}

 这种能不能用?可以用,但是非常局限

1
2
3
4
5
6
7
8
但是这种的就是本地缓存了吗? 答案显然不是,为啥呢?
1 、  没有缓存大小的设置,无法限定缓存体的大小以及存储数据的限制(max size limit);
2 、  没有缓存的失效策略(eviction policies);
3 、  没有弱键引用,在内存占用吃紧的情况下,JVM是无法回收的(weak rererences keys);
4 、  没有监控统计(statistics);
5 、  持久性存储(persistent store);
所以,这种就直接废掉了。。。

 

引入EhCache来构建缓存(详细介绍:  http://raychase.iteye.com/blog/1545906

EhCahce的核心类:

A、CacheManager:Cache的管理类;

B、Cache:具体的cache类信息,负责缓存的get和put等操作

C、CacheConfiguration :cache的配置信息,包含策略、最大值等信息

D、Element:cache中单条缓存数据的单位

典型的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public  static  void  main(String[] args) {
         //EhCache的缓存,是通过CacheManager来进行管理的
         CacheManager cacheManager = CacheManager.getInstance();
         
         //缓存的配置,也可以通过xml文件进行
         CacheConfiguration conf =  new  CacheConfiguration();
         conf.name( "cache_name_default" ); //设置名字
         conf.maxEntriesLocalHeap( 1000 ); //最大的缓存数量
         conf.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU); //设置失效策略
         
         //创建一个缓存对象,并把设置的信息传入进去
         Cache localCache =  new  Cache(conf);
         
         //将缓存对象添加到管理器中
         cacheManager.addCache(localCache);
                 
         localCache.put( new  Element( "iamzhongyong" new  Date()));
         
         System.out.println(localCache.getSize());
         System.out.println(localCache.getStatistics().toString());
         System.out.println(localCache.getName());
         System.out.println(localCache.get( "iamzhongyong" ).toString());
         System.out.println(localCache.get( "iamzhongyong" ).getObjectValue());   
     }

当然,Cache的配置信息,可以通过配置文件制定了。。。

优点:功能强大,有失效策略、最大数量设置等,缓存的持久化只有企业版才有,组件的缓存同步,可以通过jgroup来实现

缺点:功能强大的同时,也使其更加复杂

 

引入guava的cacheBuilder来构建缓存

这个非常强大、简单,通过一个CacheBuilder类就可以满足需求。

缺点就是如果要组件同步的话,需要自己实现这个功能。

典型的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public  class  GuavaCacheBuilderTest {
     public  static  void  main(String[] args)  throws  Exception{
         GuavaCacheBuilderTest cache =  new  GuavaCacheBuilderTest();
         cache.getNameLoadingCache( "bixiao" );
     }
     public  void  getNameLoadingCache(String name)  throws  Exception{
         LoadingCache<String, String> cache = CacheBuilder.newBuilder()       
             .maximumSize( 20 ) //设置大小,条目数        
             .expireAfterWrite( 20 , TimeUnit.SECONDS) //设置失效时间,创建时间      
             .expireAfterAccess( 20 , TimeUnit.HOURS)  //设置时效时间,最后一次被访问       
             .removalListener( new  RemovalListener<String, String>() {  //移除缓存的监听器
                 public  void  onRemoval(RemovalNotification<String, String> notification) {
                     System.out.println( "有缓存数据被移除了" );
                 }})
             .build( new  CacheLoader<String, String>(){  //通过回调加载缓存
                 @Override
                 public  String load(String name)  throws  Exception {
                     return  name +  "-"  "iamzhongyong" ;
                 }
         });
         System.out.println(cache.get(name));
         //cache.invalidateAll();
     }
}

 

缓存预热怎么搞?

A、全量预热,固定的时间段移除所有,然后再全量预热

适用场景:

1、数据更新不频繁,例如每天晚上3点更新即可的需求;

 2、数据基本没有变化,例如全国区域性数据;

B、增量预热(缓存查询,没有,则查询数据库,有则放入缓存)

适用场景:

1、  数据更新要求缓存中同步更新的场景

 

​集群内部,缓存的一致性如何保证?

如果采用ehcache的话,可以使用框架本身的JGroup来实现组内机器之间的缓存同步。

如果是采用google的cacheBuilder的话,需要自己实现缓存的同步。

A、非实时生效数据:数据的更新不会时时发生,应用启动的时候更新即可,然后定时程序定时去清理缓存;

B、需要实时生效数据:启动时可预热也可不预热,但是缓存数据变更后,集群之间需要同步

<think>我们正在讨论Java本地缓存的适用场景及使用条件。根据引用[1][2][3]的内容,本地缓存主要用于提高数据访问速度、减轻外部资源压力、减少重复计算等。以下将详细分析适用场景和使用条件。 ### 适用场景 1. **高频读取、低频更新的数据** 如系统配置、静态数据(国家列表、货币汇率等)[^1]。这类数据变更不频繁,适合缓存到内存中减少数据库查询。 2. **计算密集型操作的结果缓存** 例如: - 复杂数学运算结果(如报表预计算) - 机器学习模型推理结果 避免重复计算消耗CPU资源[^1]。 3. **缓解后端服务压力** 当外部服务(如数据库、第三方API)响应较慢或存在调用限制时,通过缓存结果降低调用频率。例如: - 电商商品详情页(缓存商品基本信息) - 地理位置服务(缓存经纬度解析结果)[^2] 4. **多级缓存架构的本地层** 在分布式系统中作为一级缓存(如配合Redis使用),优先从本地读取数据,未命中再查询分布式缓存[^3]。 $$ \text{访问顺序:本地缓存} \rightarrow \text{Redis} \rightarrow \text{数据库} $$ 5. **实时性要求不高的场景** 如排行榜定时更新、用户行为分析(每小时聚合一次)[^2]。 ### 使用条件 1. **内存资源充足** 本地缓存占用JVM堆内存,需确保不会引发OOM。例如: - 设置缓存最大条目数(`Caffeine.maximumSize(10_000)`) - 使用软引用/弱引用(内存不足时自动回收)[^3]。 2. **数据一致性要求可妥协** 本地缓存更新存在延迟(如异步刷新),不适用于强一致性场景。例如: - 银行账户余额(不可使用本地缓存) - 商品库存(需结合分布式锁)[^1]。 3. **单节点或小规模集群** 本地缓存无法跨节点同步数据,在大型集群中可能导致数据不一致。解决方案: - 广播失效消息(如Redis Pub/Sub) - 仅缓存全局一致性要求低的数据[^2]。 4. **需要高效的缓存淘汰策略** 根据业务选择淘汰算法: | 策略 | 适用场景 | 实现库 | |-----------|----------------------------|----------------| | LRU | 最近最少访问(通用场景) | `ConcurrentHashMap` | | LFU | 高频访问数据(热点数据) | `Caffeine` | | TTL | 定时过期(会话数据) | `Ehcache` | ### 代码示例(Caffeine基础用法) ```java import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; public class CaffeineDemo { public static void main(String[] args) { // 创建缓存实例:最大1000条,访问后5分钟过期 Cache<String, Object> cache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterAccess(5, TimeUnit.MINUTES) .build(); // 存储数据 cache.put("user:1001", loadUserFromDB("1001")); // 查询数据(不存在时自动加载) Object user = cache.get("user:1001", key -> loadUserFromDB(key)); } private static Object loadUserFromDB(String key) { // 模拟数据库查询 return new Object(); } } ``` > 说明:Caffeine通过`expireAfterAccess`设置访问过期策略,通过`maximumSize`控制内存占用[^3]。 ### 不适用本地缓存的场景 - **数据频繁更新**:如股票实时价格,缓存可能导致读到旧数据。 - **大数据量**:超出单机内存容量的数据集(需用Redis等分布式缓存)。 - **严格事务性**:涉及跨资源的事务操作(如银行转账)[^1][^2]。 ### 总结 使用本地缓存的核心原则:**以可控的内存开销换取性能提升**。需根据业务特点选择缓存库(如高并发选Caffeine[^3],复杂策略选Ehcache[^2]),并设置合理的过期策略和内存限制。 §§相关问题§§ 1. 如何监控本地缓存的有效性(如命中率)? 2. Caffeine与Ehcache在性能上有哪些差异?[^2][^3] 3. 本地缓存如何避免内存溢出? 4. 多节点环境下如何同步本地缓存数据?</think>### Java 本地缓存的适用场景及使用条件 根据引用内容[^1][^2][^3],Java 本地缓存通过内存存储高频访问数据,显著提升系统性能。以下是具体适用场景和使用条件分析: --- #### 一、核心适用场景 1. **高频读取低频更新的数据** - **典型场景**:系统配置、静态数据(如国家/城市列表)、商品类目信息 - **优势**:减少数据库/远程服务调用,响应时间从毫秒级降至微秒级 - **实现参考**: ```java // Caffeine 缓存配置示例 Cache<String, Config> cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) // 1小时更新 .maximumSize(1000) .build(); ``` 2. **计算密集型结果缓存** - **典型场景**: - 复杂报表预计算 - 机器学习模型推理结果 - 加密/解密操作结果 - **效果**:避免重复计算,CPU 使用率降低 40%-70%[^1] 3. **抗突发流量冲击** - **场景**:秒杀活动、热点新闻访问 - **机制**: - 缓存最近访问的商品详情/页面片段 - 配合 `Caffeine` 的 Window TinyLFU 算法,命中率 >99%[^3] $$ \text{缓存命中率} = \frac{\text{缓存命中次数}}{\text{总访问次数}} \times 100\% $$ 4. **多级缓存架构的本地层** - **架构**:`本地缓存 → Redis → 数据库` - **优势**: - 减少网络开销(本地读取比 Redis 快 100 倍) - 降低 Redis 负载(如 Spring Cache + Caffeine)[^2] --- #### 二、关键使用条件 1. **内存资源充足** - **要求**:缓存数据量 < JVM 可用堆内存的 30% - **风险控制**: ```java // 防止 OOM 的配置 Caffeine.newBuilder() .maximumSize(10_000) // 条目限制 .weigher((k,v) -> v.size()) // 按对象大小控制 .maximumWeight(100_000_000) // 100MB 上限 ``` 2. **可接受数据延迟更新** - **适用场景**: | 数据类型 | 最大容忍延迟 | |---------------|-------------| | 用户行为统计 | 5-10 分钟 | | 商品库存 | 10-30 秒 | - **更新策略**: - 定时刷新(`refreshAfterWrite`) - 主动失效(消息队列通知) 3. **单节点或小规模集群** - **限制原因**:本地缓存无跨节点同步能力 - **解决方案**: - 广播式缓存失效(如 Redis Pub/Sub) - 仅缓存节点独享数据(如用户会话) --- #### 三、技术选型建议 | **场景特征** | **推荐方案** | **优势** | |----------------------------|-------------------|-----------------------------------| | 高并发写入(如计数器) | `ConcurrentHashMap` | 无锁分段,写入性能高 | | 精确淘汰策略(如 LRU/LFU) | `Caffeine` | 时间/空间复杂度 O(1),命中率 99%+ [^3] | | 复杂缓存策略(如分层存储) | `Ehcache` | 支持磁盘溢出、持久化 | --- #### 四、典型代码实现(Caffeine) ```java // 高频查询缓存示例 LoadingCache<String, User> userCache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterAccess(30, TimeUnit.MINUTES) .refreshAfterWrite(5, TimeUnit.MINUTES) // 异步刷新 .build(key -> database.loadUser(key)); // 缓存未命中时自动加载 // 使用示例 User user = userCache.get("user_123"); // 微秒级响应 ``` --- #### 五、不适用场景 1. **强一致性要求**(如支付交易) 2. **大数据量存储**(>10GB) 3. **频繁全量更新**(如实时股票行情) > **经验提示**:本地缓存命中率 >85% 时收益显著,低于 60% 需重新设计缓存策略[^2]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值