redisTemplate 使用 setIfAbsent 返回 null 問題原理及解決辦法
1.簡(jiǎn)介
有的時(shí)候我們使用 redisTemplate給鎖設(shè)置超時(shí)時(shí)間的方法,設(shè)置鎖并返回的 lock 有可能不是 true 或 false,而是null。
Boolean lock = redisTemplate.opsForValue().setIfAbsent("redisTemplateTest鎖住了", "value", 10, TimeUnit.SECONDS);
從上圖我們可以看出雖然生成了鎖,但是返回的 lock 卻是 null ,這就會(huì)影響我們下面的代碼運(yùn)行。
下面我先說一下可能的產(chǎn)生原因和解決辦法,之后再詳細(xì)說一下原理。
2.產(chǎn)生原因及解決辦法
最直接的產(chǎn)生原因就是項(xiàng)目啟動(dòng)后缺少了 RedisConnectionFactory 這個(gè)bean,導(dǎo)致這一情況的發(fā)生有可能是引用了以下的jar包:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>
解決辦法很簡(jiǎn)單:
1)換個(gè)jar包使用,如使用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
或者該jar包配合 redisson 包共同使用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.6</version>
</dependency>
2)手動(dòng)加入 RedisConnectionFactory 這個(gè)bean:
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory();
}
3.產(chǎn)生的原理
首先我們要對(duì) springboot 自動(dòng)裝配有一定了解,SpringBoot 啟動(dòng)時(shí)會(huì)加載所有 META-INF 文件夾下的 spring.factories 文件,在其中找到要裝配的類。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
這只是眾多 spring.factories 的文件中的一個(gè) , 從上面我們可以看到 spring 自動(dòng)配置了 RedisAutoConfiguration 這個(gè)bean。下面我們展示一下 RedisAutoConfiguration 的源碼:
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
從上面的源碼中我們可以得知,裝配 RedisAutoConfiguration 這個(gè)bean 的時(shí)候,同時(shí)也會(huì)裝載 redisTemplate 和 StringRedisTemplate 這兩個(gè) bean ,并且這兩個(gè)bean 都是基于 RedisConnectionFactory 去構(gòu)建的。
下面我們來看一下 RedisConnectionFactory 的結(jié)構(gòu) :
從上圖我們可以看出 RedisConnectionFactory 作為一個(gè)接口,有 JedisConnectionFactory 和 LettuceConnectionFactory 這兩個(gè)實(shí)現(xiàn)類。
從上面的所有信息中我們可以得知,我們所使用的 redisTemplate 和 StringRedisTemplate 是通過 JedisConnectionFactory 和 LettuceConnectionFactory 這兩個(gè)實(shí)現(xiàn)類來構(gòu)建的。
下面我們驗(yàn)證一下該結(jié)論:
首先我們只引用 spring-boot-starter-data-redis jar包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
從上圖可以看出確實(shí)是我們推論的 redisTemplate 和 StringRedisTemplate 是通過 LettuceConnectionFactory 來構(gòu)建的。
我們可以嘗試在啟動(dòng)項(xiàng)目的時(shí)候打印 spring 容器中的所有 bean 來看一下,以上 redis 的 bean 是否存在。
@SpringBootApplication
public class ApplicationStarter {
public static void main(String[] args) {
//1.返回我們IOC容器
ConfigurableApplicationContext run = SpringApplication.run(ApplicationStarter.class, args);
//2.查看容器里面的組件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
從上圖可以看出理應(yīng)存在的 bean 全部都已經(jīng)生成完畢,致此,redisTemplate 正常的使用已經(jīng)說明完畢了。
那么接下來讓我們來演示一下為什么獲取到了鎖還會(huì)產(chǎn)生 lock = null 這種情況:
下面我們使用一下 redisson-spring-boot-starter 這個(gè)jar包:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>
從上圖我們可以看到該jar包配置了 RedissonAutoConfiguration 這個(gè)新的 bean 。那么讓我們看一下 RedissonAutoConfiguration 的源碼:
public class RedissonAutoConfiguration {
@Autowired
private RedissonProperties redissonProperties;
@Autowired
private RedisProperties redisProperties;
@Autowired
private ApplicationContext ctx;
public RedissonAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean({StringRedisTemplate.class})
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean({RedisConnectionFactory.class})
public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
return new RedissonConnectionFactory(redisson);
}
@Bean(
destroyMethod = "shutdown"
)
@ConditionalOnMissingBean({RedissonClient.class})
public RedissonClient redisson() throws IOException {
Config config = null;
Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class, "getCluster");
Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class, "getTimeout");
Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, this.redisProperties);
int timeout;
Method nodesMethod;
if (null == timeoutValue) {
timeout = 0;
} else if (!(timeoutValue instanceof Integer)) {
nodesMethod = ReflectionUtils.findMethod(timeoutValue.getClass(), "toMillis");
timeout = ((Long)ReflectionUtils.invokeMethod(nodesMethod, timeoutValue)).intValue();
} else {
timeout = (Integer)timeoutValue;
}
if (this.redissonProperties.getConfig() != null) {
try {
InputStream is = this.getConfigStream();
config = Config.fromJSON(is);
} catch (IOException var11) {
try {
InputStream is = this.getConfigStream();
config = Config.fromYAML(is);
} catch (IOException var10) {
throw new IllegalArgumentException("Can't parse config", var10);
}
}
} else if (this.redisProperties.getSentinel() != null) {
nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");
Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod, this.redisProperties.getSentinel());
String[] nodes;
if (nodesValue instanceof String) {
nodes = this.convert(Arrays.asList(((String)nodesValue).split(",")));
} else {
nodes = this.convert((List)nodesValue);
}
config = new Config();
((SentinelServersConfig)config.useSentinelServers().setMasterName(this.redisProperties.getSentinel().getMaster()).addSentinelAddress(nodes).setDatabase(this.redisProperties.getDatabase()).setConnectTimeout(timeout)).setPassword(this.redisProperties.getPassword());
} else {
Method method;
if (clusterMethod != null && ReflectionUtils.invokeMethod(clusterMethod, this.redisProperties) != null) {
Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, this.redisProperties);
method = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");
List<String> nodesObject = (List)ReflectionUtils.invokeMethod(method, clusterObject);
String[] nodes = this.convert(nodesObject);
config = new Config();
((ClusterServersConfig)config.useClusterServers().addNodeAddress(nodes).setConnectTimeout(timeout)).setPassword(this.redisProperties.getPassword());
} else {
config = new Config();
String prefix = "redis://";
method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
if (method != null && (Boolean)ReflectionUtils.invokeMethod(method, this.redisProperties)) {
prefix = "rediss://";
}
((SingleServerConfig)config.useSingleServer().setAddress(prefix + this.redisProperties.getHost() + ":" + this.redisProperties.getPort()).setConnectTimeout(timeout)).setDatabase(this.redisProperties.getDatabase()).setPassword(this.redisProperties.getPassword());
}
}
return Redisson.create(config);
}
private String[] convert(List<String> nodesObject) {
List<String> nodes = new ArrayList(nodesObject.size());
Iterator var3 = nodesObject.iterator();
while(true) {
while(var3.hasNext()) {
String node = (String)var3.next();
if (!node.startsWith("redis://") && !node.startsWith("rediss://")) {
nodes.add("redis://" + node);
} else {
nodes.add(node);
}
}
return (String[])nodes.toArray(new String[nodes.size()]);
}
}
private InputStream getConfigStream() throws IOException {
Resource resource = this.ctx.getResource(this.redissonProperties.getConfig());
InputStream is = resource.getInputStream();
return is;
}
}
從以上代碼可以看出 RedissonAutoConfiguration 是先構(gòu)建 RedissonClient ,之后通過 RedissonClient 構(gòu)建 RedissonConnectionFactory ,然后通過 RedissonConnectionFactory 構(gòu)建 redisTemplate 和 StringRedisTemplate 。和之前構(gòu)建 redisTemplate 的不同主要在于使用了不同的 RedisConnectionFactory 實(shí)現(xiàn)類:
我們同樣打印一下 spring 容器中的 bean 看一下:
從上文兩種jar包的對(duì)比,我們可以得知兩種方式獲取 redisTemplate 的區(qū)別正是在于 RedisConnectionFactory 的實(shí)現(xiàn)類不同,而這也是導(dǎo)致 lock = null 的原因所在。 redisson 底層調(diào)用了大量的 netty 包,該jar包自帶的 RedissonConnectionFactory 影響了之前的返回邏輯,下面我們簡(jiǎn)單驗(yàn)證一下推論:
從上圖可以看出該jar包確實(shí)是通過 RedissonConnectionFactory 構(gòu)建 redisTemplate ,并且返回的 lock 為 null 。
下面我們手動(dòng)加入 RedisConnectionFactory 這個(gè)bean進(jìn)行對(duì)照看下:
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory();
}
從上面的對(duì)比可以看出我們的推論完全是正確的, redisson-spring-boot-starter 這個(gè)jar包的 RedissonConnectionFactory 影響了原本的判斷,導(dǎo)致返回 lock=null 這種現(xiàn)象。文章來源:http://www.zghlxwxcb.cn/news/detail-754689.html
由于 Redisson 里面運(yùn)用了大量的回調(diào)方法、匿名表達(dá)式、異步操作等難以debug的方法,所以我這里只在表層進(jìn)行了問題的排查與解決,如果有哪位大佬具體排查出在代碼運(yùn)行中具體是什么原因?qū)е铝耸褂?RedissonConnectionFactory 返回 lock=null 這種現(xiàn)象。使用它與使用 LettuceConnectionFactory 的返回差異具體體現(xiàn)在哪里的話麻煩告知一聲。文章來源地址http://www.zghlxwxcb.cn/news/detail-754689.html
到了這里,關(guān)于redisTemplate 使用 setIfAbsent 返回 null 問題原理及解決辦法的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!