springboot2.1.3 + redisTemplate + Lock 操作 redis 3.0.5

摘要:
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。引自:https://blog.csdn.net/yifanSJ/article/details/79513179好了,有关概念的解释不在此处详细说明,这里只是记录如何快速搭建和实现操作redis,先看下我的工程结构,如图:引入相关jar包,pom.xml如下:org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-data-redisorg.apache.commonscommons-pool22.6.1˂!

近期在整合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,先看下我的工程结构,如图:

springboot2.1.3 + redisTemplate + Lock 操作 redis 3.0.5第1张

引入相关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) &&currentValue.equals(value)) {
                redisTemplate.opsForValue().getOperations().delete(key);//删除key
}
        } catch(Exception e) {
            log.error("解锁出现异常了,{}", e);
        }
    }
}

我们去控制台看下效果

springboot2.1.3 + redisTemplate + Lock 操作 redis 3.0.5第2张

现在我们能看到,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

免责声明:文章转载自《springboot2.1.3 + redisTemplate + Lock 操作 redis 3.0.5》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇JBoss入门实际体验华为云AI : ModelArts下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android Studio中一套代码多版本打包

一套代码达到以下效果: 打包不同applicationId能同时安装在同一手机上 不同logo,app名称, 不同第三方SDK接入配置(例如微信分享appid,激光推送appkey) 能区分debug和release配置 甚至不同的so文件,不同的依赖jar包 (待补充) 使用到的功能:productFlavor和buildTypes 原理:优先级bu...

.net微信公众号开发——快速入门【转载】

最近在学习微信公众号开发,将学习的成果做成了一个类库,方便重复使用。 现在微信公众号多如牛毛,开发微信的高手可以直接无视这个系列的文章了。 使用该类库的流程及寥寥数行代码得到的结果如下。 本文的源代码主要在:http://git.oschina.net/xrwang2/xrwang.weixin.PublicAccount/blob/master/xr...

redis集群学习

转载: http://arganzheng.life/redis-cluster.html Redis3.0版本加入了cluster功能,解决了Redis单点无法横向扩展的问题。 分布式系统要解决的不外乎以下两个问题: sharding/partition 以提高系统的吞吐率 replication 以提供系统的高可用 然后因为sharding了,所...

组合or继承

面向对象设计有一个原则“优先使用对象组合,而不是继承”。 下面是两者优缺点的比较: 组 合 关 系 继 承 关 系 优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 优点:具有较好的可扩展性 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价 优点:支持...

使用redis作为消息队列的用法

背景 最近项目有个需求需要动态更新规则,当时脑中想到的第一个方案是利用zk的监听机制,管理人员更新完规则将状态写入zk,集群中的机器监听zk的状态,当有状态变更后,集群中的机器开始拉取最新的配置。但由于公司技术选型,没有专门搭建zk集群,因此也不可能为这一个小需求去搭建zk集群。图为使用zk监听状态变化的流程。 最后只好退而求其次,想到了使用redis的...

数据基本类型以及相关举例

数据基本类型: 整型:字节型byte 短整型short  整型int  浮点类型:长整形long  浮点型  fioat   双精度性double  字符型:char  布尔型:boolean   引用类型( reference): 类class   接口interface   数据  array     循环:for   while      do wh...