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與現代網頁後端技術與框架,並具備金融市場交易知識