본문 바로가기

트러블슈팅

Redis를 이용한 예약 메세지 시스템 개선: RabbitMQ의 FIFO 한계를 극복한 방법

728x90
반응형
SMALL

문제상황

 

기존 예약 메세지 시스템은 RabbitMQ의 지연 큐를 이용해 메시지의 전송 시간을 TTL(Time To Live)로 설정한 후 메시지를 예약하는 방식으로 구현되었습니다. 이 방식은 선입선출(FIFO) 구조의 특성 때문에 다음과 같은 문제가 발생했습니다:

  1. FIFO로 인한 대기 현상: 예를 들어, 3시간 뒤에 전송할 메시지가 먼저 큐에 걸린 경우, 그 이후 1분 뒤에 전송할 메시지가 들어와도 3시간이 지나야 큐에서 빠져나가게 되어, 1분 뒤에 전송되어야 할 메시지가 지연되는 현상이 발생.
  2. 지연된 예약 메시지: 이로 인해 여러 메시지들이 제때 처리되지 않으면서 예약 메시지 시스템이 올바르게 동작하지 않는 문제가 발생했습니다.

해결책

 

이 문제를 해결하기 위해 Redis의 notify-keyspace-events 기능을 활용한 새로운 예약 메시지 시스템을 도입했습니다. Redis를 활용하면 TTL이 만료된 순간 이벤트를 발생시킬 수 있어, 지연 없이 정확한 시간에 메시지를 처리할 수 있습니다.

 

코드

 

@Component
@Slf4j
public class RedisKeyExpirationListener {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper objectMapper;
    private final AmqpTemplate amqpTemplate;
    
    @Autowired
    public RedisKeyExpirationListener(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    public void handleMessage(String key) throws Exception {
        Object redisObject = redisTemplate.opsForValue().get("backup-" + key);
        String siteCd = getSiteCdFromRedisObject(Objects.requireNonNull(redisObject));
        log.info("siteCd : {}", siteCd);

        if (siteCd.equals("MTNWPLUS")) {
            sendPlusQueue(key, redisObject);
        } else {
            sendQueue(key, redisObject);
        }
    }

    private void sendPlusQueue(String key, Object redisObject) {
        // 해당 메시지를 Plus 큐로 전송
        amqpTemplate.convertAndSend("sendSmsQueue.exchange", "plusMultiReserveSendSmsQueue", 
            objectMapper.convertValue(redisObject, SmsQueueReserveRunDto.class));
        redisTemplate.delete("backup-" + key);
    }

    private void sendQueue(String key, Object redisObject) {
        // 일반 큐로 전송
        amqpTemplate.convertAndSend("sendSmsQueue.exchange", "multiReserveSendSmsQueue", 
            objectMapper.convertValue(redisObject, SmsQueueReserveRunDto.class));
        redisTemplate.delete("backup-" + key);
    }
    
    // Redis 객체에서 siteCd 필드를 추출하는 메서드
    private String getSiteCdFromRedisObject(Object redisObject) throws Exception {
        Field msgSendMasterReserveDtoField = redisObject.getClass().getDeclaredField("msgSendMasterReserveDto");
        msgSendMasterReserveDtoField.setAccessible(true);
        Object msgSendMasterReserveDto = msgSendMasterReserveDtoField.get(redisObject);
        Field siteCdField = msgSendMasterReserveDto.getClass().getDeclaredField("siteCd");
        siteCdField.setAccessible(true);
        return (String) siteCdField.get(msgSendMasterReserveDto);
    }
}

 

구현 내용

 

 

  • Redis TTL 만료 이벤트 처리: Redis의 TTL 만료 이벤트를 구독해, 만료된 키와 연관된 데이터를 조회하고, 해당 데이터를 AMQP 템플릿을 통해 RabbitMQ 큐로 전송합니다.
  • siteCd 필드 기반 처리: siteCd 필드에 따라 메시지를 전송할 큐를 결정하며, 특정 사이트(예: "MTNWPLUS")에 맞춰 적절한 큐로 메시지를 보냅니다.
  • 메시지 삭제: 전송이 완료된 메시지는 Redis에서 삭제하여, 중복 처리가 발생하지 않도록 합니다.

 

결론

  • 정확한 메시지 전송: Redis TTL 만료를 이용해 정확한 시간에 메시지를 처리할 수 있어, 지연 없이 예약 메세지를 발송할 수 있게 되었습니다.
  • 큐 대기 시간 제거: RabbitMQ의 FIFO 문제를 해결함으로써, 메시지가 선입선출 순서로 처리되는 대신 설정된 시간에 따라 정확하게 전송됩니다.
  • 유연한 메시지 처리: 메시지의 종류나 사이트에 따라 적절한 큐로 메시지를 보낼 수 있는 유연성을 제공합니다.

 

728x90
반응형
LIST