zoukankan      html  css  js  c++  java
  • springboot2.1.3 + redisTemplate + Lock 操作 redis 3.0.5

    近期在整合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类:

    package com.szl.demo.common.redisConfig;
    
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.annotation.*;
    
    @EnableCaching
    @Configuration
    public class RedisConfiguration extends CachingConfigurerSupport {
        
        /**
         * @param connectionFactory
         * @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 = new ObjectMapper(); 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(); return redisTemplate; } }

    DTO 类:

    package com.szl.demo.common.dto;
    
    import java.io.Serializable;
    import lombok.Data;
    
    @Data
    public class UserDto implements Serializable {
        private static final long serialVersionUID = -8858511759866491158L;
        
        private String userName;
        private Integer userAge;
        
    }

    UserService接口:

    package com.szl.demo.service;
    
    import com.szl.demo.common.dto.UserDto;
    
    public interface UserService {
        
        /**
         * @param userDto
         * @desc 将字符串保存到redis中
         */
        public void saveUserInfoToRedis();
        
        /**
         * @param key
         * @return
         * @desc 从redis中读取字符串
         */
        public String getUserInfoFromRedis(String key);
        
        
        /**
         * @param userDto
         * @desc 将对象保存到redis中
         */
        public void saveUserObject(UserDto userDto);
        
        /**
         * @param userName
         * @return
         * @desc 从redis中获取对象
         */
        public UserDto findUserObject(String userName);
        
        /**
         * @param userDto
         * @desc 锁机制保存对象数据
         */
        public void lockOfUserProcess(UserDto userDto);
        
    }

    UserServiceImpl实现接口:

    package com.szl.demo.service.impl;
    
    import java.util.concurrent.TimeUnit;
    import javax.annotation.Resource;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import com.szl.demo.common.dto.UserDto;
    import com.szl.demo.common.redisConfig.RedisDistributedLock;
    import com.szl.demo.common.util.JsonWare;
    import com.szl.demo.service.UserService;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @Service("userService")
    public class UserServiceImpl implements UserService {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Autowired
        private RedisDistributedLock redisDistributedLock;
        
        /**
         * @param userDto
         * @desc 将字符串保存到redis中
         */
        public void saveUserInfoToRedis() {
            // 判断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哈哈"); } } /** * @param key * @return * @desc 从redis中读取字符串 */ public String getUserInfoFromRedis(String key) { String val = (String)redisTemplate.opsForValue().get(key); return val; } /** * @param userDto * @desc 将对象保存到redis中 */ public void saveUserObject(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()); } } /** * @param userName * @return * @desc 从redis中获取对象 */ public UserDto findUserObject(String userName) { UserDto userDto = (UserDto) redisTemplate.opsForValue().get(userName); return userDto; } /** * @param userDto * @desc 锁机制保存对象数据 */ public void lockOfUserProcess(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 = new UserDto(); 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类:

    package com.szl.demo.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import com.szl.demo.common.dto.UserDto;
    import com.szl.demo.service.UserService;
    
    @Controller
    public class DemoController {
        @Autowired
        private UserService userService;
        
        @RequestMapping(value = "/saveUser", method = RequestMethod.POST)
        public void saveUser(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
            userService.saveUserInfoToRedis();
        }
        
        @RequestMapping(value = "/getUserInfo", method = RequestMethod.GET)
        public void getUserInfo(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 void saveUserObject(HttpServletRequest request, HttpServletResponse response) {
            UserDto dto = new UserDto();
            dto.setUserName("Jimmy Shan");
            dto.setUserAge(21);
            userService.saveUserObject(dto);
        }
        
        @RequestMapping(value = "/getUserObject", method = RequestMethod.GET)
        public void getUserObject(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 void lockDealWithDemo(HttpServletRequest request, HttpServletResponse response) {
            UserDto dto = new UserDto();
            dto.setUserName("JimmyShan");
            dto.setUserAge(16);
            userService.lockOfUserProcess(dto);
            System.out.println("这是lock的demo请求");
        }
        
    }

    RedisDistributedLock类(锁的工具类) :

    package com.szl.demo.common.redisConfig;
    
    import javax.annotation.Resource;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @author Jimmy Shan
     * @desc Redis 锁工具类
     */
    @Slf4j
    @Component
    public class RedisDistributedLock {
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
        
        /**
         * @param key           redis key, 唯一键
         * @param value         redis value, 这里是时间戳
         * @return
         * @desc 加锁 true已锁  false未锁
         */
        public boolean setLock(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;
        }
    
        /**
         * @param key           redis key, 唯一键
         * @param value         redis value, 这里是时间戳
         * @return
         * @desc 释放锁 true已释放  false未释放
         */
        public void releaseLock(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);
            }
        }
    }

    我们去控制台看下效果

    现在我们能看到,value为 "abc123,你好,welcome." 中的 中文字体已经被序列化了, 还有 UserDto对象,是以 json格式存储。

    以上属于直连模式,这种方式在访问量不高的时候,足够应付,游刃有余,反之,该如何处理呢 ?

    答案是:连接池

    下面是如何使用连接池的方法,以上的代码项保持不变,只要修改 “ RedisConfiguration ” 这个类即可,看下面具体实现

    使用连接池的 RedisConfiguration 类:

    package com.szl.demo.common.redisConfig;
    
    import java.time.Duration;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
    import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import redis.clients.jedis.JedisPoolConfig;
    import com.fasterxml.jackson.annotation.*;
    
    @EnableCaching
    @Configuration
    public class RedisConfiguration extends CachingConfigurerSupport {
        @Autowired
        private Environment env;
        
        /**
         * @param connectionFactory
         * @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 = new ObjectMapper();
            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();
            return redisTemplate;
        }
        
        /**
         * @desc 使用jedis pool创建连接(连接池配置)
         */
        private RedisConnectionFactory connectionPoolsFactory() {
            JedisPoolConfig poolConfig = new JedisPoolConfig();
            // 最大空闲连接数, 默认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 = new RedisStandaloneConfiguration();
            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 new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
        }
        
    }

    至此,我们就跑起来看效果了,以上是本人经过测试并通过的代码和配置,另外需要说明一点,redis服务器本人使用3.0.5, 之前在使用2.4.5的时候,总是连接死锁(win环境),折腾了许久,

    最后还是更新高版本解决问题。

    如有朋友参考本人的笔记,有问题可以留言,转载请注明原著,谢谢。

    锁来源参考:https://blog.csdn.net/qq_26525215/article/details/79182687

  • 相关阅读:
    Python 编程快速上手 第八章总结
    Python 编程快速上手 第七章总结
    Python 编程快速上手 第六章总结
    Python 编程快速上手 第五章总结
    Processing 与 C 相同和不同的地方
    learn python the hard way习题31~40总结以及列表的扩展知识
    while循环
    初识python
    第九章 类
    第八章 函数
  • 原文地址:https://www.cnblogs.com/jimmyshan-study/p/11007101.html
Copyright © 2011-2022 走看看