[Spring/WebSocket] redis로 채팅방 구현하기(서버 다중화)

·2025년 6월 20일

Spring

목록 보기
22/26
post-thumbnail

이전 포스트들과 이어집니다
1. [Spring/WebSocket] 순수 WebSocket으로 채팅방 만들기
2. [Spring/WebSocket] WebSocket + STOMP으로 귓속말 가능한 채팅방 만들기
3. [Spring/WebSocket] WebSocket + STOMP + DOCKER 실습 : 여러 서버 띄우기

[Redis 실습하기]

[Spring/WebSocket] WebSocket + STOMP + DOCKER 실습 : 여러 서버 띄우기에서 사용한 프로젝트에 redis를 붙여보자

1. redis 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-data-redis’

2. stompwebsocket 하위에 redis 패키지 추가 후 코드 작성

2-1. RedisPublisher.java

package com.example.backendproject.stompwebsocket.redis;


import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class RedisPublisher {

    /** 메세지를 발행하는 클래스 **/

    private final StringRedisTemplate stringRedisTemplate;

    ///  stomp -> pub -> sub -> stomp
    public void publish(String channel,String message){

        stringRedisTemplate.convertAndSend(channel,message);
    }

}

2-2. RedisSubscriber.java

package com.example.backendproject.stompwebsocket.redis;
import com.example.backendproject.stompwebsocket.dto.ChatMessage;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RedisSubscriber implements MessageListener{


    private final SimpMessagingTemplate simpMessagingTemplate;
    private ObjectMapper objectMapper = new ObjectMapper();


    @Override
    public void onMessage(Message message, byte[] pattern) {

        try {
            String msgBody = new String(message.getBody());
            ChatMessage chatMessage = objectMapper.readValue(msgBody, ChatMessage.class);


            if (chatMessage.getTo() != null && !chatMessage.getTo().isEmpty()) {
                // 귓속말
                simpMessagingTemplate.convertAndSendToUser(chatMessage.getTo(), "/queue/private", chatMessage);
            } else {
                // 일반 메시지
                simpMessagingTemplate.convertAndSend("/topic/room." + chatMessage.getRoomId(), chatMessage);


            }
        }
        catch (Exception e) {

        }

    }

}

2-3. redisConfig.java

package com.example.backendproject.stompwebsocket.redis;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

@Configuration
@RequiredArgsConstructor
public class RedisConfig {


    private final RedisSubscriber redisSubscriber;

    @Bean
    public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
        RedisMessageListenerContainer Container = new RedisMessageListenerContainer();
        Container.setConnectionFactory(redisConnectionFactory);

        Container.addMessageListener(new MessageListenerAdapter(redisSubscriber),new PatternTopic("room.*"));
        Container.addMessageListener(new MessageListenerAdapter(redisSubscriber), new PatternTopic("private.*")); //귓속말

        return Container;

    }
}

3. 설정 파일 및 기존 코드 수정

3-1. .env 파일 추가

  • backendProject 폴더의 상위 폴더인 backend5에 .env파일 추가
DB_SERVER=database
DB_PORT=3306
DB_USER=root
DB_PASS=1234

REDIS_HOST=redis

3-2. application.properties 변경

db.server=${DB_SERVER:localhost}
db.port=${DB_PORT:3307}
db.username=${DB_USER:root}
db.password=${DB_PASS:1234}
REDIS.HOST=${REDIS_HOST:localhost}

spring.data.redis.host=${REDIS.HOST}
spring.data.redis.port=6379

spring.datasource.url=jdbc:mysql://${db.server}:${db.port}/backendDB?serverTimezone=Asia/Seoul&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.datasource.username=${db.username}
spring.datasource.password=${db.password}

3-3. docker-compose.yml 변경

services:

 redis:
    image: redis
    container_name: redis
    ports:
      - "6379:6379"

  database:
    ...
    ports:
      - "3307:3306"
    environment:
      MYSQL_DATABASE: backendDB
      MYSQL_ROOT_PASSWORD: ${DB_PASS}
    ...

  backend1:  # 서비스 이름은 컨테이너간 통신하기 위한 이름
    image: backend
    container_name: backend1
    environment:
      PROJECT_NAME: 백앤드 서버1
      DB_SERVER: ${DB_SERVER}
      DB_PORT: ${DB_PORT}
      DB_USER: ${DB_USER}
      DB_PASS: ${DB_PASS}
      REDIS_HOST: ${REDIS_HOST}
    depends_on:
      - database
      - redis

  backend2:  # 서비스 이름은 컨테이너간 통신하기 위한 이름
    image: backend
    container_name: backend2
    environment:
      PROJECT_NAME: 백앤드 서버2
      DB_SERVER: ${DB_SERVER}
      DB_PORT: ${DB_PORT}
      DB_USER: ${DB_USER}
      DB_PASS: ${DB_PASS}
      REDIS_HOST: ${REDIS_HOST}
    depends_on:
      - database
      - redis

  backend3:  # 서비스 이름은 컨테이너간 통신하기 위한 이름
    image: backend
    container_name: backend3
    environment:
      PROJECT_NAME: 백앤드 서버3
      DB_SERVER: ${DB_SERVER}
      DB_PORT: ${DB_PORT}
      DB_USER: ${DB_USER}
      DB_PASS: ${DB_PASS}
      REDIS_HOST: ${REDIS_HOST}
    depends_on:
      - database
      - redis


  nginx:
    image: nginx:1.25
    container_name: nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - backend1
      - backend2
      - backend3

3-4. chatController.java 변경

//STOMP는 이게 끝.
@Controller
@RequiredArgsConstructor
public class ChatController {

    //서버가 클라이언트에게 수동으로 메세지를 보낼 수 있도록 하는 클래스
    private final SimpMessagingTemplate template;

    //동적으로 방 생성 가능
    @Value("${PROJECT_NAME:web Server}")
    private String instansName;
	
    //redis 관련 변수
    private final RedisPublisher redisPublisher;
    private ObjectMapper objectMapper  = new ObjectMapper();

    @MessageMapping("/chat.sendMessage")
    public void sendmessage(ChatMessage message) throws JsonProcessingException {

        message.setMessage(instansName+" "+message.getMessage());

        String channel = null;
        String msg = null;

        if (message.getTo() != null && !message.getTo().isEmpty()) {
            // 귓속말
            //내 아이디로 귓속말경로를 활성화 함
            channel = "private."+message.getRoomId();
            msg = objectMapper.writeValueAsString(message);

        } else {
            // 일반 메시지
            channel = "room."+message.getRoomId();
            msg = objectMapper.writeValueAsString(message);
        }
        redisPublisher.publish(channel,msg);
    }
}

4. 웹에서 확인

redis 설정 후에는 서버가 달라도 채팅 가능!

profile
배우고 기록하며 성장하는 백엔드 개발자입니다!

0개의 댓글