CreatedAt: 20200820
SpringBoot Version: 2.3.1.RELEASE
springboot 可以自动装配 redis 相关配置, 其入口被定义在 org.springframework.boot:spring-boot-autoconfigure:2.3.1.RELEASE 包中 /METE-INF/spring.factories 文件中, redis 配置
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
spring-data-redis 默认支持的配置都在 org.springframework.boot.autoconfigure.data.redis.RedisProperties
中, 存在如下问题
- 只支持一套 redis 服务, 同时配置 集群 和 哨兵 时, 默认哨兵优先, 然后是集群, 然后是单例(standalone)
- 很多细节并不支持, 如 lettuce 客户端集群模式的拓扑结构自适应刷新, 默认是关闭的, 如果节点宕机或新增节点, 客户端不会主动刷新, 而是一直尝试连接宕机的节点
可以屏蔽 springboot 对 redis 的自动装配, 完全手动配置
说明
配置案例中有一些我不是很理解, 肯定有些配置是不太合适的, 在生产上使用可能会出问题
关于 Lettuce 使用 Pool 的一些说法
Jedis 需要配置连接池是毫无疑问的, 但是 Lettuce 呢? 网上很多例子都是有池配置的, 但是 Lettuce 官网有一些描述如下
https://lettuce.io/core/release/reference/index.html#_connection_pooling
7.10. Connection Pooling
Lettuce connections are designed to be thread-safe so one connection can be shared amongst multiple threads and Lettuce connections auto-reconnection by default. While connection pooling is not necessary in most cases it can be helpful in certain use cases. Lettuce provides generic connection pooling support.
7.10.1. Is connection pooling necessary?
Lettuce is thread-safe by design which is sufficient for most cases. All Redis user operations are executed single-threaded. Using multiple connections does not impact the performance of an application in a positive way. The use of blocking operations usually goes hand in hand with worker threads that get their dedicated connection. The use of Redis Transactions is the typical use case for dynamic connection pooling as the number of threads requiring a dedicated connection tends to be dynamic. That said, the requirement for dynamic connection pooling is limited. Connection pooling always comes with a cost of complexity and maintenance.
Application
package com.mrathena;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @author mrathena on 2019/7/27 16:00
*/
@EnableDubbo
@EnableCaching
@EnableScheduling
@SpringBootApplication(exclude = {
RedisAutoConfiguration.class,
RedisReactiveAutoConfiguration.class,
RedisRepositoriesAutoConfiguration.class
})
@MapperScan("com.mrathena.dao.mapper")
public class Application extends SpringBootServletInitializer {
/**
* war包部署的话,需要这个配置
*/
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
RedisConfig
package com.mrathena.web.configuration;
import com.mrathena.common.constant.Constant;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author mrathena on 2019-10-17 00:28
*/
@Slf4j
@Configuration
public class RedisConfig {
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.sentinel.master}")
private String sentinelMaster;
@Value("${spring.redis.sentinel.nodes}")
private String sentinelNodes;
@Bean
public RedisSerializer keySerializer() {
return StringRedisSerializer.UTF_8;
}
@Bean
public RedisSerializer
CacheConfig
package com.mrathena.web.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
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.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* @author mrathena on 2019/12/10 15:45
*/
@Slf4j
@Configuration
public class CacheConfig {
/**
* 注解 @Primary, 指定默认使用的bean, 在配置多个相同类型bean的时候使用
*/
@Bean
@Primary
public CacheManager redisClusterCacheManager(RedisConnectionFactory clusterLettuceConnectionFactory,
RedisSerializer keySerializer,
RedisSerializer valueSerializer) {
// RedisCacheConfiguration commonRedisCacheConfiguration
RedisCacheConfiguration commonRedisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.disableKeyPrefix()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer));
// 不缓存null值(有时候需要缓存,交给使用者来决定)
// .disableCachingNullValues();
// CacheConfigurationsMap
Map cacheConfigurationMap = new HashMap<>(8);
cacheConfigurationMap.put(CacheNameEnum.ONE_MINUTE.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofMinutes(1)));
cacheConfigurationMap.put(CacheNameEnum.FIVE_MINUTE.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofMinutes(5)));
cacheConfigurationMap.put(CacheNameEnum.TEN_MINUTE.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofMinutes(10)));
cacheConfigurationMap.put(CacheNameEnum.THIRTY_MINUTE.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofMinutes(30)));
cacheConfigurationMap.put(CacheNameEnum.ONE_HOUR.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofHours(1)));
cacheConfigurationMap.put(CacheNameEnum.SIX_HOUR.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofHours(6)));
cacheConfigurationMap.put(CacheNameEnum.TWELVE_HOUR.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofHours(12)));
cacheConfigurationMap.put(CacheNameEnum.ONE_DAY.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofDays(1)));
cacheConfigurationMap.put(CacheNameEnum.SEVEN_DAY.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofDays(7)));
cacheConfigurationMap.put(CacheNameEnum.FOURTEEN_DAY.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofDays(14)));
cacheConfigurationMap.put(CacheNameEnum.ONE_MONTH.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofDays(31)));
cacheConfigurationMap.put(CacheNameEnum.THREE_MONTH.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofDays(93)));
cacheConfigurationMap.put(CacheNameEnum.SIX_MONTH.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofDays(182)));
cacheConfigurationMap.put(CacheNameEnum.ONE_YEAR.name(), commonRedisCacheConfiguration.entryTtl(Duration.ofDays(366)));
cacheConfigurationMap.put(CacheNameEnum.FOREVER.name(), commonRedisCacheConfiguration.entryTtl(Duration.ZERO));
// RedisCacheManager
RedisCacheManager redisCacheManager = RedisCacheManager.builder(clusterLettuceConnectionFactory)
.withInitialCacheConfigurations(cacheConfigurationMap)
// 将缓存的操作纳入到事务管理中,即回滚事务会同步回滚缓存(我猜的)
.transactionAware()
// 不允许添加除上述定义之外的缓存名称
.disableCreateOnMissingCache()
.build();
log.info("Cache:redisClusterCacheManager:初始化完成");
return redisCacheManager;
}
public enum CacheNameEnum {
/**
* 缓存时间
*/
ONE_MINUTE, FIVE_MINUTE, TEN_MINUTE, THIRTY_MINUTE,
ONE_HOUR, SIX_HOUR, TWELVE_HOUR,
ONE_DAY, SEVEN_DAY, FOURTEEN_DAY,
ONE_MONTH, THREE_MONTH, SIX_MONTH,
ONE_YEAR,
FOREVER
}
}
参考
https://wenchao.ren/2020/06/Lettuce%E4%B8%80%E5%AE%9A%E8%A6%81%E6%89%93%E5%BC%80redis%E9%9B%86%E7%BE%A4%E6%8B%93%E6%89%91%E5%88%B7%E6%96%B0%E5%8A%9F%E8%83%BD/
https://www.cnblogs.com/gavincoder/p/12731833.html
https://juejin.im/post/6844904039096778759
https://blog.csdn.net/ankeway/article/details/100136675