一,什么情況下需要使用多個redis數據源?
為了緩存數據,通常我們會在線上使用多個redis的cluster,
每個cluster中緩存不同的數據,以方便管理.
例如:我們緩存了雜志文章/商品信息/分類頁面
同時我們又使用一個redis cluster作為分布式session
這里就會有多個redis數據源在項目中
說明:劉宏締的架構森林是一個專注架構的博客,地址:https://www.cnblogs.com/architectforest
對應的源碼可以訪問這里獲取: https://github.com/liuhongdi/
說明:作者:劉宏締 郵箱: 371125307@qq.com
二,演示項目說明:
1,項目地址:
https://github.com/liuhongdi/multiredissource
2, 項目原理
存儲session使用redis集群,
另外使用兩個redis實例做緩存
關於redis集群的搭建請參見:
https://www.cnblogs.com/architectforest/p/13196749.html
3, 項目結構:
如圖:
三,項目配置文件說明:
application.properties
#default redis,for session
spring.redis.cluster.nodes=172.17.0.2:6379,172.17.0.3:6379,172.17.0.4:6379,172.17.0.5:6379,172.17.0.6:6379,172.17.0.7:6379 spring.redis.cluster.max-redirects=3 spring.redis.password=lhddemo spring.redis.database=0 spring.session.store-type=redis spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-wait=1 spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.min-idle=0 #redis1 spring.redis1.host=127.0.0.1 spring.redis1.port=6379 spring.redis1.password=lhddemo spring.redis1.database=0 spring.redis1.lettuce.pool.max-active=8 spring.redis1.lettuce.pool.max-wait=1 spring.redis1.lettuce.pool.max-idle=8 spring.redis1.lettuce.pool.min-idle=0 #redis2 spring.redis2.host=127.0.0.1 spring.redis2.port=6380 spring.redis2.password=lhddemo spring.redis2.database=0 spring.redis2.lettuce.pool.max-active=8 spring.redis2.lettuce.pool.max-wait=1 spring.redis2.lettuce.pool.max-idle=8 spring.redis2.lettuce.pool.min-idle=0
說明:共3個redis,
存儲session的集群有6個節點(3主3從):
172.17.0.2:6379,
172.17.0.3:6379,
172.17.0.4:6379,
172.17.0.5:6379,
172.17.0.6:6379,
172.17.0.7:6379
redis1是非集群結點:
127.0.0.1:6379
redis2是非集群結點:
127.0.0.1:6380
四,java代碼說明
1,RedisConfig.java
@Configuration public class RedisConfig { @Bean @Primary public LettuceConnectionFactory redissessionLettuceConnectionFactory(RedisClusterConfiguration redisSessionRedisConfig, GenericObjectPoolConfig redisSessionPoolConfig) { LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100)) .poolConfig(redisSessionPoolConfig).build(); return new LettuceConnectionFactory(redisSessionRedisConfig, clientConfig); } @Bean public RedisTemplate<String, String> redisSessionTemplate( @Qualifier("redissessionLettuceConnectionFactory") LettuceConnectionFactory redissessionLettuceConnectionFactory) { RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); //使用StringRedisSerializer來序列化和反序列化redis的ke redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); //開啟事務 redisTemplate.setEnableTransactionSupport(true); redisTemplate.setConnectionFactory(redissessionLettuceConnectionFactory); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean @ConditionalOnBean(name = "redis1RedisConfig") public LettuceConnectionFactory redis1LettuceConnectionFactory(RedisStandaloneConfiguration redis1RedisConfig, GenericObjectPoolConfig redis1PoolConfig) { LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100)) .poolConfig(redis1PoolConfig).build(); return new LettuceConnectionFactory(redis1RedisConfig, clientConfig); } @Bean public RedisTemplate<String, String> redis1Template( @Qualifier("redis1LettuceConnectionFactory") LettuceConnectionFactory redis1LettuceConnectionFactory) { RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); //使用StringRedisSerializer來序列化和反序列化redis的ke redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); //開啟事務 redisTemplate.setEnableTransactionSupport(true); redisTemplate.setConnectionFactory(redis1LettuceConnectionFactory); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean @ConditionalOnBean(name = "redis2RedisConfig") public LettuceConnectionFactory redis2LettuceConnectionFactory(RedisStandaloneConfiguration redis2RedisConfig, GenericObjectPoolConfig redis2PoolConfig) { LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100)) .poolConfig(redis2PoolConfig).build(); return new LettuceConnectionFactory(redis2RedisConfig, clientConfig); } @Bean @ConditionalOnBean(name = "redis2LettuceConnectionFactory") public RedisTemplate<String, String> redis2Template( @Qualifier("redis2LettuceConnectionFactory") LettuceConnectionFactory redis2LettuceConnectionFactory) { RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); redisTemplate.setValueSerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); //使用StringRedisSerializer來序列化和反序列化redis的ke redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); //開啟事務 redisTemplate.setEnableTransactionSupport(true); redisTemplate.setConnectionFactory(redis2LettuceConnectionFactory); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Configuration public static class RedisSessionConfig { @Value("${spring.redis.cluster.nodes}") private String nodes; @Value("${spring.redis.cluster.max-redirects}") private Integer maxRedirects; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.database}") private Integer database; @Value("${spring.redis.lettuce.pool.max-active}") private Integer maxActive; @Value("${spring.redis.lettuce.pool.max-idle}") private Integer maxIdle; @Value("${spring.redis.lettuce.pool.max-wait}") private Long maxWait; @Value("${spring.redis.lettuce.pool.min-idle}") private Integer minIdle; @Bean public GenericObjectPoolConfig redisSessionPoolConfig() { GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxActive); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); config.setMaxWaitMillis(maxWait); return config; } @Bean public RedisClusterConfiguration redisSessionRedisConfig() { RedisClusterConfiguration config = new RedisClusterConfiguration(); String[] sub = nodes.split(","); List<RedisNode> nodeList = new ArrayList<>(sub.length); String[] tmp; for (String s : sub) { tmp = s.split(":"); nodeList.add(new RedisNode(tmp[0], Integer.valueOf(tmp[1]))); } config.setClusterNodes(nodeList); config.setMaxRedirects(maxRedirects); config.setPassword(RedisPassword.of(password)); return config; } } @Configuration public static class Redis1Config { @Value("${spring.redis1.host}") private String host; @Value("${spring.redis1.port}") private Integer port; @Value("${spring.redis1.password}") private String password; @Value("${spring.redis1.database}") private Integer database; @Value("${spring.redis1.lettuce.pool.max-active}") private Integer maxActive; @Value("${spring.redis1.lettuce.pool.max-idle}") private Integer maxIdle; @Value("${spring.redis1.lettuce.pool.max-wait}") private Long maxWait; @Value("${spring.redis1.lettuce.pool.min-idle}") private Integer minIdle; @Bean public GenericObjectPoolConfig redis1PoolConfig() { GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxActive); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); config.setMaxWaitMillis(maxWait); return config; } @Bean public RedisStandaloneConfiguration redis1RedisConfig() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName(host); config.setPassword(RedisPassword.of(password)); config.setPort(port); config.setDatabase(database); return config; } } @Configuration @ConditionalOnProperty(name = "host", prefix = "spring.redis2") public static class Redis2Config { @Value("${spring.redis2.host}") private String host; @Value("${spring.redis2.port}") private Integer port; @Value("${spring.redis2.password}") private String password; @Value("${spring.redis2.database}") private Integer database; @Value("${spring.redis2.lettuce.pool.max-active}") private Integer maxActive; @Value("${spring.redis2.lettuce.pool.max-idle}") private Integer maxIdle; @Value("${spring.redis2.lettuce.pool.max-wait}") private Long maxWait; @Value("${spring.redis2.lettuce.pool.min-idle}") private Integer minIdle; @Bean public GenericObjectPoolConfig redis2PoolConfig() { GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxActive); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); config.setMaxWaitMillis(maxWait); return config; } @Bean public RedisStandaloneConfiguration redis2RedisConfig() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName(host); config.setPassword(RedisPassword.of(password)); config.setPort(port); config.setDatabase(database); return config; } } }
說明:
分別構造了三個redisTemplate:
redisSessionTemplate:訪問redis session cluster
redis1Template:訪問redis1
redis2Template:訪問redis2
因為要供session默認使用,
所以給第一個LettuceConnectionFactory加上@Primary注解
cluster的配置要使用RedisClusterConfiguration類,
注意與:RedisStandaloneConfiguration區分
2,CacheController.java
@RestController @RequestMapping("/cache") public class CacheController { @Resource RedisTemplate<String, String> redis1Template; @Resource RedisTemplate<String, String> redis2Template; /* * get redis1 cache */ @RequestMapping("/redis1get") public String redis1Get(HttpServletRequest request){ String goodsname = redis1Template.opsForValue().get("goodsname1"); return goodsname; } /* * write redis1 cache */ @RequestMapping("/redis1set/{name}") public String redis1Set(@PathVariable String name) { //request.getSession().setAttribute("goods", name); redis1Template.opsForValue().set("goodsname1",name); return "ok"; } /* * get redis2 cache * */ @RequestMapping("/redis2get") public String redis2Get(HttpServletRequest request){ String goodsname2 = redis2Template.opsForValue().get("goodsname2"); return goodsname2; } /* * write redis2 cache * */ @RequestMapping("/redis2set/{name}") public String redis2Set(@PathVariable String name) { //request.getSession().setAttribute("goods", name); redis2Template.opsForValue().set("goodsname2",name); return "ok"; } }
說明:對redis1和redis2分別讀取和寫入
3,SessionController.java
@RestController @RequestMapping("/session") public class SessionController { /* * read session * */ @RequestMapping("/get") public Object getSession(HttpServletRequest request){ Map<String, Object> map = new HashMap<>(); map.put("sessionId", request.getSession().getId()); map.put("user", request.getSession().getAttribute("user")); map.put("maxInactiveInterval", request.getSession().getMaxInactiveInterval()); //map.put("ttl", request.getSession().); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = sdf.format(new Date(request.getSession().getCreationTime())); map.put("creationTime", time); return map; } /* * write session * */ @RequestMapping("/set/{name}") public String setSession(@PathVariable String name, HttpServletRequest request) { request.getSession().setAttribute("user", name); return "ok"; } }
說明:對session的讀取和寫入
五,多redis數據源效果測試
1,查看三個redis的數據:
redissession
[root@redis4 /]# /usr/local/soft/redis-6.0.5/bin/redis-cli -a lhddemo -c --cluster call 172.17.0.2:6379 keys \* Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Calling keys * 172.17.0.2:6379: 172.17.0.4:6379: 172.17.0.3:6379: 172.17.0.6:6379: 172.17.0.7:6379: 172.17.0.5:6379:
redis1:
127.0.0.1:6379> keys *
(empty list or set)
redis2:
127.0.0.1:6380> keys *
(empty list or set)
2,測試session:
訪問:http://127.0.0.1:8080/session/set/thislaoliu
設置一個session值為thislaoliu:
訪問:http://127.0.0.1:8080/session/get
查詢我們設置的session值是否生效?
從redis的控制台檢查寫入情況:
[root@redis4 /]# /usr/local/soft/redis-6.0.5/bin/redis-cli -a lhddemo -c --cluster call 172.17.0.2:6379 keys \* Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Calling keys * 172.17.0.2:6379: spring:session:sessions:expires:97a889f8-7122-4248-a5fc-4e559cb429e5 172.17.0.4:6379: spring:session:sessions:97a889f8-7122-4248-a5fc-4e559cb429e5 172.17.0.3:6379: spring:session:expirations:1593321720000 172.17.0.6:6379: spring:session:sessions:expires:97a889f8-7122-4248-a5fc-4e559cb429e5 172.17.0.7:6379: spring:session:expirations:1593321720000 172.17.0.5:6379: spring:session:sessions:97a889f8-7122-4248-a5fc-4e559cb429e5
我們連接到172.17.0.4,查詢session的值:
[root@redis3 /]# /usr/local/soft/redis-6.0.5/bin/redis-cli -c -h 172.17.0.4
看session的內容:
172.17.0.4:6379> hgetall spring:session:sessions:97a889f8-7122-4248-a5fc-4e559cb429e5 1) "sessionAttr:user" 2) "\xac\xed\x00\x05t\x00\nthislaoliu" 3) "creationTime" 4) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01r\xf9CW\xb7" 5) "maxInactiveInterval" 6) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b" 7) "lastAccessedTime" 8) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01r\xf9D)n"
可以看到user的值是我們所設置的:thislaoliu
3,測試redis1
訪問:http://127.0.0.1:8080/cache/redis1set/cup1
設置一個key,值為:cup1
訪問:http://127.0.0.1:8080/cache/redis1get
返回我們設置的值:
連接到redis控制台:
[liuhongdi@localhost ~]$ /usr/local/soft/redis/bin/redis-cli
從redis控制台查看kv
127.0.0.1:6379> get goodsname1 "cup1"
4,測試redis2
訪問:http://127.0.0.1:8080/cache/redis2set/phone1
設置一個key,值為:phone1
訪問:http://127.0.0.1:8080/cache/redis2get
返回我們設置的值:
連接到redis控制台
[root@localhost etc]# /usr/local/soft/redis/bin/redis-cli -p 6380
從控制台查看值
127.0.0.1:6380> get goodsname2 "phone1"
六,查看spring boot的版本
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.1.RELEASE)