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

首先来看一下依赖:

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

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

上一小节已经介绍,Redis有单机、Sentinel、Cluster三种部署方式,我们这里介绍前两种。

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

  1. package com.coder4.sbmvt.redis.configuration;
  2. import com.coder4.sbmvt.redis.utils.RedissonUtils;
  3. import org.redisson.Redisson;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.config.Config;
  6. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  7. import org.springframework.boot.context.properties.ConfigurationProperties;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import java.io.IOException;
  11. /**
  12. * @author coder4
  13. */
  14. @Configuration
  15. @ConfigurationProperties(prefix = "redis")
  16. public class RedissonAutoConfiguration {
  17. // server list
  18. private String server;
  19. // redis password
  20. private String password;
  21. // connection pool size, default 128
  22. private int connPoolSize = 128;
  23. // retry interval in ms
  24. private int retryInterval = 100;
  25. @Bean(destroyMethod = "shutdown")
  26. @ConditionalOnMissingBean(RedissonClient.class)
  27. public RedissonClient createRedissonClient() throws IOException {
  28. if (getServer() == null || getServer().isEmpty()) {
  29. throw new IllegalArgumentException("server is empty");
  30. }
  31. Config config = new Config();
  32. config.useSingleServer()
  33. .setAddress(RedissonUtils.wrapSchema(server))
  34. .setPassword(password)
  35. .setRetryInterval(retryInterval)
  36. .setConnectionPoolSize(connPoolSize);
  37. return Redisson.create(config);
  38. }
  39. public String getServer() {
  40. return server;
  41. }
  42. public void setServer(String server) {
  43. this.server = server;
  44. }
  45. public String getPassword() {
  46. return password;
  47. }
  48. public void setPassword(String password) {
  49. this.password = password;
  50. }
  51. public int getConnPoolSize() {
  52. return connPoolSize;
  53. }
  54. public void setConnPoolSize(int connPoolSize) {
  55. this.connPoolSize = connPoolSize;
  56. }
  57. public int getRetryInterval() {
  58. return retryInterval;
  59. }
  60. public void setRetryInterval(int retryInterval) {
  61. this.retryInterval = retryInterval;
  62. }
  63. }

如上所示:

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

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

  1. package com.coder4.sbmvt.redis.configuration;
  2. import com.coder4.sbmvt.redis.utils.RedissonUtils;
  3. import org.redisson.Redisson;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.config.Config;
  6. import org.redisson.config.ReadMode;
  7. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  8. import org.springframework.boot.context.properties.ConfigurationProperties;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. import java.io.IOException;
  12. import java.util.List;
  13. @Configuration
  14. @ConfigurationProperties(prefix = "redis-sentinel")
  15. public class RedissonSentinelAutoConfiguration {
  16. // server list
  17. private String sentinelServerList;
  18. // sentinel master name
  19. private String masterName;
  20. // redis password
  21. private String password;
  22. // connection pool size, default 128
  23. private int connPoolSize = 128;
  24. // retry interval in ms
  25. private int retryInterval = 100;
  26. @Bean(destroyMethod = "shutdown")
  27. @ConditionalOnMissingBean(RedissonClient.class)
  28. public RedissonClient createRedissonClient() throws IOException {
  29. List<String> sentinelAddrs = RedissonUtils.splitStr(sentinelServerList);
  30. if (sentinelAddrs == null || sentinelAddrs.size() == 0) {
  31. throw new IllegalArgumentException("sentinel address is empty");
  32. }
  33. Config config = new Config();
  34. config.useSentinelServers()
  35. .setMasterName(masterName)
  36. .addSentinelAddress(sentinelAddrs.stream().map(RedissonUtils::wrapSchema).toArray(String[]::new))
  37. .setPassword(password)
  38. .setMasterConnectionPoolSize(connPoolSize)
  39. .setSlaveConnectionPoolSize(connPoolSize)
  40. .setRetryInterval(retryInterval)
  41. .setReadMode(ReadMode.MASTER);
  42. return Redisson.create(config);
  43. }
  44. public String getSentinelServerList() {
  45. return sentinelServerList;
  46. }
  47. public void setSentinelServerList(String sentinelServerList) {
  48. this.sentinelServerList = sentinelServerList;
  49. }
  50. public String getMasterName() {
  51. return masterName;
  52. }
  53. public void setMasterName(String masterName) {
  54. this.masterName = masterName;
  55. }
  56. public String getPassword() {
  57. return password;
  58. }
  59. public void setPassword(String password) {
  60. this.password = password;
  61. }
  62. public int getConnPoolSize() {
  63. return connPoolSize;
  64. }
  65. public void setConnPoolSize(int connPoolSize) {
  66. this.connPoolSize = connPoolSize;
  67. }
  68. public int getRetryInterval() {
  69. return retryInterval;
  70. }
  71. public void setRetryInterval(int retryInterval) {
  72. this.retryInterval = retryInterval;
  73. }
  74. }

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

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

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

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

Spring Boot中集成Redis

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

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

然后在yaml中添加配置:

  1. # redis
  2. redis.server: "192.168.99.100:6379"

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

  1. @Service
  2. public class MyListRedissonImpl implements MyListRepository {
  3. @Autowired
  4. private RedissonClient redissonClient;
  5. private static String getKey(int userId) {
  6. return String.format("list:%d", userId);
  7. }
  8. private RSet<Long> obtainSet(int userId) {
  9. return redissonClient.getSet(getKey(userId), new LongCodec());
  10. }
  11. @Override
  12. public List<Long> get(int userId) {
  13. return new ArrayList(obtainSet(userId).readAll());
  14. }
  15. @Override
  16. public void add(int userId, long data) {
  17. obtainSet(userId).add(data);
  18. }

我们通过上述代码,简单看一下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官方文档