ํฌ์ŠคํŠธ

Redis - Spring ๊ณผ Redis ๊ทธ๋ฆฌ๊ณ  Message Queue

๐Ÿšฉ Spring ๊ณผ Redis ๊ทธ๋ฆฌ๊ณ  Message Queue

์ €๋ฒˆ Redis ์˜ ํฌ์ŠคํŒ…์—์„œ Local Cache ๋ฅผ ๋„˜์–ด Global Cache ์˜ Redis ์— ๊ด€ํ•ด์„œ ์ž‘์„ฑํ•˜์˜€๋‹ค.
์ด๋ฒˆ์—๋Š” Redis ์—์„œ ์ œ๊ณตํ•˜๋Š” Message Queue ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ์ž‘์„ฑํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.
์šฐ๋ฆฌ ํŒ€์€ ์ง€๊ธˆ๊นŒ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š”๋ฐ ์žˆ์–ด์„œ ํ”„๋กœ๊ทธ๋žจ๋“ค์ด ๊ฐ™์€ Local ์— ์กด์žฌํ•˜์—ฌ๋„ ์ƒํ˜ธ๊ฐ„์˜ TCP/UDP ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜์˜€๋‹ค.
๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ์—ฐ๊ฒฐ๊ด€๋ฆฌ ๋ฐ ๋ฐ์ดํ„ฐ ํŽธ๋ฆฐํ™” ๋ฐฉ์ง€ ๋“ฑ ๋ถˆํ•„์š”ํ•œ ๊ณต์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ์„œ, ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ Redis ์˜ Message Queue ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ์„ ํ•ด๋ณด๋ ค๊ณ  ๊ธ€์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

๐Ÿ“ Message Broker ( ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค ) ๋ž€ ?

Redis ์˜ Message Queue ๊ธฐ๋Šฅ์„ ์•Œ์•„๋ณด๊ธฐ ์ „์— Message Broker ์— ๋Œ€ํ•œ ๊ฐœ๋…๋ถ€ํ„ฐ ์•Œ์•„์•ผํ•œ๋‹ค.

โ–  Message Broker ์˜ ์ •์˜

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, ์‹œ์Šคํ…œ ๋ฐ ์„œ๋น„์Šค๊ฐ€ ์„œ๋กœ ๊ฐ„์— ํ†ต์‹ ํ•˜๊ณ  ์ •๋ณด๋ฅผ ๊ตํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ์†Œํ”„ํŠธ์›จ์–ด

โ–  Message Broker ์˜ ๊ตฌ์กฐ

โ–  Message Broker ์˜ ํŠน์ง•

  1. ํ”„๋กœ๊ทธ๋žจ ๊ฐ„์˜ ๋Š์Šจํ•œ ๊ฒฐํ•ฉ ( Decoupling )
    • ํ”„๋กœ๊ทธ๋žจ ๊ฐ„์˜ ์ง์ ‘์ ์ธ ์—ฐ๊ฒฐ์ด ์•„๋‹Œ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ตํ•œ ์—ฐ๊ฒฐ์ด๋ฏ€๋กœ ์ง์ ‘์ ์ธ ํ”„๋กœ๊ทธ๋žจ ๊ฐ„์˜ ์—ฐ๊ฒฐ๋ฅ ์„ ๋‚ฎ์ถฐ์ค€๋‹ค
  2. ์ „์†ก ๋ฐ์ดํ„ฐ์˜ ์•ˆ์ „ํ•œ ๋ณด๊ด€ ( Persistence and Reliability )
    • TCP/UDP ์™€ ๋‹ฌ๋ฆฌ ์ˆ˜์‹ ํ•˜๋ ค๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜์–ด ์žˆ์ง€ ์•Š๋”๋ผ๋„ ์†ก์‹ ํ•˜๋ ค๋Š” ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋ฏธ๋“ค์›จ์–ด๊นŒ์ง€์˜ ๋ฐ์ดํ„ฐ ์†ก์‹ ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  3. ๋น„๋™๊ธฐ ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ( Asynchronous communication )
    • ์†ก์‹  ํ”„๋กœ๊ทธ๋žจ๊ณผ ์ˆ˜์‹  ํ”„๋กœ๊ทธ๋žจ์ด ์„œ๋กœ์˜ ์‘๋‹ต ๋ฉ”์„ธ์ง€๋ฅผ ๊ธฐ๋‹ค๋ฆด ํ•„์š” ์—†์ด ์„œ๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ์šด์˜๋˜๋„๋ก ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. ํ™•์žฅ์˜ ์šฉ์ด ( Scalability )
    • ํ†ต์‹ ํ•˜๋ ค๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ๋งŽ์•„์ง€๋ฉด ๊ธฐ๋ณธ์ ์ธ ์„ธ์…˜์ด ๋Š˜์–ด๋‚˜์ง€๋งŒ Message Broker ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ƒˆ๋กœ์šด ํ”„๋กœ๊ทธ๋žจ๋„ Message Broker ์— ๋ถ™์ด๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋ฏ€๋กœ ํ™•์žฅ์ด ์šฉ์ดํ•˜๋‹ค.

โ–  Message Broker ์˜ ๋‹จ์ 

  1. ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ๊ฐ€๋Šฅ์„ฑ
    • ์•„๋ฌด๋ž˜๋„ ๋ฐ์ดํ„ฐ๋“ค์ด Message Broker ๋กœ ๋ชฐ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์ด ์˜ฌ ์ˆ˜ ์žˆ์ง€๋งŒ ๋‹คํ–‰์ด๋„ ์š”์ฆ˜ Message Broker ๋“ค์ด ํ™•์žฅ์„ฑ ๋ฐ ์„ฑ๋Šฅ์ด ์ข‹์•„์„œ ํ•ด๋‹น ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ๋  ํ™•๋ฅ ์€ ๋งค์šฐ ์ ๋‹ค.
  2. ์šด์˜ ๋ณต์žก๋„ ๋ถ€๊ฐ€
    • ์šด์˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•ด์•ผ๋  ํ”„๋กœ๊ทธ๋žจ์ด ํ•˜๋‚˜ ๋Š˜์–ด๋‚˜๊ณ  ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์ด ํ•ต์‹ฌ์ ์ธ ์š”์†Œ๋ฅผ ์ฐจ์ง€ํ•˜๊ธฐ์— ์šด์˜์š”์†Œ๊ฐ€ ์ฆ๊ฐ€ํ•จ์—๋Š” ์–ด์ฉ”์ˆ˜๊ฐ€ ์—†๋‹ค.


์ด์ƒ์œผ๋กœ Message Broker ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ๊ฐœ๋…์„ ์•Œ์•„๋ณด์•˜๋‹ค.
๋‹ค์Œ์œผ๋กœ๋Š” Redis ์˜ Message Queue ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ์ž‘์„ฑํ•ด๋ณด๊ฒ ๋‹ค.

๐Ÿ“ Redis - Message Queue ( Publish / Subscribe ) ๋ž€ ?

  • Redis ์—์„œ Global Cache & Queue ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋œ ์ตœ์†Œํ™”๋œ Message Broker
  • ๋‹ค๋ฅธ Message Broker ์™€ ๋‹ค๋ฅด๊ฒŒ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฉ”์„ธ์ง€๋ฅผ ์ „๋‹ฌํ•œ ํ›„ ๋ฐ”๋กœ ์‚ญ์ œ๋œ๋‹ค.

๐Ÿ“ Redis - Message Queue ๊ตฌ์กฐ

๐Ÿ“ Spring Boot & Redis - ๊ธฐ๋ณธ ์—ฐ๋™

์ด์ œ๋ถ€ํ„ฐ Spring Boot & Redis ์˜ ๊ธฐ๋ณธ์ ์ธ ์—ฐ๋™ ๋ฐฉ๋ฒ•์„ ์ž‘์„ฑํ•ด๋ณด๋ ค๊ณ ํ•œ๋‹ค. Redis ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์žˆ์–ด์„œ Template ๋ฐฉ์‹๊ณผ Repository ๋ฐฉ์‹์ด ์žˆ๋Š”๋ฐ Repository ๋ฐฉ์‹์„ ์„ ํƒํ•˜์—ฌ Global Cache ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

1. Pom.xml

1
2
3
4
5
<!--        Netty Dependency ํฌํ•จ-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. application.yml

1
2
3
4
5
6
7
8
spring:
  redis:
    host: # Redis Server IP
    port: # Redis Server Port
    publisher:
      topic: #publisher topic
    subscriber:
      topic: #subscriber topic

3. Spring Boot Main Class

Spring Boot Main Class Annotation ์—์„œ Redis Repository ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•œ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootApplication
@ImportResource({"classpath:/egovframework/springmvc/dispatcher-servlet.xml", "classpath*:/egovframework/spring/context-*.xml"})
@Import(EgovBootInitialization.class)
@ComponentScan(basePackages = {"๊ธฐ๋ณธ Package ๊ฒฝ๋กœ"})
@EnableRedisRepositories(basePackages = {"Redis Repository Package ๊ฒฝ๋กœ"})
@EnableJpaAuditing
public class EgovBootApplication {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(EgovBootApplication.class);
        springApplication.setBannerMode(Banner.Mode.OFF);
        springApplication.setLogStartupInfo(false);
        springApplication.run(args);
    }

}

4. Domain Class

Spring - Data - JPA ์™€ ๋™์ผํ•˜๊ฒŒ Repository ์—์„œ ์‚ฌ์šฉํ•  Domain Class ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@RedisHash(value = "user_auth_info")
@ToString
public class UserAuthInfo {
    @Id
    private String userId;

    @Indexed
    private String index;

    private String value;

    @TimeToLive
    private long ttl;
}

5. Repsitory Interface

Spring - Data - JPA ์™€ ๋™์ผํ•˜๊ฒŒ Repository Interface ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

1
2
3
public interface UserAuthInfoRepository extends CrudRepository<UserAuthInfo, String> {
  Optional<UserAuthInfo> findByIndex(String index);
}
  • Repository ์˜ ์‚ฌ์šฉ๋ฒ•์€ Spring-Data-JPA ์™€ ๋งค์šฐ ํก์‚ฌํ•˜๋‚˜ ์›ํ•˜๋Š” ํ•„๋“œ๋กœ ๊ฒ€์ƒ‰์„ ํ•˜๋ ค๋ฉด ํ•ด๋‹น ํ•„๋“œ์— @Indexed Annotation ์„ ๋ถ™์—ฌ์ค˜์•ผํ•œ๋‹ค.
  • Repository ์—์„œ ์›ํ•˜๋Š” ํ•„๋“œ๋กœ ๊ฒ€์ƒ‰ํ•˜๋Š” Method ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

๐Ÿ“ Spring Boot & Redis - Message Queue ๊ธฐ๋Šฅ ๊ตฌํ˜„

๋‹ค์Œ์œผ๋กœ๋Š” Redis ๋ฅผ ํ™œ์šฉํ•œ Message Queue ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๊ตฌํ˜„์ด๋‹ค. ๊ฐ„๋‹จํ•˜๊ฒŒ Pub / Sub ๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋๋‚œ๋‹ค.

1. Publisher Class

Pub ๋Š” ์–ด๋ ค์šด ๊ฒƒ๋„ ์—†๋‹ค. ํ•ด๋‹น Class ๋ฅผ Service Layer ์—์„œ DI ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
@RequiredArgsConstructor
public class Publisher {
    private static final Logger syncLogger = LoggerFactory.getLogger(Publisher.class);
    private static final Logger asyncLogger = LoggerFactory.getLogger("asyncLogger");


    @Value("${spring.redis.publisher.topic}")
    private String topic;
    private final StringRedisTemplate redisTemplate;

    public void publish(String message) {
        redisTemplate.convertAndSend(topic, message);
    }

}

2. Subscriber Class

Sub ํ•  ๋•Œ ์ฒ˜๋ฆฌ๋œ Handler Class ์ด๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@RequiredArgsConstructor
public class Subscriber implements MessageListener {
  private static final Logger syncLogger = LoggerFactory.getLogger(Subscriber.class);
  private static final Logger asyncLogger = LoggerFactory.getLogger("asyncLogger");

  private final ISubscriberService subscriberService;
  private final StringRedisTemplate redisTemplate;

  @Override
  public void onMessage(Message message, byte[] pattern) {
    String publishMessage = redisTemplate.getStringSerializer().deserialize(message.getBody());

    try {
      subscriberService.process(publishMessage);
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e);
    }
  }
}

3. Subscriber Config Class

Sub ๋Š” Message ๋ฅผ ์ˆ˜์‹ ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ Adapter ์™€ Container ๋ฅผ ์„ค์ •ํ•ด์•ผํ•œ๋‹ค. ์„ค์ •๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
@RequiredArgsConstructor
public class SubscriberConfig {
    private static final Logger syncLogger = LoggerFactory.getLogger(UserInfoController.class);
    private static final Logger asyncLogger = LoggerFactory.getLogger("asyncLogger");


    @Value("${spring.redis.subscriber.topic}")
    private String topic;
    private final Subscriber subscriber;

    @Bean
    public MessageListenerAdapter messageListenerAdapter(Subscriber subscriber) {
        return new MessageListenerAdapter(subscriber);
    }

    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter messageListenerAdapter) {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(connectionFactory);
        redisMessageListenerContainer.addMessageListener(messageListenerAdapter, new PatternTopic(topic));

        return redisMessageListenerContainer;
    }

}

๐Ÿšฉ ๋งˆ์น˜๋ฉฐ..

์šฐ๋ฆฌํŒ€์—์„œ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•œ Redis ์˜ ๊ด€ํ•œ ๊ธฐ๋ณธ ์ง€์‹์€ ๋ชจ๋‘ ์ •๋ฆฌํ•œ ๊ฒƒ ๊ฐ™๋‹ค. ์กฐ๊ธˆ ๋” ์‹ฌํ™”ํ•œ๋‹ค๋ฉด Message Broker ์˜ ์‹ฌํ™” ๊ฐœ๋…์ธ Kafka ์™€ RabbitMQ ์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

์ด ๊ธฐ์‚ฌ๋Š” ์ €์ž‘๊ถŒ์ž์˜ CC BY 4.0 ๋ผ์ด์„ผ์Šค๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.