Search code examples
redisgoogle-cloud-platformgoogle-cloud-memorystorespring-cloud-gcp

Google Cloud Memory Store (Redis), can't connect to redis when instance is just started


I have a problem to connect to redis when my instance is just started.

I use:

runtime: java
env: flex

runtime_config:  
  jdk: openjdk8

i got following exception:

Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out

RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

java.net.SocketTimeoutException: connect timed out

after 2-3 min, it works smoothly

Do i need to add some check in my code or how i should fix it properly?

p.s. also i use spring boot, with following configuration

@Value("${spring.redis.host}")
private String redisHost;

@Bean
JedisConnectionFactory jedisConnectionFactory() {
    // https://cloud.google.com/memorystore/docs/redis/quotas
    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, 6379);
    return new JedisConnectionFactory(config);
}

@Bean
public RedisTemplate<String, Object> redisTemplate(
        @Autowired JedisConnectionFactory jedisConnectionFactory
) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(jedisConnectionFactory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer(newObjectMapper()));
    return template;
}

in pom.xml

    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.1.2.RELEASE</version>

Solution

  • I solved this problem as follows: in short, I added the “ping” method, which tries to set and get the value from Redis; if it's possible, then application is ready.

    Implementation:

    First, you need to update app.yaml add following:

    readiness_check:
    path: "/readiness_check"
    check_interval_sec: 5
    timeout_sec: 4
    failure_threshold: 2
    success_threshold: 2
    app_start_timeout_sec: 300
    

    Second, in your rest controller:

    @GetMapping("/readiness_check")
    public ResponseEntity<?> readiness_check() {
    
        if (!cacheConfig.ping()) {
            return ResponseEntity.notFound().build();
        }
    
        return ResponseEntity.ok().build();
    }
    

    Third, class CacheConfig:

    public boolean ping() {
        long prefix = System.currentTimeMillis();
        try {
            redisTemplate.opsForValue().set("readiness_check_" + prefix, Boolean.TRUE, 100, TimeUnit.SECONDS);
            Boolean val = (Boolean) redisTemplate.opsForValue().get("readiness_check_" + prefix);
            return Boolean.TRUE.equals(val);
        } catch (Exception e) {
            LOGGER.info("ping failed for " + System.currentTimeMillis());
            return false;
        }
    }
    

    P.S. Also if somebody needs the full implementation of CacheConfig:

    @Configuration
    public class CacheConfig {
    
        private static final Logger LOGGER = Logger.getLogger(CacheConfig.class.getName());
    
        @Value("${spring.redis.host}")
        private String redisHost;
    
        private final RedisTemplate<String, Object> redisTemplate;
    
        @Autowired
        public CacheConfig(@Lazy RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Bean
        JedisConnectionFactory jedisConnectionFactory(
                @Autowired JedisPoolConfig poolConfig
        ) {
            // https://cloud.google.com/memorystore/docs/redis/quotas
            RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, 6379);
    
            JedisClientConfiguration clientConfig = JedisClientConfiguration
                    .builder()
                    .usePooling()
                    .poolConfig(poolConfig)
                    .build();
    
            return new JedisConnectionFactory(config, clientConfig);
        }
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(
                @Autowired JedisConnectionFactory jedisConnectionFactory
        ) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(jedisConnectionFactory);
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer(newObjectMapper()));
            return template;
        }
    
        /**
         * Example: https://github.com/PengliuIBM/pws_demo/blob/1becdca1bc19320c2742504baa1cada3260f8d93/redisData/src/main/java/com/pivotal/wangyu/study/springdataredis/config/RedisConfig.java
         */
        @Bean
        redis.clients.jedis.JedisPoolConfig jedisPoolConfig() {
            final redis.clients.jedis.JedisPoolConfig poolConfig = new redis.clients.jedis.JedisPoolConfig();
    
            // Maximum active connections to Redis instance
            poolConfig.setMaxTotal(16);
            // Number of connections to Redis that just sit there and do nothing
            poolConfig.setMaxIdle(16);
            // Minimum number of idle connections to Redis - these can be seen as always open and ready to serve
            poolConfig.setMinIdle(8);
    
            // Tests whether connection is dead when returning a connection to the pool
            poolConfig.setTestOnBorrow(true);
            // Tests whether connection is dead when connection retrieval method is called
            poolConfig.setTestOnReturn(true);
            // Tests whether connections are dead during idle periods
            poolConfig.setTestWhileIdle(true);
    
            return poolConfig;
        }
    
        public boolean ping() {
            long prefix = System.currentTimeMillis();
            try {
                redisTemplate.opsForValue().set("readiness_check_" + prefix, Boolean.TRUE, 100, TimeUnit.SECONDS);
                Boolean val = (Boolean) redisTemplate.opsForValue().get("readiness_check_" + prefix);
                return Boolean.TRUE.equals(val);
            } catch (Exception e) {
                LOGGER.info("ping failed for " + System.currentTimeMillis());
                return false;
            }
        }
    }