缓存技术
聊聊缓存:
- 缓存最终的目的是为减轻服务端压力,减少网络传输请求 比如: 浏览器访问自带缓存。 App客户端底层都有缓存技术的。
注: (移动端登录 用token 本地是有个文件的)
案例:
如果一旦请求到服务器端之后,会在本地缓存一份,快速响应给用户。
常见的缓存方案:
- 网络缓存
- 代理缓存(Nginx可以缓存)
- CDN缓存
- 服务器缓存
- 数据库缓存
- 平台缓存级缓存
总结: 缓存最终的目的是为减轻服务端压力,减少网络传输请求
单机缓存与分布式缓存:
-
Session 是存放在服务器端(保存在单个JVM中),返回SessionId(响应头),客户端存放的SessionId,下次请求时候,直接使用对于的SessionId从服务器端查询对应Session
-
分布式Session (Session共享的问题)----直接使用token解决
案例单机版: 实现单个JVM缓存框架, Session Key Value 可以用Map集合实现 过期时间需要好好设计了 写一个给予Map集合实现Jvm缓存框架:
首先定义map实现的缓存类:(concurrentHashmap保证安全)
缓存:
@Component //这样的话 就是单例的了!!!注入到容器里面 public class MapCache<K,V> { //存放缓存容器 public ConcurrentHashMap<K, V> concurrentHashMap = new ConcurrentHashMap<K,V>(); //纯手写单个JVM缓存框架 缓存概念偏向于临时 //对传统的Map包装 public void put(K key,V value){ concurrentHashMap.put(key, value); } //查询 public V get(K key){ return concurrentHashMap.get(key); } public void remove(String k){ //这个map是安全的 不需要加锁! concurrentHashMap.remove(k); } }
controller类:
@RestController public class IndexController { @Autowired private MapCache<String, Object> mapCache; @RequestMapping("/get") public String get(String key){ return (String)mapCache.get(key); } @RequestMapping("/put") public String put(String key, String value){ mapCache.put(key, value); return "成功"; } @RequestMapping("/remove") public String remove(String key){ mapCache.remove(key); return "成功"; } }
启动类
@SpringBootApplication(scanBasePackages={"com.toov5.*"}) public class app { public static void main(String[] args) { SpringApplication.run(app.class, args); } }
单机版缓存框架
-
单点缓存框架 只能针对单个jvm中,缓存容器存放jvm中,每个缓存互不影响 Ehcache gauva chache 内置缓存框架 jvm缓存框架
-
分布式缓存技术(共享缓存数据) Redis Meacache
example:
mybatis、hibernate底层都使用了Ehcache
本地缓存Ehcache 什么是Ehcache Ehcache是纯java的开源缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。它主要面向通用缓存、Java EE和轻量级容器,具有内存和磁盘存储、缓存加载器、缓存扩展、缓存异常处理程序。
Ehcache最初由Greg Luck于2003年开始开发。2009年,该项目被Terracotta购买。软件仍然开源,但一些新的主要功能(例如,快速可重启性之间的一致性的)只能在商业产品中使用。
Ehcache 被广泛用于在Hibernate、Spring、Cocoon等其他开源系统。
Ehcache的主要特性 1.快速;
2.简单;
3.多种缓存策略;
4.缓存数据有两级:内存和磁盘,因此无需担心容量问题;
5.缓存数据会在虚拟机重启的过程中写入磁盘;
6.可以通过 RMI、可插入 API 等方式进行分布式缓存;
7.具有缓存和缓存管理器的侦听接口;(为了做集群)
8.支持多缓存管理器实例,以及一个实例的多个缓存区域;
9.提供 Hibernate 的缓存实现;
Ehcache使用介绍
Ehcache是用来管理缓存的一个工具,其缓存的数据可以是存放在内存里面的,也可以是存放在硬盘上的。其核心是CacheManager,一切Ehcache的应用都是从CacheManager开始的。它是用来管理Cache(缓存)的,一个应用可以有多个CacheManager,而一个CacheManager下又可以有多个Cache。Cache内部保存的是一个个的Element,而一个Element中保存的是一个key和value的配对,相当于Map里面的一个Entry。
-
Ehcache缓存过期策略 (操作系统里面就有呀~)
-
当缓存需要被清理时(比如空间占用已经接近临界值了),需要使用某种淘汰算法来决定清理掉哪些数据。常用的淘汰算法有下面几种:
FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。
LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。(默认)
LFU:Least Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。
注意:
- 缓存是容器存放在内存中,为了保证持久化机制,将缓存中的值持久化到硬盘上(日志缓存文件格式)。否则服务器重启 就没了哦!
- 缓存框架都是支持对内存和硬盘支持
- JVM内置缓存实现 (为了减轻数据库访问压力)
- 内存吃不愁销,限制而容量大小,会有相应的算法处理机制。
单机版缓存框架
-
单点缓存框架 只能针对单个jvm中,缓存容器存放jvm中,每个缓存互不影响 Ehcache gauva chache 内置缓存框架 jvm缓存框架
-
分布式缓存技术(共享缓存数据) Redis Meacache
example:
mybatis、hibernate底层都使用了Ehcache
本地缓存Ehcache 什么是Ehcache Ehcache是纯java的开源缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。它主要面向通用缓存、Java EE和轻量级容器,具有内存和磁盘存储、缓存加载器、缓存扩展、缓存异常处理程序。
Ehcache最初由Greg Luck于2003年开始开发。2009年,该项目被Terracotta购买。软件仍然开源,但一些新的主要功能(例如,快速可重启性之间的一致性的)只能在商业产品中使用。
Ehcache 被广泛用于在Hibernate、Spring、Cocoon等其他开源系统。
Ehcache的主要特性 1.快速;
2.简单;
3.多种缓存策略;
4.缓存数据有两级:内存和磁盘,因此无需担心容量问题;
5.缓存数据会在虚拟机重启的过程中写入磁盘;
6.可以通过 RMI、可插入 API 等方式进行分布式缓存;
7.具有缓存和缓存管理器的侦听接口;(为了做集群)
8.支持多缓存管理器实例,以及一个实例的多个缓存区域;
9.提供 Hibernate 的缓存实现;
Ehcache使用介绍
Ehcache是用来管理缓存的一个工具,其缓存的数据可以是存放在内存里面的,也可以是存放在硬盘上的。其核心是CacheManager,一切Ehcache的应用都是从CacheManager开始的。它是用来管理Cache(缓存)的,一个应用可以有多个CacheManager,而一个CacheManager下又可以有多个Cache。Cache内部保存的是一个个的Element,而一个Element中保存的是一个key和value的配对,相当于Map里面的一个Entry。
-
Ehcache缓存过期策略 (操作系统里面就有呀~)
-
当缓存需要被清理时(比如空间占用已经接近临界值了),需要使用某种淘汰算法来决定清理掉哪些数据。常用的淘汰算法有下面几种:
FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。
LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。(默认)
LFU:Least Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。
注意:
- 缓存是容器存放在内存中,为了保证持久化机制,将缓存中的值持久化到硬盘上(日志缓存文件格式)。否则服务器重启 就没了哦!
- 缓存框架都是支持对内存和硬盘支持
- JVM内置缓存实现 (为了减轻数据库访问压力)
- 内存吃不愁销,限制而容量大小,会有相应的算法处理机制。
Spring Boot 整合EhCache:
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.toov5.architect</groupId> <artifactId>architect</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <!-- SpringBoot 对lombok 支持 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- SpringBoot web 核心组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <!-- SpringBoot 外部tomcat支持 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!-- springboot-log4j --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency> <!-- springboot-aop 技术 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang --> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <!--开启 cache 缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- ehcache缓存 --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.9.1</version><!--$NO-MVN-MAN-VER$ --> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!-- mysql 依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> </project>
实体类:
@Data public class Users { private String name; private Integer age; }
controller:
@RestController public class IndexController { @Autowired private UserService userService; @RequestMapping("/getUser") public List<Users> getUser(Long id){ return userService.getUser(id); } }
Service
@Service public class UserService { @Autowired private UserMapper userMapper; public List<Users> getUser(Long id){ return userMapper.getUser(id); } }
Mapper:
//引入的jar包后就有了这个注解了 非常好用 (配置缓存的基本信息) @CacheConfig(cacheNames={"userCache"}) //缓存的名字 整个类的 public interface UserMapper { @Select("SELECT ID ,NAME,AGE FROM users where id=#{id}") @Cacheable //让这个方法实现缓存 查询完毕后 存入到缓存中 不是每个方法都需要缓存呀!save()就不用了吧 List<Users> getUser(@Param("id") Long id); }
EhCache配置:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <diskStore path="java.io.tmpdir/ehcache-rmi-4000" /> <!-- 默认缓存 --> <defaultCache maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="true" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <!-- demo缓存 --><!-- name="userCache" 对应我们在 @CacheConfig(cacheNames={"userCache"}) !!!!! --> <!--Ehcache底层也是用Map集合实现的 --> <cache name="userCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <!-- LRU缓存策略 --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> </ehcache>
yml:
###端口号配置
server:
port: 8080
###数据库配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
test-while-idle: true
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
time-between-eviction-runs-millis: 300000
min-evictable-idle-time-millis: 1800000
# 缓存配置读取
cache:
type: ehcache
ehcache:
config: classpath:app1_ehcache.xml
启动:
@EnableCaching //开启缓存 @MapperScan(basePackages={"com.toov5.mapper"}) @SpringBootApplication(scanBasePackages={"com.toov5.*"}) public class app { public static void main(String[] args) { SpringApplication.run(app.class, args); } }
项目结构:
访问:
备注介绍:【EhCache参数相关】
1. diskStore :指定数据(.data and .index)存储位置,可指定磁盘中的文件夹位置期 The diskStore element is optional. It must be configured if you have overflowToDisk or diskPersistent enabled for any cache. If it is not configured, a warning will be issues and java.io.tmpdir will be used.
2. defaultCache : 默认的管理策略
Ehcache 使用Map集合实现的 element 其实就是 key 和value
一、以下属性是必须的:
1、name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。
2、maxElementsInMemory:在内存中缓存的element的最大数目。
3、maxElementsOnDisk:在磁盘上缓存的element的最大数目,默认值为0,表示不限制。
4、eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
5、overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上。
二、以下属性是可选的:
1、timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
2、timeToLiveSeconds: 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
3、diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
4、diskExpiryThreadIntervalSeconds: 对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。
5、diskSpoolBufferSizeMB: DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。
6、memoryStoreEvictionPolicy: 如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。
EhCache同步DB:
场景描述:
update 或者delete 容易造成缓存和DB不同步问题
解决方案:
- 主动通知或者定时Job
- update语句后,加上通知,进行主动通知
- 先修改成功 然后清理缓存。【都是在同一个事务中的】
- 定时Job健康检查,检查DB和缓存是否一致。然后决定执行清理缓存
@RestController public class IndexController { @Autowired private CacheManager cacheManager; @Autowired private UserService userService; //注意引入的jar包是 org.springframework.cache.CacheManager; @RequestMapping("/remoKey") public void remoKey() { cacheManager.getCache("userCache").clear(); //传入名字 进行清除 } @RequestMapping("/getUser") public List<Users> getUser(Long id){ return userService.getUser(id); } }
1.访问:
2.修改数据
3.手动清理缓存,接口调用
4.访问:
分布式缓存
EhCache+Redis实现分布式缓存
Ehcache集群模式介绍
由于 EhCache 是进程中的缓存系统,一旦将应用部署在集群环境中,每一个节点维护各自的缓存数据,当某个节点对缓存数据进行更新,这些更新的数据无法在其它节点中共享,这不仅会降低节点运行的效率,而且会导致数据不同步的情况发生。例如某个网站采用 A、B 两个节点作为集群部署,当 A 节点的缓存更新后,而 B 节点缓存尚未更新就可能出现用户在浏览页面的时候,一会是更新后的数据,一会是尚未更新的数据,尽管我们也可以通过 Session Sticky 技术来将用户锁定在某个节点上,但对于一些交互性比较强或者是非 Web 方式的系统来说,Session Sticky 显然不太适合。
EhCache常用集群模式
EhCache从1.7版本开始,支持五种集群方案,分别是:
- Terracotta
- RMI
RMi集群模式
你如何知道集群环境中的其他缓存?
• 分布式传送的消息是什么形式?
• 什么情况需要进行复制?增加(Puts),更新(Updates)或是失效(Expiries)?
• 采用什么方式进行复制?同步还是异步方式?
1、正确的元素类型:只有可序列化的元素可以进行复制。一些操作,比如移除,只需要元素的键值而不用整个元素;在这样的操作中即使元素不是可序列化的但键值是可序列化的也可以被复制。
2、成员发现(Peer Discovery):Ehcache进行集群的时候有一个cache组的概念。每个cache都是其他cache的一个peer,没有主cache的存在。成员发现(Peer Discovery)正是用来解决 “你如何知道集群环境中的其他缓存?” 这个问题的。Ehcache提供了两种机制用来进行成员发现,即:自动成员发现和手动成员发现。要使用一个内置的成员发现机制要在ehcache的配置文件中指定cacheManagerPeerProviderFactory元素的class属性为
net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory。
- JMS
- JGroups
- EhCache Server
Ehcache的使用场景
- 使用纯java的ehcache作为本地缓存
- Reids 作为远程分布式缓存
- 解决redis缓存压力过大,提高缓存速度,以及缓存性能。
Redis和Ehcache缓存的区别
- 如果是单个应用或者对缓存访问要求很高的应用,用EhCache。
- 如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。
- EHCache适合单体项目缓存,不适合于分布式。(不支持共享),因为他们存放在JVM中,互不影响。
- 分布式缓存架构中,使用Redis+EHCache实现分布式缓存。分布式 一级、二级缓存。 比如: 如果Redis集群 因为 某些原因 全部宕机 。 客户端会一直查询数据库!
- Redis支持分布式共享查询
注: EhCache支持分布式缓存 但是不推荐
EhCache实际使用场景总结:
- 我们在项目中使用集中式缓存(Redis或者式Memcached等),通常都是检查缓存中是否存在期望值的数据,如果存在直接返回,如果不存在就查询数据库让后在将数据库缓存,这个时候如果缓存系统因为某写原因宕机,造成服务无法访问,那么大的量请求直接穿透到数据库,最数据库压力非常大。这时候我们让ehcache作为二级缓存,当redis服务器宕机后,可以查询ehcache缓存。 这样能够有效的扛住服务器请求压力。
分布式缓存图 :
Spring Boot2.0+Redis+Ehcache实现二级缓存
前言:
- 先走本地,本地没有再走网络 尽量少走Redis 效率会高一些
- Ehchache 不需要走网络,直接从内存中获取.由于Ehchache容器限制,会持久化在硬盘上。
实现:
-
spring boot中集成了spring cache,并有多种缓存方式的实现,如:Redis、Caffeine、JCache、EhCache等等。但如果只用一种缓存,要么会有较大的网络消耗(如Redis),要么就是内存占用太大(如Caffeine这种应用内存缓存)。在很多场景下,可以结合起来实现一、二级缓存的方式,能够很大程度提高应用的处理效率。
-
spring cache:主要包含spring cache定义的接口方法说明和注解中的属性说明
补充: 缓存、两级缓存
简单的理解,缓存就是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘-->内存。平时我们会将数据存储到磁盘上,如:数据库。如果每次都从数据库里去读取,会因为磁盘本身的IO影响读取速度,所以就有了像redis这种的内存缓存。可以将数据读取出来放到内存里,这样当需要获取数据时,就能够直接从内存中拿到数据返回,能够很大程度的提高速度。但是一般redis是单独部署成集群,所以会有网络IO上的消耗,虽然与redis集群的链接已经有连接池这种工具,但是数据传输上也还是会有一定消耗。所以就有了应用内缓存,如:caffeine。当应用内缓存有符合条件的数据时,就可以直接使用,而不用通过网络到redis中去获取,这样就形成了两级缓存。应用内缓存叫做一级缓存,远程缓存(如redis)叫做二级缓存
spring boot + spring cache 实现两级缓存(redis + caffeine)代码实现:
实体类:
@Data public class Users implements Serializable{ private String name; private Integer age; }
controller
@RestController public class IndexController { @Autowired private UserService userService; @RequestMapping("/userId") public Users getUserId(Long id){ return userService.getUser(id); } }
Service
@Component public class EhCacheUtils { // @Autowired // private CacheManager cacheManager; @Autowired private EhCacheCacheManager ehCacheCacheManager; // 添加本地缓存 (相同的key 会直接覆盖) public void put(String cacheName, String key, Object value) { Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName); Element element = new Element(key, value); cache.put(element); } // 获取本地缓存 public Object get(String cacheName, String key) { Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName); Element element = cache.get(key); return element == null ? null : element.getObjectValue(); } public void remove(String cacheName, String key) { Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName); cache.remove(key); } }
@Component public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; //这样该方法支持多种数据类型 public void set(String key , Object object, Long time){ //开启事务权限 stringRedisTemplate.setEnableTransactionSupport(true); try { //开启事务 stringRedisTemplate.multi(); String argString =(String)object; //强转下 stringRedisTemplate.opsForValue().set(key, argString); //成功就提交 stringRedisTemplate.exec(); } catch (Exception e) { //失败了就回滚 stringRedisTemplate.discard(); } if (object instanceof String ) { //判断下是String类型不 String argString =(String)object; //强转下 //存放String类型的 stringRedisTemplate.opsForValue().set(key, argString); } //如果存放Set类型 if (object instanceof Set) { Set<String> valueSet =(Set<String>)object; for(String string:valueSet){ stringRedisTemplate.opsForSet().add(key, string); //此处点击下源码看下 第二个参数可以放好多 } } //设置有效期 if (time != null) { stringRedisTemplate.expire(key, time, TimeUnit.SECONDS); } } //做个封装 public void setString(String key, Object object){ String argString =(String)object; //强转下 //存放String类型的 stringRedisTemplate.opsForValue().set(key, argString); } public void setSet(String key, Object object){ Set<String> valueSet =(Set<String>)object; for(String string:valueSet){ stringRedisTemplate.opsForSet().add(key, string); //此处点击下源码看下 第二个参数可以放好多 } } public String getString(String key){ return stringRedisTemplate.opsForValue().get(key); } }
@Component public class UserService { @Autowired private EhCacheUtils ehCacheUtils; @Autowired private RedisService redisService; @Autowired private UserMapper userMapper; //定义个全局的cache名字 private String cachename ="userCache"; public Users getUser(Long id){ //先查询一级缓存 key以当前的类名+方法名+id+参数值 String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName() + "-id:" + id; //查询一级缓存数据有对应值的存在 如果有 返回 Users user = (Users)ehCacheUtils.get(cachename, key); if (user != null) { System.out.println("key"+key+",直接从一级缓存获取数据"+user.toString()); return user; } //一级缓存没有对应的值存在,接着查询二级缓存 // redis存对象的方式 json格式 然后反序列号 String userJson = redisService.getString(key); //如果rdis缓存中有这个对应的值,修改一级缓存 最下面的会有的 相同会覆盖的 if (!StringUtil.isNullOrEmpty(userJson)) { //有 转成json JSONObject jsonObject = new JSONObject();//用的fastjson Users resultUser = jsonObject.parseObject(userJson,Users.class); ehCacheUtils.put(cachename, key, resultUser); return resultUser; } //都没有 查询DB Users user1 = userMapper.getUser(id); if (user1 == null) { return null; } //存放到二级缓存 redis中 redisService.setString(key, new JSONObject().toJSONString(user1)); //存放到一级缓存 Ehchache ehCacheUtils.put(cachename, key, user1); return user1; } }
启动类
@EnableCaching //开启缓存 @MapperScan(basePackages={"com.toov5.mapper"}) @SpringBootApplication(scanBasePackages={"com.toov5.*"}) public class app { public static void main(String[] args) { SpringApplication.run(app.class, args); } }
app1.ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <diskStore path="java.io.tmpdir/ehcache-rmi-4000" /> <!-- 默认缓存 --> <defaultCache maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="true" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <!-- demo缓存 --><!-- name="userCache" 对应我们在 @CacheConfig(cacheNames={"userCache"}) !!!!! --> <!--Ehcache底层也是用Map集合实现的 --> <cache name="userCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <!-- LRU缓存策略 --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用于在初始化缓存,以及自动设置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> </ehcache>
yml:
###端口号配置
server:
port: 8080
###数据库配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
test-while-idle: true
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
time-between-eviction-runs-millis: 300000
min-evictable-idle-time-millis: 1800000
# 缓存配置读取
cache:
type: ehcache
ehcache:
config: classpath:app1_ehcache.xml
redis:
database: 0
host: 192.168.91.3
port: 6379
password: 123
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
运行结果
后面的访问,在控制台打印:
代码一定要注意:
- 一级缓存过期时间, 比二级要短一些。
- 这里的两级缓存时间问题,执行设置二级缓存时候需要时间的,所以这两个时间设置问题一定要注意了。如下图:
分布式缓存的思考:
- 应用缓存: 缓存命中率、缓存回收策略 Java缓存类型、磁盘缓存
- Http缓存: Http客户端缓存、 NginxHttp缓存设置、Nginx代理缓存
- 多级缓存: 缓存数据、 分布式缓存与负载均衡、热点数据与更新缓存、缓存崩溃与快速修复