Springboot集成Redis缓存及分布式锁示例

1、pom依赖

<!-- redis -->
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.10.2</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-20</artifactId>
            <version>3.10.2</version>
        </dependency>
        <!--End redis -->

2、yml配置


spring:
  cache:
    type: redis
  redis:
    # redis库
    database: 0
#    #方式一: 集群
#    cluster:
#      nodes: 192.168.56.99:6379,192.168.59.100:6379
    #方式二: 单机  二选一
    port: 6379
    host: 127.0.0.1
    # redis 密码
    password:
    # 连接超时时间(毫秒)
    timeout: 1000
    jedis:
      pool:
        # 连接池最大链接数(负数表示没有限制)
        max-active: 8
        # 连接池最大阻塞等待时间(负数表示没有限制)
        max-wait: 3000
        # 连接池最大空闲连接数
        max-idle: 8
        # 连接池最小空闲连接数
        min-idle: 0

3、config配置


import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;
import java.time.Duration;

@Configuration
@EnableCaching
@Slf4j
@AllArgsConstructor
public class RedisConfig extends CachingConfigurerSupport {

    @Resource
    private final RedisProperties redisProperties;

    /**
     * 配置cacheManager
     *
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//      redisCacheManager构造器需要提供一个redisCacheWriter和一个redisCacheConfigurer
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//      配置cache 序列化为jsonSerializer
        RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration
                .defaultCacheConfig()
                .prefixCacheNameWith("cache:")
                .serializeValuesWith(pair)
                .entryTtl(Duration.ofDays(1));//      设置默认过期时间一天
//                .computePrefixWith(cacheName -> "caching:" + cacheName);

//        也可以通过builder来构建
//        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(defaultCacheConfig).transactionAware().build();
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);

    }

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();

        //单机
        config.useSingleServer()
                .setAddress("redis://" + redisProperties.getHost()+":"+redisProperties.getPort())
        ;

//        //集群
//        //redis服务器地址,多个逗号分隔
//        RedisProperties.Cluster cluster = redisProperties.getCluster();
//        List<String> nodes = cluster.getNodes();
//        Set<String> nodeSet = new HashSet<>();
//        for (String node : nodes) {
//            nodeSet.add("redis://" + node);
//        }
//        config.useClusterServers()
//                .setScanInterval(2000)
//                .addNodeAddress(nodeSet.toArray(new String[nodeSet.size()]))
//                ;
        return Redisson.create(config);
    }

    /**
     * @param redisConnectionFactory:配置不同的客户端,这里注入的redis连接工厂不同: JedisConnectionFactory、LettuceConnectionFactory
     * @功能描述 :配置Redis序列化,原因如下:
     * (1) StringRedisTemplate的序列化方式为字符串序列化,
     * RedisTemplate的序列化方式默为jdk序列化(实现Serializable接口)
     * (2) RedisTemplate的jdk序列化方式在Redis的客户端中为乱码,不方便查看,
     * 因此一般修改RedisTemplate的序列化为方式为JSON方式【建议使用GenericJackson2JsonRedisSerializer】
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // key采用String的序列化方式
        redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
        // value序列化方式采用jackson
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
        //hash的value序列化方式采用jackson
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }

    /**
     * 设置自动key的生成规则,配置spring boot的注解,进行方法级别的缓存
     * 使用:进行分割,可以很多显示出层级关系
     * 这里其实就是new了一个KeyGenerator对象,只是这是lambda表达式的写法,我感觉很好用,大家感兴趣可以去了解下
     * @return
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(":");
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(":" + String.valueOf(obj));
            }
            String rsToUse = String.valueOf(sb);
            log.info("自动生成Redis Key -> [{}]", rsToUse);
            return rsToUse;
        };
    }

    /**
     * 异常处理,当Redis发生异常时,打印日志,但是程序正常走
     * @return
     */
    @Bean
    public CacheErrorHandler errorHandler() {
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key)    {
                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("Redis occur handleCacheClearError:", e);
            }
        };
        return cacheErrorHandler;
    }
}

4、redis锁工具类

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

/***
 * redis分布式锁工具类
 */
@Component
public class RedissonUtil {
    private static final Logger logger = LoggerFactory.getLogger(RedissonUtil.class);
    /**
     * redis锁前缀
     */
    public static final String SYS_LOCK_FLAG = "lock";

    /**
     * 用于隔开缓存前缀与缓存键值
     */
    public static final String KEY_SPLIT = ":";
    // 静态属性注入
    private static RedissonClient redissonClient;
    @Autowired
    public void setRedisson(RedissonClient redissonClient) {
        RedissonUtil.redissonClient = redissonClient;
    }


    /**
     * 加锁
     * @param lockName    锁名  相同的key表示相同的锁,建议针对不同的业务使用不同的key
     * @param expiresTime 过期时间,单位:秒
     * @param waitTime 最大等待时间时间,单位:秒
     * @return
     */
    public static boolean getLock(String lockName, long expiresTime,long waitTime) {
        //最长等待时间
        String key = getLockKey(lockName);
        //获取锁对象
        RLock lock = redissonClient.getLock(key);
        //设置锁过期时间,防止死锁的产生
        boolean flag = true;
        try {
            flag = lock.tryLock(waitTime,expiresTime, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("获取锁成功,Redis Lock key: {}", key);
        return flag;
    }

    /**
     * 释放锁,建议放在 finally里面
     * @param lockName 锁名称
     */
    public static void unlock(String lockName) {
        String key = getLockKey(lockName);
        //获取所对象
        RLock lock = redissonClient.getLock(key);
        // 释放锁,判断要解锁的key是否已被锁定并且是否被当前线程保持
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
            logger.info("释放Redis锁成功,key:{}", key);
        }
    }

    /**
     * 对锁的key添加系统标识前缀
     * @return
     */
    private static String getLockKey(String key) {
        return RedissonUtil.SYS_LOCK_FLAG + RedissonUtil.KEY_SPLIT + key;
    }

}

5、缓存及锁示例

@Service
public class UserDemoService {

    /**
     * 缓存示例:第一次调用记录缓存
     * @param userName
     * @return
     */
    @Cacheable(cacheNames = "users",key = "#userName")
    public UserDemo getByUserName(String userName){
        UserDemo userDemo = new UserDemo();
        userDemo.setUserName("zhangsan");
        userDemo.setPwd("123456");
        System.out.println("getByUserName:"+userDemo.toString());
        return userDemo;
    }

    /**
     * 缓存示例:更新缓存
     * @param userName
     * @param pwd
     * @return
     */
    @CachePut(cacheNames = "users",key = "#userName")
    public UserDemo updateUser(String userName, String pwd){
        UserDemo userDemo = new UserDemo();
        userDemo.setUserName(userName);
        userDemo.setPwd(pwd);
        System.out.println("updateUser:"+userDemo.toString());
        return userDemo;
    }

    /**
     * 缓存示例:删除缓存
     * @param userName
     */
    @CacheEvict(cacheNames = "users",key = "#userName")
    public void delUser(String userName) {
        System.out.println("delUser:"+userName);
    }

    /**
     * 缓存示例:
     * 查询全部用户
     * @return
     */
    @Cacheable(cacheNames = "users", key = "'allUsers'", unless = "#result==null")
    public List<UserDemo> allUsers() {
        System.out.println("allUsers:");
        List<UserDemo> list = new ArrayList<>();
        return list;
    }

    /**
     * 分布式锁示例
     */
    public void test(){
        System.out.println("========开始=====");
        if(RedissonUtil.getLock("mylock",5L,3L)){
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            RedissonUtil.unlock("mylock");
            System.out.println("解锁:"+System.currentTimeMillis());
        }else {
            System.out.println("未获得锁:"+System.currentTimeMillis());
        }

    }


}


版权声明:本文为yancaobisi原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。