在SpringBoot項目中優(yōu)雅的連接多臺Redis的操作方法
如何在SpringBoot項目中優(yōu)雅的連接多臺Redis
在Spring Boot項目中,連接單個Redis實例是常見需求,但有時需要同時連接多個Redis實例(例如,主Redis用于業(yè)務(wù)數(shù)據(jù)存儲,另一個Redis用于爬蟲數(shù)據(jù)緩存)。本文將基于一個實際案例,詳細(xì)介紹如何在Spring Boot中優(yōu)雅地配置和使用多個Redis實例,解決常見的注入歧義問題,并提供一個通用的Redis工具類來簡化操作。
背景
在一個Spring Boot項目中,我們需要連接兩臺Redis實例:
- 主Redis:用于常規(guī)業(yè)務(wù)數(shù)據(jù),配置在
spring.redis(數(shù)據(jù)庫索引4)。 - 爬蟲Redis:用于爬蟲相關(guān)數(shù)據(jù),配置在
spring.redis-crawler(數(shù)據(jù)庫索引7)。
項目中遇到以下問題:
- 配置兩個
RedisTemplate時,Spring容器找不到redisCrawlerConnectionFactory。 - 配置多個
RedisProperties導(dǎo)致注入歧義,拋出NoUniqueBeanDefinitionException。 - 需要一個通用的
RedisCache工具類,支持動態(tài)選擇Redis實例,同時兼容現(xiàn)有代碼。
下面,我們將逐步解決這些問題,并展示如何優(yōu)雅地實現(xiàn)多Redis連接。
項目配置
1. 配置文件(application.yml)
首先,在application.yml中定義兩套Redis配置:
spring:
redis:
host: [REDACTED_HOST]
port: 6379
database: 4
# password: [REDACTED_PASSWORD]
timeout: 10s
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1ms
redis-crawler:
host: [REDACTED_HOST]
port: 6379
database: 7
# password: [REDACTED_PASSWORD]
timeout: 10s
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1msspring.redis:主Redis,數(shù)據(jù)庫索引4。spring.redis-crawler:爬蟲Redis,數(shù)據(jù)庫索引7。- 如果Redis需要密碼,取消
password字段的注釋并設(shè)置正確值(此處已脫敏)。
2. 依賴配置(pom.xml)
確保項目包含以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.51</version>
</dependency>spring-boot-starter-data-redis:提供Redis支持。lettuce-core:使用Lettuce作為Redis客戶端。fastjson:用于自定義序列化(項目中使用了FastJson2JsonRedisSerializer)。
配置多個Redis實例
問題1:找不到redisCrawlerConnectionFactory
最初,我們嘗試在RedisConfig.java中定義兩個RedisTemplate:
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 主Redis模板
}
@Bean(name = "redisTemplateCrawl")
public RedisTemplate<Object, Object> redisTemplateCrawl(@Qualifier("redisCrawlerConnectionFactory") RedisConnectionFactory redisCrawlerConnectionFactory) {
// 爬蟲Redis模板
}啟動時拋出異常:
No qualifying bean of type 'org.springframework.data.redis.connection.RedisConnectionFactory' available
原因:Spring Boot自動為spring.redis創(chuàng)建了一個RedisConnectionFactory,但不會為spring.redis-crawler創(chuàng)建。redisTemplateCrawl依賴的redisCrawlerConnectionFactory未定義。
解決方法:顯式定義redisCrawlerConnectionFactory和對應(yīng)的RedisProperties:
@Bean(name = "redisCrawlerProperties")
@ConfigurationProperties(prefix = "spring.redis-crawler")
public RedisProperties redisCrawlerProperties() {
return new RedisProperties();
}
@Bean(name = "redisCrawlerConnectionFactory")
public RedisConnectionFactory redisCrawlerConnectionFactory(@Qualifier("redisCrawlerProperties") RedisProperties redisCrawlerProperties) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisCrawlerProperties.getHost());
config.setPort(redisCrawlerProperties.getPort());
config.setDatabase(redisCrawlerProperties.getDatabase());
if (redisCrawlerProperties.getPassword() != null) {
config.setPassword(redisCrawlerProperties.getPassword());
}
return new LettuceConnectionFactory(config);
}redisCrawlerProperties:綁定spring.redis-crawler配置。redisCrawlerConnectionFactory:根據(jù)redisCrawlerProperties創(chuàng)建連接工廠。
問題2:RedisProperties注入歧義
配置了redisCrawlerProperties后,啟動時又遇到新問題:
Parameter 0 of constructor in org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration required a single bean, but 2 were found: - redisCrawlerProperties - spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties
原因:Spring Boot自動為spring.redis創(chuàng)建了一個RedisProperties,而我們又定義了redisCrawlerProperties,導(dǎo)致LettuceConnectionConfiguration無法確定使用哪個RedisProperties。
解決方法:顯式定義主Redis的RedisProperties,并用@Primary標(biāo)記為默認(rèn):
@Bean(name = "redisProperties")
@Primary
@ConfigurationProperties(prefix = "spring.redis")
public RedisProperties redisProperties() {
return new RedisProperties();
}
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory(@Qualifier("redisProperties") RedisProperties redisProperties) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisProperties.getHost());
config.setPort(redisProperties.getPort());
config.setDatabase(redisProperties.getDatabase());
if (redisProperties.getPassword() != null) {
config.setPassword(redisProperties.getPassword());
}
return new LettuceConnectionFactory(config);
}redisProperties:綁定spring.redis,用@Primary標(biāo)記為默認(rèn)。redisConnectionFactory:為主Redis創(chuàng)建連接工廠,確保redisTemplate使用正確配置。
最終的RedisConfig.java
以下是完整的RedisConfig.java(敏感信息已脫敏):
package com.caven.framework.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean(name = "redisProperties")
@Primary
@ConfigurationProperties(prefix = "spring.redis")
public RedisProperties redisProperties() {
return new RedisProperties();
}
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory(@Qualifier("redisProperties") RedisProperties redisProperties) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisProperties.getHost());
config.setPort(redisProperties.getPort());
config.setDatabase(redisProperties.getDatabase());
if (redisProperties.getPassword() != null) {
config.setPassword(redisProperties.getPassword());
}
return new LettuceConnectionFactory(config);
}
@Bean(name = "redisTemplate")
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<Object, Object> redisTemplate(@Qualifier("redisConnectionFactory") RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean(name = "redisCrawlerProperties")
@ConfigurationProperties(prefix = "spring.redis-crawler")
public RedisProperties redisCrawlerProperties() {
return new RedisProperties();
}
@Bean(name = "redisCrawlerConnectionFactory")
public RedisConnectionFactory redisCrawlerConnectionFactory(@Qualifier("redisCrawlerProperties") RedisProperties redisCrawlerProperties) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisCrawlerProperties.getHost());
config.setPort(redisCrawlerProperties.getPort());
config.setDatabase(redisCrawlerProperties.getDatabase());
if (redisCrawlerProperties.getPassword() != null) {
config.setPassword(redisCrawlerProperties.getPassword());
}
return new LettuceConnectionFactory(config);
}
@Bean(name = "redisTemplateCrawl")
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<Object, Object> redisTemplateCrawl(@Qualifier("redisCrawlerConnectionFactory") RedisConnectionFactory redisCrawlerConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisCrawlerConnectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public DefaultRedisScript<Long> limitScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
private String limitScriptText() {
return "local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local time = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key);\n" +
"if current and tonumber(current) > count then\n" +
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', key)\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', key, time)\n" +
"end\n" +
"return tonumber(current);";
}
}實現(xiàn)通用的Redis工具類
為了簡化Redis操作,我們創(chuàng)建了一個RedisCache工具類,支持動態(tài)選擇RedisTemplate,同時兼容現(xiàn)有代碼。
問題3:動態(tài)選擇Redis實例
最初的RedisCache.java只注入了一個RedisTemplate:
@Autowired public RedisTemplate redisTemplate;
這導(dǎo)致無法操作爬蟲Redis。我們希望:
- 現(xiàn)有代碼繼續(xù)使用主Redis(
redisTemplate),無需修改。 - 新代碼可以通過參數(shù)選擇主Redis或爬蟲Redis(
redisTemplateCrawl)。
解決方法:
- 注入兩個
RedisTemplate(redisTemplate和redisTemplateCrawl)。 - 保留原有方法,默認(rèn)使用
redisTemplate。 - 為每個方法添加帶
templateName參數(shù)的重load版本,支持選擇Redis實例。
最終的RedisCache.java
package com.caven.framework.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Autowired
@Qualifier("redisTemplateCrawl")
private RedisTemplate redisTemplateCrawl;
private RedisTemplate getRedisTemplate(String templateName) {
if ("crawl".equalsIgnoreCase(templateName)) {
return redisTemplateCrawl;
}
return redisTemplate;
}
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
public <T> void setCacheObject(final String templateName, final String key, final T value) {
getRedisTemplate(templateName).opsForValue().set(key, value);
}
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
public <T> void setCacheObject(final String templateName, final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
getRedisTemplate(templateName).opsForValue().set(key, value, timeout, timeUnit);
}
// 其他方法類似,省略完整代碼
}關(guān)鍵點:
- 使用
@Qualifier注入redisTemplate和redisTemplateCrawl。 - 保留原有方法(如
setCacheObject(String key, T value)),默認(rèn)使用redisTemplate。 - 新增帶
templateName的重載方法(如setCacheObject(String templateName, String key, T value)),支持選擇Redis實例。 getRedisTemplate方法根據(jù)templateName返回對應(yīng)的RedisTemplate("crawl"返回redisTemplateCrawl,否則返回redisTemplate)。
使用示例
在Service層中:
@Service
public class MyService {
@Autowired
private RedisCache redisCache;
public void example() {
// 現(xiàn)有代碼:默認(rèn)使用主Redis (database: 4)
redisCache.setCacheObject("key1", "value1");
String value1 = redisCache.getCacheObject("key1");
// 新代碼:使用爬蟲Redis (database: 7)
redisCache.setCacheObject("crawl", "key2", "value2");
String value2 = redisCache.getCacheObject("crawl", "key2");
}
}- 現(xiàn)有代碼無需修改,繼續(xù)使用主Redis。
- 新代碼通過
templateName="crawl"操作爬蟲Redis。
優(yōu)化建議
使用枚舉替代字符串:
為避免templateName的硬編碼,可使用枚舉:
public enum RedisInstance {
DEFAULT,
CRAWL
}
private RedisTemplate getRedisTemplate(RedisInstance instance) {
return instance == RedisInstance.CRAWL ? redisTemplateCrawl : redisTemplate;
}使用示例:
redisCache.setCacheObject(RedisInstance.CRAWL, "key2", "value2");
錯誤處理:
在getRedisTemplate中添加空檢查:
private RedisTemplate getRedisTemplate(String templateName) {
if (redisTemplate == null || redisTemplateCrawl == null) {
throw new IllegalStateException("RedisTemplate not initialized");
}
return "crawl".equalsIgnoreCase(templateName) ? redisTemplateCrawl : redisTemplate;
}連接測試:
確保Redis服務(wù)器可訪問(此處IP已脫敏):
redis-cli -h [REDACTED_HOST] -p 6379 -n 4 # 主Redis redis-cli -h [REDACTED_HOST] -p 6379 -n 7 # 爬蟲Redis
序列化器:
確保FastJson2JsonRedisSerializer實現(xiàn)正確,支持序列化和反序列化。
總結(jié)
通過以下步驟,我們在Spring Boot項目中實現(xiàn)了優(yōu)雅的多Redis連接:
- 在
application.yml中配置兩套Redis(spring.redis和spring.redis-crawler)。 - 在
RedisConfig.java中定義兩個RedisProperties、RedisConnectionFactory和RedisTemplate,使用@Primary和@Qualifier解決注入歧義。 - 實現(xiàn)
RedisCache工具類,支持動態(tài)選擇Redis實例,同時兼容現(xiàn)有代碼。
這種方案既靈活又易于擴展,適合需要操作多個Redis實例的場景。如果你有更多Redis實例,只需重復(fù)上述步驟,定義新的RedisProperties和RedisTemplate,并在RedisCache中擴展支持。
參考:
- Spring Boot官方文檔:https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.redis
- Lettuce官方文檔:https://lettuce.io/
到此這篇關(guān)于在SpringBoot項目中優(yōu)雅的連接多臺Redis的操作方法的文章就介紹到這了,更多相關(guān)SpringBoot連接多臺Redis內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot連接Redis集群教程
- SpringBoot實現(xiàn)自定義Redis的連接的流程步驟
- 關(guān)于SpringBoot集成Lettuce連接Redis的方法和案例
- springboot連接不上redis的三種解決辦法
- springboot連接redis并動態(tài)切換database的實現(xiàn)方法
- springboot 如何使用jedis連接Redis數(shù)據(jù)庫
- 關(guān)于Springboot2.x集成lettuce連接redis集群報超時異常Command timed out after 6 second(s)
- springboot連接Redis的教程詳解
- springboot2整合redis使用lettuce連接池的方法(解決lettuce連接池?zé)o效問題)
相關(guān)文章
SpringMVC實現(xiàn)簡單跳轉(zhuǎn)方法(專題)
這篇文章主要介紹了SpringMVC實現(xiàn)簡單跳轉(zhuǎn)方法(專題),詳細(xì)的介紹了SpringMVC跳轉(zhuǎn)的幾種方法,非常具有實用價值,需要的朋友可以參考下2018-03-03
Spring中的@PostConstruct注解使用方法解析
這篇文章主要介紹了Spring中的@PostConstruct注解使用方法解析,@PostConstruct注解是用來處理在@Autowired注入屬性后init()方法之前,對一些零散的屬性進行賦值的注解,需要的朋友可以參考下2023-11-11
Spring?Boot?+?Mybatis?Plus實現(xiàn)樹狀菜單的方法
這篇文章主要介紹了Spring?Boot?+?Mybatis?Plus實現(xiàn)樹狀菜單,包括實體類中添加子菜單列表和集合及構(gòu)建菜單樹的詳細(xì)代碼,代碼簡單易懂,需要的朋友可以參考下2021-12-12
Feign利用自定義注解實現(xiàn)路徑轉(zhuǎn)義詳解
這篇文章主要講解一下如何通過注解實現(xiàn)對路由中的路徑進行自定義編碼,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)或工作有一定的幫助,需要的可以參考一下2022-06-06
java商城項目實戰(zhàn)之購物車功能實現(xiàn)
這篇文章主要為大家詳細(xì)介紹了java商城項目實戰(zhàn)之購物車功能實現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-04-04
httpclient 請求http數(shù)據(jù),json轉(zhuǎn)map的實例
下面小編就為大家?guī)硪黄猦ttpclient 請求http數(shù)據(jù),json轉(zhuǎn)map的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12

