Spring Boot整合Redis

在上一章中,我们讨论了Redis服务的运维,包括单机运行和Sentinel运行。

在本小节中,我们讨论如何在Spring Boot中集成Redis。

Spring Boot内置了Redis的接入方式,spring-data-redis,这种方案在Jedis客户端的基础上尽心过了简单的封装。若只使用Redis的KV存储特性,该方案可以满足要求。但对于Redis的高级特性(如SortedSet、SETNX等),则需要手动调用底层Jedis客户端的API,使用方式较为晦涩且容易出错。

为此,我们推荐使用Redisson作为接入客户端,它提供了简单易用的封装,可以用最小的编程代价来发挥Redis的最大功能。

库依赖及自动配置

为了方便类库的复用,我们将Redisson的依赖及自动配置抽成一个单独的项目lmsia-redis

首先来看一下依赖:

    compileOnly 'org.springframework.boot:spring-boot-autoconfigure:1.5.6.RELEASE'
    compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.9.0'
    compileOnly 'ch.qos.logback:logback-classic:1.2.3'
    compile 'org.redisson:redisson:3.7.3'
    // Use JUnit test framework
    testCompile 'junit:junit:4.12'

如上述的代码片段所示,编译依赖了Sping Boot的自动注解、jackson、以及logback,此外显式依赖了redisson库。

Redis 数据库的运维 已经介绍,Redis有单机、Sentinel、Cluster三种部署方式,我们这里介绍前两种。

首先看一下单机Redis的自动配置:

package com.coder4.sbmvt.redis.configuration;
import com.coder4.sbmvt.redis.utils.RedissonUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
/**
 * @author coder4
 */
@Configuration
@ConfigurationProperties(prefix = "redis")
public class RedissonAutoConfiguration {
    // server list
    private String server;
    // redis password
    private String password;
    // connection pool size, default 128
    private int connPoolSize = 128;
    // retry interval in ms
    private int retryInterval = 100;
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient createRedissonClient() throws IOException {
        if (getServer() == null || getServer().isEmpty()) {
            throw new IllegalArgumentException("server is empty");
        }
        Config config = new Config();
        config.useSingleServer()
                .setAddress(RedissonUtils.wrapSchema(server))
                .setPassword(password)
                .setRetryInterval(retryInterval)
                .setConnectionPoolSize(connPoolSize);
        return Redisson.create(config);
    }
    public String getServer() {
        return server;
    }
    public void setServer(String server) {
        this.server = server;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public int getConnPoolSize() {
        return connPoolSize;
    }
    public void setConnPoolSize(int connPoolSize) {
        this.connPoolSize = connPoolSize;
    }
    public int getRetryInterval() {
        return retryInterval;
    }
    public void setRetryInterval(int retryInterval) {
        this.retryInterval = retryInterval;
    }
}

如上所示:

  • 若YAML配置中包含"redis"前缀的配置,则注解被激活。
  • 尝试解析server、password、connPoolSize、retryInterval4个配置字段。
    • server是Redis服务器的IP:Port
    • password是Redis服务器的密码
    • connPoolSize是连接池默认大小,默认是128
    • retryInterval是命令执行失败后的重试间隔,默认是100ms
  • 根据上述配置自动生成ResissonClient

再来看一下Sentinel方式的自动配置:

package com.coder4.sbmvt.redis.configuration;
import com.coder4.sbmvt.redis.utils.RedissonUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.ReadMode;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "redis-sentinel")
public class RedissonSentinelAutoConfiguration {
    // server list
    private String sentinelServerList;
    // sentinel master name
    private String masterName;
    // redis password
    private String password;
    // connection pool size, default 128
    private int connPoolSize = 128;
    // retry interval in ms
    private int retryInterval = 100;
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient createRedissonClient() throws IOException {
        List<String> sentinelAddrs = RedissonUtils.splitStr(sentinelServerList);
        if (sentinelAddrs == null || sentinelAddrs.size() == 0) {
            throw new IllegalArgumentException("sentinel address is empty");
        }
        Config config = new Config();
        config.useSentinelServers()
                .setMasterName(masterName)
                .addSentinelAddress(sentinelAddrs.stream().map(RedissonUtils::wrapSchema).toArray(String[]::new))
                .setPassword(password)
                .setMasterConnectionPoolSize(connPoolSize)
                .setSlaveConnectionPoolSize(connPoolSize)
                .setRetryInterval(retryInterval)
                .setReadMode(ReadMode.MASTER);
        return Redisson.create(config);
    }
    public String getSentinelServerList() {
        return sentinelServerList;
    }
    public void setSentinelServerList(String sentinelServerList) {
        this.sentinelServerList = sentinelServerList;
    }
    public String getMasterName() {
        return masterName;
    }
    public void setMasterName(String masterName) {
        this.masterName = masterName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public int getConnPoolSize() {
        return connPoolSize;
    }
    public void setConnPoolSize(int connPoolSize) {
        this.connPoolSize = connPoolSize;
    }
    public int getRetryInterval() {
        return retryInterval;
    }
    public void setRetryInterval(int retryInterval) {
        this.retryInterval = retryInterval;
    }
}

上述自动配置和RedissonAutoConfiguration基本一致,唯一的差别是配置了Sentinal服务集群列表和masterName。

最后,在别的项目引用这个包时,我们要将上述两个自动配置暴露给Spring Boot扫描,添加到spring.factories中:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.coder4.sbmvt.redis.configuration.RedissonAutoConfiguration,\
com.coder4.sbmvt.redis.configuration.RedissonSentinelAutoConfiguration

接下来,我们来看一下在Spring Boot中的具体集成方法。

Spring Boot中集成Redis

在Spring Boot中集成Redis,首先依赖刚才的lmsia-redis类库:

compile 'com.github.liheyuan:lmsia-redis:0.0.4'

然后在YAML中添加配置:

# redis
redis.server: "192.168.99.100:6379"

经过上述配置后,Spring Boot在启动后,会自动注入RedissonClient,我们可以直接Autowired使用:

@Service
public class MyListRedissonImpl implements MyListRepository {
    @Autowired
    private RedissonClient redissonClient;
    private static String getKey(int userId) {
        return String.format("list:%d", userId);
    }
    private RSet<Long> obtainSet(int userId) {
        return redissonClient.getSet(getKey(userId), new LongCodec());
    }
    @Override
    public List<Long> get(int userId) {
        return new ArrayList(obtainSet(userId).readAll());
    }
    @Override
    public void add(int userId, long data) {
        obtainSet(userId).add(data);
    }

我们通过上述代码,简单看一下Redisson的用法。

  • 通过getKey拼接一个key
  • 通过redissonClient.getSet获取key对应的RSet,编码是Long,这里的RSet和Java的Set完全兼容。
  • add进行添加、get返回set中全部数据。

Redisson将较为繁琐的Redis命令进行了封装和组合,我们操作的是类Java的数据结构,但实际底层命令都是Redis的。

关于Sentinel的配置方法,是类似的,这里不再赘述。

至此,我们完成了Spring Boot中Redis服务的整合工作。

拓展阅读

  1. Redisson还提供了锁、排序集合等许多高级数据结构,可以参考Redisson官方文档
下一节:RabbitMQ支持单机部署,也提供了"高可用"的集群部署方式,以提升性能和(或)可用性。