本地缓存是将数据存储在应用程序所在的本地内存中的缓存方式。既然,已经有了 Redis 可以实现分布式缓存了,为什么还需要本地缓存呢?接下来,我们一起来看。

为什么需要本地缓存?

尽管已经有 Redis 缓存了,但本地缓存也是非常有必要的,因为它有以下优点:

  1. 速度优势:本地缓存直接利用本地内存,访问速度非常快,能够显著降低数据访问延迟。
  2. 减少网络开销:使用本地缓存可以减少与远程缓存(如 Redis)之间的数据交互,从而降低网络 I/O 开销。
  3. 降低服务器压力:本地缓存能够分担服务器的数据访问压力,提高系统的整体稳定性。

因此,在生产环境中,我们通常使用本地缓存+Redis 缓存一起组合成多级缓存,来共同保证程序的运行效率

多级缓存

多级缓存是一种缓存架构策略,它使用多个层次的缓存来存储数据,以提高数据访问速度和系统性能,最简单的多级缓存就是由本地缓存 + Redis 分布式缓存组成的,如图所示:

多级缓存在获取时的实现代码如下:

public Object getFromCache(String key) {
// 先从本地缓存中查找
Cache.ValueWrapper localCacheValue = cacheManager.getCache("localCache").get(key);
if (localCacheValue!= null) {
return localCacheValue.get();
}
// 如果本地缓存未命中,从 Redis 中查找
Object redisValue = redisTemplate.opsForValue().get(key);
if (redisValue!= null) {
// 将 Redis 中的数据放入本地缓存
cacheManager.getCache("localCache").put(key, redisValue);
return redisValue;
}
return null;
}

本地缓存的实现

本地缓存常见的方式实现有以下几种:

  1. Ehcache
  2. Caffeine
  3. Guava Cache

它们的基本使用如下。

1.Ehcache

1.1 添加依赖

在 pom.xml 文件中添加 Ehcache 依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

1.2 配置 Ehcache

在 src/main/resources 目录下创建 ehcache.xml 文件:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd">
<cache name="myCache"
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"/>
</ehcache>

1.3 启用缓存

在 Spring Boot 应用的主类或配置类上添加 @EnableCaching 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}

1.4 使用缓存

创建一个服务类并使用 @Cacheable 注解:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; @Service
public class MyService { @Cacheable(value = "myCache", key = "#id")
public String getData(String id) {
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data for " + id;
}
}

2.Caffeine

2.1 添加依赖

在 pom.xml 文件中添加 Caffeine 依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>

2.2 启用缓存

在 Spring Boot 应用的主类或配置类上添加 @EnableCaching 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}

2.3 配置 Caffeine 缓存

创建一个配置类来配置 Caffeine 缓存:

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class CacheConfig { @Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("myCache");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(120, TimeUnit.SECONDS));
return cacheManager;
}
}

2.4 使用缓存

创建一个服务类并使用 @Cacheable 注解:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; @Service
public class MyService { @Cacheable(value = "myCache", key = "#id")
public String getData(String id) {
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data for " + id;
}
}

3.Guava Cache

3.1 添加依赖

在 pom.xml 文件中添加 Guava 依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>

3.2 启用缓存

在 Spring Boot 应用的主类或配置类上添加 @EnableCaching 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}

3.3 配置 Guava 缓存

创建一个配置类来配置 Guava 缓存:

import com.google.common.cache.CacheBuilder;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration
public class CacheConfig { @Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
@Override
protected Cache createConcurrentMapCache(String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(120, TimeUnit.SECONDS)
.build().asMap(), false);
}
};
return cacheManager;
}
}

3.4 使用缓存

创建一个服务类并使用 @Cacheable 注解:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; @Service
public class MyService { @Cacheable(value = "myCache", key = "#id")
public String getData(String id) {
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data for " + id;
}
}

知识扩展:@Cacheable、@CachePut、@CacheEvict

在 Spring 框架中,@Cacheable、@CachePut 和 @CacheEvict 是用于缓存管理的注解,它们的含义如下:

  1. @Cacheable:用于声明一个方法的返回值是可以被缓存的。当方法被调用时,Spring Cache 会先检查缓存中是否存在相应的数据。如果存在,则直接返回缓存中的数据,避免重复执行方法;如果不存在,则执行方法并将返回值存入缓存中。它的使用示例如下:
@Cacheable(value = "users", key = "#id")
public User getUserById(String id) {
// 模拟从数据库中获取用户信息
System.out.println("Fetching user from database: " + id);
return new User(id, "User Name " + id);
}
  1. @CachePut:用于更新缓存中的数据。与 @Cacheable 不同,@CachePut 注解的方法总是会执行,并将返回值更新到缓存中。无论缓存中是否存在相应的数据,该方法都会执行,并将新的数据存入缓存中(如果缓存中已存在数据,则覆盖它)。它的使用示例如下:
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
// 模拟更新数据库中的用户信息
System.out.println("Updating user in database: " + user.getId());
// 假设更新成功
return user;
}
  1. @CacheEvict:用于删除缓存中的数据。当方法被调用时,指定的缓存项将被删除。这可以用于清除旧数据或使缓存项失效。它的使用示例如下:
@CacheEvict(value = "users", key = "#id")
public void deleteUser(String id) {
// 模拟从数据库中删除用户信息
System.out.println("Deleting user from database: " + id);
}
// 清除整个缓存,而不仅仅是特定的条目
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsersCache() {
System.out.println("Clearing all users cache");
}

小结

生产环境通常会使用本地缓存 + Redis 缓存,一起实现多级缓存,以提升程序的运行效率,而本地缓存的常见实现有 Ehcache、Caffeine、Guava Cache 等。然而,凡事有利就有弊,那么多级缓存最大的问题就是数据一致性问题,对于多级缓存的数据一致性问题要如何保证呢?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:并发编程、MySQL、Redis、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、JVM、设计模式、消息队列等模块。

有Redis为什么还要本地缓存?谈谈你对本地缓存的理解?的更多相关文章

  1. lua模块demo(redis,http,mysql,cjson,本地缓存)

    1. lua模块demo(redis,http,mysql,cjson,本地缓存) 1.1. 配置 在nginx.conf中设置lua_shared_dict my_cache 128m; 开启ngi ...

  2. 同时使用Redis缓存和Google Guava本地缓存注意事项(深拷贝和浅拷贝)

    目录 1.问题场景及说明 2.Redis 缓存是深拷贝 3.Guava本地缓存直接获取则是浅拷贝 4.如何实现Guava获取本地缓存是深拷贝? 1.问题场景及说明 系统中同时使用 Redis 缓存和 ...

  3. 本地缓存google.guava及分布式缓存redis 随笔

    近期项目用到了缓存,我选用的是主流的google.guava作本地缓存,redis作分布式 缓存,先说说我对本地缓存和分布式缓存的理解吧,可能不太成熟的地方,大家指出,一起 学习.本地缓存的特点是速度 ...

  4. 【开源项目系列】如何基于 Spring Cache 实现多级缓存(同时整合本地缓存 Ehcache 和分布式缓存 Redis)

    一.缓存 当系统的并发量上来了,如果我们频繁地去访问数据库,那么会使数据库的压力不断增大,在高峰时甚至可以出现数据库崩溃的现象.所以一般我们会使用缓存来解决这个数据库并发访问问题,用户访问进来,会先从 ...

  5. 基于.NET CORE微服务框架 -谈谈Cache中间件和缓存降级

    1.前言 surging受到不少.net同学的青睐,也提了不少问题,提的最多的是什么时候集成API 网关,在这里回答大家最近已经开始着手研发,应该在1,2个月内会有个初版API网关,其它像Token身 ...

  6. Redis的n种妙用,不仅仅是缓存

    redis是键值对的数据库,常用的五种数据类型为字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset) Redis用作缓存,主要两个用途: ...

  7. Redis 多级缓存架构和数据库与缓存双写不一致问题

    采用三级缓存:nginx本地缓存+redis分布式缓存+tomcat堆缓存的多级缓存架构 时效性要求非常高的数据:库存 一般来说,显示的库存,都是时效性要求会相对高一些,因为随着商品的不断的交易,库存 ...

  8. Redis: 缓存过期、缓存雪崩、缓存穿透、缓存击穿(热点)、缓存并发(热点)、多级缓存、布隆过滤器

    Redis: 缓存过期.缓存雪崩.缓存穿透.缓存击穿(热点).缓存并发(热点).多级缓存.布隆过滤器 2019年08月18日 16:34:24 hanchao5272 阅读数 1026更多 分类专栏: ...

  9. Redis 雪崩、穿透、击穿、并发、缓存讲解以及解决方案

    1.缓存雪崩 数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机. 比如一个雪崩的简单过程 1.redis集群大面积故障 2.缓存 ...

  10. JAVA中使用最广泛的本地缓存?Ehcache的自信从何而来3 —— 本地缓存变身分布式集群缓存,打破本地缓存天花板

    大家好,又见面了. 本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面.如果感兴趣,欢迎关注以获取后续更新. 上一篇文章中,我们知晓了如何在项目中通 ...

随机推荐

  1. Python 开发环境的准备以及一些常用类库模块的安装

    在学习和开发Python的时候,第一步的工作就是先准备好开发环境,包括相关常用的插件,以及一些辅助工具,这样我们在后续的开发工作中,才能做到事半功倍.下面介绍一些Python 开发环境的准备以及一些常 ...

  2. csv导入导出组件jcsv

    jcsv 介绍 jcsv一个简单的.轻量级的csv导入.导出库,相对于opencsv与javacsv,jcsv侧重于导入导出,包括导入校验.导出模板等. 源代码地址:https://gitee.com ...

  3. quartz监控日志(一)

    最近几个月,现网总是出现定时器不执行的情况,或者定时器卡死的情况,而又不方便排查,只能依靠quartz的debug日志以及错误日志来监控定时器的执行情况,并且随着我们系统中job越来越多,而使得job ...

  4. 树上倍增求 LCA 模板

    void dfs(int x,int fa,int d){ deep[x]=d;dp[x][0]=fa; for(int i=1;i<=lg2[deep[x]];++i){ dp[x][i]=d ...

  5. android 访问域名接口报错

    1. 移动端访问https域名及接口,显示 java.net.UnknownHostException: Unable to resolve host "xxx" : No add ...

  6. Spring:基于注解管理bean

    标记与扫描 注解 和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测 到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作. 本质上:所有一切 ...

  7. CoST: 时间序列预测中分离季节趋势特征的对比学习《CoST: CONTRASTIVE LEARNING OF DISENTANGLED SEASONAL-TREND REPRESENTATIONS FOR TIME SERIES FORECASTING》(时序预测、表征学习、对比学习、因果关系、分离趋势季节特征)

    2022/6/18 11:32,简单记录一下随笔(因为不写点东西,根本注意力不集中,看5分钟可能要摸鱼10分钟,还是要写点,突然发现,草稿箱里最早的一篇没写完的博客是去年的7月2日,救命啊,我拖了一年 ...

  8. Angular 18+ 高级教程 – Component 组件 の Dependency Injection & NodeInjector

    前言 在 Dependency Injection 依赖注入 文章中,我们学习了 50% 的 Angular DI 知识,由于当时还不具备组件知识,所以我们无法完成另外 50% 的学习. 经过了几篇组 ...

  9. ASP.NET Core – Case Style Conversion

    前言 之前就有写过一篇 <<前后端沟通 naming conversion 转换需要知道的事>> 这篇做一个总结整理. 我们知道 C# 的 Property 是 PascalC ...

  10. CSP提高组模拟1

    我的微軟輸入法莫名其妙變成繁體了,你們有什麽頭緒嗎 狀態 題目 20 Time Exceeded A 最短路 25 Time Exceeded B 方格取数 0 Time Exceeded C 数组 ...