springboot中redis的使用
springboot中redis的使用
一、springboot整合redis
1.1 基本使用
1、导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
2、添加redis配置
spring.redis.password=123456
此时已经可以在代码中使用redisTemplate对象了。例如:
@ServicepublicclassDepartmentServiceImplimplementsDepartmentService{@ResourceprivateDepartmentDAO departmentDAO;@ResourceprivateRedisTemplate redisTemplate;@OverridepublicList<Department>findAll(){// json处理的对象ObjectMapper mapper =newObjectMapper();// 1、查询缓存// 定义一个key(唯一、尽量不会被推导)// 使用一个唯一的名字 + 参数值 + md5String key =MD5Utils.md5("findAllDepartments");// 得到redisTemplate中的操作对象BoundValueOperations<String,String> boundValueOps = redisTemplate.boundValueOps(key);// 得到缓存中的值String value = boundValueOps.get();System.out.println("value==="+ value);// 2、判断缓存是否有该数据if(value !=null){// 3、如果有则返回该数据System.out.println("查询缓存");List<Department> list =null;try{
list = mapper.readValue(value,newTypeReference<List<Department>>(){});}catch(Exception e){
e.printStackTrace();}return list;}else{// 4、如果没有,则去数据库中查询,并保存到缓存中System.out.println("查询数据库");List<Department> departments = departmentDAO.findAll();if(departments !=null&& departments.size()>0){// 转换成json格式try{String string = mapper.writeValueAsString(departments);// 保存到缓存中
boundValueOps.set(string);}catch(Exception e){
e.printStackTrace();}}return departments;}}}
但是,查看redis中存储的数据发现,key和value前面都有一段看似乱码的数据,而且中文也进行编码。为了解决该问题,需要参考前面ssm中整合redis的方案,对redis的key和value进行配置。
3、添加redis的设置
@ConfigurationpublicclassMyRedisConfig{@BeanpublicRedisTemplate<String,String>redisTemplate(RedisConnectionFactory factory){StringRedisTemplate template =newStringRedisTemplate(factory);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer(Object.class);ObjectMapper om =newObjectMapper();
om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();return template;}}
再使用前面的service代码进行测试,发现上述问题已解决。
1.2 使用缓存注解
直接编写redis的逻辑,相对代码比较复杂,而且逻辑基本固定,可以使用缓存注解来简化该流程。
spring框架提供一套缓存注解:
@Cacheable:表示当前查询结果会使用缓存。(如果有缓存,直接使用,否则进行数据库查询并放入缓存) @CachePut:表示当前操作的数据的结果会更新到缓存中。(如果修改的数据在缓存存在,会同时修改缓存中的数据) @CacheEvict:表示如果删除的数据在缓存中,也会对应的删除。
1、添加缓存注解的配置
@Configuration// 配置文件@EnableCaching// 允许缓存使用,开启缓存的配置,需要继承CachingConfigurerSupport类publicclassMyRedisConfigextendsCachingConfigurerSupport{@BeanpublicCacheManagercacheManager(RedisConnectionFactory factory){RedisSerializer<String> keyRedisSerializer =newStringRedisSerializer();// redis的key序列化方式Jackson2JsonRedisSerializer valueRedisSerializer =newJackson2JsonRedisSerializer(Object.class);// redis的value的序列化//解决查询缓存转换异常的问题ObjectMapper om =newObjectMapper();
om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
valueRedisSerializer.setObjectMapper(om);//配置序列化(解决乱码的问题)RedisCacheConfiguration config =RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ZERO)// 默认生存时间.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keyRedisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueRedisSerializer)).disableCachingNullValues();//缓存配置mapMap<String,RedisCacheConfiguration> cacheConfigurationMap=newHashMap<>();//自定义缓存名,后面使用的@Cacheable的CacheName
cacheConfigurationMap.put("myRedis",config);// cacheConfigurationMap.put("default",config);RedisCacheManager cacheManager =RedisCacheManager.builder(factory).cacheDefaults(config).withInitialCacheConfigurations(cacheConfigurationMap).build();return cacheManager;}@BeanpublicRedisTemplate<String,String>redisTemplate(RedisConnectionFactory factory){StringRedisTemplate template =newStringRedisTemplate(factory);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer(Object.class);ObjectMapper om =newObjectMapper();
om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();return template;}}
4、使用
@ServicepublicclassEmployeeServiceImplimplementsEmployeeService{@ResourceprivateEmployeeDAO employeeDAO;@OverridepublicPageInfofindAll(Integer page){PageHelper.startPage(page,10);List<Employee> list = employeeDAO.findAll();PageInfo info =newPageInfo(list);return info;}@Override// @Cacheable将查询结果进行缓存// key的定义:// T()里面写一个类型,表示当作一个类处理,后面调用该类方法// 如果是字符串,需要使用单引号// 如果要使用方法中的参数,需要在变量名前面加上#@Cacheable(cacheNames ="myRedis", key ="T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)")publicEmployeefindById(Integer id){System.out.println("进入方法,去数据库查询");return employeeDAO.findById(id);}@Overridepublicvoidsave(Employee employee){
employeeDAO.save(employee);}@Override// @CachePut将返回值作为数据替换掉原来的缓存数据@CachePut(cacheNames ="myRedis", key ="T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #employee.id)")publicEmployeeupdate(Employee employee){
employee.setUpdatetime(newDate());
employeeDAO.update(employee);return employee;}@Override// @CacheEvict删除缓存@CacheEvict(cacheNames ="myRedis", key ="T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)")publicvoiddelete(Integer id){
employeeDAO.delete(id);}}
二、redis做缓存过程中的难点以及解决方案
1、问题
为了让缓存使用方式通用,选择了使用注解的方案,即@Cacheable, @CachePut等。
思考一下缓存中需要处理的问题,在使用注解过程中是否得以解决。
问题如下:缓存击穿,缓存穿透,大量的key过期导致的雪崩问题。
2、问题的思考
1、缓存击穿问题的原因是同一时间大量的请求访问,导致访问到数据库,可以使用缓存预热来解决。或者对请求使用削峰。【已解决】
2、穿透问题的原因是无法缓存null值,导致每次都访问数据库。解决问题的方案需要缓存空值。而且需要设置一个较短的过期时间,相较于非空数据的时间要短很多。【未解决】
3、大量key过期导致的缓存雪崩问题。原因主要是由于缓存时间设置一致。解决方案是设置不同的时间。【未解决】
3、思考的过程以及解决方案
1、在@Cache注解中使用unless属性。
@Cacheable(value=“XXX”,key=“#info”,unless = “#result==null”)
上面的办法能够解决空值出现的异常问题,但是又出现了新的问题------缓存穿透,每一次都会访问数据库。
2、使用自定义的AOP方案,切入到缓存存储的时机,自定义过程。
【方式比较麻烦,而且中间还会产生aop失效问题https://my.oschina.net/guangshan/blog/1807721】
3、自己使用redisTemplet的opsForValue().set()方法。
可能需要写很多次代码,也比较麻烦,而且没有充分利用到@Cacheable注解。
4、我还是希望站在巨人的肩膀上
找到了一个分布式落地方案:layering-cache
5、在springboot中的使用
1、导入启动器
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 启动器--><dependency><groupId>com.github.xiaolyuh</groupId><artifactId>layering-cache-starter</artifactId><version>3.1.8</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></dependency><!--默认的键值序列化方式--><dependency><groupId>com.esotericsoftware.kryo</groupId><artifactId>kryo</artifactId><version>2.21</version></dependency><!--默认使用的json解析--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency>
2、在application.properties中添加配置
#layering-cache 配置
layering-cache.stats=true
# 缓存命名空间,如果不配置取 "spring.application.name"
layering-cache.namespace=layering-cache-web
# redis单机
layering-cache.redis.database=0
layering-cache.redis.host=127.0.0.1
layering-cache.redis.port=6379
layering-cache.redis.password=
# redis集群
#layering-cache.redis.password=
#layering-cache.redis.cluster=127.0.0.1:6379,127.0.0.1:6378
3、配置类中添加注解
@EnableLayeringCache
启用
layering-cache
@SpringBootApplication@EnableLayeringCache// 启用多级缓存框架publicclassLayeringCacheStartDemoApplication{publicstaticvoidmain(String[] args){SpringApplication.run(LayeringCacheStartDemoApplication.class, args);}}
4、使用注解配置
不需要使用MyRedisConfig配置类。
importcom.github.xiaolyuh.annotation.*;@ServicepublicclassEmployeeServiceImplimplementsEmployeeService{@ResourceprivateEmployeeDAO employeeDAO;@OverridepublicList<Employee>findAll(){return employeeDAO.findAll();}@Override@Cacheable(value ="employee:info", key ="T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)", depict ="用户信息缓存", enableFirstCache =true,
firstCache =@FirstCache(expireTime =400, timeUnit =TimeUnit.SECONDS),
secondaryCache =@SecondaryCache(expireTime =1000, preloadTime =200,
forceRefresh =true, timeUnit =TimeUnit.SECONDS, isAllowNullValue =true, magnification =100))publicEmployeefindById(Integer id){System.out.println("查询数据库");return employeeDAO.findById(id);}@Override@CachePut(value ="employee:info", key ="T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)", depict ="用户信息缓存", enableFirstCache =true,
firstCache =@FirstCache(expireTime =400, timeUnit =TimeUnit.SECONDS),
secondaryCache =@SecondaryCache(expireTime =1000, preloadTime =200,
forceRefresh =true, timeUnit =TimeUnit.SECONDS, isAllowNullValue =true, magnification =100))publicEmployeeupdate(Employee employee){
employee.setUpdatetime(newDate());
employeeDAO.update(employee);returnfindById(employee.getId());}@CacheEvict(value ="employee:info", key ="T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)")publicvoiddelete(Integer id){// employeeDAO.delete(id);}}
版权归原作者 盖伦暴打诺手 所有, 如有侵权,请联系我们删除。