SpringBoot實現(xiàn)Redis多數(shù)據(jù)庫切換(多數(shù)據(jù)源配置)
在實際開發(fā)中,隨著業(yè)務復雜度提升,我們常常會遇到 Redis 需要按不同業(yè)務模塊(如認證、聊天等)分庫管理的需求。這樣做可以實現(xiàn)數(shù)據(jù)隔離、便于運維和擴展。本文將以 Spring Boot 為例,手把手教你如何優(yōu)雅地實現(xiàn) Redis 多數(shù)據(jù)庫(多數(shù)據(jù)源)切換。
效果展示
這里只是范例,大家拿到我的實現(xiàn)即可集成到項目中
接口Auth和Chat使用了不同的redis庫

配置了不同的redis庫

Auth

發(fā)送請求并拿到redis的鍵值

并出現(xiàn)在redis數(shù)據(jù)庫1中

正常拿到數(shù)據(jù)
Chat

發(fā)送請求并拿到redis的鍵值

并出現(xiàn)在redis數(shù)據(jù)庫2中

正常拿到數(shù)據(jù)
實現(xiàn)了不同的redis數(shù)據(jù)庫
需求場景
假設我們有如下需求:
- auth 業(yè)務使用 Redis 的第 1 號數(shù)據(jù)庫(database=1)
- chat 業(yè)務使用 Redis 的第 2 號數(shù)據(jù)庫(database=2)
- 還有一個默認 Redis(database=0)供其他業(yè)務使用
我們希望通過配置和代碼實現(xiàn),能夠靈活地在不同 Redis 數(shù)據(jù)庫間切換和操作。
配置文件編寫
首先,在 application-redis.yml 中分別配置不同的 Redis 數(shù)據(jù)源:
spring:
data:
redis:
host: localhost
port: 6379
database: 0 # 默認 Redis,JWT 用
auth-redis:
host: localhost
port: 6379
database: 1 # 默認 Redis,JWT 用
chat-redis:
host: localhost
port: 6379
database: 2 # 用于 ChatHistory
屬性類封裝
為每個自定義 Redis 數(shù)據(jù)源編寫屬性類,方便后續(xù)自動注入:
AuthRedisProperties.java
package com.anfioo.common.bean;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "spring.data.auth-redis")
@Data
public class AuthRedisProperties {
private String host;
private int port;
private int database;
}
ChatRedisProperties.java
package com.anfioo.common.bean;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "spring.data.chat-redis")
@Data
public class ChatRedisProperties {
private String host;
private int port;
private int database;
}
多數(shù)據(jù)源 Redis 配置
核心配置類如下:
RedisConfig.java
package com.anfioo.admin.config;
import com.anfioo.common.bean.AuthRedisProperties;
import com.anfioo.common.bean.ChatRedisProperties;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.CacheManager;
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.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* Redis配置類,啟用Spring Cache并定義多個Redis實例和模板
*/
@Configuration
@EnableCaching
public class RedisConfig {
// ======================= 默認 Redis (database=0) =======================
/**
* 創(chuàng)建默認Redis連接工廠
* @param redisProperties Redis屬性配置
* @return LettuceConnectionFactory實例
*/
@Primary
@Bean(name = "defaultRedisConnectionFactory")
public LettuceConnectionFactory defaultRedisConnectionFactory(RedisProperties redisProperties) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisProperties.getHost());
config.setPort(redisProperties.getPort());
config.setDatabase(redisProperties.getDatabase());
return new LettuceConnectionFactory(config);
}
/**
* 創(chuàng)建默認Redis模板
* @param factory Redis連接工廠
* @return RedisTemplate實例
*/
@Primary
@Bean(name = "defaultRedisTemplate")
public RedisTemplate<Object, Object> defaultRedisTemplate(
@Qualifier("defaultRedisConnectionFactory") RedisConnectionFactory factory) {
return createRedisTemplate(factory);
}
/**
* Spring Cache使用的CacheManager(綁定 defaultRedis database=0)
* @param factory Redis連接工廠
* @return CacheManager實例
*/
@Primary
@Bean
public CacheManager cacheManager(@Qualifier("defaultRedisConnectionFactory") RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new Jackson2JsonRedisSerializer<>(Object.class)));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
// ======================= Auth Redis (database=1) =======================
/**
* 創(chuàng)建Auth Redis連接工廠
* @param properties Auth Redis屬性配置
* @return LettuceConnectionFactory實例
*/
@Bean(name = "authRedisConnectionFactory")
public LettuceConnectionFactory authRedisConnectionFactory(AuthRedisProperties properties) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(properties.getHost());
config.setPort(properties.getPort());
config.setDatabase(properties.getDatabase());
return new LettuceConnectionFactory(config);
}
/**
* 創(chuàng)建Auth Redis模板
* @param factory Redis連接工廠
* @return RedisTemplate實例
*/
@Bean(name = "authRedisTemplate")
public RedisTemplate<Object, Object> authRedisTemplate(
@Qualifier("authRedisConnectionFactory") RedisConnectionFactory factory) {
return createRedisTemplate(factory);
}
// ======================= Chat Redis (database=2) =======================
/**
* 創(chuàng)建Chat Redis連接工廠
* @param properties Chat Redis屬性配置
* @return LettuceConnectionFactory實例
*/
@Bean(name = "chatRedisConnectionFactory")
public LettuceConnectionFactory chatRedisConnectionFactory(ChatRedisProperties properties) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(properties.getHost());
config.setPort(properties.getPort());
config.setDatabase(properties.getDatabase());
return new LettuceConnectionFactory(config);
}
/**
* 創(chuàng)建Chat Redis模板
* @param factory Redis連接工廠
* @return RedisTemplate實例
*/
@Bean(name = "chatRedisTemplate")
public RedisTemplate<Object, Object> chatRedisTemplate(
@Qualifier("chatRedisConnectionFactory") RedisConnectionFactory factory) {
return createRedisTemplate(factory);
}
// ======================= 公共 RedisTemplate 構造方法 =======================
/**
* 創(chuàng)建RedisTemplate實例
* @param connectionFactory Redis連接工廠
* @return RedisTemplate實例
*/
private RedisTemplate<Object, Object> createRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jacksonSerializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
// ======================= 限流 Lua 腳本 =======================
/**
* 創(chuàng)建限流Lua腳本
* @return DefaultRedisScript實例
*/
@Bean
public DefaultRedisScript<Long> limitScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 限流Lua腳本文本
* @return Lua腳本字符串
*/
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);";
}
}
要點說明:
- 每個 Redis 數(shù)據(jù)庫都對應一個
LettuceConnectionFactory和一個RedisTemplate。 - 通過
@Qualifier注解區(qū)分不同的 Bean。 @Primary標注默認 Redis,Spring Cache 相關功能會自動使用它。
工具類封裝
為了方便業(yè)務調用,我們可以為每個 Redis 數(shù)據(jù)源封裝一個工具類,繼承自通用的 RedisCache:
RedisCache.java(通用工具類,支持常用操作,參考ruoyi實現(xiàn))
package com.anfioo.common.utils;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* spring redis 工具類
*
* @author ruoyi
**/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
private final RedisTemplate redisTemplate;
public RedisCache(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 緩存基本的對象,Integer、String、實體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 緩存基本的對象,Integer、String、實體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
* @param timeout 時間
* @param timeUnit 時間顆粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 設置有效時間
*
* @param key Redis鍵
* @param timeout 超時時間
* @return true=設置成功;false=設置失敗
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 設置有效時間
*
* @param key Redis鍵
* @param timeout 超時時間
* @param unit 時間單位
* @return true=設置成功;false=設置失敗
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 獲取有效時間
*
* @param key Redis鍵
* @return 有效時間
*/
public long getExpire(final String key) {
return redisTemplate.getExpire(key);
}
/**
* 判斷 key是否存在
*
* @param key 鍵
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 獲得緩存的基本對象。
*
* @param key 緩存鍵值
* @return 緩存鍵值對應的數(shù)據(jù)
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 刪除單個對象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 刪除集合對象
*
* @param collection 多個對象
* @return
*/
public boolean deleteObject(final Collection collection) {
return redisTemplate.delete(collection) > 0;
}
/**
* 緩存List數(shù)據(jù)
*
* @param key 緩存的鍵值
* @param dataList 待緩存的List數(shù)據(jù)
* @return 緩存的對象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 獲得緩存的list對象
*
* @param key 緩存的鍵值
* @return 緩存鍵值對應的數(shù)據(jù)
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 緩存Set
*
* @param key 緩存鍵值
* @param dataSet 緩存的數(shù)據(jù)
* @return 緩存數(shù)據(jù)的對象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 獲得緩存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 緩存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 獲得緩存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入數(shù)據(jù)
*
* @param key Redis鍵
* @param hKey Hash鍵
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 獲取Hash中的數(shù)據(jù)
*
* @param key Redis鍵
* @param hKey Hash鍵
* @return Hash中的對象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 獲取多個Hash中的數(shù)據(jù)
*
* @param key Redis鍵
* @param hKeys Hash鍵集合
* @return Hash對象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 刪除Hash中的某條數(shù)據(jù)
*
* @param key Redis鍵
* @param hKey Hash鍵
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey) {
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 獲得緩存的基本對象列表
*
* @param pattern 字符串前綴
* @return 對象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
AuthRedisCache.java
package com.anfioo.common.utils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class AuthRedisCache extends RedisCache {
public AuthRedisCache(@Qualifier("authRedisTemplate") RedisTemplate redisTemplate) {
super(redisTemplate);
}
}
ChatRedisCache.java
package com.anfioo.common.utils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class ChatRedisCache extends RedisCache {
public ChatRedisCache(@Qualifier("chatRedisTemplate") RedisTemplate redisTemplate) {
super(redisTemplate);
}
}
這樣,業(yè)務層只需注入 AuthRedisCache 或 ChatRedisCache,即可操作對應的 Redis 數(shù)據(jù)庫。
業(yè)務調用示例
以 Controller 為例,演示如何分別操作不同 Redis 數(shù)據(jù)庫:
package com.anfioo.sys.controller;
import com.anfioo.common.core.ApiResponse;
import com.anfioo.common.core.BaseController;
import com.anfioo.sys.service.RedisDemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/redis-demo")
public class RedisDemoController extends BaseController {
@Autowired
private RedisDemoService redisDemoService;
@PostMapping("/create-auth")
public ApiResponse<String> setAuthRedis() {
String s = redisDemoService.setAuthRedisValue();
return success(s);
}
@PostMapping("/create-chat")
public ApiResponse<String> setChatRedis() {
String s = redisDemoService.setChatRedisValue();
return success(s);
}
@GetMapping("/auth/{id}")
public ApiResponse<String> getAuthRedis(@PathVariable String id) {
String value = redisDemoService.getAuthRedisValueById(id);
return success(value);
}
@GetMapping("/chat/{id}")
public ApiResponse<String> getChatRedis(@PathVariable String id) {
String value = redisDemoService.getChatRedisValueById(id);
return success(value);
}
}
對應Service和其實現(xiàn)類
package com.anfioo.sys.service.impl;
import java.util.ArrayList;
import com.anfioo.common.utils.AuthRedisCache;
import com.anfioo.common.utils.ChatRedisCache;
import com.anfioo.sys.service.RedisDemoService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
public class RedisDemoServiceImpl implements RedisDemoService {
@Autowired
private AuthRedisCache authRedisCache;
@Autowired
private ChatRedisCache chatRedisCache;
private static final String LOGIN_PREFIX = "login_token:";
private static final String CHAT_PREFIX = "chat_token:";
@Override
public String setAuthRedisValue() {
//todo 獲取生成的jwt等等,驗證登錄成功與否 ,看你的設計,
//todo 鍵 - 可以使用唯一uuid 值 -你要放入的緩存的值
String AuthId = UUID.randomUUID().toString();
String redisKey = LOGIN_PREFIX + AuthId;
YouSaveData youSaveData = new YouSaveData();
youSaveData.setId("1");
ArrayList<String> roles = new ArrayList<>();
roles.add("user");
roles.add("manager");
youSaveData.setRole(roles);
youSaveData.setUsername("Anfioo");
youSaveData.setPassword("Anfioo666");
authRedisCache.setCacheObject(redisKey, youSaveData);
return AuthId;
}
@Override
public String setChatRedisValue() {
String chatId = UUID.randomUUID().toString();
String redisKey = CHAT_PREFIX + chatId;
YouSaveChat youSaveChat = new YouSaveChat();
youSaveChat.setChatId(chatId);
ArrayList<String> members = new ArrayList<>();
members.add("userA");
members.add("userB");
youSaveChat.setMembers(members);
youSaveChat.setChatName("Anfioo聊天室");
youSaveChat.setLastMessage("Hello, world!");
chatRedisCache.setCacheObject(redisKey, youSaveChat);
return chatId;
}
@Override
public String getAuthRedisValueById(String id) {
String redisKey = LOGIN_PREFIX + id;
Object obj = authRedisCache.getCacheObject(redisKey);
return obj != null ? obj.toString() : null;
}
@Override
public String getChatRedisValueById(String id) {
String redisKey = CHAT_PREFIX + id;
Object obj = chatRedisCache.getCacheObject(redisKey);
return obj != null ? obj.toString() : null;
}
@Data
public static class YouSaveChat {
private String chatId;
private List<String> members;
private String chatName;
private String lastMessage;
}
@Data
public static class YouSaveData {
private String id;
private List<String> role;
private String username;
private String password;
}
}
package com.anfioo.sys.service;
public interface RedisDemoService {
String setAuthRedisValue();
String setChatRedisValue();
String getAuthRedisValueById(String id);
String getChatRedisValueById(String id);
}
總結
通過以上配置和代碼實現(xiàn),我們就可以在 Spring Boot 項目中靈活地切換和操作多個 Redis 數(shù)據(jù)庫,實現(xiàn)數(shù)據(jù)隔離和多業(yè)務場景支持。核心思路是:
- 配置文件中分模塊配置不同 Redis 數(shù)據(jù)源
- 編寫屬性類和配置類,分別注入不同的
RedisTemplate - 通過工具類封裝,業(yè)務層按需注入和調用
這種方式不僅適用于 auth、chat 場景,也適用于任何需要 Redis 多數(shù)據(jù)源的業(yè)務需求。
以上就是SpringBoot實現(xiàn)Redis多數(shù)據(jù)庫切換(多數(shù)據(jù)源配置)的詳細內容,更多關于SpringBoot Redis多數(shù)據(jù)庫切換的資料請關注腳本之家其它相關文章!
相關文章
springboot掃描自定義的servlet和filter代碼詳解
本文是一篇根據(jù)作者工作經(jīng)歷總結出來的關于springboot掃描自定義的servlet和filter代碼詳解的文章,小編覺得非常不錯,這里給大家分享下,和朋友們一起學習,進步。2017-10-10
使用Servlet Filter實現(xiàn)系統(tǒng)登錄權限
這篇文章主要為大家詳細介紹了使用Servlet Filter實現(xiàn)系統(tǒng)登錄權限,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10

