【SpringBoot】整合Redis(使用spring-boot-starter-data-redis)

  • Post author:
  • Post category:其他




前言



Jedis和Lettuce


Jedis



Lettuce

的都是连接

Redis Server



客户端程序




Jedis

在实现上是

直连redis server,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接




Lettuce


基于Netty

(事件驱动模型)的连接实例(StatefulRedisConnection),

可以在多个线程间并发访问,且线程安全

,满足多线程环境下的并发访问,

同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例



spring-data-redis


Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能。它提供了用于与存储交互的低级和高级抽象,使用户不必再关注基础设施



spring-data-redis

的优势:

可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache


spring-data-redis

提供了一个

RedisConnectionFactory

接口,通过它可以生成一个

RedisConnection

接口对象,而

RedisConnection

接口对象是

对Redis底层接口的封装

。例如,我们使用的

Jedis

驱动,那么Spring就会提供

RedisConnection

接口的实现类

JedisConnection

去封装原有的

Jedis(redis.clients.jedis.Jedis)

对象。

public interface RedisConnectionFactory extends PersistenceExceptionTranslator {

	/**
	 * Provides a suitable connection for interacting with Redis.
	 *
	 * @return connection for interacting with Redis.
	 */
	RedisConnection getConnection();

	/**
	 * Provides a suitable connection for interacting with Redis Cluster.
	 *
	 * @return
	 * @since 1.7
	 */
	RedisClusterConnection getClusterConnection();

	/**
	 * Specifies if pipelined results should be converted to the expected data type. If false, results of
	 * {@link RedisConnection#closePipeline()} and {RedisConnection#exec()} will be of the type returned by the underlying
	 * driver This method is mostly for backwards compatibility with 1.0. It is generally always a good idea to allow
	 * results to be converted and deserialized. In fact, this is now the default behavior.
	 *
	 * @return Whether or not to convert pipeline and tx results
	 */
	boolean getConvertPipelineAndTxResults();

	/**
	 * Provides a suitable connection for interacting with Redis Sentinel.
	 *
	 * @return connection for interacting with Redis Sentinel.
	 * @since 1.4
	 */
	RedisSentinelConnection getSentinelConnection();
}


在SpringDataRedis中是使用RedisConnection接口对象去操作Redis的。要获取RedisConnection接口对象,是通过RedisConnectionFactory接口去生成的,所以第一步要配置的便是这个工厂了

,而配置这个工厂主要是配置

Redis

的连接池,对于连接池可以限定其最大连接数、超时时间等属性。

@Configuration
public class RedisConfig {

    private RedisConnectionFactory connectionFactory = null;

	/**
	 * 若使用了spring-boot-autoconfigure,只需在application.yml中配置spring.redis和spring.redis.jedis/lettuce即可,JedisConnectionConfiguration/LettuceConnectionConfiguration会自动注册JedisConnectionFactory/LettuceConnectionFactory-Bean
	 */
    @Bean(name = "RedisConnectionFactory")
    public RedisConnectionFactory initRedisConnectionFactory() {
        if (this.connectionFactory != null) {
            return this.connectionFactory;
        }
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 最大空闲数
        poolConfig.setMaxIdle(30);
        // 最大连接数
        poolConfig.setMaxTotal(50);
        // 最大等待毫秒数
        poolConfig.setMaxWaitMillis(2000);
        // 创建Jedis连接工厂
        JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
        // 获取单机的Redis配置
        RedisStandaloneConfiguration rsCfg = connectionFactory.getStandaloneConfiguration();
        ConnectionFactory.setHostName("192.168.11.131");
        ConnectionFactory.setPort(6379);
        ConnectionFactory.setPassword("123456");
        this.connectionFactory = connectionFactory;
        return connectionFactory;
    }
    //......
}

但是我们在使用一条连接时,要先从

RedisConnectionFactory

工厂获取,然后在使用完成后还要自己关闭它。SpringDataRedis为了进一步简化开发,提供了

RedisTemplate



RedisTemplate是一个强大的类,首先它会自动从RedisConnectionFactory工厂中获取连接,然后执行对应的Redis命令,在最后还会关闭Redis的连接



spring-boot-starter-data-redis


spring-boot-starter-redis

在2017年6月后就改为了

spring-boot-starter-data-redis

,是基于

spring-data-redis

开发的Redis场景启动器。

阅读源码发现

spring-boot-starter-data-redis

没有任何代码,只是引入了

spring-data-redis

的依赖,其实Redis的自动配置代码都在

spring-boot-autoconfigure



在这里插入图片描述

关于

spring-boot-starter-data-redis

的源码分析参考

spring-boot-starter-data-redis源码解析与使用实战


SpringBoot2.x

后默认使用的不在是

Jedis

而是

lettuce

,所以

spring-boot-starter-data-redis

依赖了:

lettuce-core



spring-data-redis



spring-boot-starter



操作Redis数据

推荐使用

RedisTemplate



pom

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

<!--lettuce 依赖commons-pool-->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
</dependency>



properties

# ====================================================================================
# spring-redis
# ====================================================================================
# 若没有密码,则需要注释这个配置项
# spring.redis.password=
spring.redis.timeout=10000

# 单机
# spring.redis.host=10.210.13.203
# spring.redis.port=6381

# 集群
spring.redis.cluster.nodes=10.210.13.203:6381,10.210.13.203:6382,10.210.13.203:6383,10.210.13.203:6384,10.210.13.203:6385,10.210.13.203:6386
spring.redis.cluster.maxRedirects=5

spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-idle=8

完整配置项参考

org.springframework.boot.autoconfigure.data.redis.RedisProperties



config

package com.xx.yyy.config;

import org.springframework.cache.CacheManager;
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.*;

import java.time.Duration;

@Configuration
public class RedisConfiguration {

	/**
	 * spring-boot-autoconfigure的RedisAutoConfiguration自动注册的RedisTemplate,使用的序列化器为默人的JdkSerializationRedisSerializer,序列化后生成的是不利于阅读的编码字符串。
	 * 所以我们手动注册一个RedisTemplate,设置RedisConnectionFactory属性为spring-boot-autoconfigure的JedisConnectionConfiguration/LettuceConnectionConfiguration自动注册的RedisConnectionFactory,并设置序列化器为StringRedisSerializer。
	 * 其实也可以直接在主启动类中使用@Autowired注入SpringBoot自动注册的RedisTemplate,并添加一个@PostConstruct的方法来修改它的序列化器为StringRedisSerializer。
	 */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        final RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置ConnectionFactory,SpringBoot会自动注册ConnectionFactory-Bean
        template.setConnectionFactory(redisConnectionFactory);
		
		// 设置序列化器为StringRedisSerializer(默认是 JdkSerializationRedisSerializer , java操作时会产生乱码)
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(stringRedisSerializer);

        template.setValueSerializer(new JdkSerializationRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

	/**
	 * 配置StringRedisTemplate
     * @param factory 
     * @return
     */
	@Bean
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory factory) {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

	/**
	 * 支持使用Spring缓存注解操作Redis。(注意:主启动类上必须加@EnableCaching,开启支持缓存注解!)
	 * 若使用了spring-boot-autoconfigure,只需在application.yml中配置spring.cacheh和spring.cache.redis即可,RedisCacheConfiguration会自动注册RedisCacheManager
	 */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);

        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
        // 默认不过期
        defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ZERO)
                // 设置 key为string序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                // 设置value为json序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new Jackson2JsonRedisSerializer<>(
                                Object.class)));
                // 不缓存空值
                // .disableCachingNullValues();

        // 初始化RedisCacheManager
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }
}



Redis分布式锁



方式一(使用JedisCluster)



pom

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath />
</parent>

<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 1.5的版本默认采用的连接池技术是jedis,2.0以上版本默认连接池是lettuce, 因为此次是采用jedis,所以需要排除lettuce的jar -->
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- jedis客户端 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2,使用jedis必须依赖它-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <optional>true</optional>
</dependency>



properties

spring:
  redis: 
    password: xxxx123456
    cluster:  
      nodes:  
        - 10.163.xx.yyy:7001
        - 10.163.xxx.yyy:7002
        - 10.163.xx.yyy:7003
        - 10.163.xx.zzz:7001
        - 10.163.xx.zzz:7002
        - 10.163.xx.zzz:7003
    jedis:
      pool:
        #最大连接数
        max-active: 8
        #最大阻塞等待时间(负数表示没限制)
        max-wait: -1
        #最小空闲 
        min-idle: 0
        #最大空闲 
        max-idle: 8
    #连接超时时间  
    timeout: 10000  



config

package org.springframework.boot.autoconfigure.data.redis;// 这个类是SpringBoot-autoconfigure自带的

import java.time.Duration;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Configuration properties for Redis. 
 *
 * @author Dave Syer
 * @author Christoph Strobl
 * @author Eddú Meléndez
 * @author Marco Aust
 * @author Mark Paluch
 * @author Stephane Nicoll
 */
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {}
package com.xx.proxy.config;
 
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

/**
 * JedisCluster 配置
 *
 */
@Configuration
public class JedisClusterConfig {
   
   @Autowired
   private RedisProperties redisProperties;
   // 连接redis超时时间
   private static final Integer CONNECTION_TIMEOUT = 5000;
   // 等待redis响应超时时间
   private static final Integer SO_TIMEOUT = 10000;
   // 操作redis的重试次数
   private static final Integer MAX_ATTEMPTS = 3;
   private static final Integer MAX_POOL_SIZE = 100;
   
   @Bean
   public JedisCluster jedisCluster(){
      // 添加集群的服务节点Set集合
      Set<HostAndPort> hostAndPortsSet = new HashSet<>();
      // 添加节点
      List<String> nodes = redisProperties.getCluster().getNodes();
      for(String node : nodes){
         String[] host = node.split(":");
         hostAndPortsSet.add(new HostAndPort(host[0], Integer.parseInt(host[1])));
      }
      
      // Jedis连接池配置
      JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
      // 最大空闲连接数, 默认8个
      jedisPoolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle());
      // 最大连接数, 默认8个
      jedisPoolConfig.setMaxTotal(MAX_POOL_SIZE);
      //最小空闲连接数, 默认0
      jedisPoolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle());
      // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞无时间限制, 默认-1
      jedisPoolConfig.setMaxWaitMillis(redisProperties.getJedis().getPool().getMaxWait().toMillis());
      //对拿到的connection进行validateObject校验
      jedisPoolConfig.setTestOnBorrow(true);
      JedisCluster jedis = new JedisCluster(
            hostAndPortsSet, CONNECTION_TIMEOUT, SO_TIMEOUT, MAX_ATTEMPTS, redisProperties.getPassword(), jedisPoolConfig);
      return jedis;
   }
 
}
package com.xx.proxy.utils;

import java.util.Collections;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.JedisCluster;

/**
 * JedisCluster实现的分布式锁
 *
 */
@Slf4j
public class RedisDistributeLockUtil {
   private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    
    private static final String UNLOCK_SUCCESS = "1";
    // 分布式锁的默认过期时间:5秒
    private static final Integer DEFAULT_LOCK_TIME = 2000;
    
    /**
     * 
     * @param jedis jedis客户端
     * @param lockKey 分布式锁的key
     * @param cilentId 尝试获取分布式锁的客户端(线程)
     * @param expireTime 锁的过期时间,防止死锁
     * @return 获取锁成功返回true,获取失败返回false
     */
   public static boolean tryLock(JedisCluster jedis, String lockKey, String clientId, int expireTime) {
      if(expireTime <= 0){
         expireTime = DEFAULT_LOCK_TIME;
      }
        try {
         String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
         if (LOCK_SUCCESS.equals(result)) {
             return true;
         }
      } catch (Exception e) {
         log.error("线程 "+clientId+" 获取分布式锁错误:", e);
         return false;
      }
        return false;
    }
   
   /**
    * 
    * @param jedis jedis客户端
    * @param lockKey 分布式锁的key
    * @param cilentId 尝试释放分布式锁的客户端(线程)
    * @param script Lua脚本,以原子方式在redis中执行
    * @return 释放锁成功返回true,释放失败返回false
    */
   public static boolean unLock(JedisCluster jedis, String lockKey, String clientId, String script) {
        try {
         Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));
         if (UNLOCK_SUCCESS.equals(result.toString())) {
             return true;
         }
      } catch (Exception e) {
         log.error("线程 "+clientId+" 释放分布式锁错误:", e);
         return false;
      }
        return false;
    }
}



方式二(使用Redisson)

参考

SpringBoot整合Redisson(使用redisson-spring-boot-starter)



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