데이터베이스/Redis

Redis를 이용한 접속 대기열 구현 방법

Jinwookoh 2025. 5. 10. 22:09

1. 왜 Redis인가?

  • 빠른 성능: Redis는 메모리 기반 데이터 저장소이기 때문에 대기열 처리 속도가 매우 빠릅니다.
  • 명령어의 다양성: LPUSH, RPOP, BLPOP 등을 사용해 큐(queue) 구조를 손쉽게 구현할 수 있습니다.
  • 분산 환경에서 유용: 여러 서버가 동시에 대기열을 공유할 수 있어, 대규모 시스템에서도 적용 가능.

📌 구현 시나리오

  1. 유저가 접속 요청을 하면 Redis의 대기열 리스트에 유저 ID를 넣음 (LPUSH).
  2. 서버는 일정 간격으로 큐에서 유저를 하나씩 꺼내어 자원을 할당 (RPOP).
  3. 특정 시간 동안 응답이 없거나 실패하면 다시 큐에 넣을 수 있음 (optional).

🗃️ Redis 구조 설계

Key 이름 예시 설명
waiting_queue 접속 대기열을 저장하는 리스트
allowed_users 접속 허용된 사용자 저장용 Set 또는 TTL 기반 Key

✅ 주요 Redis 명령어

LPUSH waiting_queue user_123     # 대기열 앞쪽에 삽입
RPOP waiting_queue               # 대기열 뒤쪽에서 추출
LLEN waiting_queue               # 대기열 길이 확인
SADD allowed_users user_123      # 접속 허용 사용자 등록
EXPIRE allowed_users 60         # 60초 후 자동 삭제

💻 Java(Spring Boot) 예제

1. 의존성 설정 (build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

2. RedisConfig.java

@Configuration
public class RedisConfig {
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory("localhost", 6379);
    }

    @Bean
    public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }
}

3. 대기열 서비스 구현

@Service
public class WaitingQueueService {

    private final StringRedisTemplate redisTemplate;
    private static final String QUEUE_KEY = "waiting_queue";
    private static final String ALLOWED_KEY = "allowed_users";
    private static final long ALLOW_TTL_SECONDS = 60;

    public WaitingQueueService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void enqueue(String userId) {
        redisTemplate.opsForList().leftPush(QUEUE_KEY, userId);
    }

    public String dequeue() {
        return redisTemplate.opsForList().rightPop(QUEUE_KEY);
    }

    public long getQueueSize() {
        return redisTemplate.opsForList().size(QUEUE_KEY);
    }

    public void allowUser(String userId) {
        redisTemplate.opsForSet().add(ALLOWED_KEY, userId);
        redisTemplate.expire(ALLOWED_KEY, Duration.ofSeconds(ALLOW_TTL_SECONDS));
    }

    public boolean isAllowed(String userId) {
        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(ALLOWED_KEY, userId));
    }
}

4. REST 컨트롤러 예시

@RestController
@RequestMapping("/connect")
public class ConnectController {

    private final WaitingQueueService queueService;

    public ConnectController(WaitingQueueService queueService) {
        this.queueService = queueService;
    }

    @PostMapping("/request")
    public ResponseEntity<String> request(@RequestParam String userId) {
        queueService.enqueue(userId);
        return ResponseEntity.ok("접속 요청 완료: 대기열에 등록되었습니다.");
    }

    @GetMapping("/status")
    public ResponseEntity<String> status(@RequestParam String userId) {
        if (queueService.isAllowed(userId)) {
            return ResponseEntity.ok("접속 가능");
        } else {
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("대기 중");
        }
    }

    @Scheduled(fixedRate = 1000)
    public void processQueue() {
        String userId = queueService.dequeue();
        if (userId != null) {
            queueService.allowUser(userId);
            System.out.println("접속 허용: " + userId);
        }
    }
}

⚠️ 고려해야 할 점

  • 중복 요청 방지: 대기열에 중복 삽입을 막기 위해 중복 체크용 Set을 둘 수 있습니다.
  • TTL 설정: 허용된 유저가 일정 시간 내 접속하지 않으면 자동 만료.
  • 스케일링: 여러 서버에서 동시에 Redis 대기열을 사용 가능.

🏁 정리

Redis를 활용하면 접속 대기열을 빠르고 간단하게 구현할 수 있으며, 분산 환경에서도 확장성이 뛰어납니다. 스프링에서 StringRedisTemplate을 이용하면 다양한 방식으로 유저 흐름을 제어할 수 있어, 게임 서버, 이벤트 참가 제한, API Rate Limiting 등에 효과적으로 활용할 수 있습니다.