스프링 부트 스타터 레디스에는 Lettuce 라이브러리를 사용 (Spring-Boot-Starter-Redis)
Lettuce는 Netty 기반의 비동기 방식으로 Jedis보다 사용이 권장됨.
그러나 스핀락 방식을 활용하고 있기 때문에 락 만료시간에 대한 정책이 필요하다.(순회 횟수 제한, 시간으로 제한 etc...)
스핀락 방식 : 계속해서 Lock을 획득하기 위해 순회하는 방식. 만약 lock을 획득한 스레드나 프로세스가 lock을 정상적으로 해제해주지 못 한다면 다른 스레드에서는 계속 lock을 획득하기 위해 시도하느라 실행되는 애플리케이션에 장애가 될 수 있음.
=> 이를 위해 락 만료시간에 대한 정책이 필요
+ 또한 Lock을 획득하기 위해 순회하는 동안 계속해서 레디스에 요청을 보내게 되는데 이런 요청을 보내는 스레드가 많으면 레디스에 부하가 발생할 수 있다.
- Redisson이란 ?)
스핀락 방식의 문제를 해결하기 위한 분산락 방식을 도입한 Lettuc와 비슷한 자바 레디스 클라이언트.
Lettuce와 비슷하게 Netty를 사용해서 비동기 논블로킹 I/O를 제공.
특이하게 레디스의 명령어를 직접 제공하지 않고 Lock과 같은 특정한 구현체의 형태를 제공한다.
- Redisson 특징)
pub-sub 기반으로 Lock 구현 제공
pub-sub 방식이란 채널을 하나 만들고 락을 점유 중인 스레드가 락을 해제했음을 대기 중인 스레드에게 알려주면 대기 중인 스레드가 락 점유를 시도하는 방식.
이 방식은 Lettue와 다르게 대부분 별도의 Retry 방식을 작성하지 않아도 된다.
- 프로젝트 적용)
1. 의존성 추가
// https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter
implementation group: 'org.redisson', name: 'redisson-spring-boot-starter', version: '3.27.2'
나는 스프링부트 3.1.3 버전을 사용하고 있으므로 redisson-spring-data-3x 버전을 사용해야한다.
https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter
참고.
+ 또 Spring boot 2.x에서 3.x에서 변경될 때 properties 설정도 변경된다.
Spring Boot 2.x
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=16379
spring.redis.password=mypass
spring.redis.timeout=60000
Spring Boot 3.x
spring.data.redis.database=0
spring.data.redis.host=localhost
spring.data.redis.port=16379
spring.data.redis.password=mypass
spring.data.redis.timeout=60000
2. LocalRedis 실행 설정
- Spring Boot가 기동하면서 Bean을 등록할 때 레디스를 실행하고, 종료되면서 Bean을 삭제할 때 레디스를 종료하도록 설정
- 주의점 : 해당 Bean이 Redis Repository보다 빨리 뜰 수 있도록 패키지 순서를 위쪽으로 해야한다.
@Configuration
public class LocalRedisConfig {
@Value("${spring.data.redis.port}")
private int redisPort;
private RedisServer redisServer;
@PostConstruct
public void startRedis() { //빈을 생성 하자마자 실행
redisServer = new RedisServer(redisPort);
redisServer.start(); //redis하나를 별도의 인스턴스로 띄워줌
}
@PreDestroy
public void stopRedis() { //빈 파괴 직전에 실행
if (redisServer != null) {
redisServer.stop();
}
}
}
3. RedisRepository 등록 → redis 접근 위한 redisson client를 bean으로 등록.
@Configuration
public class RedisRepositoryConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("{$spring.data.redis.port}")
private int redisPort;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
return Redisson.create(config);
}
}
4. Controller, RedisTestService 작성
Redis를 테스트하기 위해 컨트롤러와 서비스 코드를 작성했다.
<RedisTestController>
@RestController
@RequiredArgsConstructor
public class RedisTestController {
private final RedisTestService redisTestService;
@GetMapping("/get-lock")
public String getLock() {
return redisTestService.getLock();
}
<RedisTestService>
@Slf4j
@Service
@RequiredArgsConstructor
public class RedisTestService {
private final RedissonClient redissonClient;
//RedisRepositoryConfig에서 만들어둔 redissonClient bean과 이름이 같으므로 자동 주입이 됨.
public String getLock() {
RLock lock = redissonClient.getLock("sampleLock");
//1. redissonClient에 sampleLock을 키로 가져오고
try { //2. spinLock 시도 1초동안 lock을 시도하고 3초동안 lock을 가지고 있음.
boolean isLock = lock.tryLock(1, 5, TimeUnit.SECONDS);
if (!isLock) { //lock 취둑 실패
log.error("====== Lock acquisition failed =====");
return "Lock failed";
}
} catch (Exception e) {
log.error("Redis lock failed");
}
//명시적으로 unlock을 하고 있지 않기 때문에
//다른 녀석이 lock취득하려고 하면 5초동안은 실패하게 될 것임
return "Lock success";
}
}
* Trouble Shooting
(Spring boot 3점대는 spring.data.redis.xxx 사용해야함)
1. spring.redis.port 실행시키니까 아래와 같이 redisRepositoryConfig를 만들 수 없는데 String으로 입력된 port값을 int로 바꿀 수 없다는 것이었다. 강의에서는 int형으로 했지만 RedisRepositoryConfig에 String으로 바꿔줘본다.
2024-03-19T20:05:33.410+09:00 INFO 8816 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-03-19T20:05:33.622+09:00 WARN 8816 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'redisRepositoryConfig': Unsatisfied dependency expressed through field 'redisPort': Failed to convert value of type 'java.lang.String' to required type 'int'; For input string: "{$spring.data.redis.port}"
2024-03-19T20:05:33.623+09:00 INFO 8816 --- [ main] redis.embedded.AbstractRedisInstance : Stopping redis server...
2024-03-19T20:05:33.629+09:00 INFO 8816 --- [ main] redis.embedded.AbstractRedisInstance : Redis exited
2024-03-19T20:05:33.629+09:00 INFO 8816 --- [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
-> ㅋㅋ 문제는 해결되고 또다른 문제가 났었는데 이제보니까 그냥 오타 때문이었다.
ㅡㅡ "${spring.data.redis.port}"로 바꾸니 모든 문제 해결..
음 ? 근데 spring.data.redis.port가 아닌 spring.redis.port로 해줘도 정상적으로 동작한다 뭐징? ㅋㅋ.....
'공부' 카테고리의 다른 글
빌드란 ?, 빌드 도구 Gradle, gradle build vs ./gradlew build (1) | 2024.04.28 |
---|---|
AOP로 동시성 이슈 해결 - 중복 거래 방지 (0) | 2024.03.28 |
Docker) Docker에 Jenkins 설치 (0) | 2024.01.22 |
Github Actions 적용해보기 (0) | 2024.01.19 |
Github Actions란 (0) | 2024.01.19 |