Spring boot中如何防止Redis服务器等外部连接失败时快速失败?

aka*_*ash 6 java spring spring-data-redis spring-boot

有没有办法防止spring-boot应用程序因外部连接失败而启动失败?我发现其他类似的问题建议使用@Lazy注释来防止bean 初始化,但此解决方案对我使用客户端@Configuration不起作用。Spring Data RedisJedis

此外,像这样的其他解决方案特定于应用程序中使用的依赖项。例如,Spring Cloud具有以下属性来控制快速失败行为 -

spring.cloud.config.fail-fast=true
Run Code Online (Sandbox Code Playgroud)

您可以使用我为我的问题创建的这个项目,通过关闭redis服务器来重现。

下面是我的代码的样子 -

@Lazy
@Configuration
public class RedisConfiguration {
    @Value("${spring.redis.sentinel.master}")
    private String SENTINEL_MASTER;

    @Value("${spring.redis.sentinel.nodes}")
    private String SENTINEL_NODES;

    @Value("${spring.redis.security.enabled:false}")
    private boolean REDIS_SECURITY_ENABLED;

    @Value("${spring.redis.security.password:}")
    private String REDIS_PASSWORD;

    @Lazy
    @Bean // somehow this always gets initialized
    public RedisConnectionFactory jedisConnectionFactory() { 
        // create set of sentinel nodes
        System.out.println(SENTINEL_NODES);
        Set<String> sentinelNodesSet = new HashSet<>(5);
        StringTokenizer st = new StringTokenizer(SENTINEL_NODES, ",");
        while (st.hasMoreTokens())
            sentinelNodesSet.add(st.nextToken());
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration(SENTINEL_MASTER, sentinelNodesSet);
        if (REDIS_SECURITY_ENABLED) {
            sentinelConfig.setPassword(REDIS_PASSWORD);
        }
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfig);
        return jedisConnectionFactory;
    }
Run Code Online (Sandbox Code Playgroud)

下面是异常跟踪 -

org.springframework.beans.factory.UnsatisfiedDependencyException:创建类路径资源[org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.class]中定义的名为“stringRedisTemplate”的bean时出错:通过方法“stringRedisTemplate”参数表达的依赖关系不满足0; 嵌套异常是org.springframework.beans.factory.BeanCreationException:创建类路径资源[com/springboot/redisintegration/RedisConfiguration.class]中定义的名为“jedisConnectionFactory”的bean时出错:调用init方法失败;嵌套异常是 redis.clients.jedis.exceptions.JedisConnectionException:所有哨兵都已关闭,无法确定 mysentinel master 正在哪里运行...在 org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:797) ~ [spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:538) ~[spring-beans-5.2.8. RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE ] 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1176) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory .support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory. java:516) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[ spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.8.RELEASE .jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]在 org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory。 support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879) )~[spring-context-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java: [551] 〜[spring-context-5.2.8.RELEASE.jar:5.2.8.RELEASE]在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)〜[spring-boot -2.3.3.RELEASE.jar:2.3.3.RELEASE] 在 org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3。发布] 在 org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE] 在 org.springframework.boot.SpringApplication.refreshContext( SpringApplication.java:397) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE] 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.3 .3.RELEASE.jar:2.3.3.RELEASE] 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE]在 org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.3.3.RELEASE.jar:2.3.3.RELEASE] 在 com.springboot.redisintegration.RedisIntegrationApplication.main(RedisIntegrationApplication. java:21) ~[classes/:na] 引起:org.springframework.beans.factory.BeanCreationException:创建在类路径资源 [com/springboot/redisintegration/RedisConfiguration.class] 中定义的名为“jedisConnectionFactory”的 bean 时出错:调用init方法失败;嵌套异常是 redis.clients.jedis.exceptions.JedisConnectionException:所有哨兵都已关闭,无法确定 mysentinel master 正在哪里运行...在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1794) ~ [spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.8。 RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE ] 在 org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework。 beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 位于 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean (AbstractBeanFactory.java:322) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[ spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE] 在 org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.8.RELEASE .jar:5.2.8.RELEASE] 位于 org.springframework.beans.factory.support。

简而言之

  1. @Lazy注释适用于RedisStandaloneConfiguration但不适用于RedisSentinelConfiguration,不知道为什么?
  2. 使用@Lazy注释是有风险的,因为您需要确保所有正在使用的服务Redis也都是延迟加载的。
  3. 寻找像spring.cloud.config.fail-fast=true为 Spring Cloud 提供的解决方案。

更新

我为此功能创建了以下 Jira 问题 -

https://jira.spring.io/browse/DATAREDIS-1208

jcc*_*ero 1

Spring Cloud Config 在底层使用spring-retryAOP ( spring-boot-starter-aop) 来配置重试机制。

此过程在ConfigServiceBootstrapConfiguration中实现。

代码的相关部分是这样的:

/* @ConditionalOnProperty("spring.cloud.config.fail-fast") */
@ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class })
@Configuration(proxyBeanMethods = false)
@EnableRetry(proxyTargetClass = true)
@Import(AopAutoConfiguration.class)
@EnableConfigurationProperties(RetryProperties.class)
protected static class RetryConfiguration {

  @Bean
  @ConditionalOnMissingBean(name = "configServerRetryInterceptor")
  public RetryOperationsInterceptor configServerRetryInterceptor(
      RetryProperties properties) {
    return RetryInterceptorBuilder.stateless()
        .backOffOptions(properties.getInitialInterval(),
            properties.getMultiplier(), properties.getMaxInterval())
        .maxAttempts(properties.getMaxAttempts()).build();
  }

}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,基本思想是提供一个RetryConfiguration在考虑应用程序失败之前处理一定次数的重试的方法。

Spring Cloud Client 的文档提供了有关用于配置此机制的不同属性的更多信息您还可以在类的源代码中看到默认值RetryProperties

请尝试包含两个必需的依赖项spring-retryspring-boot-starter-aop,以及RetryConfiguration作为主配置的子项,为重试机制的配置属性提供一些合理的默认值,然后看看会发生什么。

您可以将解决方案推向极限,并尝试在大量情况下重新连接,也许可以增加它们之间的节奏,等待服务器可用。

我认为@Lazy注释将不再需要并且可以安全地删除。

编辑

查看错误堆栈跟踪,您认为您还可以尝试禁用 String Boot Redis 自动配置类。

您可以在注释中执行此操作:

@SpringBootApplication(
  exclude = { RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class }
)
Run Code Online (Sandbox Code Playgroud)

或者在您的属性文件中:

spring.autoconfigure.exclude= \
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration, \
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration

Run Code Online (Sandbox Code Playgroud)

您还可以使用此属性禁用 Redis 存储库配置:

spring.data.redis.repositories.enabled: false
Run Code Online (Sandbox Code Playgroud)

一旦禁用 Redis 自动配置,您就可以RedisTemplate在您认为合适的时候自由实例化与 Redis 交互所需的内容。

例如,您可以按需初始化它,通过在实际需要时初始化所有必需的工厂来尝试建立与 Redis 的连接。您可以使用初始化 Redis 连接所需的逻辑,并且仅在连接可用时才初始化try,如下所示。catchRedisTemplate

一方面:

spring.autoconfigure.exclude= \
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration, \
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration

Run Code Online (Sandbox Code Playgroud)

在另一:

spring.data.redis.repositories.enabled: false
Run Code Online (Sandbox Code Playgroud)

您可以RedisTemplate按照您认为合适的方式使用它,当然,可以根据需要缓存和重用它。

这些方法可以在为此任务创建的服务或帮助程序类中定义,当然不能在您的配置类中定义。