本篇作为SpringBoot2.1版本的个人开发框架 子章节,请先阅读 SpringBoot2.1版本的个人开发框架 再次阅读本篇文章
项目地址: SpringBoot2.1版本的个人应用开发框架
项目中为什么要用Redis缓存?其实在我实习时是用到过Redis缓存的,但是我只是知道用到了Redis,如何使用的,为什么要用,这些我统统都不知道。引用网上一句话就是,我这次集成也是为了Redis而Redis了,并不是拿Redis来解决实际的问题,但是我觉得只有先学会了,才能知道在什么情况下可以用Redis来解决问题。
为什么要用redis?引用网上的言论
1.解决应用服务器的cpu和内存压力 2.在项目中使用 Redis,主要考虑两个角度:性能和并发。 3.我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。 4.在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。 5.排行榜及相关问题。排行榜(leader board)按照得分进行排序。zadd命令可以直接实现这个功能,而zrevrange命令可以用来按照得分来获取前100名的用户,zrank可以用来获取用户排名,非常直接而且操作容易。 6.计数的问题,比如点赞和转发数,通过原子递增保持计数;getset用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。
我的学习笔记:
win10安装redis和liunx安装redis
Redis学习 RDB和AOF两种持久化介绍以及实现
而且Redis可以master-slave(主从)模式进行数据备份,在分布式的系统中,可以很好保证数据的备份,Redis会自动把主数据库(master)中的数据备份到从数据库(slave)中,关于为什么要用Redis这件事情上,除了本身自己项目中的原因;剩下的有很多原因都可以事先在网络上汲取知识以及经验。
我们把缓存的事情放到ywh-starter-cache这个模块中来集成,在cache中的pom.xml文件中引入 spring-boot-starter-data-redis 依赖
<!-- redis依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.1.1.RELEASE</version> </dependency> 复制代码
cache项目结构分为
为什么用户需要自己创建一个redis的配置类呢?
SpringBoot提供了对Redis的自动配置功能,在 RedisAutoConfiguration 类中默认为我们配置了客户端连接(Lettuce和Jedis),以及数据操作模板(StringRedisTemplate和RedisTemplate),下列代码有一个@ConditionalOnMissingBean和@Bean的注解,@ConditionalOnMissingBean注解判断是否执行初始化代码,即如果用户已经创建了bean,则相关的初始化代码不再执行。这导致了默认的是redisTemplate方法会被执行。
public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } } 复制代码
RedisTemplate这个数据操作模板类我们可以点击去看一看,在类中有一段代码
if (this.defaultSerializer == null) { this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader()); } 复制代码
如果默认的序列化为空则使用jdk来序列化我们的数据,而 defaultSerializer 这个私有属性,默认为NULL,所以默认的序列方式时jdk的方式,但是这个序列化方式会把数据变得人看不懂,所以才需要创建一个Redis配置类覆盖默认的序列化,如果我有分析的有不对的地方,望指正。
分析过后,就以两种方式来进行连接。第一种:不更改默认配置使用StringRedisTemplate和RedisTemplate
在我们写代码测试之前需要在cache模块中配置application-redis.yml文件,并且把core下的application.yml文件中的active属性添加redis 以逗号相隔这样就可在运行的时候读取application-redis.yml的内容,把cache模块下application.properties修改成application-redis.yml文件进行配置。
spring: redis: # Redis数据库索引(默认为0) database: 0 # Redis服务器地址 host: 127.0.0.1 # Redis服务器连接端口 port: 6379 # Redis服务器连接密码(默认为空)如果没有配置密码就不要写这个属性了 password: 123456 #连接池 lettuce: pool: #连接池最大连接数(使用负值表示没有限制) max-active: 8 #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: 60000 #连接池中的最大空闲连接 max-idle: 8 #连接池中的最小空闲连接 min-idle: 0 #连接超时时间(毫秒) timeout: 10000 复制代码
在SpringBoot测试类中编写代码并运行添加数据。
@Autowired private StringRedisTemplate stringRedisTemplate; /** * 测试连接redis,并存入数据 */ @Test public void redisTest(){ List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); stringRedisTemplate.opsForValue().set("abc","测试"); stringRedisTemplate.opsForList().leftPushAll("ywh",list); } 复制代码
我们在用StringRedisTemplate添加的数据显示正常,也是我们人眼能读懂的,接下来我们用RedisTemplate这个类来连接Redis添加数据看一看数据是什么样的,默认的序列化JdkSerializationRedisSerializer的二进制数据序列化方式,代码跟上面差不多。
@Autowired private RedisTemplate<String,Object> redisTemplate; @Test public void redisTest1(){ List<String> list = new ArrayList<>(); list.add("y"); list.add("w"); list.add("h"); redisTemplate.opsForValue().set("redisTemplate","连接成功了"); redisTemplate.opsForList().leftPushAll("redis",list); } 复制代码
果然添加的数据我们人眼分辨不出来这是什么,所以下面我们要进行覆盖默认的配置,定制自己的序列化方式。
spring为我们提供了多种序列化方式,都在org.springframework.data.redis.serializer包下,常用的分别是:
这四种我们只使用StringRedisSerializer来序列化Key值,value值由我们自己创建的序列化类,serializer包下创建我们自定义的 FastJsonRedisSerializer 类,需要实现RedisSerializer接口,实现接口中的序列化方法和反序列化方法,使用的是alibaba的Fastjson实现。
package com.ywh.cache.serializer; /** * CreateTime: 2018-12-19 16:51 * ClassName: FastJsonRedisSerializer * Package: com.ywh.cache.serializer * Describe: * 自定义的序列化类 * @author YWH */ public class FastJsonRedisSerializer<T> implements RedisSerializer<T> { private ObjectMapper objectMapper = new ObjectMapper(); public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class<T> clazz; public FastJsonRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); } public void setObjectMapper(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "'objectMapper' must not be null"); this.objectMapper = objectMapper; } protected JavaType getJavaType(Class<?> clazz) { return TypeFactory.defaultInstance().constructType(clazz); } } 复制代码
创建好自定义的序列化类后,进行覆盖默认的配置,接下来在config包下创建 RedisCacheConfig 类,配置好Redis配置类以后我们再次重新运行测试类,会发现值不再是乱码了。
package com.ywh.cache.config; /** * CreateTime: 2018-12-18 23:34 * ClassName: RedisCacheConfig * Package: com.ywh.cache.config * Describe: * Redis缓存配置 @EnableCaching 开启声明式缓存支持. 之后就可以使用 @Cacheable/@CachePut/@CacheEvict 注解缓存数据. * @author YWH */ @Configuration @EnableCaching public class RedisCacheConfig { private RedisConnectionFactory redisConnectionFactory; @Autowired public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory){ this.redisConnectionFactory = redisConnectionFactory; } /** * 覆盖默认的配置 * @return RedisTemplate */ @Bean public RedisTemplate<String,Object> redisTemplate(){ RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); // 设置value的序列化规则和key的序列化规则 template.setKeySerializer(stringRedisSerializer); template.setValueSerializer(fastJsonRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer); template.setDefaultSerializer(fastJsonRedisSerializer); template.afterPropertiesSet(); return template; } /** * 解决注解方式存放到redis中的值是乱码的情况 * @param factory 连接工厂 * @return CacheManager */ @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); // 配置注解方式的序列化 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)) //配置注解默认的过期时间 .entryTtl(Duration.ofDays(1)); // 加入白名单 https://github.com/alibaba/fastjson/wiki/enable_autotype ParserConfig.getGlobalInstance().addAccept("com.ywh"); ParserConfig.getGlobalInstance().addAccept("com.baomidou"); return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build(); } } 复制代码
redis可以对五种类型操作,可以自己再进行扩展,工具类我们放在cache模块下创建utils包,创建 RedisUtil 工具类,类上添加@Component注解,交给Spring来管理,我们可以直接在其他的方法中用@AtuoWired获取。
package com.ywh.cache.utils; /** * CreateTime: 2018-12-19 17:45 * ClassName: RedisUtil * Package: com.ywh.cache.utils * Describe: * Redis工具类 * * @author YWH */ @Component public class RedisUtil { @Autowired private RedisTemplate<String,Object> redisTemplate; //---------------------- common -------------------------- /** * 指定缓存失效时间 * @param key key值 * @param time 缓存时间 * @return true设置成功,time <=0 设置失败 */ public void expire(String key, long time){ if(time > 0){ redisTemplate.expire(key,time,TimeUnit.SECONDS); }else{ throw MyExceptionUtil.mxe("设置的时间不能为0或者小于0!!"); } } /** * 判断key是否存在 * @param key * @return true 存在 false 不存在 */ public Boolean existsKey(String key){ return redisTemplate.hasKey(key); } 。。。。省略代码,具体代码可前往github查看 } 复制代码