반응형

기본 예제

설명

  • spring boot 프로젝트로 Embedded Redis를 띄우고 Pub/Sub 기능을 테스트하는 예제

의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>it.ozimov</groupId>
    <artifactId>embedded-redis</artifactId>
    <version>0.7.2</version>
</dependency>

application.properties

spring.profiles.active=local
spring.redis.host=localhost
spring.redis.port=6379

EmbeddedRedisConfig

@Profile("local")
@Configuration
public class EmbeddedRedisConfig {
    private RedisServer redisServer;

    @Value("${spring.redis.port}")
    private int port;

    @PostConstruct
    public void startRedis() {
        redisServer = new RedisServer(port);
        redisServer.start();
    }

    @PreDestroy
    public void stopRedis() {
        if (redisServer != null) {
            redisServer.stop();
        }
    }
}

ChatMessage

@Data
public class ChatMessage {
    private String senderId;
    private String message;
}

RedisConfig

@Configuration
public class RedisConfig {
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }

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

RedisPubSubController

@Slf4j
@AllArgsConstructor
@RestController
public class RedisPubSubController implements MessageListener {
    private final RedisTemplate<String, ChatMessage> redisTemplate;
    private final RedisMessageListenerContainer redisMessageListenerContainer;

    @PostMapping("/api/rooms/{roomId}")
    public void createChatRoom(@PathVariable String roomId) {
        redisMessageListenerContainer.addMessageListener(this, new ChannelTopic(roomId));
    }

    @DeleteMapping("/api/rooms/{roomId}")
    public void deleteChatRoom(@PathVariable String roomId) {
        redisMessageListenerContainer.removeMessageListener(this, new ChannelTopic(roomId));
    }

    @PostMapping("/api/rooms/{roomId}/chat")
    public void sendChatMessage(@PathVariable String roomId, @RequestBody ChatMessage chatMessage) {
        redisTemplate.convertAndSend(roomId, chatMessage);
    }

    @Override
    public void onMessage(Message message, byte[] bytes) {
        String roomId = new String(message.getChannel());
        ChatMessage chatMessage = (ChatMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());

        log.info("[onMessage] roomId: {}, message: {}", roomId, chatMessage);
    }
}

채널 목록 확인

PUBSUB CHANNELS *

참고

단일 topic으로 관리하는 예제

설명

  • 위의 기본 예제에서는 topic을 동적으로 추가/삭제하여 처리하도록 되어있음
  • 채팅방이 여러개 생성될 경우 topic이 여러개 생성되고, 여러 topic의 메시지 수신은 onMessage 메소드 한군데서 처리하고 있음
  • 각 room마다 topic을 부여하여 관리하는게 의미없으므로 하나의 topic을 두고, onMessage에서 각 room으로 분기 처리하는 방향으로 개선한 예제

RedisMessageListener

public interface RedisMessageListener extends MessageListener {
    String topic();
}

RedisConfig

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

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
        RedisConnectionFactory connectionFactory,
        List<RedisMessageListener> redisMessageListeners
    ) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        redisMessageListeners.forEach(listener -> container.addMessageListener(listener, new ChannelTopic(listener.topic())));
        return container;
    }
}

ChatController

@Slf4j
@AllArgsConstructor
@RestController
public class ChatController implements RedisMessageListener {
    private final RedisTemplate<String, String> redisTemplate;
    private final ObjectMapper objectMapper;

    @Override
    public String topic() {
        return "chat";
    }

    @SneakyThrows
    @PostMapping("/api/chat")
    public void publishMessage(@RequestBody ChatMessage chatMessage) {
        redisTemplate.convertAndSend(topic(), objectMapper.writeValueAsString(chatMessage));
    }

    @SneakyThrows
    @Override
    public void onMessage(Message message, byte[] bytes) {
        String topic = new String(message.getChannel());
        ChatMessage chatMessage = objectMapper.readValue(message.getBody(), ChatMessage.class);

        log.info("[onMessage] topic: {}, message: {}", topic, chatMessage);
    }

    @Data
    public static class ChatMessage {
        private String roomId;
        private String senderId;
        private String message;
    }
}

메시지 생성

curl --location --request POST 'http://localhost:8080/api/chat' \
--header 'Content-Type: application/json' \
--data-raw '{
    "roomId": "1234",
    "senderId": "john",
    "message": "Hello World"
}'

메시지 수신

[onMessage] topic: chat, message: ChatController.ChatMessage(roomId=1234, senderId=john, message=Hello World)
반응형

'Development > Redis' 카테고리의 다른 글

[Redis] Data Type  (0) 2020.12.30
[Redis] Sentinel  (0) 2020.12.30
[Redis] Master-Slave Replication  (0) 2020.12.30
[Redis] 명령어  (0) 2019.03.11
[Redis] 스프링 연동  (0) 2019.03.10

+ Recent posts