๐Ÿ”ฅํ”„๋กœ์ ํŠธ๐Ÿ”ฅ

~ 6/17 ํ•œ์ด์Œ ํ”„๋กœ์ ํŠธ ๊ณต๋ชจ์ „ sprint 1๐Ÿ”ฅ

jmboy 2024. 6. 17. 15:15

Kafka

  • Spring ์—์„œ Kafka๋ฅผ ์‚ฌ์šฉํ•˜๋ ค ํ•˜๋‹ค ๋ณด๋‹ˆ, ๊ด€๋ จ ์„ค์ •๋“ค์„ ํ•ด์ฃผ์–ด์•ผ ํ–ˆ๋‹ค. ํ•ด๋‹น ์„ค์ •๋“ค์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค!

ConsumerConfig

package com.hanium.diarist.common.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.listener.ContainerProperties;
import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer;
import org.springframework.kafka.support.serializer.JsonDeserializer;

import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

    @Value("${kafka.bootstrap-servers}")
    private String bootstrapServers;

    @Bean
    public ConsumerFactory<String, Object> consumerFactory() {
        Map<String, Object> config = new HashMap<>();
        config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        config.put(ConsumerConfig.GROUP_ID_CONFIG, "diary");
        config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
        config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
        config.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, JsonDeserializer.class.getName());
        config.put(JsonDeserializer.TRUSTED_PACKAGES, "*");
        config.put(JsonDeserializer.VALUE_DEFAULT_TYPE, "java.util.HashMap");

        return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(),
                new ErrorHandlingDeserializer<>(new JsonDeserializer<>()));
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
        return factory;
    }
}

  • Consumer ์— ๋Œ€ํ•œ ์„ค์ •์„ map์„ ํ†ตํ•ด ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๊ทธ๋ฃนID, serializer ๋ฐฉ์‹, ๋“ฑ๋“ฑ์˜ ์„ค์ •์„ ConsumerFactory๋ฅผ ํ†ตํ•ด ํ•˜์˜€๋‹ค.

ProducerConfig

package com.hanium.diarist.common.config;

import com.hanium.diarist.domain.diary.exception.KafkaConnectException;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.ProducerListener;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

@Configuration
public class KafkaProducerConfig {

    @Value("${kafka.bootstrap-servers}")
    private String bootstrapServers;

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        return new DefaultKafkaProducerFactory<>(configProps);
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        KafkaTemplate<String, String> kafkaTemplate = new KafkaTemplate<>(producerFactory());
        kafkaTemplate.setProducerListener(new ProducerListener<String, String>() {
            @Override
            public void onSuccess(ProducerRecord<String, String> producerRecord, RecordMetadata recordMetadata) {
                ProducerListener.super.onSuccess(producerRecord, recordMetadata);
            }

            @Override
            public void onError(ProducerRecord<String, String> producerRecord, RecordMetadata recordMetadata, Exception exception) {
                if (exception instanceof TimeoutException) {
                    throw new KafkaConnectException();
                }
        }

});
        return kafkaTemplate;
    }
}

  • Producer ์— ๋Œ€ํ•œ ์„ค์ •์œผ๋กœ, bootstrap-server, ์ง๋ ฌํ™” ๋ฐฉ์‹๋“ฑ์— ๋Œ€ํ•œ ์„ค์ •์„ ํ•ด์ฃผ์—ˆ๋‹ค.
  • ์ดํ›„ KafkaTemplate๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ํ…œํ”Œ๋ฆฟ์„ ์„ค์ •ํ•˜์˜€๋‹ค.

ProducerService

package com.hanium.diarist.domain.diary.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hanium.diarist.domain.diary.domain.Diary;
import com.hanium.diarist.domain.diary.dto.CreateDiaryRequest;
import com.hanium.diarist.domain.diary.exception.JsonProcessException;
import com.hanium.diarist.domain.diary.exception.KafkaConnectException;
import com.hanium.diarist.domain.diary.repository.DiaryRepository;
import com.hanium.diarist.domain.user.domain.User;
import com.hanium.diarist.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaProducerException;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;

@Service
@Slf4j
@RequiredArgsConstructor
public class CreateDiaryProducerService {

    private final KafkaTemplate<String,String> kafkaTemplate;
    private final ObjectMapper objectMapper;
    private final DiaryRepository diaryRepository;
    private final UserRepository userRepository;
    private final String CreateTopicName = "create-diary";
    private final String reCreateTopicName = "re-create-diary";

    public boolean sendCreateDiaryMessageWithAd(CreateDiaryRequest createDiaryRequest) {
        LocalDate date = createDiaryRequest.getDiaryDate();// ์ผ๊ธฐ ์ž‘์„ฑ ๋‚ ์งœ ๊ฐ€์ ธ์˜ด
        User user = userRepository.findById(createDiaryRequest.getUserId()).orElseThrow();// ํ•ด๋‹น ์œ ์ € ์ฐพ์Œ
        Optional<Diary> existingDiary = diaryRepository.findByUserAndDiaryDate(user, date);// ํ•ด๋‹น ์œ ์ €์˜ ์ผ๊ธฐ ์ž‘์„ฑ ๋‚ ์งœ์— ์ผ๊ธฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ

        try{
            String message = objectMapper.writeValueAsString(createDiaryRequest);
            if (existingDiary.isPresent()|| !date.isEqual(LocalDate.now())) { // ๋‹น์‹œ ๋‚ ์งœ์˜ ์ผ๊ธฐ๊ฐ€ ์žˆ๊ฑฐ๋‚˜, ๊ณผ๊ฑฐ์˜ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•  ๊ฒฝ์šฐ
                // ๊ด‘๊ณ  ์‹œ์ฒญ ํ•„์ˆ˜
                sendKafkaMessage(reCreateTopicName, message);
                watchAd();
                return true;
            }else{// ๊ณผ๊ฑฐ ๋‚ ์งœ ์ผ๊ธฐ ์ž‘์„ฑ
                 sendKafkaMessage(CreateTopicName, message);
                 return false;
            }
        } catch (KafkaProducerException e){
            throw new KafkaConnectException();
        } catch (JsonProcessingException e) {
            throw new JsonProcessException();
        }
    }

    private void sendKafkaMessage(String topic,String message) {
        CompletableFuture<SendResult<String, String>> future = kafkaTemplate.send(topic, message);
        future.thenAccept(result -> {
            // ์„ฑ๊ณต ์‹œ ๋กœ์ง
            log.info("Message sent successfully to topic {} with offset {}", result.getRecordMetadata().topic(), result.getRecordMetadata().offset());
        }).exceptionally(ex -> {
            // ์‹คํŒจ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
            if (ex.getCause() instanceof TimeoutException) {
                throw new KafkaConnectException();
            }
            return null;
        });
    }

    @Async
    public void watchAd() {
        System.out.println("๊ด‘๊ณ  ์‹œ์ฒญ ์ค‘");
    }

}

  • ๋‚ ์งœ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์กฐ๊ฑด ( ์˜ค๋Š˜ ์ฒซ ์ž‘์„ฑ, ์˜ค๋Š˜ ์žฌ ์ž‘์„ฑ, ๊ณผ๊ฑฐ ์ผ๊ธฐ ์ž‘์„ฑ) ์— ๋”ฐ๋ผ ๋กœ์ง์ด ๋‹ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์— ํ† ํ”ฝ์„ ๋‚˜๋ˆ„์–ด์ฃผ์—ˆ๋‹ค.
  • ์ผ๊ธฐ๋ฅผ ๋ณด๋‚ผ ๊ฒฝ์šฐ ์นดํ”„์นด ๋ธŒ๋กœ์ปค์— ๋“ค์–ด๊ฐ€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค!

ConsumerService

package com.hanium.diarist.domain.diary.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hanium.diarist.domain.diary.domain.Diary;
import com.hanium.diarist.domain.diary.dto.CreateDiaryResponse;
import com.hanium.diarist.domain.diary.exception.DiaryIdInvalidException;
import com.hanium.diarist.domain.diary.exception.DiaryNotFoundException;
import com.hanium.diarist.domain.diary.exception.JsonProcessException;
import com.hanium.diarist.domain.diary.exception.SseException;
import com.hanium.diarist.domain.diary.repository.DiaryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;

@Service
@RequiredArgsConstructor
public class CreateDiaryConsumerService {

    private final DiaryRepository diaryRepository;
    private final ObjectMapper objectMapper;
    private final CopyOnWriteArrayList<SseEmitter> emitters = new CopyOnWriteArrayList<>();// ํด๋ผ์ด์–ธํŠธ์™€ sse ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฆฌ์ŠคํŠธ

    // ํด๋ผ์ด์–ธํŠธ๊ฐ€ sse ์—ฐ๊ฒฐ์„ ์š”์ฒญํ•  ๋•Œ ์ƒˆ๋กœ์šด sseEmitter๋ฅผ ์ƒ์„ฑํ•ด ๋ฐ˜ํ™˜ํ•จ
    public SseEmitter addEmitter() {
        SseEmitter emitter = new SseEmitter(60000L);// ํƒ€์ž„์•„์›ƒ ์‹œ๊ฐ„ 60์ดˆ
        this.emitters.add(emitter);
        emitter.onCompletion(() -> this.emitters.remove(emitter));// ์—ฐ๊ฒฐ ์™„๋ฃŒ์‹œ ๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๊ฑฐ
        emitter.onTimeout(() -> this.emitters.remove(emitter));// ํƒ€์ž„์•„์›ƒ์‹œ ๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๊ฑฐ
        emitter.onError(e -> this.emitters.remove(emitter));// ์—๋Ÿฌ์‹œ ๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๊ฑฐ

        return emitter;
    }

    // ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฉ”์„ธ์ง€๋ฅผ ์ „์†กํ•จ.
    private void sendEmitters(CreateDiaryResponse diaryResponse){
        for (SseEmitter emitter : emitters) {
            try {
                String jsonResponse = objectMapper.writeValueAsString(diaryResponse);
                //System.out.println("Sending to emitter: " + jsonResponse);
                emitter.send(SseEmitter.event().data(jsonResponse));
            }catch (JsonProcessingException e) {
                throw new JsonProcessException();
            } catch (IOException e) {
                this.emitters.remove(emitter);
                throw new SseException();
            }
        }
    }

    // kafka ์˜ create-diary-response ํ† ํ”ฝ์—์„œ ๋ฉ”์„ธ์ง€๋ฅผ ์ˆ˜์‹ ํ•ด์„œ ์ฒ˜๋ฆฌํ•จ
    @KafkaListener(topics = "create-diary-response", groupId = "diary")
    public void consumeCreateDiaryResponse(HashMap<String,Integer> message) {
        try {
            long diaryId = Long.parseLong(String.valueOf(message.get("diaryId")));
            Diary diary = diaryRepository.findByDiaryIdWithDetails(diaryId).orElseThrow(() -> new DiaryNotFoundException());
            CreateDiaryResponse createDiaryResponse = new CreateDiaryResponse(diary.getEmotion().getEmotionName(), diary.getEmotion().getEmotionPicture(), diary.getContent(), diary.getArtist().getArtistName(), diary.getArtist().getArtistPicture(), diary.getDiaryDate(), diary.getImage().getImageUrl());
//            System.out.println(createDiaryResponse);
            sendEmitters(createDiaryResponse);
        } catch (NumberFormatException e) {
            throw new DiaryIdInvalidException();
        }
    }

}

  • ์ปจ์Šˆ๋จธ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ํ•œ๊ฐ€์ง€ ์ƒ๊ฐํ•ด๋ณด์•ผ ํ•  ์ผ์ด ์žˆ์—ˆ๋‹ค.
graph LR
	a[app] --> b[server]
	b --> c([kafka])
	c--> d[model]
	d-->c
	c-->b

	
  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด app→ ์„œ๋ฒ„๋กœ ์š”์ฒญ, ์„œ๋ฒ„์—์„œ ์นดํ”„์นด , ๋ชจ๋ธ์—์„œ ์นดํ”„์นด์˜ ๋ฉ”์„ธ์ง€๋ฅผ ๊ตฌ๋…ํ•ด์„œ ์ฝ์–ด์˜จ๋‹ค.
  • ์ดํ›„ ๋ชจ๋ธ์—์„œ ์นดํ”„์นด๋กœ ๋ฉ”์„ธ์ง€ํ๋ฅผ ๋ณด๋‚ด๊ณ  ์„œ๋ฒ„๊ฐ€ ํ•ด๋‹น ๋ฉ”์„ธ์ง€๋ฅผ ๊ตฌ๋…์„ ํ•œ๋‹ค๋Š” ๊ฐœ๋…๊นŒ์ง€๋Š” ๊ฐ€๋Šฅํ•˜์˜€๋‹ค.

<aside> ๐Ÿ’ก ๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ์„œ๋ฒ„์—์„œ app์œผ๋กœ ์ผ๊ธฐ๊ฐ€ ์ž‘์„ฑ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ์„๊นŒ?

</aside>

  • ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ์ƒ๊ฐํ•ด๋ณผ ํ•„์š”๊ฐ€ ์žˆ์—ˆ๋‹ค.

→ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋ช‡๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋‹ค

1. ์›น์†Œ์ผ“ (WebSocket)

  • ์žฅ์ :
    • ์–‘๋ฐฉํ–ฅ ํ†ต์‹ : ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ ์ง€์†์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ €์ง€์—ฐ: ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ์— ์ ํ•ฉํ•˜๋ฉฐ, ์ฆ‰๊ฐ์ ์ธ ์ƒํ˜ธ์ž‘์šฉ์ด ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ๋กœ ๋„๋ฆฌ ์ง€์›๋ฉ๋‹ˆ๋‹ค.
  • ๋‹จ์ :
    • ์—ฐ๊ฒฐ ์œ ์ง€ ๊ด€๋ฆฌ: ์„œ๋ฒ„๋Š” ๊ฐ ํด๋ผ์ด์–ธํŠธ์™€ ์ง€์†์ ์œผ๋กœ ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•ด์•ผ ํ•˜๋ฉฐ, ์ด๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์ƒ๋‹นํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์Šค์ผ€์ผ๋ง ๋ฌธ์ œ: ๋งŽ์€ ์ˆ˜์˜ ๋™์‹œ ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ถ”๊ฐ€ ์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜์™€ ๋ณต์žก์„ฑ์ด ์š”๊ตฌ๋ฉ๋‹ˆ๋‹ค.

2. ์„œ๋ฒ„-์ „์†ก ์ด๋ฒคํŠธ (SSE)

  • ์žฅ์ :
    • ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ์˜ ๋‹จ๋ฐฉํ–ฅ ํ†ต์‹ : ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘ธ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ €๋ ดํ•œ ์œ ์ง€ ๋น„์šฉ: ์›น์†Œ์ผ“์— ๋น„ํ•ด ์„œ๋ฒ„ ๋ถ€๋‹ด์ด ์ ์Šต๋‹ˆ๋‹ค.
    • ์ž๋™ ์žฌ์—ฐ๊ฒฐ ๋ฐ ๊ฐ„๋‹จํ•œ ๋ฉ”์‹œ์ง€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ.
  • ๋‹จ์ :
    • ๋‹จ๋ฐฉํ–ฅ ํ†ต์‹ ๋งŒ ๊ฐ€๋Šฅ: ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ๋Š” ์ง์ ‘์ ์ธ ๋ฉ”์‹œ์ง€ ์ „์†ก์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
    • ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ: ์ผ๋ถ€ ์˜ค๋ž˜๋œ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ง€์›๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ํŒŒ์ด์–ด๋ฒ ์ด์Šค ํด๋ผ์šฐ๋“œ ๋ฉ”์‹œ์ง• (FCM)

  • ์žฅ์ :
    • ๊ด‘๋ฒ”์œ„ํ•œ ๊ธฐ๊ธฐ ์ง€์›: ๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ์™€ ๋ฐ์Šคํฌํ†ฑ์„ ํฌํ•จํ•ด ๋‹ค์–‘ํ•œ ํ”Œ๋žซํผ์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
    • ๋†’์€ ํ™•์žฅ์„ฑ ๋ฐ ์‹ ๋ขฐ์„ฑ: ๊ตฌ๊ธ€์˜ ์ธํ”„๋ผ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ๋ฉ”์‹œ์ง€๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์˜คํ”„๋ผ์ธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ: ๊ธฐ๊ธฐ๊ฐ€ ์˜คํ”„๋ผ์ธ์ผ ๋•Œ ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅํ–ˆ๋‹ค๊ฐ€ ๊ธฐ๊ธฐ๊ฐ€ ์˜จ๋ผ์ธ ์ƒํƒœ๊ฐ€ ๋˜๋ฉด ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹จ์ :
    • ์™ธ๋ถ€ ์„œ๋น„์Šค ์˜์กด์„ฑ: FCM์€ ๊ตฌ๊ธ€์˜ ์„œ๋น„์Šค์ด๋ฏ€๋กœ ์ด๋ฅผ ํ†ตํ•œ ๋ฉ”์‹œ์ง€ ์ „์†ก์€ ๊ตฌ๊ธ€์˜ ์ •์ฑ…๊ณผ ์„œ๋น„์Šค ์ƒํƒœ์— ํฌ๊ฒŒ ์˜์กดํ•ฉ๋‹ˆ๋‹ค.
    • ์ถ”๊ฐ€ ๋น„์šฉ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ: ๋ฌด๋ฃŒ ์‚ฌ์šฉ๋Ÿ‰์„ ์ดˆ๊ณผํ•  ๊ฒฝ์šฐ ๋น„์šฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ตœ์ ์˜ ์„ ํƒ

  • ์›น์†Œ์ผ“์€ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž์˜ ์‹ค์‹œ๊ฐ„ ์ƒํ˜ธ์ž‘์šฉ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ ๊ฐ€์žฅ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
  • SSE๋Š” ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ์ฃผ๊ธฐ์ ์ด๊ฑฐ๋‚˜ ์ง€์†์ ์ธ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์ด ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•˜๋ฉฐ, ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ์ด ๋œํ•ฉ๋‹ˆ๋‹ค.
  • FCM์€ ๋‹ค์–‘ํ•œ ๊ธฐ๊ธฐ์™€ ํ”Œ๋žซํผ ๊ฐ„์˜ ๋ฉ”์‹œ์ง€ ์ „์†ก์ด ์ค‘์š”ํ•˜๊ณ , ์™ธ๋ถ€ ์„œ๋น„์Šค์— ์˜์กดํ•˜๋Š” ๊ฒƒ์ด ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

์นดํ”„์นด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†กํ•˜๋Š” ๊ฒฝ์šฐ, ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ๊ฐ„์˜ ํ†ต์‹  ๋ฐฉ์‹, ์ง€์›ํ•ด์•ผ ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์œ ํ˜•, ํ•„์š”ํ•œ ์ธํ”„๋ผ ๋“ฑ์„ ๊ณ ๋ คํ•ด ๊ฐ€์žฅ ์ ํ•ฉํ•œ ๊ธฐ์ˆ ์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋†’์€ ์‹ค์‹œ๊ฐ„ ๋ฐ˜์‘์„ฑ๊ณผ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์›น์†Œ์ผ“์„, ๋น„๊ต์  ๊ฐ„๋‹จํ•œ ์•Œ๋ฆผ์ด๋‚˜ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ์ „์†ก์—๋Š” SSE๋‚˜ FCM์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋‹ค์Œ์˜ ์ด์œ ๋ฅผ ๊ณ ๋ คํ•ด SSE๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ SSE๋กœ ์—ฐ๊ฒฐ์„ ์š”์ฒญํ•œ๋‹ค. ( ํƒ€์ž„์•„์›ƒ์‹œ๊ฐ„ 1๋ถ„ )
  2. ํ•ด๋‹น 1๋ถ„ ๋‚ด์— ์นดํ”„์นด์˜ ์ปจ์Šˆ๋จธ๋กœ ์ผ๊ธฐ ์ž‘์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ๋ฉ”์„ธ์ง€๋ฅผ ์ฝ์œผ๋ฉด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค,.
  3. ์ผ๊ธฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ SSE๋กœ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†กํ•œ๋‹ค.
  • ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋กœ์ง์˜ ์ˆœ์„œ๋กœ ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๊นŒ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†ก ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.