SpringBoot使用Redis缓存
本文基于SpringBoot2.2.8在SpringBooot中引入一个组件,往往只需要引入一个starter,他会帮你将所有相关依赖自动引入,大大简化了使用流程。一、使用步骤通过starter引入所有依赖,只需一个依赖即可完成所有相关依赖的引入:<dependency><groupId>org.springframework.boot</groupId><
本文基于SpringBoot2.2.8
在SpringBooot中引入一个组件,往往只需要引入一个starter,他会帮你将所有相关依赖自动引入,大大简化了使用流程。
一、使用步骤
通过starter引入所有依赖,只需一个依赖即可完成所有相关依赖的引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
starter引入了其他的jar:
其中spring-data-redis包是Spring提供的对于Redis访问的统一标准接口,主要通过:
org.springframework.data.redis.core.RedisTemplate
RedisTemplate是Spring提供的应用层接口,向下屏蔽Redis客户端,连接池等信息,也就是说用户可以无缝替换Redis客户端,而不用修改应用使用RedisTemplate的相关代码。但是RedisTemplate只封装了Redis对数据的存取能力,没有分布式功能。所以如果要使用Redis上扩展的分布式能力,需要直接使用客户端提供的接口。
二、配置原理
引入jar包后,需要进行配置文件的处理,Redis服务的架构一定程度决定了配置文件的配置,不同架构需要的配置是不一样的。
redis架构分为单机和集群,当然,为了高可用,无论是单机还是集群,都会要求做主从配置。
如果是单机版,最简单配置如下,这里配置的是域名,会比IP方便。
spring:
redis:
host: d0nameredis.x.com.cn
port: 4567
password: 123456
如果是集群版,就会需要配置多个节点,注意,所有节点的密码是一样的。
spring:
redis:
cluster:
nodes:
- d0name.x.com.cn:6781
- d0name2.x.com.cn:6782
- d0name3.x.com.cn:6783
password: 123456
三、Redis客户端
Redis服务端进行数据存储时是有自己的命令的,应用在使用Redis是希望的是一个友好的接口,而不是直接使用Redis提供的各种基础命令,所以这个时候就出现了Redis客户端,他们向上给应用提供友好的接口,向下将应用接口对应的功能翻译成Redis服务支持的操作命令。客户端在整个架构中地位如下:
当前主流的客户端为:
1、jedis(已经慢慢过时)
2、lettuce(SpringBoot官方默认,高性能)
3、redisson
它提供的功能远远超出了一个Redis
客户端的范畴,它基于Redis
实现了各种分布式环境下的常用功能。使用它来替换spring-boot-data-redis
默认使用的 Lettuce
。在可以使用基本Redis
功能的同时,也能使用它提供的一些服务。redisson-spring-boot-starter
实现了 spring-boot-data-redis
。所以跟平时没有区别。直接使用 springboot提供的,RedisTemplate
即可。
五、连接池
以上配置中都没有谈到连接池,对于有网络连接的2个服务,通常都是需要连接池来提高响应速度的。
这里是应用服务和Redis服务之间的连接,他们之间是可以有连接池的,当然,连接池不是必须要使用过的。
六、SpringBoot自动配置Redis源码
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
可以看到SpringBoot是会自动注入lettuce和jedis的,当然必须是存在相关的jar。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)// 必须引入spring-data-redis包
@EnableConfigurationProperties(RedisProperties.class)// 读取spring.redis.xxx配置
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })//引入lettuce和jedis客户端
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")// 没有redisTemplate就创建
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean// 没有stringRedisTemplate就创建
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration
根据是否引入了lettuce-core来进行自动配置,如果配置文件中配置了pool相关配置,就会自动启用带连接池的连接工厂,同时需要手动引入commons-pool2包。所以我们可以看到,如果你需要使用jedis,办法就是排除lettuce包,并引入jedis包。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)// 必须引入lettuce-core包
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
}
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(ClientResources.class)
DefaultClientResources lettuceClientResources() {
return DefaultClientResources.create();
}
// 没有RedisConnnectionFactory就创建,读取配置文件中配置,根据配置文件中是否配置了pool来决定是否创建带连接池的连接工厂
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) throws UnknownHostException {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
getProperties().getLettuce().getPool());// 根据pool配置判断
return createLettuceConnectionFactory(clientConfig);
}
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
if (getSentinelConfig() != null) {
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if (getClusterConfiguration() != null) {
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
}
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
private LettuceClientConfiguration getLettuceClientConfiguration(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources, Pool pool) {
LettuceClientConfigurationBuilder builder = createBuilder(pool);
applyProperties(builder);
if (StringUtils.hasText(getProperties().getUrl())) {
customizeConfigurationFromUrl(builder);
}
builder.clientResources(clientResources);
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
private LettuceClientConfigurationBuilder createBuilder(Pool pool) {
if (pool == null) { // 根据pool配置判断
return LettuceClientConfiguration.builder();
}
return new PoolBuilderFactory().createBuilder(pool);
}
private LettuceClientConfigurationBuilder applyProperties(
LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
if (getProperties().isSsl()) {
builder.useSsl();
}
if (getProperties().getTimeout() != null) {
builder.commandTimeout(getProperties().getTimeout());
}
if (getProperties().getLettuce() != null) {
RedisProperties.Lettuce lettuce = getProperties().getLettuce();
if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout());
}
}
if (StringUtils.hasText(getProperties().getClientName())) {
builder.clientName(getProperties().getClientName());
}
return builder;
}
private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
if (connectionInfo.isUseSsl()) {
builder.useSsl();
}
}
/**
* Inner class to allow optional commons-pool2 dependency.使用pool一定要手动引入commons-pool2包
*/
private static class PoolBuilderFactory {
LettuceClientConfigurationBuilder createBuilder(Pool properties) {
return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
}
private GenericObjectPoolConfig<?> getPoolConfig(Pool properties) {
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(properties.getMaxActive());
config.setMaxIdle(properties.getMaxIdle());
config.setMinIdle(properties.getMinIdle());
if (properties.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
}
if (properties.getMaxWait() != null) {
config.setMaxWaitMillis(properties.getMaxWait().toMillis());
}
return config;
}
}
}
七、想要使用Redisson的分布式能力
org.redisson.spring.starter.RedissonAutoConfiguration
可以看出,使用redisson必须引入spring-data-redis包,且redisson的自动配置会在spring-data-redis自动配置之前,也就是说Redisson会提前注入redisTemplate,这样其他客户端(如lettuce)就不会创建,同时在Redisson配置中,版本升级时,spring.redis.redisson.config设置更名为spring.redis.redisson.file,所以使用是需要注意,你可以不配置Redisson的特有配置,他也能正常工作。逻辑为,首先读取Redisson独有的配置,根据独有的配置创建,如果没有Redisson独有配置,就读spring.redis配置,创建哨兵模式,或者集群模式,或者单机模式。
@Configuration
@ConditionalOnClass({Redisson.class, RedisOperations.class})// 必须引入redisson包和spring-data-redis包
@AutoConfigureBefore(RedisAutoConfiguration.class) // 在Spring的RedisAutoConfiguration自动配置之前配置
@EnableConfigurationProperties({RedissonProperties.class, RedisProperties.class}) // 读取spring.redis.redisson.x和spring.redis.x配置
public class RedissonAutoConfiguration {
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
@Autowired(required = false)
private List<RedissonAutoConfigurationCustomizer> redissonAutoConfigurationCustomizers;
@Autowired
private RedissonProperties redissonProperties;
@Autowired
private RedisProperties redisProperties;
@Autowired
private ApplicationContext ctx;
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")// 没有redisTemplate就创建,依赖RedisConnectionFactory
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class) // 没有StringRedisTemplate就创建,依赖RedisConnectionFactory
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class) // 没有RedisConnectionFactory就创建,依赖 RedissonClient
public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
return new RedissonConnectionFactory(redisson);
}
@Bean
@Lazy
@ConditionalOnMissingBean(RedissonReactiveClient.class) // 没有RedissonReactiveClient就创建,依赖 RedissonClient
public RedissonReactiveClient redissonReactive(RedissonClient redisson) {
return redisson.reactive();
}
@Bean
@Lazy
@ConditionalOnMissingBean(RedissonRxClient.class) // 没有RedissonRxClient就创建,依赖 RedissonClient
public RedissonRxClient redissonRxJava(RedissonClient redisson) {
return redisson.rxJava();
}
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(RedissonClient.class) // 没有RedissonClient就创建
public RedissonClient redisson() throws IOException {
Config config = null;
Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class, "getCluster");// 获取get spring.redis.cluster配置方法
Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class, "getTimeout"); // 获取get spring.redis.timeout配置方法
Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, redisProperties); // 获取get spring.redis.timeout 值
int timeout;
if(null == timeoutValue){
timeout = 10000;
}else if (!(timeoutValue instanceof Integer)) {
Method millisMethod = ReflectionUtils.findMethod(timeoutValue.getClass(), "toMillis");
timeout = ((Long) ReflectionUtils.invokeMethod(millisMethod, timeoutValue)).intValue();
} else {
timeout = (Integer)timeoutValue;
}
if (redissonProperties.getConfig() != null) {// 有配置spring.redis.redisson.config=classpath:redisson.yaml
try {
config = Config.fromYAML(redissonProperties.getConfig()); // 用yaml格式读取
} catch (IOException e) {
try {
config = Config.fromJSON(redissonProperties.getConfig()); // 用json格式读取
} catch (IOException e1) {
throw new IllegalArgumentException("Can't parse config", e1);
}
}
} else if (redissonProperties.getFile() != null) { // 有配置spring.redis.redisson.file=classpath:redisson.yaml,新版file配置取代config
try {
InputStream is = getConfigStream();
config = Config.fromYAML(is);
} catch (IOException e) {
// trying next format
try {
InputStream is = getConfigStream();
config = Config.fromJSON(is);
} catch (IOException e1) {
throw new IllegalArgumentException("Can't parse config", e1);
}
}
} else if (redisProperties.getSentinel() != null) { // 读取 spring.redis.sentinel 配置并使用
Method nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");
Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod, redisProperties.getSentinel());
String[] nodes;
if (nodesValue instanceof String) {
nodes = convert(Arrays.asList(((String)nodesValue).split(",")));
} else {
nodes = convert((List<String>)nodesValue);
}
config = new Config();
config.useSentinelServers()
.setMasterName(redisProperties.getSentinel().getMaster())
.addSentinelAddress(nodes)
.setDatabase(redisProperties.getDatabase())
.setConnectTimeout(timeout)
.setPassword(redisProperties.getPassword());
} else if (clusterMethod != null && ReflectionUtils.invokeMethod(clusterMethod, redisProperties) != null) { // 读取 spring.redis.cluster 配置并使用
Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, redisProperties);
Method nodesMethod = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");
List<String> nodesObject = (List) ReflectionUtils.invokeMethod(nodesMethod, clusterObject);
String[] nodes = convert(nodesObject);
config = new Config();
config.useClusterServers()
.addNodeAddress(nodes)
.setConnectTimeout(timeout)
.setPassword(redisProperties.getPassword());
} else {
config = new Config();
String prefix = REDIS_PROTOCOL_PREFIX;
Method method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
if (method != null && (Boolean)ReflectionUtils.invokeMethod(method, redisProperties)) {
prefix = REDISS_PROTOCOL_PREFIX;
}
config.useSingleServer()
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(timeout)
.setDatabase(redisProperties.getDatabase())
.setPassword(redisProperties.getPassword());
}
if (redissonAutoConfigurationCustomizers != null) {
for (RedissonAutoConfigurationCustomizer customizer : redissonAutoConfigurationCustomizers) {
customizer.customize(config);
}
}
return Redisson.create(config);
}
private String[] convert(List<String> nodesObject) {
List<String> nodes = new ArrayList<String>(nodesObject.size());
for (String node : nodesObject) {
if (!node.startsWith(REDIS_PROTOCOL_PREFIX) && !node.startsWith(REDISS_PROTOCOL_PREFIX)) {
nodes.add(REDIS_PROTOCOL_PREFIX + node);
} else {
nodes.add(node);
}
}
return nodes.toArray(new String[nodes.size()]);
}
private InputStream getConfigStream() throws IOException {
Resource resource = ctx.getResource(redissonProperties.getFile());
InputStream is = resource.getInputStream();
return is;
}
}
配置Redisson参数的方式有3种;
1、使用单独的配置文件,spring.redis.redisson.file=classpath:redisson.yaml
2、直接写在主配置文件中:
spring:
redis:
redisson:
config: |
singleServerConfig:
password: 123456
address: "redis://127.0.0.1:6379"
database: 1
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.FstCodec> {}
transportMode: "NIO"
3、写死在代码中,可以自定义配置
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.setTransportMode(TransportMode.NIO);
SingleServerConfig singleServerConfig = config.useSingleServer();
//可以用"rediss://"来启用SSL连接
singleServerConfig.setAddress("redis://127.0.0.1:6379");
singleServerConfig.setPassword("123456");
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
更多推荐
所有评论(0)