Redis Pub/Sub과 Spring Boot를 활용한 실시간 알림 시스템 구현

2024. 9. 15. 19:27·프로그래밍 언어/스프링부트

 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 주요 명령어

  1. SUBSCRIBE channel [channel ...]: 하나 이상의 채널 구독
  2. PUBLISH channel message: 특정 채널에 메시지 발행
  3. PSUBSCRIBE pattern [pattern ...]: 패턴에 맞는 채널 구독
  4. UNSUBSCRIBE [channel [channel ...]]: 채널 구독 취소
  5. 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. 시스템 동작 방식

  1. 클라이언트가 /api/notifications/send 엔드포인트로 POST 요청을 보냅니다.
  2. NotificationController가 요청을 받아 NotificationPublisher를 통해 메시지를 발행합니다.
  3. Redis가 메시지를 해당 채널의 모든 구독자에게 전달합니다.
  4. 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
'프로그래밍 언어/스프링부트' 카테고리의 다른 글
  • Webflux - reactor 실습
  • webflux - CPU Bound vs IO Bound
  • Spring Session
  • OneToMany 관계 설정 시 필드 타입 설정은 뭘로 하나?
hyeseong-dev
hyeseong-dev
안녕하세요. 백엔드 개발자 이혜성입니다.
  • hyeseong-dev
    어제 오늘 그리고 내일
    hyeseong-dev
  • 전체
    오늘
    어제
    • 분류 전체보기 (282)
      • 여러가지 (107)
        • 알고리즘 & 자료구조 (72)
        • 오류 (4)
        • 이것저것 (29)
        • 일기 (1)
      • 프레임워크 (39)
        • 자바 스프링 (39)
        • React Native (0)
      • 프로그래밍 언어 (38)
        • 파이썬 (30)
        • 자바 (3)
        • 스프링부트 (5)
      • 운영체제 (0)
      • DB (17)
        • SQL (0)
        • Redis (17)
      • 클라우드 컴퓨팅 (2)
        • 도커 (2)
        • AWS (0)
      • 스케쥴 (65)
        • 세미나 (0)
        • 수료 (0)
        • 스터디 (24)
        • 시험 (41)
      • 트러블슈팅 (1)
      • 자격증 (0)
        • 정보처리기사 (0)
      • 재태크 (4)
        • 암호화폐 (4)
        • 기타 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    AWS
    Redis
    파이썬
    EC2
    자바
    mybatis
    ecs
    완전탐색
    celery
    Spring WebFlux
    #개발자포트폴리오 #개발자이력서 #개발자취업 #개발자취준 #코딩테스트 #항해99 #취리코 #취업리부트코스
    java
    DP
    WebFlux
    항해99
    취업리부트
    OOP
    그리디
    Python
    SAA
    Spring Boot
    프로그래머스
    RDS
    spring
    docker
    Docker-compose
    백준
    reactor
    시험
    FastAPI
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
hyeseong-dev
Redis Pub/Sub과 Spring Boot를 활용한 실시간 알림 시스템 구현
상단으로

티스토리툴바