最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在SpringBoot項目中優(yōu)雅的連接多臺Redis的操作方法

 更新時間:2025年09月05日 15:23:44   作者:CavenWang  
本文將基于一個實際案例,詳細(xì)介紹如何在Spring Boot中優(yōu)雅地配置和使用多個Redis實例,解決常見的注入歧義問題,并提供一個通用的Redis工具類來簡化操作,本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧

如何在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)。

項目中遇到以下問題:

  1. 配置兩個RedisTemplate時,Spring容器找不到redisCrawlerConnectionFactory
  2. 配置多個RedisProperties導(dǎo)致注入歧義,拋出NoUniqueBeanDefinitionException
  3. 需要一個通用的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: -1ms
  • spring.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)。

解決方法

  • 注入兩個RedisTemplateredisTemplateredisTemplateCrawl)。
  • 保留原有方法,默認(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注入redisTemplateredisTemplateCrawl。
  • 保留原有方法(如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連接:

  1. application.yml中配置兩套Redis(spring.redisspring.redis-crawler)。
  2. RedisConfig.java中定義兩個RedisPropertiesRedisConnectionFactoryRedisTemplate,使用@Primary@Qualifier解決注入歧義。
  3. 實現(xiàn)RedisCache工具類,支持動態(tài)選擇Redis實例,同時兼容現(xiàn)有代碼。

這種方案既靈活又易于擴展,適合需要操作多個Redis實例的場景。如果你有更多Redis實例,只需重復(fù)上述步驟,定義新的RedisPropertiesRedisTemplate,并在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)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringMVC實現(xiàn)簡單跳轉(zhuǎn)方法(專題)

    SpringMVC實現(xiàn)簡單跳轉(zhuǎn)方法(專題)

    這篇文章主要介紹了SpringMVC實現(xiàn)簡單跳轉(zhuǎn)方法(專題),詳細(xì)的介紹了SpringMVC跳轉(zhuǎn)的幾種方法,非常具有實用價值,需要的朋友可以參考下
    2018-03-03
  • Java原生操作JDBC連接以及原理詳解

    Java原生操作JDBC連接以及原理詳解

    這篇文章主要給大家介紹了關(guān)于Java原生操作JDBC連接以及原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Spring中的@PostConstruct注解使用方法解析

    Spring中的@PostConstruct注解使用方法解析

    這篇文章主要介紹了Spring中的@PostConstruct注解使用方法解析,@PostConstruct注解是用來處理在@Autowired注入屬性后init()方法之前,對一些零散的屬性進行賦值的注解,需要的朋友可以參考下
    2023-11-11
  • Spring?Boot?+?Mybatis?Plus實現(xiàn)樹狀菜單的方法

    Spring?Boot?+?Mybatis?Plus實現(xiàn)樹狀菜單的方法

    這篇文章主要介紹了Spring?Boot?+?Mybatis?Plus實現(xiàn)樹狀菜單,包括實體類中添加子菜單列表和集合及構(gòu)建菜單樹的詳細(xì)代碼,代碼簡單易懂,需要的朋友可以參考下
    2021-12-12
  • Feign利用自定義注解實現(xiàn)路徑轉(zhuǎn)義詳解

    Feign利用自定義注解實現(xiàn)路徑轉(zhuǎn)義詳解

    這篇文章主要講解一下如何通過注解實現(xiàn)對路由中的路徑進行自定義編碼,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)或工作有一定的幫助,需要的可以參考一下
    2022-06-06
  • 詳談fastjson將對象格式化成json時的兩個問題

    詳談fastjson將對象格式化成json時的兩個問題

    下面小編就為大家?guī)硪黄斦刦astjson將對象格式化成json時的兩個問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • SpringBoot Shiro權(quán)限管理方式

    SpringBoot Shiro權(quán)限管理方式

    這篇文章主要介紹了SpringBoot Shiro權(quán)限管理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • 基于maven的springboot的"過時"用法解析

    基于maven的springboot的"過時"用法解析

    這篇文章主要為大家介紹了基于maven的springboot"過時"用法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • java商城項目實戰(zhàn)之購物車功能實現(xiàn)

    java商城項目實戰(zhàn)之購物車功能實現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了java商城項目實戰(zhàn)之購物車功能實現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • httpclient 請求http數(shù)據(jù),json轉(zhuǎn)map的實例

    httpclient 請求http數(shù)據(jù),json轉(zhuǎn)map的實例

    下面小編就為大家?guī)硪黄猦ttpclient 請求http數(shù)據(jù),json轉(zhuǎn)map的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12

最新評論

延边| 奈曼旗| 务川| 道孚县| 张家口市| 蚌埠市| 山西省| 铁力市| 渭源县| 济宁市| 巴彦县| 七台河市| 大余县| 共和县| 西乌珠穆沁旗| 冕宁县| 高淳县| 赤壁市| 勐海县| 浑源县| 太和县| 安多县| 中宁县| 遵化市| 穆棱市| 凉山| 连南| 鹿邑县| 枣庄市| 汉中市| 宜兰县| 麦盖提县| 大理市| 眉山市| 广东省| 乃东县| 开封县| 营口市| 巨鹿县| 宣化县| 常山县|