Redis에서 발생하는 ClassCastException: java.util.ArrayList cannot be cast to java.lang.Long 문제 해결

/**
     * 연결 해제
     * @param pool
     * @param jedis
     * @param <T>
     */
    public static <T> void releaseConnection(Pool<T> pool, T jedis) {
        if (pool != null && jedis != null) {
            pool.returnResource(jedis);
        }
    }

    /**
     * 타임아웃 등 예외 발생 시 객체의 이전 명령 결과 캐시를 지움
     * @param pool
     * @param jedis
     * @param <T>
     */
    public static <T> void clearBuffer(Pool<T> pool, T jedis) {
        if (pool != null && jedis != null) {
            pool.returnBrokenResource(jedis);
        }
    }

잘못된 구현 방식

public Long appendData(String key, String value) {
		Long result = null;
		List<JedisPool> pools = cluster.getWriteRedisPool(key);
		for(JedisPool pool : pools){
			Jedis jedis = null;
			try {
				jedis = pool.getResource();
				result = jedis.append(key, value);
			} catch (Exception e) {
				throw new RedisException(e);
			} finally{
				ConnectionUtils.releaseConnection(pool,jedis);
			}
		}
		return result;
	}

위 메서드를 실행할 때 때때로 다음과 같은 예외가 발생합니다:

java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.Long at redis.clients.jedis.Connection.getIntegerReply(Connection.java:161) at redis.clients.jedis.Jedis.del(Jedis.java:108)

코드상으로는 문제가 없어 보이지만, 실제로는 문제가 있습니다. 만약 jedis가 명령을 실행하는 동안 Redis가 과부하 상태로 인해 타임아웃 예외를 반환할 경우, 어떤 일이 발생하는지 생각해 보세요. 이 예외를 처리하지 않고 해당 jedis 연결을 연결 풀로 반환하면 문제가 발생할 수 있습니다.

Jedis 소스 코드를 확인하면 connection이 네트워크 출력 스트림을 래핑하는데, 이 과정에서 자체 buffer를 생성합니다. 따라서 예외가 발생했을 때 이 buffer에는 이전에 전송되지 않았거나 불완전한 명령이 남아있을 수 있습니다. 이 상태에서 예외를 처리하지 않고 연결을 연결 풀로 반환하면, 해당 연결을 다음에 재사용할 때 이전에 전송되지 않았던 명령이 함께 전송되어 위와 같은 "반환 값 타입이 맞지 않는" 오류가 발생합니다.

따라서 올바른 접근 방식은 예외 발생 시 해당 연결을 파기하고 재사용하지 않는 것입니다.

올바른 구현 방식

public Long appendData(String key, String value) {
		Long result = null;
		List<JedisPool> pools = cluster.getWriteRedisPool(key);
		for(JedisPool pool : pools){
			Jedis jedis = null;
			try {
				jedis = pool.getResource();
				result = jedis.append(key, value);
			} catch (Exception e) {
				ConnectionUtils.clearBuffer(pool,jedis);
				throw new RedisException(e);
			} finally{
				ConnectionUtils.releaseConnection(pool,jedis);
			}
		}
		return result;
	}

태그: Redis jedis ClassCastException Connection Pooling Exception Handling

7월 21일 07:20에 게시됨