
회사에서 redis 6 을 사용중이었는데
검증계를 redis 7 로 버전업 하였더니
검증 서비스에서 redis 커넥션이 되지않는 문제가 발생하였다.
에러를 2개 발견하였다.
1. ERROR Exception:20 - io.lettuce.core.RedisConnectionException: Unable to connect
2. ERROR HashedWheelTimer:452 - You are creating too many HashedWheelTimer instances. HashedWheelTimer is a shared resource that must be reused across the JVM,so that only a few instances are created.
1번 에러
- 현상: redis 7 부터 ACL 도입으로 업그레이드되면서 인증 방식이 변경되었는데, 클라이언트(Java 애플리케이션) 설정이 이를 따라가지 못해 연결 실패가 발생하고 있었다.
- 원인: Lettuce 라이브러리를 사용중이었는데 Lettuce 5.2.0.RELEASE 버전은 Redis 7(ACL) 환경에서 사용하기에 매우 불안정하며 권장하지 않고 있었고, 해당 버전은 2019년 말에 출시된 버전으로, Redis 6 이상의 ACL 기능을 완벽하게 지원하지 않거나 버그가 있을 가능성이 높았다.
Lettuce 6.2.6 버전으로 올려도 연결이 안되었는데 내부에서 사용되는 reactor-core의 버전이 낮아서 호환이 안되어 같이 올려주어 해결하였다. Lettuce 6.x 버전은 내부적으로 Project Reactor 3.4 이상 버전을 사용한다고 한다. - 해결: 라이브러리 버전업으로 해결
- lettuce-core 5.2.0 -> 6.2.6
- reactor-core 3.3.0 -> 3.4.34
2번 에러
위 연결이 안되는 에러보다 더 심각한 메모리 누수가 발생하고 있는 에러였다.
이 에러 때문에 서버 리소스를 잡아먹어 연결이 매우 지연되고 있었다.
- 현상: 연결 실패 -> 재시도 로직에서 클라이언트 객체 계속 생성 -> 쓰레드 폭증 -> 서버 다운 위험
- 원인: 현재 코드가 Redis 연결이 안 될 때마다 매 요청마다 새로운 RedisClient 객체를 생성(new)하고 있는 것으로 작성되어 있었다. (싱글톤으로 관리되지 않음)
- 해결: 클라이언트와 커넥션을 static(싱글톤)으로 관리 하도록 코드를 수정하였다.
private static RedisClusterClient redisClient;
private static StatefulRedisClusterConnection<String, String> connection;
/**
* 초기화 메서드: 최초 1회만 실행됨 (동기화 처리)
*/
private static synchronized void init() {
if (connection != null && connection.isOpen()) {
return;
}
try {
// 1. Redis URI 생성 (ACL 적용)
String redisUrl = settingUrl();
logger.info("Connecting to Redis Cluster: {}", redisUrl.replaceAll(":.*@", ":****@"));
// 2. Client 생성
redisClient = RedisClusterClient.create(redisUrl);
// 토폴로지 자동 갱신 옵션 추가
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enableAllAdaptiveRefreshTriggers() // 이벤트(MOVED, RECONNECT 등) 발생 시 즉시 갱신
.enablePeriodicRefresh(Duration.ofMinutes(10)) // 10분마다 주기적 갱신
.build();
redisClient.setOptions(ClusterClientOptions.builder()
.topologyRefreshOptions(topologyRefreshOptions)
.build());
// 3. 커넥션 맺기
connection = redisClient.connect();
logger.info("Redis Cluster Connected Successfully.");
} catch (Exception e) {
logger.error("Redis Connection Failed!", e);
}
}
추가 궁금증
레디스는 커넥션 풀처럼 사용하는 건가 ?
결론: Lettuce는 일반적인 DB 커넥션 풀처럼 사용하지 않는다.
전통적인 JDBC(오라클, MySQL 등)나 예전 Redis 라이브러리(Jedis)와는 동작 방식이 완전히 다르다.
이해를 돕기 위해 은행 창구와 고속도로로 비유
- 전통적인 방식 (JDBC, Jedis) = "은행 창구 (Connection Pool)"
- 커넥션 하나당 한 번에 하나의 요청만 처리
- 요청이 100개 들어오면 커넥션(창구)도 100개가 필요
- 그래서 미리 50~100개를 만들어두고 빌려 쓰고 반납하는 "Connection Pool"이 필수
- Lettuce 방식 (Netty 기반) = "고속도로 (Multiplexing)"
- 커넥션(도로)은 딱 하나만 뚫어놓음
- 이 하나의 도로 위로 수천 개의 자동차(요청)가 동시에 달림 (비동기 처리)
- 데이터가 섞이지 않게 내부적으로 잘 관리하므로(Thread-Safe),
풀(Pool)을 만들 필요 없이 커넥션 하나를 모든 쓰레드가 공유해서 쓰는 것이 훨씬 빠르다.
'ERROR FIX' 카테고리의 다른 글
| Exception: Received fatal alert: protocol_version 에러 (0) | 2024.06.29 |
|---|---|
| org.apache.jsp.WEB_002dINF.views.index_jsp]org.apache.jasper.JasperException: java.lang.ClassNotFoundException (0) | 2024.06.22 |
| 외부 톰캣 세팅시 artifact(war exploded) 자동 생성 안될때 (1) | 2024.06.15 |
| 인텔리제이 롬복 에러 Cause: class lombok.javac.apt.LombokProcessor (0) | 2024.03.26 |
| 인텔리제이 프로젝트 안열림 (0.1초만에 닫힘) (2) | 2024.03.19 |
댓글