SpringBoot實現(xiàn)Redis多數(shù)據(jù)庫切換(多數(shù)據(jù)源配置)
在實際開發(fā)中,隨著業(yè)務(wù)復(fù)雜度提升,我們常常會遇到 Redis 需要按不同業(yè)務(wù)模塊(如認證、聊天等)分庫管理的需求。這樣做可以實現(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ù)庫
需求場景
假設(shè)我們有如下需求:
- auth 業(yè)務(wù)使用 Redis 的第 1 號數(shù)據(jù)庫(database=1)
- chat 業(yè)務(wù)使用 Redis 的第 2 號數(shù)據(jù)庫(database=2)
- 還有一個默認 Redis(database=0)供其他業(yè)務(wù)使用
我們希望通過配置和代碼實現(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 構(gòu)造方法 ======================= /** * 創(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ù)庫都對應(yīng)一個
LettuceConnectionFactory
和一個RedisTemplate
。 - 通過
@Qualifier
注解區(qū)分不同的 Bean。 @Primary
標(biāo)注默認 Redis,Spring Cache 相關(guān)功能會自動使用它。
工具類封裝
為了方便業(yè)務(wù)調(diào)用,我們可以為每個 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); } /** * 設(shè)置有效時間 * * @param key Redis鍵 * @param timeout 超時時間 * @return true=設(shè)置成功;false=設(shè)置失敗 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 設(shè)置有效時間 * * @param key Redis鍵 * @param timeout 超時時間 * @param unit 時間單位 * @return true=設(shè)置成功;false=設(shè)置失敗 */ 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 緩存鍵值對應(yīng)的數(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 緩存鍵值對應(yīng)的數(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è)務(wù)層只需注入 AuthRedisCache
或 ChatRedisCache
,即可操作對應(yīng)的 Redis 數(shù)據(jù)庫。
業(yè)務(wù)調(diào)用示例
以 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); } }
對應(yīng)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等等,驗證登錄成功與否 ,看你的設(shè)計, //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); }
總結(jié)
通過以上配置和代碼實現(xiàn),我們就可以在 Spring Boot 項目中靈活地切換和操作多個 Redis 數(shù)據(jù)庫,實現(xiàn)數(shù)據(jù)隔離和多業(yè)務(wù)場景支持。核心思路是:
- 配置文件中分模塊配置不同 Redis 數(shù)據(jù)源
- 編寫屬性類和配置類,分別注入不同的
RedisTemplate
- 通過工具類封裝,業(yè)務(wù)層按需注入和調(diào)用
這種方式不僅適用于 auth、chat 場景,也適用于任何需要 Redis 多數(shù)據(jù)源的業(yè)務(wù)需求。
以上就是SpringBoot實現(xiàn)Redis多數(shù)據(jù)庫切換(多數(shù)據(jù)源配置)的詳細內(nèi)容,更多關(guān)于SpringBoot Redis多數(shù)據(jù)庫切換的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java基礎(chǔ)高級綜合練習(xí)題撲克牌的創(chuàng)建
今天小編就為大家分享一篇關(guān)于Java基礎(chǔ)高級綜合練習(xí)題撲克牌的創(chuàng)建,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01Maven中插件調(diào)試與性能調(diào)優(yōu)的學(xué)習(xí)指南
在現(xiàn)代Java生態(tài)系統(tǒng)中,Apache Maven作為項目構(gòu)建的事實標(biāo)準(zhǔn)工具,其核心價值不僅體現(xiàn)在依賴管理能力上,更在于其靈活的插件體系,本文小編就來和大家聊聊Maven中插件調(diào)試與性能調(diào)優(yōu)的相關(guān)知識吧2025-05-05springboot掃描自定義的servlet和filter代碼詳解
本文是一篇根據(jù)作者工作經(jīng)歷總結(jié)出來的關(guān)于springboot掃描自定義的servlet和filter代碼詳解的文章,小編覺得非常不錯,這里給大家分享下,和朋友們一起學(xué)習(xí),進步。2017-10-10使用Servlet Filter實現(xiàn)系統(tǒng)登錄權(quán)限
這篇文章主要為大家詳細介紹了使用Servlet Filter實現(xiàn)系統(tǒng)登錄權(quán)限,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10