SpringBoot - 整合Redis缓存详解

SpringBoot对Redis进行了封装,使用起来十分方便。

【1】安装redis并进行配置

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(highavailability)。

Redis中文官网:http://www.redis.cn/

Centos7下安装Redis:https://blog.csdn.net/j080624/article/details/78281840


【2】SpringBoot中引入Redis并配置

① 在pom文件中引入redis的starter

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

② 在application.properties中配置redis

spring.redis.host=192.168.2.110
spring.redis.port=6379
spring.redis.pool.max-active=50
//根据情况自定义配置

引入Redis的starter,容器中默认保存的是RedisCacheManager。RedisCacheManager帮帮我们创建RedisCache,RedisCache通过操作Redis缓存数据。默认保存数据k/v 都是Object,使用默认的JDK序列化器。


【3】RedisTemplate和StringRedisTemplate

RedisTemplate是SpringBoot为我们提供的redis操作Helper,K:V均为Object;StringRedisTemplate继承自RedisTemplate,是专为String:String提供服务。

在RedisAutoConfiguration 中SpringBoot自动注册了RedisTemplate和StringRedisTemplate。源码示例如下:

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

    /**
     * Redis connection configuration.
     */
    @Configuration
    @ConditionalOnClass(GenericObjectPool.class)
    protected static class RedisConnectionConfiguration {

        private final RedisProperties properties;

        private final RedisSentinelConfiguration sentinelConfiguration;

        private final RedisClusterConfiguration clusterConfiguration;

        public RedisConnectionConfiguration(RedisProperties properties,
                ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
                ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
            this.properties = properties;
            this.sentinelConfiguration = sentinelConfiguration.getIfAvailable();
            this.clusterConfiguration = clusterConfiguration.getIfAvailable();
        }
//注册 JedisConnectionFactory 
        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory()
                throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }

        protected final JedisConnectionFactory applyProperties(
                JedisConnectionFactory factory) {
            configureConnection(factory);
            if (this.properties.isSsl()) {
                factory.setUseSsl(true);
            }
            factory.setDatabase(this.properties.getDatabase());
            if (this.properties.getTimeout() > 0) {
                factory.setTimeout(this.properties.getTimeout());
            }
            return factory;
        }

        private void configureConnection(JedisConnectionFactory factory) {
            if (StringUtils.hasText(this.properties.getUrl())) {
                configureConnectionFromUrl(factory);
            }
            else {
                factory.setHostName(this.properties.getHost());
                factory.setPort(this.properties.getPort());
                if (this.properties.getPassword() != null) {
                    factory.setPassword(this.properties.getPassword());
                }
            }
        }

        private void configureConnectionFromUrl(JedisConnectionFactory factory) {
            String url = this.properties.getUrl();
            if (url.startsWith("rediss://")) {
                factory.setUseSsl(true);
            }
            try {
                URI uri = new URI(url);
                factory.setHostName(uri.getHost());
                factory.setPort(uri.getPort());
                if (uri.getUserInfo() != null) {
                    String password = uri.getUserInfo();
                    int index = password.indexOf(":");
                    if (index >= 0) {
                        password = password.substring(index + 1);
                    }
                    factory.setPassword(password);
                }
            }
            catch (URISyntaxException ex) {
                throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url,
                        ex);
            }
        }

        protected final RedisSentinelConfiguration getSentinelConfig() {
            if (this.sentinelConfiguration != null) {
                return this.sentinelConfiguration;
            }
            Sentinel sentinelProperties = this.properties.getSentinel();
            if (sentinelProperties != null) {
                RedisSentinelConfiguration config = new RedisSentinelConfiguration();
                config.master(sentinelProperties.getMaster());
                config.setSentinels(createSentinels(sentinelProperties));
                return config;
            }
            return null;
        }

        /**
         * Create a {@link RedisClusterConfiguration} if necessary.
         * @return {@literal null} if no cluster settings are set.
         */
        protected final RedisClusterConfiguration getClusterConfiguration() {
            if (this.clusterConfiguration != null) {
                return this.clusterConfiguration;
            }
            if (this.properties.getCluster() == null) {
                return null;
            }
            Cluster clusterProperties = this.properties.getCluster();
            RedisClusterConfiguration config = new RedisClusterConfiguration(
                    clusterProperties.getNodes());

            if (clusterProperties.getMaxRedirects() != null) {
                config.setMaxRedirects(clusterProperties.getMaxRedirects());
            }
            return config;
        }

        private List<RedisNode> createSentinels(Sentinel sentinel) {
            List<RedisNode> nodes = new ArrayList<RedisNode>();
            for (String node : StringUtils
                    .commaDelimitedListToStringArray(sentinel.getNodes())) {
                try {
                    String[] parts = StringUtils.split(node, ":");
                    Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                    nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
                }
                catch (RuntimeException ex) {
                    throw new IllegalStateException(
                            "Invalid redis sentinel " + "property '" + node + "'", ex);
                }
            }
            return nodes;
        }

        private JedisConnectionFactory createJedisConnectionFactory() {
            JedisPoolConfig poolConfig = this.properties.getPool() != null
                    ? jedisPoolConfig() : new JedisPoolConfig();

            if (getSentinelConfig() != null) {
                return new JedisConnectionFactory(getSentinelConfig(), poolConfig);
            }
            if (getClusterConfiguration() != null) {
                return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
            }
            return new JedisConnectionFactory(poolConfig);
        }

        private JedisPoolConfig jedisPoolConfig() {
            JedisPoolConfig config = new JedisPoolConfig();
            RedisProperties.Pool props = this.properties.getPool();
            config.setMaxTotal(props.getMaxActive());
            config.setMaxIdle(props.getMaxIdle());
            config.setMinIdle(props.getMinIdle());
            config.setMaxWaitMillis(props.getMaxWait());
            return config;
        }

    }

    /**
     * Standard Redis configuration.
     */
    @Configuration
    protected static class RedisConfiguration {
//注册 RedisTemplate<Object, Object>
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(
                RedisConnectionFactory redisConnectionFactory)
                throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
// 注册 StringRedisTemplate 
        @Bean
        @ConditionalOnMissingBean(StringRedisTemplate.class)
        public StringRedisTemplate stringRedisTemplate(
                RedisConnectionFactory redisConnectionFactory)
                throws UnknownHostException {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }

    }

}

【3】对五大类数据进行操作

Redis中常见的五大类数据类型:String,List,Set,Hash和ZSet。

RedisTemplate封装了方法对五大类数据进行操作,示例如下:

//针对String
RedisTemplate.opsForValue();

//针对List
RedisTemplate.opsForList();

//针对Set
RedisTemplate.opsForSet();

//针对Hash
RedisTemplate.opsForHash();

//针对ZSet
RedisTemplate.opsForZSet();

其他还有,如下图:

这里写图片描述


每一个方法都会返回一个Operations对象,该对象封装了该类型的一系列操作。
以opsForValue为例:

public ValueOperations<K, V> opsForValue() {
        if (valueOps == null) {
            valueOps = new DefaultValueOperations<K, V>(this);
        }
        return valueOps;
    }

ValueOperations源码如下:

public interface ValueOperations<K, V> {

    /**
     * Set {@code value} for {@code key}.
     *
     * @param key must not be {@literal null}.
     * @param value
     * @see <a href="http://redis.io/commands/set">Redis Documentation: SET</a>
     */
    void set(K key, V value);

    /**
     * Set the {@code value} and expiration {@code timeout} for {@code key}.
     *
     * @param key must not be {@literal null}.
     * @param value
     * @param timeout
     * @param unit must not be {@literal null}.
     * @see <a href="http://redis.io/commands/setex">Redis Documentation: SETEX</a>
     */
    void set(K key, V value, long timeout, TimeUnit unit);

    /**
     * Set {@code key} to hold the string {@code value} if {@code key} is absent.
     *
     * @param key must not be {@literal null}.
     * @param value
     * @see <a href="http://redis.io/commands/setnx">Redis Documentation: SETNX</a>
     */
    Boolean setIfAbsent(K key, V value);

    /**
     * Set multiple keys to multiple values using key-value pairs provided in {@code tuple}.
     *
     * @param map must not be {@literal null}.
     * @see <a href="http://redis.io/commands/mset">Redis Documentation: MSET</a>
     */
    void multiSet(Map<? extends K, ? extends V> map);

    /**
     * Set multiple keys to multiple values using key-value pairs provided in {@code tuple} only if the provided key does
     * not exist.
     *
     * @param map must not be {@literal null}.
     * @see <a href="http://redis.io/commands/mset">Redis Documentation: MSET</a>
     */
    Boolean multiSetIfAbsent(Map<? extends K, ? extends V> map);

    /**
     * Get the value of {@code key}.
     *
     * @param key must not be {@literal null}.
     * @see <a href="http://redis.io/commands/get">Redis Documentation: GET</a>
     */
    V get(Object key);

    /**
     * Set {@code value} of {@code key} and return its old value.
     *
     * @param key must not be {@literal null}.
     * @see <a href="http://redis.io/commands/getset">Redis Documentation: GETSET</a>
     */
    V getAndSet(K key, V value);

    /**
     * Get multiple {@code keys}. Values are returned in the order of the requested keys.
     *
     * @param keys must not be {@literal null}.
     * @see <a href="http://redis.io/commands/mget">Redis Documentation: MGET</a>
     */
    List<V> multiGet(Collection<K> keys);

    /**
     * Increment an integer value stored as string value under {@code key} by {@code delta}.
     *
     * @param key must not be {@literal null}.
     * @param delta
     * @see <a href="http://redis.io/commands/incr">Redis Documentation: INCR</a>
     */
    Long increment(K key, long delta);

    /**
     * Increment a floating point number value stored as string value under {@code key} by {@code delta}.
     *
     * @param key must not be {@literal null}.
     * @param delta
     * @see <a href="http://redis.io/commands/incrbyfloat">Redis Documentation: INCRBYFLOAT</a>
     */
    Double increment(K key, double delta);

    /**
     * Append a {@code value} to {@code key}.
     *
     * @param key must not be {@literal null}.
     * @param value
     * @see <a href="http://redis.io/commands/append">Redis Documentation: APPEND</a>
     */
    Integer append(K key, String value);

    /**
     * Get a substring of value of {@code key} between {@code begin} and {@code end}.
     *
     * @param key must not be {@literal null}.
     * @param start
     * @param end
     * @see <a href="http://redis.io/commands/getrange">Redis Documentation: GETRANGE</a>
     */
    String get(K key, long start, long end);

    /**
     * Overwrite parts of {@code key} starting at the specified {@code offset} with given {@code value}.
     *
     * @param key must not be {@literal null}.
     * @param value
     * @param offset
     * @see <a href="http://redis.io/commands/setrange">Redis Documentation: SETRANGE</a>
     */
    void set(K key, V value, long offset);

    /**
     * Get the length of the value stored at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @see <a href="http://redis.io/commands/strlen">Redis Documentation: STRLEN</a>
     */
    Long size(K key);

    /**
     * Sets the bit at {@code offset} in value stored at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @param offset
     * @param value
     * @since 1.5
     * @see <a href="http://redis.io/commands/setbit">Redis Documentation: SETBIT</a>
     */
    Boolean setBit(K key, long offset, boolean value);

    /**
     * Get the bit value at {@code offset} of value at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @param offset
     * @since 1.5
     * @see <a href="http://redis.io/commands/setbit">Redis Documentation: GETBIT</a>
     */
    Boolean getBit(K key, long offset);

    RedisOperations<K, V> getOperations();

}

其他数据类型类似。


【4】Redis命令接口

如下图所示,Redis提供了许多命令供我们使用,同样在SpringBoot中也封装了对应类型的命令接口。

这里写图片描述

SpringBoot中RedisCommands 源码示例如下:

/**
 * Interface for the commands supported by Redis.
 * 
 * @author Costin Leau
 * @author Christoph Strobl
 */
public interface RedisCommands extends RedisKeyCommands, RedisStringCommands, RedisListCommands, RedisSetCommands,
        RedisZSetCommands, RedisHashCommands, RedisTxCommands, RedisPubSubCommands, RedisConnectionCommands,
        RedisServerCommands, RedisScriptingCommands, RedisGeoCommands, HyperLogLogCommands {

    /**
     * 'Native' or 'raw' execution of the given command along-side the given arguments. The command is executed as is,
     * with as little 'interpretation' as possible - it is up to the caller to take care of any processing of arguments or
     * the result.
     * 
     * @param command Command to execute
     * @param args Possible command arguments (may be null)
     * @return execution result.
     */
    Object execute(String command, byte[]... args);
}

基本上,在Linux Redis中能够使用的命令,在接口中都能找到!如RedisTemplate.hasKey()判断Redis是否存在该key。


【5】测试示例

测试源码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootCacheApplicationTests {

    @Autowired
    EmployeeMapper employeeMapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test01() {
        Boolean aBoolean = stringRedisTemplate.hasKey("msg");
        if (aBoolean){
            String msg = stringRedisTemplate.opsForValue().get("msg");
            System.out.println(msg);
        }
    }
    @Test
    public void test02() {
        Employee empById = employeeMapper.getEmpById(1);
        redisTemplate.opsForValue().set("emp01",empById);
        Object emp01 = redisTemplate.opsForValue().get("emp01");
        System.out.println(emp01);
    }

}

其中test01主要对String进行测试,test02则向redis中保存了一个对象!

使用Redis桌面管理工具查看如下:

这里写图片描述


注意,Redis中对象的保存是需要进行序列化的,默认使用JdkSerializationRedisSerializer序列化器,所以图片中看到的是序列化后的二进制。

正常使用是没有问题的,查询出来的时候会自动反序列化,如下所示:

Employee{id=1, lastName='张三', gender=1, email='[email protected]', dId=1}

如果习惯于使用String,那么可以将其JSON化,两种方式:使用第三方JSON或者向容器中添加自定义RedisTemplate,改变其默认序列化器。

添加自定义RedisTemplate源码如下:

@Configuration
public class MyRedisConfig {

    @Bean
    public RedisTemplate<Object,Employee> empRedisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<Object,Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(serializer);
        return template;
    }
}

测试如下:

@Autowired
    RedisTemplate redisTemplate;

    @Autowired
    RedisTemplate<Object,Employee> empRedisTemplate;

    @Test
    public void test02() {
        Employee empById = employeeMapper.getEmpById(1);
        empRedisTemplate.opsForValue().set("emp01",empById);
        Object emp01 = empRedisTemplate.opsForValue().get("emp01");
        System.out.println(emp01);
    }

此时桌面管理工具显示如下:

这里写图片描述


有趣的测试

redisTemplate.opsForValue().set("emp01","123456");
Object emp01 = redisTemplate.opsForValue().get("emp01");
System.out.println(emp01);

empRedisTemplate.opsForValue().set("emp01",empById);
Object emp02 = empRedisTemplate.opsForValue().get("emp01");
System.out.println(emp02);

输出结果如下:

123456
Employee{id=1, lastName='张三', gender=1, email='[email protected]', dId=1}

使用不同模板向Redis中存储在我们看来一样的key,不同的value时,Redis存储机制不同,且使用对应的Template从Redis获取同一个key得到对应值!

进入Linux环境查看Redis中的key :

这里写图片描述


分别使用三种模板存储key”emp01”,如下:

stringRedisTemplate.opsForValue().set("emp01","hello world");
redisTemplate.opsForValue().set("emp01","123456");
empRedisTemplate.opsForValue().set("emp01",empById);

其在Redis中key的对应存储形式如下:

1) "emp01"
2) "\xac\xed\x00\x05t\x00\x05emp01"
3) "\"emp01\""

猜你喜欢

转载自blog.csdn.net/j080624/article/details/80860715