近期在整合springboot + redis 的功能,本来想用原生的jedit api,最后想想有点 low,搜了一把,boot已经提供给我们操作的方法,那就是
使用 redisTemplate 或 StringRedisTemplate, 两者是有区别的,可以看下面的说明
1. 两者的关系是StringRedisTemplate继承RedisTemplate。
2. 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
3. SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
引自: https://blog.csdn.net/yifanSJ/article/details/79513179
好了,有关概念的解释不在此处详细说明,这里只是记录如何快速搭建和实现操作redis,先看下我的工程结构,如图:
引入相关jar包,pom.xml如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
<!-- 因为需要使用lettuce连接池,这个包必须添加 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.1</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.8</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.8</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version><!--$NO-MVN-MAN-VER$-->
</dependency> </dependencies>
配置Redis参数 application.properties:
# 配置redis参数
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# 连接超时时间,单位(毫秒)
spring.redis.timeout=5000
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=20000
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=10
# 集群
#spring.redis.cluster.nodes=192.168.211.134:7000,192.168.211.134:7001,192.168.211.134:7002
#spring.redis.cluster.max-redirects=6
创建RedisConfiguration类:
packagecom.szl.demo.common.redisConfig; importorg.springframework.cache.annotation.CachingConfigurerSupport; importorg.springframework.cache.annotation.EnableCaching; importorg.springframework.context.annotation.Configuration; importorg.springframework.data.redis.connection.RedisConnectionFactory; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; importorg.springframework.data.redis.serializer.RedisSerializer; importorg.springframework.data.redis.serializer.StringRedisSerializer; importcom.fasterxml.jackson.annotation.PropertyAccessor; importcom.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.*; @EnableCaching @Configuration public class RedisConfiguration extendsCachingConfigurerSupport { /*** @paramconnectionFactory * @return* @desc redis模板,存储关键字是字符串, * 值jackson2JsonRedisSerializer是序列化后的值 */
@Bean public RedisTemplate<String, Object>redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); //开启事务 redisTemplate.setEnableTransactionSupport(true); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = newObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); //使用StringRedisSerializer来序列化和反序列化redis的key值 RedisSerializer<?> redisSerializer = new StringRedisSerializer(); //key redisTemplate.setKeySerializer(redisSerializer); redisTemplate.setHashKeySerializer(redisSerializer); //value redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); returnredisTemplate; } }
DTO 类:
packagecom.szl.demo.common.dto; importjava.io.Serializable; importlombok.Data; @Data public class UserDto implementsSerializable { private static final long serialVersionUID = -8858511759866491158L; privateString userName; privateInteger userAge; }
UserService接口:
packagecom.szl.demo.service; importcom.szl.demo.common.dto.UserDto; public interfaceUserService { /*** @paramuserDto * @desc 将字符串保存到redis中 */ public voidsaveUserInfoToRedis(); /*** @paramkey * @return* @desc 从redis中读取字符串 */ publicString getUserInfoFromRedis(String key); /*** @paramuserDto * @desc 将对象保存到redis中 */ public voidsaveUserObject(UserDto userDto); /*** @paramuserName * @return* @desc 从redis中获取对象 */ publicUserDto findUserObject(String userName); /*** @paramuserDto * @desc 锁机制保存对象数据 */ public voidlockOfUserProcess(UserDto userDto); }
UserServiceImpl实现接口:
packagecom.szl.demo.service.impl; importjava.util.concurrent.TimeUnit; importjavax.annotation.Resource; importorg.springframework.beans.BeanUtils; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.stereotype.Service; importcom.szl.demo.common.dto.UserDto; importcom.szl.demo.common.redisConfig.RedisDistributedLock; importcom.szl.demo.common.util.JsonWare; importcom.szl.demo.service.UserService; importlombok.extern.slf4j.Slf4j; @Slf4j @Service("userService") public class UserServiceImpl implementsUserService { @Autowired private RedisTemplate<String, Object>redisTemplate; @Autowired privateRedisDistributedLock redisDistributedLock; /*** @paramuserDto * @desc 将字符串保存到redis中 */ public voidsaveUserInfoToRedis() { //判断redis中是否存在key boolean isExist = redisTemplate.hasKey("demo_test02"); if (!isExist) { //保存key,有效期为30秒
String msg = "abc123,你好,welcome."; redisTemplate.opsForValue().set("demo_test02", msg, 30, TimeUnit.SECONDS); } else{ //删除key redisTemplate.delete("demo哈哈"); } } /*** @paramkey * @return* @desc 从redis中读取字符串 */ publicString getUserInfoFromRedis(String key) { String val =(String)redisTemplate.opsForValue().get(key); returnval; } /*** @paramuserDto * @desc 将对象保存到redis中 */ public voidsaveUserObject(UserDto userDto) { //判断redis中是否存在key boolean isExist =redisTemplate.hasKey(userDto.getUserName()); if (!isExist) { //保存key,有效期为30秒 redisTemplate.opsForValue().set(userDto.getUserName(), userDto, 30, TimeUnit.SECONDS); } else{ //删除key redisTemplate.delete(userDto.getUserName()); } } /*** @paramuserName * @return* @desc 从redis中获取对象 */ publicUserDto findUserObject(String userName) { UserDto userDto =(UserDto) redisTemplate.opsForValue().get(userName); returnuserDto; } /*** @paramuserDto * @desc 锁机制保存对象数据 */ public voidlockOfUserProcess(UserDto userDto) { String key = "myLock_" +userDto.getUserName(); int timeout = 300 * 1000;//超时时间 5分钟 long value = System.currentTimeMillis() +timeout; try{ //加锁 if (!redisDistributedLock.setLock(key, String.valueOf(value))) { throw new Exception("对不起,redis被挤爆了,请休息片刻再重试。"); } //做一些业务相关操作,这里只是demo,随便保存个对象信息 redisTemplate.opsForValue().set(userDto.getUserName(), userDto, 60, TimeUnit.SECONDS); log.info("原来值内容:" +JsonWare.beanToJson(userDto)); //修改值,重新保存到redis中 UserDto dto = newUserDto(); BeanUtils.copyProperties(userDto, dto); dto.setUserAge(30); redisTemplate.opsForValue().set(dto.getUserName(), dto, 60, TimeUnit.SECONDS); log.info("修改值内容:" +JsonWare.beanToJson(dto)); } catch(Exception e) { log.error("异常发生,信息如下:", e.getMessage()); } finally{ //释放锁 redisDistributedLock.releaseLock(key, String.valueOf(value)); } } }
Controller类:
packagecom.szl.demo.controller; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Controller; importorg.springframework.ui.ModelMap; importorg.springframework.web.bind.annotation.RequestMapping; importorg.springframework.web.bind.annotation.RequestMethod; importorg.springframework.web.bind.annotation.RequestParam; importcom.szl.demo.common.dto.UserDto; importcom.szl.demo.service.UserService; @Controller public classDemoController { @Autowired privateUserService userService; @RequestMapping(value = "/saveUser", method =RequestMethod.POST) public voidsaveUser(HttpServletRequest request, HttpServletResponse response, ModelMap model) { userService.saveUserInfoToRedis(); } @RequestMapping(value = "/getUserInfo", method =RequestMethod.GET) public voidgetUserInfo(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "key", required = false) String key) { String msg =userService.getUserInfoFromRedis(key); System.out.println(msg); } @RequestMapping(value = "/saveUserObject", method =RequestMethod.POST) public voidsaveUserObject(HttpServletRequest request, HttpServletResponse response) { UserDto dto = newUserDto(); dto.setUserName("Jimmy Shan"); dto.setUserAge(21); userService.saveUserObject(dto); } @RequestMapping(value = "/getUserObject", method =RequestMethod.GET) public voidgetUserObject(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "key", required = false) String key) { UserDto dto =userService.findUserObject(key); System.out.println("姓名: " + dto.getUserName() + ", 年龄: " +dto.getUserAge()); } @RequestMapping(value = "/lockDealWithDemo", method =RequestMethod.GET) public voidlockDealWithDemo(HttpServletRequest request, HttpServletResponse response) { UserDto dto = newUserDto(); dto.setUserName("JimmyShan"); dto.setUserAge(16); userService.lockOfUserProcess(dto); System.out.println("这是lock的demo请求"); } }
RedisDistributedLock类(锁的工具类) :
packagecom.szl.demo.common.redisConfig; importjavax.annotation.Resource; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.stereotype.Component; importorg.springframework.util.StringUtils; importlombok.extern.slf4j.Slf4j; /*** @authorJimmy Shan * @desc Redis 锁工具类 */@Slf4j @Component public classRedisDistributedLock { @Resource private RedisTemplate<String, Object>redisTemplate; /*** @paramkey redis key, 唯一键 * @paramvalue redis value, 这里是时间戳 * @return* @desc 加锁 true已锁 false未锁 */ public booleansetLock(String key, String value) { if(redisTemplate.opsForValue().setIfAbsent(key, value)) { //对应setnx命令 //可以成功设置,也就是key不存在 return true; } //判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁 String currentValue =(String) redisTemplate.opsForValue().get(key); //如果锁过期 //currentValue 不为空且小于当前时间 if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) <System.currentTimeMillis()) { //获取上一个锁的时间value //对应getset,如果key存在返回当前key的值,并重新设置新的值 //redis是单线程处理,即使并发存在,这里的getAndSet也是单个执行 //所以,加上下面的 !StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue) //就能轻松解决并发问题 String oldValue =(String) redisTemplate.opsForValue().getAndSet(key,value); if(!StringUtils.isEmpty(oldValue) &&oldValue.equals(currentValue)) { return true; } } return false; } /*** @paramkey redis key, 唯一键 * @paramvalue redis value, 这里是时间戳 * @return* @desc 释放锁 true已释放 false未释放 */ public voidreleaseLock(String key, String value) { try{ String currentValue =(String) redisTemplate.opsForValue().get(key); if(!StringUtils.isEmpty(currentValue) &¤tValue.equals(value)) { redisTemplate.opsForValue().getOperations().delete(key);//删除key } } catch(Exception e) { log.error("解锁出现异常了,{}", e); } } }
我们去控制台看下效果
现在我们能看到,value为 "abc123,你好,welcome." 中的 中文字体已经被序列化了, 还有 UserDto对象,是以 json格式存储。
以上属于直连模式,这种方式在访问量不高的时候,足够应付,游刃有余,反之,该如何处理呢 ?
答案是:连接池
下面是如何使用连接池的方法,以上的代码项保持不变,只要修改 “RedisConfiguration ” 这个类即可,看下面具体实现
使用连接池的 RedisConfiguration 类:
packagecom.szl.demo.common.redisConfig; importjava.time.Duration; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.cache.annotation.CachingConfigurerSupport; importorg.springframework.cache.annotation.EnableCaching; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importorg.springframework.core.env.Environment; importorg.springframework.data.redis.connection.RedisConnectionFactory; importorg.springframework.data.redis.connection.RedisStandaloneConfiguration; importorg.springframework.data.redis.connection.jedis.JedisClientConfiguration; importorg.springframework.data.redis.connection.jedis.JedisConnectionFactory; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; importorg.springframework.data.redis.serializer.RedisSerializer; importorg.springframework.data.redis.serializer.StringRedisSerializer; importcom.fasterxml.jackson.annotation.PropertyAccessor; importcom.fasterxml.jackson.databind.ObjectMapper; importredis.clients.jedis.JedisPoolConfig; import com.fasterxml.jackson.annotation.*; @EnableCaching @Configuration public class RedisConfiguration extendsCachingConfigurerSupport { @Autowired privateEnvironment env; /*** @paramconnectionFactory * @return* @desc redis模板,存储关键字是字符串, * 值jackson2JsonRedisSerializer是序列化后的值 */@Bean public RedisTemplate<String, Object>redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionPoolsFactory()); //开启事务 //redisTemplate.setEnableTransactionSupport(true); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = newObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); //使用StringRedisSerializer来序列化和反序列化redis的key值 RedisSerializer<?> redisSerializer = newStringRedisSerializer(); //key redisTemplate.setKeySerializer(redisSerializer); redisTemplate.setHashKeySerializer(redisSerializer); //value redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); returnredisTemplate; } /*** @desc 使用jedis pool创建连接(连接池配置) */ privateRedisConnectionFactory connectionPoolsFactory() { JedisPoolConfig poolConfig = newJedisPoolConfig(); //最大空闲连接数, 默认8个 poolConfig.setMaxIdle(Integer.parseInt(env.getProperty("spring.redis.jedis.pool.max-idle"))); //最小空闲连接数, 默认0 poolConfig.setMinIdle(Integer.parseInt(env.getProperty("spring.redis.jedis.pool.min-idle"))); //最大连接数, 默认8个 poolConfig.setMaxTotal(Integer.parseInt(env.getProperty("spring.redis.jedis.pool.max-active"))); //获取连接时的最大等待毫秒数, 如果不超时设置: -1 poolConfig.setMaxWaitMillis(Long.parseLong(env.getProperty("spring.redis.jedis.pool.max-wait"))); //逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 poolConfig.setTimeBetweenEvictionRunsMillis(-1); //在获取连接的时候检查有效性, 默认false poolConfig.setTestOnBorrow(true); //在空闲时检查有效性, 默认false poolConfig.setTestWhileIdle(true); JedisClientConfiguration jedisClientConfiguration =JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).and() .readTimeout(Duration.ofMillis(Long.parseLong(env.getProperty("spring.redis.timeout")))) .connectTimeout(Duration.ofMillis(Long.parseLong(env.getProperty("spring.redis.timeout")))) .build(); RedisStandaloneConfiguration redisStandaloneConfiguration = newRedisStandaloneConfiguration(); redisStandaloneConfiguration.setDatabase(Integer.parseInt(env.getProperty("spring.redis.database"))); redisStandaloneConfiguration.setHostName(env.getProperty("spring.redis.host")); redisStandaloneConfiguration.setPassword(env.getProperty("spring.redis.password")); redisStandaloneConfiguration.setPort(Integer.parseInt(env.getProperty("spring.redis.port"))); return newJedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration); } }
至此,我们就跑起来看效果了,以上是本人经过测试并通过的代码和配置,另外需要说明一点,redis服务器本人使用3.0.5, 之前在使用2.4.5的时候,总是连接死锁(win环境),折腾了许久,
最后还是更新高版本解决问题。
如有朋友参考本人的笔记,有问题可以留言,转载请注明原著,谢谢。
锁来源参考:https://blog.csdn.net/qq_26525215/article/details/79182687