Spring Boot 使用 Lettuce 設定多個 Redis 連線

Jeff Hsieh
10 min readMay 27, 2020

最近公司產品遇到了有人使用爬蟲來獲取頁面上的資料,這樣的行為導致了 Redis 的使用量增加,造成其他共用同一台 Redis 的服務受到了影響

評估過後,除了請 Devops 做爬蟲的阻擋外,另外也決定將系統的 Redis 作分流,除了避免影響到其他服務外也可以降低成本

前情提要完畢,開始進入正題!

目前大部分網路上的文章在實作 Redis 分流時,都是以 Jedis 為範例,但 Spring Boot 在 2.x 版本之後已經將預設 Redis library 改為 Lettuce 了
話雖如此,其實不管是 Jedis 或是 Lettuce,基本的思路是一樣的,也就是要將原本框架預設建立連線的行為,透過 Config 的方式改成自己實作

首先是基本的引入 dependency:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>

由於我有使用到 connection pool,因此需要另外引入 commons-pool2 的套件,若沒有用到 pool 相關的設定的話可以移除掉

再來是連線參數的設定檔:

# first redis
spring.redis.host = 127.0.0.1
spring.redis.port = 6379
spring.redis.database = 0
spring.redis.timeout = 10
spring.redis.lettuce.pool.min-idle = 0
spring.redis.lettuce.pool.max-idle = 150
spring.redis.lettuce.pool.max-wait = -1
spring.redis.lettuce.pool.max-active = 150

# second redis
spring.redis.second.host = 127.0.0.1
spring.redis.second.port = 6379
spring.redis.second.database=0

在 first redis的設定中,這邊採用的是預設的 config 名稱,當然也可以改成自己希望的名稱,例如 spring.redis.first.host 等,只是後續在操作上會比較麻煩一點,所以保留預設值

而 second redis 就是另一條 Redis 連線的設定了,由於我只需要修改連線位址,其他關於 pool 或 timeout 的設定和原本的連線一樣就好,所以就不另外設定囉~

接下來是撰寫連線建立的 Configuration:

@Configuration
public class RedisConfig {
/**
* 主要的redis連線
*/
@Bean
@Primary
public StringRedisTemplate first(
RedisConnectionFactory redisConnectionFactory
) {
return createRedisTemplate(redisConnectionFactory);
}

/**
* 次要的redis連線,另外設定connectionFactory以連線到另一個redis
*
*
@param database Redis資料庫索引
*
@param timeout 連線超時時間(毫秒)
*
@param maxActive 連線池最大連線數(使用負值表示沒有限制)
*
@param maxWait 連線池最大等待時間(使用負值表示沒有限制)
*
@param maxIdle 連線池中的最大空閒連線
*
@param minIdle 連線池中的最小空閒連線
*
@param host Redis伺服器地址
*
@param port Redis伺服器連線埠
*/
@Bean
public StringRedisTemplate second(
@Value("${spring.redis.second.database}")
int database,
@Value("${spring.redis.timeout}")
long timeout,
@Value("${spring.redis.lettuce.pool.max-active}")
int maxActive,
@Value("${spring.redis.lettuce.pool.max-wait}")
int maxWait,
@Value("${spring.redis.lettuce.pool.max-idle}")
int maxIdle,
@Value("${spring.redis.lettuce.pool.min-idle}")
int minIdle,
@Value("${spring.redis.second.host}")
String host,
@Value("${spring.redis.second.port}")
int port
) {
// connection config
var configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
configuration.setDatabase(database);

// pool config
var genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxTotal(maxActive);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMaxWaitMillis(maxWait);

// create connection factory
var builder = LettucePoolingClientConfiguration.builder();
builder.poolConfig(genericObjectPoolConfig);
builder.commandTimeout(Duration.ofSeconds(timeout));
var connectionFactory = new LettuceConnectionFactory(
configuration, builder.build()
);
connectionFactory.afterPropertiesSet();

// create redis template
return createRedisTemplate(connectionFactory);

}

/**
* 建立StringRedisTemplate
* 此function不能加 @Bean 否則connectionFactory將會一律採用預設值
*/
private StringRedisTemplate createRedisTemplate(
RedisConnectionFactory redisConnectionFactory
) {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}

呼~設定檔有點長,我們來一個一個講解

首先是第一個 function,這個function的名稱是 first,代表第一組 Redis 連線,function 名稱會影響到後續在使用上的操作,如果想要另外取名的話可以將 bean 加上名稱@Bean(name=”your_name”)

在這個 function 接收了框架預設建立的 RedisConnectionFactory 為參數,並將該 factory 傳到最底下的 createRedisTemplate() 來建立 StringRedisTemplate 物件,所以如果希望建立的是其他 RedisTemplate 物件的話就可以修改 createRedisTemplate()

值得注意的是,這邊加上了@Primary的 annotation,意味著這是 default Redis 連線,如此一來要是其他開發者在操作時忘了標註要使用哪個 Redis的話,便會採用這條連線,另外也可以相容於先前只有一條連線的寫法

接著來看第二個 function,在這個 function 中可以看到引入了設定檔中第二條連線的資料作為參數,前面提到我只需要修改連線位址,所以關於 pool 等相關設定還是抓第一條連線的,這邊就看大家有沒有需要自行修改~

抓到第二條連線的設定後,所做的事情其實就是自己建立 connection factory ,先設定好 Config 相關物件,接著創建 LettuceConnectionFactory 物件,最後和第一個 function 一樣,將 factory 傳入 createRedisTemplate() 來生成 RedisTemplate 物件

然後就沒了,設定就這樣 XD

所以其實全部要做的事情就是,預設的連線就用預設的 factory 建立 RedisTemplate 物件來操作,額外的連線就讀取設定檔後,自己新增 factory 來生成 RedisTemplate 物件,就是這麼簡單~

連線建立好後就是實際的操作啦,操作方式也很簡單,就用@Autowired就好,只是要帶名稱讓 Spring Boot 知道你要使用的是哪個 RedisTemplate 物件

@Autowired
@Qualifier("first")
StringRedisTemplate firstRedisTemplate;
@Autowired
@Qualifier("second")
StringRedisTemplate secondRedisTemplate;

我們透過 Qualifier 來指定要使用那個 RedisTemplate 物件,如果沒有帶 Qualifier 則會得到一開始我們設定成@Primary的那個物件

Qualifier 的名稱就是我們在設定檔中的 function 名稱,若有另外在 Bean 裡面設定 name 的話就會以那個名字為主

以上就是 Spring Boot 如何建立多個 Redis 啦~是不是很簡單呢?XD

--

--

Jeff Hsieh

資深後端工程師,熟悉JAVA與現代網頁後端技術與框架,並具備金融市場交易知識