Redis의 Pub/Sub 기능과 Spring Boot를 사용하여 실시간 알림 시스템을 구현하는 방법에 대해 알아보겠습니다. 먼저 Redis Pub/Sub에 대한 기본 개념부터 시작하여 실제 구현까지 단계별로 살펴보겠습니다.
1. Redis Pub/Sub 소개
Redis Pub/Sub(Publish/Subscribe)은 메시지 브로커 패턴을 구현한 기능으로, 발행자(Publisher)가 메시지를 특정 채널에 보내면 해당 채널을 구독하고 있는 모든 구독자(Subscriber)가 실시간으로 메시지를 받을 수 있는 시스템입니다.
1.1 Redis Pub/Sub의 주요 특징
- 비동기 통신: 발행자와 구독자 간 직접적인 연결이 필요 없음
- 다대다 통신: 여러 발행자가 여러 구독자에게 메시지를 전달할 수 있음
- 실시간 처리: 메시지가 발행되면 즉시 구독자에게 전달됨
- 확장성: 구독자를 쉽게 추가하거나 제거할 수 있음
1.2 Redis Pub/Sub의 주요 용도
- 실시간 알림 시스템
- 채팅 애플리케이션
- 실시간 분석 및 모니터링
- 마이크로서비스 간 이벤트 기반 통신
- IoT 디바이스 메시징
1.3 Redis Pub/Sub 주요 명령어
SUBSCRIBE channel [channel ...]
: 하나 이상의 채널 구독PUBLISH channel message
: 특정 채널에 메시지 발행PSUBSCRIBE pattern [pattern ...]
: 패턴에 맞는 채널 구독UNSUBSCRIBE [channel [channel ...]]
: 채널 구독 취소PUNSUBSCRIBE [pattern [pattern ...]]
: 패턴 구독 취소
1.4 Redis Pub/Sub 사용 예시
# 터미널 1 (subscriber)
> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
# 터미널 2 (publisher)
> PUBLISH news "Breaking news: Redis is awesome!"
(integer) 1
# 터미널 1 (subscriber)에서 메시지 수신
1) "message"
2) "news"
3) "Breaking news: Redis is awesome!"
# 패턴 구독
> PSUBSCRIBE news.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "news.*"
3) (integer) 1
이제 이러한 Redis Pub/Sub의 개념을 바탕으로 Spring Boot에서 어떻게 구현하는지 살펴보겠습니다.
2. 프로젝트 구조
우리의 프로젝트는 다음과 같은 주요 컴포넌트로 구성됩니다:
NotificationMessage
: 알림 메시지 DTO 클래스NotificationPublisher
: 메시지 발행 서비스 클래스NotificationSubscriber
: 메시지 구독 서비스 클래스NotificationController
: REST API 엔드포인트 컨트롤러 클래스RedisConfig
: Redis 설정 클래스RedisSubscriberConfig
: Redis 구독자 설정 클래스
3. 코드 구현
3.1 알림 메시지 DTO (NotificationMessage.java)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NotificationMessage implements Serializable {
private String id;
private String type;
private String content;
private LocalDateTime timestamp;
}
3.2 NotificationPublisher.java
@Service
@Slf4j
public class NotificationPublisher {
private final RedisTemplate<String, Object> redisTemplate;
public NotificationPublisher(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void publish(String channel, NotificationMessage message) {
try {
redisTemplate.convertAndSend(channel, message);
log.info("Message published to channel {}: {}", channel, message);
} catch (Exception e) {
log.error("Error publishing message to channel {}: {}", channel, e.getMessage());
}
}
}
3.3 NotificationSubscriber.java
@Service
@Slf4j
public class NotificationSubscriber implements MessageListener {
private final ObjectMapper objectMapper;
public NotificationSubscriber(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void onMessage(Message message, byte[] pattern) {
try {
String channel = new String(message.getChannel());
NotificationMessage notificationMessage = objectMapper.readValue(message.getBody(), NotificationMessage.class);
log.info("Received message from channel {}: {}", channel, notificationMessage);
processMessage(notificationMessage);
} catch (Exception e) {
log.error("Error processing received message: {}", e.getMessage());
}
}
private void processMessage(NotificationMessage message) {
// 메시지 처리 로직 구현
}
}
3.4 컨트롤러 (NotificationController.java)
@RestController
@RequestMapping("/api/notifications")
@Slf4j
public class NotificationController {
private final NotificationPublisher notificationPublisher;
public NotificationController(NotificationPublisher notificationPublisher) {
this.notificationPublisher = notificationPublisher;
}
@PostMapping("/send")
public ResponseEntity<String> sendNotification(@RequestBody NotificationMessage message) {
try {
String channel = "notifications:" + message.getType();
notificationPublisher.publish(channel, message);
return ResponseEntity.ok("Notification sent successfully");
} catch (Exception e) {
log.error("Error sending notification: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error sending notification");
}
}
}
3.5 RedisConfig.java
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(objectMapper(), Object.class));
return redisTemplate;
}
}
3.6 RedisSubscriberConfig.java
@Configuration
public class RedisSubscriberConfig {
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory,
NotificationSubscriber notificationSubscriber) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(notificationSubscriber, new PatternTopic("notifications:*"));
return container;
}
}
4. 시스템 동작 방식
- 클라이언트가
/api/notifications/send
엔드포인트로 POST 요청을 보냅니다. NotificationController
가 요청을 받아NotificationPublisher
를 통해 메시지를 발행합니다.- Redis가 메시지를 해당 채널의 모든 구독자에게 전달합니다.
NotificationSubscriber
가 메시지를 수신하고 처리합니다.
5. 테스트 및 확인
시스템이 정상적으로 동작하는지 확인하기 위해 다음과 같은 curl 명령어를 사용할 수 있습니다:
curl -X POST http://localhost:8080/api/notifications/send \
-H "Content-Type: application/json" \
-d '{
"id": "1",
"type": "INFO",
"content": "This is a test notification",
"timestamp": "2024-03-16T12:00:00"
}'
이 요청을 보내면, 서버 로그에서 메시지가 발행되고 구독되는 것을 확인할 수 있습니다.
6. 결론 및 향후 개선 방안
이 시스템은 Redis의 Pub/Sub 기능을 Spring Boot와 통합하여 확장 가능한 실시간 알림 시스템을 구현합니다. 이는 다양한 실시간 애플리케이션에 적용할 수 있는 강력한 기반이 될 수 있습니다.
향후 개선 방안으로는 다음과 같은 것들이 있습니다:
- 메시지 영속성 추가: 중요한 알림의 경우 Redis의 데이터 구조를 활용하여 저장
- 클라이언트 측 실시간 업데이트: WebSocket을 사용하여 브라우저에 실시간으로 알림 전달
- 메시지 필터링 및 우선순위 지정: 사용자별 또는 중요도에 따른 알림 필터링 구현
- 분산 환경에서의 확장성 테스트: Redis 클러스터를 활용한 대규모 처리 능력 테스트
- 보안 강화: 메시지 암호화 및 인증 메커니즘 추가
Redis Pub/Sub과 Spring Boot를 결합한 이 시스템은 실시간 기능이 필요한 다양한 애플리케이션에서 활용될 수 있습니다. 마이크로서비스 아키텍처, 실시간 대시보드, 협업 도구 등 다양한 분야에서 응용이 가능할 것입니다.
이 글이 Redis Pub/Sub과 Spring Boot를 이용한 실시간 알림 시스템 구현에 도움이 되었기를 바랍니다. 추가 질문이나 의견이 있으시면 언제든 댓글로 남겨주세요!
'프로그래밍 언어 > 스프링부트' 카테고리의 다른 글
Webflux - reactor 실습 (1) | 2024.09.18 |
---|---|
webflux - CPU Bound vs IO Bound (2) | 2024.09.17 |
Spring Session (0) | 2024.09.15 |
OneToMany 관계 설정 시 필드 타입 설정은 뭘로 하나? (0) | 2024.05.28 |