트러블슈팅
Redis를 이용한 예약 메세지 시스템 개선: RabbitMQ의 FIFO 한계를 극복한 방법
초록색거북이
2024. 10. 4. 13:52
728x90
반응형
SMALL
문제상황
기존 예약 메세지 시스템은 RabbitMQ의 지연 큐를 이용해 메시지의 전송 시간을 TTL(Time To Live)로 설정한 후 메시지를 예약하는 방식으로 구현되었습니다. 이 방식은 선입선출(FIFO) 구조의 특성 때문에 다음과 같은 문제가 발생했습니다:
- FIFO로 인한 대기 현상: 예를 들어, 3시간 뒤에 전송할 메시지가 먼저 큐에 걸린 경우, 그 이후 1분 뒤에 전송할 메시지가 들어와도 3시간이 지나야 큐에서 빠져나가게 되어, 1분 뒤에 전송되어야 할 메시지가 지연되는 현상이 발생.
- 지연된 예약 메시지: 이로 인해 여러 메시지들이 제때 처리되지 않으면서 예약 메시지 시스템이 올바르게 동작하지 않는 문제가 발생했습니다.
해결책
이 문제를 해결하기 위해 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