[기술공부] WebSocket, STOMP, Redis

90000e·2023년 9월 9일

[기술공부]

목록 보기
1/2

실시간 채팅 서비스를 구현하면서 이렇게 많은 기술이 있는지 처음알았다.

원래는 Zookeeper와 Kafka를 사용하려 했지만 1대1 대화만 사용하면, kafka를 사용하는것보다는 Redis만 사용하는게 가장 편리하고 좋다! 라는 이야기를 어디선가 본적이있어 Redis로 기술을 바꾸게 되었다!


WebSocket부터 천천히 학습하면서, STOMP의 Pub / Sub 구조를 살펴봤고, 이제 Redis의 Pub / Sub구조에 대해 살펴보았다.

공부하다보니 드는 의문점!

바로바로바로 전체적으로 WebSocket과 STOMP와 Redis는 정확히 뭐고 어디서 어떻게 쓰이고 Pub / Sub 는 무엇일까? 라는 좀 많은 의문점을 가지게 되었다.

🧑 WebScoket 이란?

보통 동적 웹 사이트에서 API통신은 단방향 통신만 가능하다. API 통신을 통해 값의 수정 및 변경이 가능하다. 하지만 WebSocket을 사용하게되면, 양방향 통신이 가능하다!

WebSocket은 보통 알람이나 채팅에서 많이 사용한다.

implementation 'org.springframework.boot:spring-boot-starter-websocket'

위 명령어를 build.gradle에 넣고 실행하면 된다.

WebScoket은 WebSocketHandlerWebSocketConfig 클래스를 생성해야 한다.

WebSocketHandler는 Service와 비슷한 부분이라고 생각하면 되고, WebSocketConfig는 초기설정 이라고 생각하면 쉽다.

WebSocket 초기설정

@Slf4j
@Component
public class WebSockChatHandler extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        log.info("payload {}", payload);
        TextMessage textMessage = new TextMessage("WebSocket");
        session.sendMessage(textMessage);
    }
}

이런식으로 TextWebSocketHandler를 상속받아오면, handleTextMessage 메서드는 클라이언트가 서버로 메시지를 보낼 때 자동으로 호출된다고 한다.

Front-end에서 데이터를 보내주면, handleTextMessage 메서드는 자동으로 Session과 message로 나누어 매개변수를 받게되고, message안에는 payload라는 객체(?)로 묶어 message 내용이 발송되게 된다. 때문에 payload를 얻어 이 payload를 해석해보면 전송된 메세지를 알 수 있다.

그리고 Session에는 객체에는 세션 ID, 연결된 사용자의 Principal 정보, 연결 상태 등이 담겨있다.

@RequiredArgsConstructor
@Configuration
@EnableWebSocket
public class WebSockConfig implements WebSocketConfigurer {
    private final WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/ws/chat").setAllowedOrigins("*");
    }
}

이건 초기설정과 같은 WebSocketConfig부분인데, WebSocket의 uri와 CORS 설정을 해주게 된다.

이렇게 하면 WebSocket의 기본 설정은 끝이났다.

WebSocket 깃허브 https://github.com/gamgam330/WebSocket

🧑 STOMP 이란?

메세지 전송을 효율적으로 하기위해 등장한 프로토콜이다.

STOMP 설명 내 벨로그

https://velog.io/@gamgam330/%ED%86%A0%EC%9D%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Kafka%EC%99%80-Zookeeper-%EC%82%AC%EC%9A%A9%EA%B8%B0

이전 글에 자세히는 아니지만.. 그래도 설명이 있기때문에 기본 설정만 설명하도록 하겠다.

STOMP 초기설정

@Configuration
@EnableWebSocketMessageBroker
public class WebSockConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/sub");
        config.setApplicationDestinationPrefixes("/pub");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp").setAllowedOrigins("*")
                .withSockJS();
    }
}

기본적으로 WebSocket과 같이 사용하는 기술이다. 그래서 WebSocketConfig에서 configureMessageBroker 을 추가해 Pub과 Sub을 구분해주면 된다.

그래서 어떻게 돌아가는 거라고?!

채팅방 마다 존재하는 TOPIC을 구독하고 메세지 발행채팅방 구독의 형태로 진행된다.

채팅방이 만들어지면 그 해당 채팅방마다 TOPIC이 발행되는데 이 TOPIC을 잡지사라고 생각하면 편할 것 같다.

해당 잡지사(TOPIC)를 구독하고 새로운 잡지(메세지)가 발행되면, 그 잡지사를 구독한 회원들이 각 잡지를 가지러가는 형태와 비슷하다.

STOMP를 이용해 채팅방이 만들어 질때마다 새로운 TOPIC을 발행하고, 그 TOPIC을 구독한 회원을 관리하고, 메세지가 발생할 때마다 해당 TOPIC을 구독한 구독자에게 보내주도록 코드를 짜면된다.

🧑 Redis 란?

In-Memory 데이터 스토어, 데이터베이스와 메세지 브로커, 캐싱 시스템 등 다양한 용도로 사용될 수 있는 저장 및 처리 시스템

Redis는 주로 DB로 사용되지만, Key-Value형태의 데이터를 저장하고 빠르게 조회, 수정이 가능하다. 그래서 여러가지 방면으로 사용이 되고있다.

Redis 초기설정

@Configuration
public class RedisConfig {

    @Bean
    public RedisMessageListenerContainer redisMessageListener(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));
        return redisTemplate;
    }
}

Redis 기본 설정이다. 이제 STOMP에서 사용했던 Pub / Sub을 Redis로 대체하려고 한다. 이에 앞서 RedisConfig부분을 먼저 살펴보도록 하겠다.

Redis의 Pub/Sub을 사용하기 위해 redisMessageListener 를 설정해 주어야한다.

어플리케이션에서도 Redis를 사용하기 떄문에, redisTemplate도 추가해야한다. 코드를 짜다보면 publisher와 Subscriber을 만드는 부분과 메세지가 왔다갔다 거릴때 redisTemplate를 사용하게 된다. 위에서 설명했듯이 Key-Value 구조라 첫 설정은 이렇게 된다.

나는 채팅서비스를 개발중이라, Key값은 String형으로 Value값은 JSON형태로 진행했다.

Redis 발행 서비스 구현

@RequiredArgsConstructor
@Service
public class RedisPublisher {
    private final RedisTemplate<String, Object> redisTemplate;

    public void publish(ChannelTopic topic, ChatMessage message) {
        redisTemplate.convertAndSend(topic.getTopic(), message);
    }
}

채팅방에 입장하여 메세지 작성 후 해당 메세지를 Redis TOPIC에 발행 한다.

Redis 구독 서비스 구현

@Slf4j
@RequiredArgsConstructor
@Service
public class RedisSubscriber implements MessageListener {

    private final ObjectMapper objectMapper;
    private final RedisTemplate redisTemplate;
    private final SimpMessageSendingOperations messagingTemplate;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        try {
            String publishMessage = (String) redisTemplate.getStringSerializer().deserialize(message.getBody());
            ChatMessage roomMessage = objectMapper.readValue(publishMessage, ChatMessage.class);
            messagingTemplate.convertAndSend("/sub/chat/room/" + roomMessage.getRoomId(), roomMessage);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}

메세지가 발행될때까지 기다렸다가 메세지가 발행되면, 해당 메세지를 읽어 처리하는 역할이다.

STOMP & Redis 깃허브 https://github.com/gamgam330/ChatWithRedis


이번 학습을 통해 채팅서비스의 전체적인 흐름을 학습할 수 있었지만.. 아직도 너무 어려운것같다. 조금 더 학습해야겠다.

profile
내가 복습하려고 쓰는 블로그

2개의 댓글

comment-user-thumbnail
2023년 9월 11일

킵고잉 하고 계시는군요! 전에 말씀드린 것처럼 새로운 기술들을 사용해보면 성장에 큰 도움이 될 거에요. 너무 잘 하고 계십니다 🔥🔥🔥

1개의 답글