Inter microservices invocation and event pub sub with Kafka - TheOpenCloudEngine/uEngine-cloud GitHub Wiki
microservices ๋ค ๊ฐ์ ๋ด๋ถ ํธ์ถ(invocation) ๋ฐฉ๋ฒ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์๋ค.
- ์ธ๋ถ์์ ํธ์ถ์ ํ๋ฏ์ด REST API ๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ
- FeignClient ๋ฅผ ํตํ์ฌ ๋ฐ๋ก ์๋น์ค๋ฅผ ์ ๊ทผ๋ฐฉ๋ฒ(spring-cloud)
- Publishโsubscribe pattern ์ ์ฌ์ฉํ Event Driven ๋ฐฉ๋ฒ
1๋ฒ ๋ฐฉ๋ฒ์ Spring์์ ์๋ฅผ ๋ค์๋ฉด @RestController
์ @RequestMapping
์ ์ฌ์ฉํ ํธ์ถ ๋ฐฉ๋ฒ์ด๋ค.
์ธ๋ถ ์๋น์ค๋ค์ ์ฐ๊ฒฐํ ๋ ๋ง์ด ์ฐ์ด๋ ๋ฐฉ๋ฒ์ด๊ธฐ์, ๋ด๋ถ์์คํ
๋ ๋ฌผ๋ก ์ฌ์ฉ์ด ๊ฐ๋ฅํ์ง๋ง,
๊ฐ microservices ์ ip ๋ฐ domain ์ ๊ธฐ์ตํด์ผ ํ๋ค.
2๋ฒ ๋ฐฉ๋ฒ์ @FeignClient("์๋น์ค๋ช
")
์ ์ฌ์ฉํ ๋ฐฉ๋ฒ์ธ๋ฐ,
์ฌ๊ธฐ์ ์๋น์ค๋ช
์ eureka ์ ๋ฑ๋ก์ด ๋์ด์์ด์ผํ๋ค.
์ด ๋ฐฉ๋ฒ์ ๊ธฐ์กด์ ์ค๋ช
ํ์๋ Integration with Feign Client ์ spring-cloud-netflix-feign-tutorial ์์ ์์ธํ ์ค๋ช
์ ์ฐธ๊ณ ํ ์ ์๋ค.
์ด ๋ฐฉ๋ฒ์ ํด๋น ๋ง์ดํฌ๋ก ์๋น์ค๋ฅผ ๊ฐ์ง๊ณ ์์ local ๊ฐ์ฒด์ฒ๋ผ ์ฌ์ฉ ํ ์ ์๋ ์ฅ์ ์ด ์๋ค.
์ด๋ฒ์๊ฐ์ ์ง์คํ ๋ด์ฉ์ 3๋ฒ ๋ฐฉ๋ฒ์ด๋ค.
1๋ฒ๊ณผ 2๋ฒ ๋ฐฉ๋ฒ์ ํธ์ถ ๊ธฐ๋ฐ ์ํคํ
์ฒ(Request-Driven Architecture)๋ค. ์ด์ ๊ฐ์ด ์ค๊ณ์ ์๋น์ค๋ฅผ
๊ต์ฒด์ ์ํฅ์ ๋ฏธ์น๋ ์์๊ฐ ๋ง์ ์ ๋ฐ์ ์๋ค.
3๋ฒ ๋ฐฉ๋ฒ์ ์ด๋ฒคํธ ์ค์ฌ ์ํคํ
์ฒ(EDA, Event-Driven Architecture)์ธ๋ฐ, ์ด๋ ์ด๋ฒคํธ๊ฐ ๋ฐํ(Publish)๋๊ณ ,
์ด ์ด๋ฒคํธ๊ฐ ํ์ํ ์๋น์ค์์ ์ด๋ฒคํธ๋ฅผ ์์ (subscribe)ํ์ฌ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค.
์ฌ์ด ์๋ฅผ ๋ค์๋ฉด,
์ผํ๋ชฐ์์ ๊ณ ๊ฐ์ด ๋ฌผ๊ฑด์ ์ฃผ๋ฌธ
ํ๊ณ ๋๋ฉด, ํฌ์ธํธ
,์
์
,์ถํ๋ฉ์ผ
,๋ฐฐ์ก
,์ฅ๋ฐ๊ตฌ๋
๋ฑ์ ์๋น์ค๊ฐ
์ฐ๋ฌ์ ์คํ์ ํด์ผ ํ๋ค๊ณ ๊ฐ์ ์ ํ์. ์ด๋ฅผ ํธ์ถ๊ธฐ๋ฐ ์์ผํฑ์ณ๋ก ๊ตฌํ์ ํ๋ค๋ฉด, 5๊ฐ์ ์๋น์ค์
์๋น์ค๋ช
์ด๋, doamin ๋ช
๋ฑ์ ๋ชจ๋ ์๊ณ ์์ด์ผ ํ๊ณ , ๋ชจ๋ call์ ํด์ผํ๋ค.
์ด๊ฒ์ event pub sub ์ผ๋ก ๋ณ๊ฒฝํ๋ค๋ฉด, ์ฃผ๋ฌธ
์๋น์ค์์๋ ์ด๋ฒคํธ๋ฅผ ๋ฐํ๋ง ํ๊ณ ,
๋๋จธ์ง ์๋น์ค์์๋ ์์ ๋ง ํ์ฌ, ์ด๋ฒคํธ๊ฐ ๋ฐ์ ํ ํด์ผํ ์ผ๋ง ๊ฐ์ ์๋น์ค์์ ์ค๋นํ๋ฉด ๋๋ค.
์ฌ๊ธฐ์ microservice ๋ค์ด ์ถ๊ฐ๋๋ค๊ณ ํ์ฌ๋, ๊ธฐ์กด ์์คํ
์ ๋ฏธ์น๋ ์ํฅ์ด ์ต์ํ ๋๋ค.
์ด๋ฒคํธ๋ฅผ ์ฃผ๊ณ ๋ฐ์๋ ์ฌ์ฉ๋๋ Event-Broker ์๋ Apache ActiveMQ, Apache Kafka, RabbitMQ ๋ฑ
์ฌ๋ฌ๊ฐ์ง๊ฐ ์๋ค. ์ด์ค์์ spring ์์ ๋ง์ด ์ฐ์ด๋ Kafka ๋ก ๊ตฌํ๋ฐฉ๋ฒ์ ์ค๋ช
ํ๊ฒ ๋ค.
Kafka ์ ๋ํ ์ค๋ช
๋ค์ ๋ง์ ๋ธ๋ก๊ทธ๊ฐ ์์ด์ ํด๋น ๋ธ๋ก๊ทธ ๋งํฌ๋ก ๋์ฒดํ๊ฒ ๋ค.
ํต์ฌ๊ฐ๋
์ธ topic ๊ณผ partition, Producer(publish)์ Consumer(subscribe) ์ ๋ํ ์ดํด๊ฐ ํ์ํ๋ค.
-
์ฐ์ kafka๋ฅผ ํธ์ถํ๊ธฐ ์ํ์ฌ localํ๊ฒฝ์ kafka๋ฅผ ์ค์นํ๋ค.
์นดํ์นด๋ ๋ถ์ฐํ๊ฒฝ์ธ Apache ZooKeeper ์์์ ๋์๊ฐ๊ธฐ๋๋ฌธ์ ์นดํ์นด๋ฅผ ์คํ์ ์ ZooKeeper๋ฅผ ๋จผ์ ์คํํด์ผํ๋ค.
์นดํ์นด ์ค์น ๋ฐ ์คํ ๋ฐฉ๋ฒ์ https://kafka.apache.org/quickstart ๋ฅผ ๋ฐ๋ผํ๋ฉด๋๋ค. -
์ด์ ์ฐ๋ฆฌ์ ์๋น์ค์ kafka ๋ฅผ ํตํ์ฌ ๋ฉ์ธ์ง๋ฅผ ๋ฐํํ๊ณ , ์์ ํ๋ ๋ก์ง์ ์์ฑํ์ฌ ๋ณด์.
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
์ฌ๊ธฐ์ ์ฃผ์ํ ์ ์ ํ์ฌ (2018๋
8์) spring-kafka ๋ 2.x ๋ release version ์ด ๋์์๋ค.
2.x ๋ฒ์ ์ spring-boot 2.x ๋ฒ์ ์์๋ง ๋์๊ฐ๋, ๋ง์ฝ ์๊ธฐ project๊ฐ spring-boot 1.x ๋ฅผ
์ฌ์ฉ์ค์ด๋ผ๋ฉด <version>1.3.x.RELEASE</version>
๋ก ์ค์ ์ ํด์ค์ผ ํ๋ค.
spring:
profiles: local
kafka:
bootstrap-servers: localhost:9092
@EnableKafka
public class PublishConfig {
@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<String, Object>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return props;
}
@Bean
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory(producerConfigs());
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<String, String>(producerFactory());
}
}
๊ฐ๋จํ ์ค๋ช
์ ํ์๋ฉด @EnableKafka
๋ฅผ ์ ์ธํ์ฌ Kafka์ฌ์ฉ์ spring์ ์๋ฆฌ๊ณ ,
KafkaTemplate ์ @Bean
์ผ๋ก ์ ์ธ ํ์๋ค.
@Autowired
KafkaTemplate<String, String> kafkaTemplate;
public void sendkafka(){
String topic = "topicName";
String payload = "๋ฉ์ธ์ง ๋ด์ฉ";
kafkaTemplate.send(topic, payload);
}
@EnableKafka
public class SubscribeConfig {
@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.GROUP_ID_CONFIG, "mail");
return props;
}
@Bean
public ConsumerFactory<String, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@Bean
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
PublishConfig ์ ์ค์ ์ด ๋น์ทํด ๋ณด์ด์ง๋ง ConsumerConfig ๊ฐ์ ์ง์ ํ์ฌ ์ฃผ๊ณ , GROUP_ID_CONFIG ๋ฅผ
์ง์ ํ์ฌ ์ฃผ์๋ค. Consumer ์์ GROUP_ID ๋ ๋ฐ๋์ ์ง์ ์ ํด์ค์ผ ํ๋ค.
์ด๋ ์ปจ์๋จธ ๊ทธ๋ฃน์ ํ๋์ topic์ ๋ํ ์ฑ
์์ ๊ฐ์ง๊ณ ์๊ธฐ๋๋ฌธ์ด๋ค.
๋ง์ง๋ง์ผ๋ก ํ ํฝ์ ๋ฐ๋ ๋ถ๋ถ์ด๋ค.
@KafkaListener(topics = "topicName")
public void receiveTopic(ConsumerRecord<?, ?> consumerRecord) {
System.out.println("Receiver on topic: "+consumerRecord.toString());
String data = (String)consumerRecord.value();
}