EHCache 本地缓存
Redis 分布式缓存(可以共享)
一级 Redis 二级Ehcache 当redis挂了 有备胎
反之:
先走本地,本地没有再走网络 尽量少走Redis 效率会高一些
Redis与数据库的区别:
相同点 都是需要进行网络连接
不同点 是存放的介质 内存 和 硬盘
数据库需要做IO操作 性能比直接操作内存效率要低
Ehchache
不需要走网络 直接从内存中获取
由于Ehchache容器限制,会持久化在硬盘上,
Redis+ehCache实现两级级缓存
spring boot中集成了spring cache,并有多种缓存方式的实现,如:Redis、Caffeine、JCache、EhCache等等。但如果只用一种缓存,要么会有较大的网络消耗(如Redis),要么就是内存占用太大(如Caffeine这种应用内存缓存)。在很多场景下,可以结合起来实现一、二级缓存的方式,能够很大程度提高应用的处理效率。
内容说明:
缓存、两级缓存
spring cache:主要包含spring cache定义的接口方法说明和注解中的属性说明
spring boot + spring cache:RedisCache实现中的缺陷
caffeine简介
spring boot + spring cache 实现两级缓存(redis + caffeine)
缓存、两级缓存
简单的理解,缓存就是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘–>内存。平时我们会将数据存储到磁盘上,如:数据库。如果每次都从数据库里去读取,会因为磁盘本身的IO影响读取速度,所以就有了像redis这种的内存缓存。可以将数据读取出来放到内存里,这样当需要获取数据时,就能够直接从内存中拿到数据返回,能够很大程度的提高速度。但是一般redis是单独部署成集群,所以会有网络IO上的消耗,虽然与redis集群的链接已经有连接池这种工具,但是数据传输上也还是会有一定消耗。所以就有了应用内缓存,如:caffeine。当应用内缓存有符合条件的数据时,就可以直接使用,而不用通过网络到redis中去获取,这样就形成了两级缓存。应用内缓存叫做一级缓存,远程缓存(如redis)叫做二级缓存
过期问题:
一级缓存 过期 时间 比二级要短一些
目录结构:
实体类:
注意一定要 序列号
package com.toov5.entity; import java.io.Serializable; import lombok.Data; @Data public class Users implements Serializable{ private String name; private Integer age; }
controller层
package com.toov5.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.toov5.entity.Users; import com.toov5.service.UserService; @RestController public class IndexController { @Autowired private UserService userService; @RequestMapping("/userId") public Users getUserId(Long id){ return userService.getUser(id); } }
service层
package com.toov5.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.stereotype.Component; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; @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); } }
package com.toov5.service; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @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); } }
package com.toov5.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject; import com.toov5.entity.Users; import com.toov5.mapper.UserMapper; import ch.qos.logback.core.net.SyslogOutputStream; import io.netty.util.internal.StringUtil; @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; } }
启动类:
package com.toov5.app; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @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
运行结果:
后面的访问,在控制台打印:
其中一定要注意
这里的 两级缓存时间问题 执行设置二级缓存时候 需要时间的 所以这两个时间设置问题一定要注意了哦
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/16145.html