springboot2.1.3 + redisTemplate + Lock 操做 redis 3.0.5

近期在整合springboot + redis 的功能,原本想用原生的jedit api,最後想一想有點 low,搜了一把,boot已經提供給咱們操做的方法,那就是java

使用 redisTemplate 或 StringRedisTemplate, 二者是有區別的,能夠看下面的說明node

1. 二者的關係是StringRedisTemplate繼承RedisTemplate。web

2. 二者的數據是不共通的;也就是說StringRedisTemplate只能管理StringRedisTemplate裏面的數據,RedisTemplate只能管理RedisTemplate中的數據。redis

3. SDR默認採用的序列化策略有兩種,一種是String的序列化策略,一種是JDK的序列化策略。spring

StringRedisTemplate默認採用的是String的序列化策略,保存的key和value都是採用此策略序列化保存的。數據庫

RedisTemplate默認採用的是JDK的序列化策略,保存的key和value都是採用此策略序列化保存的。apache

引自: https://blog.csdn.net/yifanSJ/article/details/79513179json

 

好了,有關概念的解釋不在此處詳細說明,這裏只是記錄如何快速搭建和實現操做redis,先看下個人工程結構,如圖:api

 

 

引入相關jar包,pom.xml以下:springboot

    <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

相關文章
相關標籤/搜索