728x90
안녕하세요. 현재 데브코스 4기로 활동 중인 모아밤팀 서버 개발자 홍혁준입니다.
이번 포스팅에서 Embedded Redis에 대해 이야기를 풀어내 보려고 합니다. 감사합니다.
배경
모아밤에서는 다음 요구사항을 위해 Redis 저장소를 활용하게 되었습니다.
- 사용자는 다른 사용자에게 12시간 마다 1회 콕 알림을 보낼 수 있다.
- 사용자는 자신이 속한 방의 인증타임에 알림이 울린다.
개인적으로 프로젝트 코드를 누군가가 클론했다면, 그 즉시 개발 및 테스트를 시작할 수 있어야 한다고 생각합니다. 때문에, 테스트 할 수 있는 환경 구성을 위해 Embedded Redis를 적용하게 되었습니다.
기술 환경
Spring Data Redis
- Redis를 마치 JPA Repository 이용하듯 인터페이스를 제공하는 Spring Module 입니다.
Lettuce
- Redis Java Client로 현재 Spring Data Redis에서 공식 지원하는 클라이언트입니다.
- 추가로 Jedis도 공식 지원하지만, 거의 업데이트 되지 않아서 Lettuce를 선택하게 되었습니다.
Embedded Redis
- H2와 같은 내장 Redis 데몬입니다. 즉, 로컬 환경 Redis를 실행하지 않아도 됩니다.
Embedded Redis 적용하기
1) build.gradle 설정
- 내장 Redis는 it.ozimov 외에도 kstyrc가 있는데, kstyrc 오랫동안 업데이트가 없어서 kstyrc를 Fork해서 만들어진 it.ozimov 모듈을 사용하게 되었습니다.
- it.ozimov는 최신 버전으로 0.7.3이 있습니다. 다만, 해당 버전은 SLF4J가 여러번 바인딩되는 문제가 발생해서 0.7.2 버전을 사용하게 되었습니다. 또 다른 해결 방법으로는 컴파일 시 SLF4J를 제외하는 방법이 있기도 합니다.
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// Embedded-Redis
implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'
// 0.7.3 버전으로 할 때, SLF4J를 제외하는 방법
implementation ('it.ozimov:embedded-redis:0.7.3') { exclude group: "org.slf4j", module: "slf4j-simple" }
2) Embedded Redis 시작 및 종료 설정
- @Profile로 로컬 환경일 때만, 실행되도록 합니다.
@Slf4j
@Profile("local")
@Configuration
public class EmbeddedRedisConfig {
@Value("${spring.data.redis.port}")
private int redisPort;
private RedisServer redisServer;
@PostConstruct
public void startRedis() throws IOException {
redisServer = new RedisServer(port);
redisServer.start();
}
@PreDestroy
public void stopRedis() {
redisServer.stop();
}
}
3) 통합 테스트 환경 구성
- 2단계까지만 설정 시, 여러 스프링 컨텍스트 실행할 때, Embedded Redis 포트 충돌이 날 수 있습니다. 즉, 서로 다른 Property를 가진 테스트 코드가 있는 경우 충돌이 날 수 있습니다. 때문에, 아래와 같이 추가 설정이 필요합니다.
- startRedis()에서 메모리 할당을 명시했습니다. 이를 안할 시, 윈도우 환경에서는 문제가 발생합니다.
- 그 외 설명은 주석을 확인바랍니다.
// ...
public class EmbeddedRedisConfig {
// ...
/**
* REDIS_SERVER_MAX_MEMORY : "maxmemory 128M"
*/
@PostConstruct
public void startRedis() {
int port = isRedisRunning() ? findAvailablePort() : redisPort;
redisServer = RedisServer.builder()
.port(port)
.setting(REDIS_SERVER_MAX_MEMORY)
.build();
redisServer.start();
}
@PreDestroy
public void stopRedis() {
if (redisServer != null) {
redisServer.stop();
}
}
/**
* 현재 PC 서버에서 사용 가능한 포트 조회
*/
public int findAvailablePort() {
for (int port = 10000; port <= 65535; port++) {
Process process = executeGrepProcessCommand(port);
if (!isRunning(process)) {
return port;
}
}
throw new MoabamException(ErrorMessage.NOT_FOUND_AVAILABLE_PORT);
}
/**
* Embedded Redis가 현재 실행 중인지 확인
*/
private boolean isRedisRunning() {
return isRunning(executeGrepProcessCommand(redisPort));
}
/**
* 해당 Port를 사용 중인 프로세스를 확인하는 sh 실행
*/
private Process executeGrepProcessCommand(int redisPort) {
// netstat -nat : 시스템 네트워크 연결 상태 확인 명령어
// grep LISTEN : 'LISTEN'이 포함된 줄 확인 명령어
// grep %d : 앞서 주어진 출력 내용에서 포트번호가 포함된 줄 확인 명령어
String command = String.format("netstat -nat | grep LISTEN | grep %d", redisPort);
// '/bin/sh'에서 '-c' 옵션과 함께 위에서 만든 command를 실행하는 명령을 배열 형태로 구성
String[] shell = {"/bin/sh", "-c", command};
try {
return Runtime.getRuntime().exec(shell);
} catch (IOException e) {
throw new MoabamException(e.getMessage());
}
}
/**
* 해당 Process가 현재 실행 중인지 확인
*/
private boolean isRunning(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) {
throw new MoabamException(ErrorMessage.ERROR_EXECUTING_EMBEDDED_REDIS);
}
return StringUtils.hasText(pidInfo.toString());
}
}
트러블 슈팅 - M1 ARM
문제 상황
테스트 환경에서 Ebmedded Redis가 동작하게 구성했는데, M1에서는 Database가 실행되지 않는 현상이 발생했습니다.
원인
Embedded Redis 라이브러리에서 mac_arm64 용 바이너리가 준비되어 있지 않고 소스 코드에도 MAC_OS_X_arm64가 없는 것이 원인입니다.
해결 방법
1) brew를 통한 wget 설치
brew install wget
2) 소스 코드 다운로드 및 컴파일
// 소스 코드 다운로드
wget https://download.redis.io/releases/redis-7.2.3.tar.gz
// 압축 해제
tar xzf redis-7.2.3.tar.gz
// 디렉토리 이동
cd redis-7.2.3
// 파일 링크 및 설치 등
make
3) 바이너리 파일 실행 여부 확인
src/redis-server
4) 바이너리 파일을 프로젝트에 추가
5) EmbeddedRedisConfig에 설정 추가
// ...
public class EmbeddedRedisConfig {
// ...
@PostConstruct
public void startRedis() {
int port = isRedisRunning() ? findAvailablePort() : redisPort;
if (isArmMac()) {
redisServer = new RedisServer(getRedisFileForArcMac(), port);
} else {
redisServer = RedisServer.builder()
.port(port)
.setting(REDIS_SERVER_MAX_MEMORY)
.build();
}
try {
redisServer.start();
} catch (Exception e) {
throw new MoabamException(e.getMessage());
}
}
/**
* 현재 시스템이 ARM 아키텍처를 사용하는 MAC인지 확인
* System.getProperty("os.arch") : JVM이 실행되는 시스템 아키텍처 반환
* System.getProperty("os.name") : 시스템 이름 반환
*/
private boolean isArmMac() {
return Objects.equals(System.getProperty("os.arch"), "aarch64")
&& Objects.equals(System.getProperty("os.name"), "Mac OS X");
}
/**
* ARM 아키텍처를 사용하는 Mac에서 실행할 수 있는 Redis 바이너리 파일을 반환
*/
private File getRedisFileForArcMac() {
try {
return new ClassPathResource("binary/redis/redis-server-arm64").getFile();
} catch (Exception e) {
throw new MoabamException(e.getMessage());
}
}
}
실행 결과
Reference
728x90
'Activity > 데브코스 - 모아밤 테크' 카테고리의 다른 글
Moabam Tech - Caching으로 성능 개선하기 (2) | 2024.01.12 |
---|---|
Moabam Tech - 서브모듈 도입기 (0) | 2023.11.09 |
Moabam Tech - 모아밤의 예외 처리 (0) | 2023.11.09 |
Moabam Tech - 실시간 선착순 쿠폰 이벤트 도입기 (0) | 2023.11.08 |
Moabam Tech - 모아밤의 FCM Push Notification 도입기 (0) | 2023.10.29 |