Redis

[Spring / Redis] Spring Boot에 Embedded Redis를 적용하기

Woong이 2024. 7. 31. 00:38
반응형

개요


개인 프로젝트에서 Redis를 사용하고 있다. 여태까지 개발 및 테스트를 로컬 Redis를 실행시킨 상태로 진행했는데, 최근에 개발 환경을 바꾸면서 새로운 환경에서 프로젝트를 실행하기 위해  Redis를 설치해야 하는 과정에 불편함을 느끼게 되었다.

 

이 부분에서 개선이 필요하다 생각했고, Redis가 프로젝트에 의존하는 환경을 구성하여 어떤 개발 환경이든 Github에서 프로젝트를 clone한 이후 바로 실행할 수 있도록 만들었다. 이 글에서는 해당 부분의 개선 과정을 적어본다.

 

참고로, 이 글에서는 Spring 프로젝트에 Redis를 도입하는 과정은 설명하지 않는다. 해당 과정이 궁금하다면 이전에 적어둔 글을 참고하길 바란다.

 

[Spring] Spring에 Redis를 사용하는 방법

개요 개인 프로젝트에서 Refresh Token 정보를 In-memory DB로 저장하기로 결정하였다. 이 글에서는 Spring에서 Redis를 사용하는 방법을 알아보고, 프로젝트에 어떻게 적용시켰는지 설명하려고 한다. Sprin

davidy87.tistory.com

 

 

기본 환경


 먼저 내장 Redis를 사용하기 위해 gradle 의존성을 추가했다.

implementation 'it.ozimov:embedded-redis:0.7.2'

 

 

이후, config 클래스를 하나 생성하였다.

@Slf4j
@Profile({"dev", "test"})
@Configuration
public class EmbeddedRedisConfig {

    private final RedisServer redisServer;

    public EmbeddedRedisConfig(RedisProperties redisProperties) throws IOException {
        int port = PortUtils.isPortRunning(redisProperties.getPort()) ?
                PortUtils.findAvailablePort() : redisProperties.getPort();

        this.redisServer = new RedisServer(port);
        redisServer.start();
    }

    @PreDestroy
    public void preDestroy() {
        if (redisServer != null) {
            redisServer.stop();
        }
    }
}

위 config 클래스는 spring profile이 dev와 test일 경우에만 내장 Redis가 실행되도록 만드는 설정이다. 여기서 주의 깊게 살펴봐야 할 부분은 PortUitls.isPortRunningPortUtils.findAvailablePort 메서드이다. 이 메서드를 갖고 있는 PortUitls 클래스에 대해 더 자세히 알아보자.

 

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class PortUtils {

    /**
     * port가 현재 사용 중인지 확인
     */
    public static boolean isPortRunning(int port) throws IOException {
        return isProcessRunning(executeGrepProcessCommand(port));
    }

    /**
     * 현재 PC/서버에서 사용 가능한 포트 조회
     */
    public static int findAvailablePort() throws IOException {
        for (int port = 10000; port <= 65535; port++) {
            Process process = executeGrepProcessCommand(port);

            if (!isProcessRunning(process)) {
                return port;
            }
        }

        throw new IllegalArgumentException("Not Found Available port: 10000 ~ 65535");
    }

    /**
     * 해당 port를 사용 중인 프로세스 확인하는 sh 실행
     */
    private static Process executeGrepProcessCommand(int port) throws IOException {
        OS os = OS.detectOS();
        String[] shell;

        if (os == WINDOWS) {
            String command = String.format("netstat -nao | find \"LISTENING\" | find \"%d\"", port);
            shell = new String[]{"cmd.exe", "/y", "/c", command};
        } else {
            String command = String.format("netstat -nat | grep LISTEN|grep %d", port);
            shell = new String[]{"/bin/sh", "-c", command};
        }

        return Runtime.getRuntime().exec(shell);
    }

    /**
     * 해당 Process가 현재 실행 중인지 확인
     */
    private static boolean isProcessRunning(Process process) {
        String line;
        StringBuilder pidInfo = new StringBuilder();

        try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            while ((line = input.readLine()) != null) {
                pidInfo.append(line);
            }
        } catch (Exception e) {
            log.info("Exception occurred when checking process = ", e);
        }

        return StringUtils.hasText(pidInfo.toString());
    }
}

(코드 참고: https://jojoldu.tistory.com/297)

 

위의 코드는 설정된 내장 Redis port로 실행 중인 프로세스가 있다면 새로운 port를 찾아 반환하는 메서드를 갖고 있다.

 

그런데, 왜 현재 사용 중인 port를 확인하는 것일까?

 

Spring에서 통합 테스트를 실행할 때, 여러 Spring 테스트 컨텍스트가 실행되는 경우, Redis port가 충돌하는 경우가 생길 수 있다. 새로운 테스트 컨텍스트가 실행된다면 Redis config를 하나 더 실행하게 되고, 이때 새로운 port를 사용하지 않는다면 지정된 port가 이미 이전 테스트 컨텍스트에서 사용 중이기 때문에 오류를 던지며 테스트가 실패하게 된다.

 

그래서 이런 상황을 피하기 위해서는 Redis port가 사용 중이 아니라면 그대로 사용하고, 사용 중일 경우 새로운 port를 찾아 사용하는 과정이 꼭 필요하다.

 

 

참고


 

[Redis] SpringBoot Data Redis 로컬/통합 테스트 환경 구축하기

안녕하세요? 이번 시간엔 SpringBoot Data Redis 로컬 테스트 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Git

jojoldu.tistory.com

 

반응형