/**
* 연결 해제
* @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;
}