Guide_Messaging with Redis

Dev.Hammy·2023년 12월 12일
0

Spring Guides

목록 보기
7/46

이 가이드는 Spring Data Redis를 사용하여 Redis로 전송된 메시지를 게시하고 구독하는 프로세스를 안내합니다.

메시징 모델의 유형

주요 용어

  • Receiver(수신자), Listener(리스너), Consumer(소비자), Subscriber(구독자) :모두 메시징 시스템에서 메시지를 수신하고 처리하는 역할을 합니다.
  • Sender(송신자), Producer(생산자), Publisher(발행자) : 모두 메시징 시스템에서 메시지를 생성하고 보내는 역할을 합니다.
  • 메시지(Message): 데이터 덩어리로, 전달하고자 하는 정보를 포함합니다.

Pub/Sub

  • 주제(Topic): Pub/Sub 모델에서 메시지를 구분하는 데 사용되는 명명된 채널이나 주제입니다.
  • 메시지 브로커(Message Broker): Pub/Sub 모델에서 발행자와 구독자 간의 중간 매개체로, 메시지를 중개하고 보관하는 시스템입니다.
  • Pub/Sub은 여러 구독자가 메시지를 동시에 수신할 수 있음
  • Pub/Sub은 이벤트 기반 시스템에서 활발하게 사용

P2P

  • 큐(Queue): P2P 모델에서 메시지가 대기하는 FIFO(First-In-First-Out) 구조의 저장 공간입니다.
  • 메시지 큐 서버(Message Queue Server): P2P 모델에서 메시지를 보내고 받는 데 사용되는 서버입니다.
  • P2P는 메시지를 처리할 수신자가 반드시 존재해야 하며, 한 번에 하나의 수신자만 메시지를 처리
  • P2P는 메시지 큐 시스템에서 대기열 처리에 적합

이벤트 수신 객체의 종류

개념형식
ReceiverPOJO
Listener인터페이스 구현체
Subscriber인터페이스 구현체(ActionEvent 타입 매개변수)

Receiver

  • 이벤트를 수신할 수 있습니다.
  • 이벤트 발생 시 특정 작업을 수행합니다.
// Receiver 예시
public class ButtonReceiver {

    public void onButtonClick() {
        // 이벤트 발생 시 수행할 작업
    }
}

Listener

  • 이벤트를 수신할 수 있습니다.
  • 이벤트 발생 시 호출될 메서드를 등록할 수 있습니다.
  • 이벤트 발생에 대한 응답을 제공할 수 있습니다.
// Listener 예시
public class ButtonListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        // 이벤트 발생 시 수행할 작업
    }
}

Subscriber

  • 이벤트를 구독할 수 있습니다.
  • 이벤트 발생 시 특정 작업을 수행합니다.
// Subscriber 예시
public class ButtonSubscriber implements Subscriber<ActionEvent> {

    @Override
    public void onNext(ActionEvent event) {
        // 이벤트 발생 시 수행할 작업
    }
}

PoJO (Plain Old Java Object)
특정 프레임워크나 라이브러리에 종속되지 않은 단순한 자바 객체를 의미합니다. POJO는 다음과 같은 특징을 가지고 있습니다.

  • 인터페이스나 추상 클래스를 상속하지 않습니다.
  • 특정 프레임워크나 라이브러리에서 제공하는 특수한 메서드나 속성을 사용하지 않습니다.
  • 기본 자바 클래스와 같은 방식으로 생성, 사용, 수정할 수 있습니다.

What You Will Build

StringRedisTemplate을 사용하여 문자열 메시지를 게시하고 MessageListenerAdapter를 사용하여 POJO가 메시지를 구독하도록 하는 애플리케이션을 빌드합니다.

메시지를 게시하는 수단으로 Spring Data Redis를 사용하는 것이 이상하게 들릴 수도 있지만, Redis는 NoSQL 데이터 저장소뿐만 아니라 메시징 시스템도 제공합니다.

Standing up a Redis Server

메시징 애플리케이션을 구축하기 전에 메시지 수신 및 전송을 처리할 서버를 설정해야 합니다.

Redis는 메시징 시스템과 함께 제공되는 오픈 소스 BSD 라이선스 키-값 데이터 저장소입니다. 서버는 https://redis.io/download에서 무료로 사용할 수 있습니다. 수동으로 다운로드하거나 Mac을 사용하는 경우 Homebrew를 사용하여 터미널 창에서 다음 명령을 실행하여 다운로드할 수 있습니다.

https://redis.io/docs/install/install-redis/install-redis-on-linux/

prerequisites
If you're running a very minimal distribution (such as a Docker container) you may need to install lsb-release, curl and gpg first:

sudo apt install lsb-release curl gpg

install on ubuntu

curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list

sudo apt-get update
sudo apt-get install redis

redis-server

Starting with Spring Initializr

Create a Redis Message Receiver

모든 메시징 기반 애플리케이션에는 메시지 게시자(Publisher)와 메시징 수신자(Reciever)가 있습니다. 메시지 수신기를 생성하려면 다음 예제(src/main/java/guides/messagingredis/Receiver.java)에 표시된 대로 메시지에 응답하는 메서드를 사용하여 수신기를 구현합니다.

데모(demonstration) 목적으로 수신자(receiver)는 수신된 메시지 수를 계산(count)합니다. 이렇게 하면 메시지를 받았을 때 신호를 보낼 수 있습니다.

package guides.messagingredis;

import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Receiver {
    private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class);

    private AtomicInteger counter = new AtomicInteger();

    public void receiveMessage(String message) {
        LOGGER.info("Received <" + message + ">");
        counter.incrementAndGet();
    }

    public int getCount() {
        return counter.get();
    }
}

Receiver는 메시지 수신 방법을 정의하는 POJO입니다. Reciever를 메시지 Listner로 등록할 때 메시지 처리 방법의 이름을 원하는 대로 지정할 수 있습니다.

  • LoggerFactory.getLogger(클래스.class)가 현재 클래스의 로거 인스턴스를 생성한다. 로거 인스턴스는 파일, 콘솔, 네트워크 등을 대상으로 추적하고 오류를 진단하는 역할을 한다.

Register the Listener and Send a Message

Spring Data Redis는 Redis로 메시지를 보내고 받는 데 필요한 모든 구성 요소를 제공합니다. 특히 다음을 구성해야 합니다.

  1. 연결 팩토리 (Connection Factory) : 템플릿과 메시지 리스너 컨테이너를 모두 구동하여 Redis 서버에 연결

  2. 메시지 리스너 컨테이너 (Message Listener Container)

  • 메시지를 수신할 수 있도록 메시지 리스너 컨테이너에 Receiver를 등록
  1. Redis 템플릿 (Redis Template) : 템플릿을 사용하여 메시지를 송신

이 예제에서는 Jedis Redis 라이브러리를 기반으로 하는 JedisConnectionFactory의 인스턴스인 Spring Boot의 기본 RedisConnectionFactory를 사용합니다. 다음 예제(src/main/java/guides/messagingredis/MessagingRedisApplication.java)에 표시된 것처럼 연결 팩토리는 메시지 리스너 컨테이너와 Redis 템플릿 모두에 주입됩니다.

package guides.messagingredis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

@SpringBootApplication
public class MessagingRedisApplication {
    private static final Logger LOGGER =
            LoggerFactory.getLogger(MessagingRedisApplication.class);

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, new PatternTopic("chat"));

        return container;
    }

    @Bean
    MessageListenerAdapter listenerAdapter(Receiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    @Bean
    Receiver receiver() {
        return new Receiver();
    }

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

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = SpringApplication.run(MessagingRedisApplication.class, args);

        StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class);

        Receiver receiver = ctx.getBean(Receiver.class);

        while (receiver.getCount() == 0) {
            LOGGER.info("Sending message...");
            template.convertAndSend("chat", "Hello from Redis!");
            Thread.sleep(500L);
        }

        System.exit(0);
    }
}

이 코드들은 Redis를 통해 메시지를 주고받는 과정을 담고 있어요. 여러 부분이 상호작용하며 메시지를 생성, 송수신하고 처리하는 구조입니다. 이들의 상호작용은 다음과 같습니다:

  1. Spring Boot Application 시작

    • main() 메서드 실행
    • SpringApplication.run(MessagingRedisApplication.class, args) 호출
    • Spring 컨텍스트가 생성되고, ApplicationContext가 반환됨
  2. Spring 컨텍스트 설정

    • @Bean 어노테이션을 사용하여 빈을 설정하고 등록
    • container(), listenerAdapter(), receiver(), template() 메서드가 실행되어 각각의 빈이 생성되고 컨테이너에 등록됨
  3. RedisMessageListenerContainer 설정

    • container() 메서드가 호출되어 RedisMessageListenerContainer 빈이 생성됨
    • setConnectionFactory()로 Redis 연결 설정
    • addMessageListener()listenerAdapter를 등록하고, "chat" 토픽을 구독하여 해당 토픽의 메시지를 처리할 준비를 함
  4. 메시지 전송 및 수신

    • StringRedisTemplate을 이용해 convertAndSend()가 호출되어 "chat" 채널로 메시지 전송
    • Redis에 "Hello from Redis!" 메시지가 전송됨
    • RedisMessageListenerContainer는 등록된 리스너로부터 "chat" 토픽에 도착하는 메시지를 감지하고, MessageListenerAdapter를 통해 수신된 메시지를 Receiver 클래스의 receiveMessage() 메서드로 라우팅함

이런 과정을 통해 메시지가 생성되고 Redis를 통해 전송되며, RedisMessageListenerContainer가 해당 토픽을 구독하여 메시지를 수신하고, 지정된 리스너를 통해 메시지를 처리하는 과정이 이뤄집니다.

while 루프:

  • Receiver 클래스의 getCount() 메서드는 카운터 값을 반환합니다.
  • MessagingRedisApplication 클래스의 main() 메서드에서는 Receiver의 인스턴스를 얻고, getCount()를 사용하여 현재까지 받은 메시지의 수를 확인합니다.
  • while (receiver.getCount() == 0) 구문은 받은 메시지의 수가 0인 경우에만 실행됩니다.
  • 이 구문은 Redis를 통해 "chat" 채널로 메시지를 전송하고, Receiver 클래스에서 메시지를 받을 때까지 루프를 돌게끔 설계되어 있습니다.
  • 따라서 이 루프는 메시지를 받을 때까지 기다리고, Receiver 클래스에서 메시지를 처리할 수 있도록 로직을 구성하고 있습니다.

while 루프는 받은 메시지의 수가 0인 경우에만 실행되며, 메시지가 도착할 때까지 메시지를 기다립니다. 메시지가 도착하면 Receiver 클래스의 getCount()가 0이 아닌 값을 반환하고 루프를 종료하게 됩니다.


main에서 ApplicationContext.getBean을 사용하는 방식은 CommandLineRunner를 이용해서도 같은 기능을 구현할 수 있다.

예시 1

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class RedisMessageProcessor implements CommandLineRunner {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisMessageProcessor.class);

    private final Receiver receiver;
    private final StringRedisTemplate template;

    public RedisMessageProcessor(Receiver receiver, StringRedisTemplate template) {
        this.receiver = receiver;
        this.template = template;
    }

    @Override
    public void run(String... args) throws InterruptedException {
        while (receiver.getCount() == 0) {
            LOGGER.info("Sending message...");
            template.convertAndSend("chat", "Hello from Redis!");
            Thread.sleep(500L);
        }
    }
}

예시 2

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;

@Configuration
public class CommandLineRunnerConfig {

    @Autowired
    private Receiver receiver;

    @Autowired
    private StringRedisTemplate template;

    @Bean
    public CommandLineRunner redisMessageProcessor() {
        return args -> {
            while (receiver.getCount() == 0) {
                LOGGER.info("Sending message...");
                template.convertAndSend("chat", "Hello from Redis!");
                Thread.sleep(500L);
            }
        };
    }
}

listenerAdapter 메서드에서 정의된 빈은 container 메서드에서 정의된 메시지 리스너 컨테이너에 메시지 리스너로 등록되어 "chat" 토픽의 메시지를 수신합니다.

Receiver 클래스는 POJO이므로 addMessageListener()에 필요한 MessageListener 인터페이스를 구현하는 메시지 리스너 어댑터로 래핑해야 합니다. 메시지 리스너 어댑터는 또한 메시지가 도착할 때 Receiver 클래스의 receiveMessage() 메서드를 호출하도록 구성됩니다.

메시지를 리슨하기 위해 필요한 것은 연결 팩토리와 메시지 리스너 컨테이너 빈 뿐입니다. 메시지를 보내려면 Redis 템플릿도 필요합니다. 여기에서는 키와 값 모두 String 인스턴스인 Redis의 일반적인 사용에 초점을 맞춘 RedisTemplate 구현인 StringRedisTemplate로 구성된 빈입니다.

main() 메서드는 스프링 애플리케이션 컨텍스트를 만들면서 모든 것을 시작합니다. 애플리케이션 컨텍스트는 메시지 리스너 컨테이너를 시작하고, 메시지 리스너 컨테이너 빈은 메시지를 수신하기 시작합니다. main() 메서드는 애플리케이션 컨텍스트에서 StringRedisTemplate 빈을 검색하고 그것을 사용하여 "chat" 토픽에 "Hello from Redis!" 메시지를 보냅니다. 마지막으로 스프링 애플리케이션 컨텍스트를 닫고 애플리케이션이 종료됩니다.

실행해보기

0개의 댓글