spring数据缓存
A. SpringBoot整合SpringSeesion实现Redis缓存
使用Spring Boot开发项目时我们经常需要存储Session,因为Session中会存一些用户信息或者登录信息。传统的web服务是将session存储在内存中的,一旦服务挂了,session也就消失了,这时候我们就需要将session存储起来,而Redis就是用来缓存seesion的一种非关系型数据库,我们可以通过配置或者注解的方式将Spring Boot和Redis整合。而在分布式系统中又会涉及到session共享的问题,多个服务同时部署时session需要共享,Spring Session可以帮助我们实现这一功能。将Spring Session集成到Spring Boot框架中并使用Redis进行缓存是目前非常流行的解决方案,接下来就跟着我一起学习吧。
工具/材料
IntelliJ IDEA
首先我们创建一个Spring Boot 2.x的项目,在application.properties配置文件中添加Redis的配置,Spring和Redis的整合可以参考我其他的文章,此处不再详解。我们设置服务端口server.port为8080端口用于启动第一个服务。
接下来我们需要在pom文件中添加spring-boot-starter-data-redis和spring-session-data-redis这两个依赖,spring-boot-starter-data-redis用于整合Spring Boot和Redis,spring-session-data-redis集成了spring-session和spring-data-redis,提供了session与redis的整合方案。
接下来我们创建一个配置类RedisSessionConfig,这个类使用@Configuration注解表明这是一个配置类。在这个类上我们同时添加注解@EnableRedisHttpSession,表示开启Redis的Session管理。如果需要设置失效时间可以使用@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)表示一小时后失效。若同时需要设置Redis的命名空间则使用@EnableRedisHttpSession(maxInactiveIntervalInSeconds=3600, redisNamespace="{spring.session.redis.namespace}") ,其中{spring.session.redis.namespace}表示从配置文件中读取这个命名空间。
配置完成后我们写一个测试类SessionController,在这个类中我们写两个方法,一个方法用于往session中存数据,一个用于从session中取数据,代码如下图所示,我们存取请求的url。启动类非常简单,一般都是通用的,我们创建一个名为SpringbootApplication的启动类,使用main方法启动。
接下来我们使用Postman分别请求上面两个接口,先请求存数据接口,再请求取数据接口,结果如下图所示,我们可以看到数据已从redis中取出。另外需要注意sessionId的值,这是session共享的关键。
为了验证两个服务是否共享了session,我们修改项目的配置文件,将服务端口server.port改为8090,然后再启动服务。此时我们不必在请求存数据的接口,只需要修改请求端口号再一次请求取数据的接口即可。由下图可以看到两次请求的sessionId值相同,实现了session的共享。
以上我们完成了SpringBoot整合SpringSeesion实现Redis缓存的功能,在此我们还要推荐一个Redis的可视化工具RedisDesktopManager,我们可以配置Redis数据库的连接,然后便可以非常直观地查看到存储到Redis中的session了,如下图所示,session的命名空间是share,正是从配置文件中读取到的。
特别提示
如果Redis服务器是很多项目共用的,非常建议配置命名空间,否则同时打开多个项目的浏览器页面可能会导致session错乱的现象。
B. Spring的三级缓存
例如:A依赖B , B 依赖 C , C 依赖 A 这样就是一个简单的循环依赖。
创建bean的流程为:
一级缓存:singletonObjects
二级缓存:earlySingletonObjects
三级缓存:singletonFactories
1、创建A的时候 去一级缓存中查询A对象 为 null,这时会继续将A的实例创建出来(调用构造方法),未进行初始化,创建半成品A,将半成品A 放入到 三级缓存中。
2、初始化A的时候发现需要B,但是B未创建,这时走创建B的流程,这时会继续将B的实例创建出来(调用构造方法),未进行初始化,创建半成品B,将半成品B放入到三级缓存中。
3、初始化B的时候发现需要C,但是C未创建,这时走创建C的流程,这时会继续将C的实例创建出来(调用构造方法),未进行初始化,创建半成品C,将半成品C放入到三级缓冲中。
4、接下来初始化C发现需要A,从一级缓存和二级缓存中取不到A,但是从三级缓冲中取到A,将A放入到二级缓存并从三级缓存中清除。
5、C对象创建成功放入到一级缓存,C对象创建好了以后B对象也可以初始化成功,再将B对象放入一级缓存。
6、最后初始化A对象,放入一级缓存。
二级缓存可以解决循环依赖的问题,但是不能解决aop场景下的循环依赖,这样注入到其他bean的都是原始对象,而不是最终的代理对象。
C. 如何在spring中使用缓存
在Spring缓存机制中,包括了两个方面的缓存操作:1.缓存某个方法返回的结果;2.在某个方法执行前或后清空缓存。
Spring仅仅是提供了对缓存的支持,但它并没有任何的缓存功能的实现,spring使用的是第三方的缓存框架来实现缓存的功能。其中,spring对EHCache提供了很好的支持。
参考博客:http://www.cnblogs.com/fysola/p/6378400.html
D. SpringBoot缓存(Ehcache)
(3)如果我们想要自定 Ehcache 配置文件的名称和位置,可以在 application.properties 中添加如下配置:
执行结果下如下:
可以看到第一次从DB查询了数据,第二次则从缓存读取,当更新DB数据时同时也更新了缓存数据,再次取数据仍然走缓存。
对于高频数据,一般希望长时间缓存,对于临时数据,如验证码,token等,一般需要一个指定的时间,到期则取消。
可以通过一个类去控制缓存的有效时间,一般指定 timeToIdleSeconds=0 表示数据一直有效, timeToLiveSeconds=X秒 指定过期时间,如下:
然后在controller灵活的调用
对于无状态的系统,可以方便的缓存token,验证码等短时有效的数据,方便存取。
E. spring 如何解决循环依赖,为什么要创建三个缓存
spring是如何解决单例的循环依赖注入
首先看下面三个缓存:
1.singletonObjects:存放初始化好的bean
2.earlySingletonObjects:存放了刚实例化好的,但是还未配置属性和初始化的bean,我们在获取该bean的时候会调用beanPostProcessor的getEarlyReference进行一些提前获取bean的必要操作
3.singletonFactories:存放我刚实例化的bean,通过ObjectFactory,可以让如果有提前需要bean的需要可以调用该
objectfactory 其会将刚实例化好的bean经过beanPostProcessor的getEarlyReference处理进行返回
我们先实例化A,实例化好后
调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
() -> getEarlyBeanReference(beanName, mbd, bean)是objectFactory的实现匿名类,然后此时我们设置属性的时候会发现我们还依赖B,于是我们去实例化B而当我们此时实例化B的会经过下面这个方法
由此可见 如果之前已经把A存放在singletonFactories,那么B会把其调出来放入earlySingletonObjects,然后整个单例创建完成在塞入singletonObjects。存放在singletonFactories好处是可扩展,我们在这个里面会调用beanPostProcessor 从而可以在我们实现提前获取对象引用的时候进行一些操作
F. SpringBoot进阶之缓存中间件Redis
大家好,一直以来我都本着 用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 “基础知识” 的铺垫
“大佬可以绕过 ~”
本节给大家讲讲 “java的SpringBoot框架” , 之前我们学习的都是java的基础知识和底层提供的一些能力,我们日常工作都是在写接口。在我们在产品开发中,一般我们都会选择比较稳定的框架来帮我们加速开发,不会自己去造轮子,而在java众多框架中,spring框架表现的非常好,大部分公司都会首选它作为开发框架,而至今,大部分企业都是以 springboot 来构建项目了,一个稳健的系统需要引入稳定的技术~
如果你是一路看过来的,很高兴你能够耐心看完。前几期都是带大家学习了 SpringBoot 的基础使用以及集成 mybatis 开发,这也是我们写业务的基础,如果你还不熟悉这些,请先看完它们。接下来的几期内容将会带大家进阶使用,会先讲解基础 中间件 的使用和一些场景的应用,或许这些技术你听说过,没看过也没关系,我会带大家一步一步的入门,耐心看完你一定会有 收获 ,本期将会给大家讲解最热门的缓存中间件技术 Redis ,同样的,我们集成到 Springboot 中。最近github可能会被墙,所以我把源码放到了国内gitee上,本节我们依然使用上期的代码
Redis 是由意大利人Salvatore Sanfilippo(网名:antirez)开发的一款内存高速缓存数据库。全称叫 Remote Dictionary Server(远程数据服务) 是由 C语言 编写的,Redis是一个 key-value 存储系统,它支持丰富的数据类型,如: string、list、set、zset(sorted set)、hash 。
它本质上是一种键值对数据库,我们之前学习的 mysql 它是持久层的关系型数据库,而 redis 它的存储主要存在 内存 中。我们都知道在 内存 中的数据读取是非常快的,就好比你把一个变量存到磁盘读取和直接放到代码中运行,肯定是在代码中拿到的速度快,因为运行时期,都是直接存到内存的。
给大家总结一下:
有了基本的概念之后,我们下面进行环境搭建,在学习阶段,安装 redis 很简单,生产环境一般我们也会选择云产品,一切为了服务保障,虽说它只是做缓存用,但也是系统的一把 保护伞
如果你是 mac 用户,你可以运行如下命令:
安装完成后会提示你运行命令,运行即可。
win 用户也很简单,直接下载 redis 软件,双击运行即可,运行之后它会有一个小方块的图案,和 locahost:6379 的log,说明运行成功了。初始阶段没有配置的 redis 默认 host 就是本地, port 就是 6379 , 而且是 没有密码 就可以访问的。
推荐一个客户端软件 Redis Desktop Manager ,它是 redis 的客户端界面软件,方便面我们学习的时候 清理缓存 使用,生产慎连。
我们不给大家讲它的基本命令使用,它也有语法,可以通过类似命令执行,如果想学习的小伙伴,可以自行搜索。本期重点内容是在 sprinboot 中的使用,我们平时开发不可能是去命令行里敲的,都是代码里执行,而目前市面上有很多封装好的库,我们可以直接调用它的方法,很方便的就可以操作它了,不用记一些繁琐的命令,下面我们就实际操作一下:
修改 pom.xml
修改 application.yml :
redis 默认是有 16 个库,不是 15 个啊,从 0 开始算的,我们随便连一个
通过代码很好理解, 首先需要引入 StringRedisTemplate ,然后需要设置一个 key ,那么思考一下,这个 key 允许重复吗
我们进客户端看一下,发现 key 还是只有一个,但是值变成了新的值了,所以可以得知 key 是唯一的,我们重新设置的时候相当于刷新了它。
在 redis 中删除缓存有两种方式,一种是自我消亡,也就是 过期 销毁,还有有一种是 主动 销毁,我们先看一下,过期时间如何设置
我们设置了 10s 后过期,过完10s后发现,这个```key data``消失了。我们在看看如何主动删除
我们可以利用 Redis 做一个计数器,实现自增功能,你可以用它做网站访问统计
通常做法,我们会把它封装一下,后续使用直接引入封装好的即可,把它直接交给 Springboot容器 管理
其实这个类,你还可以继续进一步封装,比如约束 key 的规范,约束过期时间,约束数据类型等等,这一切也都是为了规范和后期维护,防止滥用缓存
缓存的主要场景是用于解决热点数据问题,因为这些数据是访问频率比较高的,当大量的请求进来, mysql 可能压力很大,这样一来,数据查询效率就很慢,用户肯不高兴等了,这样用户体验很不好。所以我们一般做法,都是把这些热点数据放到缓存里,因为缓存读取速度很快。当有新数据的时候,我们再及时更新它,一般流程是先查询缓存,查到了直接返回缓存数据,查不到再走数据库,然后再刷回缓存。
但是并发足够大的时候,还是会暴露出很多问题,比如面试常问的一些高频问题 缓存雪崩、缓存穿透、缓存雪崩 ,这些问题后边会给大家专门讲,和如何去防范。所以总的来说,引入任何一门技术并不是万事大吉,还需我们不断的在实践中积累经验
本期到这里就结束了,总结一下,我们了解了什么是 redis ,以及在 springboot 中如何去使用它们,很简单,没什么复杂的东西。但这里想多说一点的是,缓存的设计却是很复杂的,因为工具是死的,人是活的,我们如何正确设计,需要我们在项目中不断的积累。
我们之前教大家查询列表数据,都是所有数据返回,还没有教大家如何去做分页,下期将带大家学习一下 mybatis 分页插件的使用 ,下期不见不散, 关注我,不迷路~
G. Spring本地缓存的使用方法
我们现在在用的Spring Cache,可以直接看Spring Boot提供的缓存枚举类,有如下这些:
EhCache:一个纯Java的进程内缓存框架,所以也是基于本地缓存的。(注意EhCache2.x和EhCache3.x相互不兼容)。
Redis:分布式缓存,只有Client-Server(CS)模式,Java一般使用Jedis/Luttuce来操纵。
Hazelcast:基于内存的数据网格。虽然它基于内存,但是分布式应用程序可以使用Hazelcast进行分布式缓存、同步、集群、处理、发布/订阅消息等。
Guava:它是Google Guava工具包中的一个非常方便易用的本地化缓存实现,基于LRU(最近最少使用)算法实现,支持多种缓存过期策略。在Spring5.X以后的版本已经将他标记为过期了。
Caffeine:是使用Java8对Guava缓存的重写版本,在Spring5中将取代了Guava,支持多种缓存过期策略。
SIMPLE:使用ConcurrentMapCacheManager,因为不支持缓存过期时间,所以做本地缓存基本不考虑该方式。
关于分布式缓存,我们需要后面会专门讨论Redis的用法,这里只看本地缓存。性能从高到低,依次是Caffeine,Guava,ConcurrentMapCacheManager,其中Caffeine在读写上都快了Guava近一倍。
这里我们只讨论在Spring Boot里面怎么整合使用Caffeine和EhCache。
主要有以下几个步骤:
1)加依赖包:
2)配置缓存:
这里有两种方法,通过文件配置或者在配置类里面配置,先看一下文件配置,我们可以写一个properties文件,内容像这样:
然后还要在主类中加上@EnableCaching注解:
另外一种更灵活的方法是在配置类中配置:
应用类:
测试类:
导入依赖包,分为2.x版本和3.x版本。
其中2.x版本做如下导入:
3.x版本做如下导入:
导包完成后,我们使用JCacheManagerFactoryBean + ehcache.xml的方式配置:
参考资料:
https://blog.csdn.net/f641385712/article/details/94982916
http://www.360doc.com/content/17/1017/20/16915_695800687.shtml
H. spring自带缓存机制怎么弄
此缓存方法既适用于层,也适用于service层。
spring配置文件配置:<!--缓存配置-->
<!--启用缓存注解功能-->
<cache:annotation-drivencache-manager="cacheManager"/>
<!--spring自己的基于java.util.concurrent.ConcurrentHashMap实现的缓存管理器(该功能是从Spring3.1开始提供)-->
<beanid="cacheManager"class="org.springframework.cache.support.SimpleCacheManager">
<propertyname="caches">
<set>
<!--此处类concurrentMapCacheFactoryBean的作用是
-->
<beanname="myCache"class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"/>
</set>
</property>
</bean>service层示例如下:@Transactional(readOnly=true)
@Cacheable(value="myCache")
publicJsonObjectgetWFK(JsonObjectparams){
OneObob=newOneOb();
try{
List<Map<String,Object>>map=LawAssistantMapper.getWFK();
ob.setOb(map);
}catch(Exceptione){
e.printStackTrace();
ob.setCode(500);
ob.setMsg("服务器错误!!!");
returnnewJsonObject(Json.encode(ob));
}
ob.setCode(200);
ob.setMsg("ok");
logger.debug(Json.encode(ob));
returnnewJsonObject(Json.encode(ob));
}
由于使用的是spring自带的缓存类,所以,仅仅需要两步:1.在spring配置文件中声明,2.在service层,方法代码前增加注解,即可。
缺点:
spring自带的缓存功能,实质上是通过java类来保存缓存的数据,这样会占用一定的内存消耗,并发率越高,对内存的压力越大。
码民直接使用的缓存类:org.springframework.cache.support.SimpleCacheManager,org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean
I. windows环境下Redis+Spring缓存实例
一、Redis了解
1.1、Redis介绍:
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis数据库完全在内存中,使用磁盘仅用于持久性。相比许多键值数据存储,Redis拥有一套较为丰富的数据类型。Redis可以将数据复制到任意数量的从服务器。
1.2、Redis优点:
(1)异常快速:Redis的速度非常快,每秒能执行约11万集合,每秒约81000+条记录。
(2)支持丰富的数据类型:Redis支持最大多数开发人员已经知道像列表,集合,有序集合,散列数据类型。这使得它非常容易解决各种各样的问题,因为我们知道哪些问题是可以处理通过它的数据类型更好。
(3)操作都是原子性:所有Redis操作是原子的,这保证了如果两个客户端同时访问的Redis服务器将获得更新后的值。
(4)多功能实用工具:Redis是一个多实用的工具,可以在多个用例如缓存,消息,队列使用(Redis原生支持发布/订阅),任何短暂的数据,应用程序,如Web应用程序会话,网页命中计数等。
1.3、Redis缺点:
(1)单线程
(2)耗内存
二、64位windows下Redis安装
Redis官方是不支持windows的,但是Microsoft Open Tech group 在 GitHub上开发了一个Win64的版本,下载地址:https://github.com/MSOpenTech/redis/releases。注意只支持64位哈。
小宝鸽是下载了Redis-x64-3.0.500.msi进行安装。安装过程中全部采取默认即可。
安装完成之后可能已经帮你开启了Redis对应的服务,博主的就是如此。查看资源管理如下,说明已经开启:
已经开启了对应服务的,我们让它保持,下面例子需要用到。如果没有开启的.,我们命令开启,进入Redis的安装目录(博主的是C:Program FilesRedis),然后如下命令开启:
redis-server redis.windows.conf
OK,下面我们进行实例。
三、详细实例
本工程采用的环境:Eclipse + maven + spring + junit
3.1、添加相关依赖(spring+junit+redis依赖),pom.xml:
4.0.0 com.luo redis_project 0.0.1-SNAPSHOT 3.2.8.RELEASE 4.10 org.springframework spring-core ${spring.version} org.springframework spring-webmvc ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-context-support ${spring.version} org.springframework spring-aop ${spring.version} org.springframework spring-aspects ${spring.version} org.springframework spring-tx ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-web ${spring.version} junit junit ${junit.version} test org.springframework spring-test ${spring.version} test org.springframework.data spring-data-redis 1.6.1.RELEASE redis.clients jedis 2.7.3
3.2、spring配置文件application.xml:
<"1.0" encoding="UTF-8"> classpath:properties/*.properties
3.3、Redis配置参数,redis.properties:
#redis中心#绑定的主机地址redis.host=127.0.0.1#指定Redis监听端口,默认端口为6379redis.port=6379#授权密码(本例子没有使用)redis.password=123456 #最大空闲数:空闲链接数大于maxIdle时,将进行回收redis.maxIdle=100 #最大连接数:能够同时建立的“最大链接个数”redis.maxActive=300 #最大等待时间:单位msredis.maxWait=1000 #使用连接时,检测连接是否成功 redis.testOnBorrow=true#当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能redis.timeout=10000
3.4、添加接口及对应实现RedisTestService.Java和RedisTestServiceImpl.java:
package com.luo.service; public interface RedisTestService { public String getTimestamp(String param);}
package com.luo.service.impl; import org.springframework.stereotype.Service;import com.luo.service.RedisTestService; @Servicepublic class RedisTestServiceImpl implements RedisTestService { public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } }
3.5、本例采用spring aop切面方式进行缓存,配置已在上面spring配置文件中,对应实现为MethodCacheInterceptor.java:
package com.luo.redis.cache; import java.io.Serializable;import java.util.concurrent.TimeUnit;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations; public class MethodCacheInterceptor implements MethodInterceptor { private RedisTemplate redisTemplate; private Long defaultCacheExpireTime = 10l; // 缓存默认的过期时间,这里设置了10秒 public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); try { // 判断是否有缓存 if (exists(key)) { return getCache(key); } // 写入缓存 value = invocation.proceed(); if (value != null) { final String tkey = key; final Object tvalue = value; new Thread(new Runnable() { public void run() { setCache(tkey, tvalue, defaultCacheExpireTime); } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null) { return invocation.proceed(); } } return value; } /** * 创建缓存key * * @param targetName * @param methodName * @param arguments */ private String getCacheKey(String targetName, String methodName, Object[] arguments) { StringBuffer sbu = new StringBuffer(); sbu.append(targetName).append("_").append(methodName); if ((arguments != null) && (arguments.length != 0)) { for (int i = 0; i < arguments.length; i++) { sbu.append("_").append(arguments[i]); } } return sbu.toString(); } /** * 判断缓存中是否有对应的value * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * 读取缓存 * * @param key * @return */ public Object getCache(final String key) { Object result = null; ValueOperations operations = redisTemplate .opsForValue(); result = operations.get(key); return result; } /** * 写入缓存 * * @param key * @param value * @return */ public boolean setCache(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations operations = redisTemplate .opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } public void setRedisTemplate( RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }}
3.6、单元测试相关类:
package com.luo.baseTest; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; //指定bean注入的配置文件 @ContextConfiguration(locations = { "classpath:application.xml" }) //使用标准的JUnit @RunWith注释来告诉JUnit使用Spring TestRunner @RunWith(SpringJUnit4ClassRunner.class) public class SpringTestCase extends { }
package com.luo.service; import org.junit.Test;import org.springframework.beans.factory.annotation.Autowired; import com.luo.baseTest.SpringTestCase; public class RedisTestServiceTest extends SpringTestCase { @Autowired private RedisTestService redisTestService; @Test public void getTimestampTest() throws InterruptedException{ System.out.println("第一次调用:" + redisTestService.getTimestamp("param")); Thread.sleep(2000); System.out.println("2秒之后调用:" + redisTestService.getTimestamp("param")); Thread.sleep(11000); System.out.println("再过11秒之后调用:" + redisTestService.getTimestamp("param")); } }
3.7、运行结果:
四、源码下载:redis-project().rar
以上就是本文的全部内容,希望对大家的学习有所帮助。