๐Ÿš€ Apache Flink: ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ์˜ ๋ํŒ์™•! ๐Ÿš€

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Apache Flink: ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ์˜ ๋ํŒ์™•! ๐Ÿš€

 

 

์•ˆ๋…•ํ•˜์„ธ์š”, ์—ฌ๋Ÿฌ๋ถ„! ์˜ค๋Š˜์€ ์ •๋ง ํ•ซํ•œ ์ฃผ์ œ๋กœ ์ฐพ์•„์™”์–ด์š”. ๋ฐ”๋กœ Apache Flink! ์ด ๋…€์„, ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ ๊ตฌ์ถ•์˜ ์ ˆ๋Œ€๊ฐ•์ž๋ผ๊ณ  ํ•ด๋„ ๊ณผ์–ธ์ด ์•„๋‹ˆ์ฃ . ใ…‹ใ…‹ใ…‹ ์—ฌ๋Ÿฌ๋ถ„, ์ค€๋น„๋˜์…จ๋‚˜์š”? ์ง€๊ธˆ๋ถ€ํ„ฐ Flink์˜ ์„ธ๊ณ„๋กœ ํ’๋ฉ~ ๋น ์ ธ๋ณผ๊ฒŒ์š”! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ’ก ์ž ๊น! ์•Œ๊ณ  ๊ฐ€์„ธ์š”!

Apache Flink๋Š” ๋น…๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์˜ ํ˜๋ช…์„ ์ผ์œผํ‚ค๊ณ  ์žˆ๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์˜ˆ์š”. ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—„์ฒญ๋‚œ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์„œ, ์š”์ฆ˜ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ํ™์ˆ˜ ์‹œ๋Œ€์— ๊ผญ ํ•„์š”ํ•œ ๋…€์„์ด์ฃ !

๐ŸŒŠ Flink, ๋„ˆ ๋„๋Œ€์ฒด ๋ญ๋‹ˆ?

Flink๋Š” ๋…์ผ์–ด๋กœ '์žฌ๋น ๋ฅด๋‹ค'๋Š” ๋œป์ด์—์š”. ์ด๋ฆ„๋ถ€ํ„ฐ ๋ฒŒ์จ ์Šคํ”ผ๋“œ๊ฐ€ ๋Š๊ปด์ง€์ง€ ์•Š๋‚˜์š”? ใ…‹ใ…‹ Apache Flink๋Š” ๋ถ„์‚ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์—”์ง„์ด์—์š”. ์‰ฝ๊ฒŒ ๋งํ•ด์„œ, ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์Šˆํผ ํŒŒ์›Œ๋ฅผ ๊ฐ€์ง„ ๋…€์„์ด์ฃ !

์—ฌ๋Ÿฌ๋ถ„, ํ˜น์‹œ ๋„ทํ”Œ๋ฆญ์Šค์—์„œ ์˜ํ™” ๋ณด๋‹ค๊ฐ€ "์ด ์˜ํ™” ์žฌ๋ฐŒ๋„ค~ ๋น„์Šทํ•œ ๊ฑฐ ์—†๋‚˜?" ํ•˜๊ณ  ์ถ”์ฒœ ๋ฐ›์•„๋ณธ ์  ์žˆ๋‚˜์š”? ๊ทธ๊ฒŒ ๋ฐ”๋กœ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์˜ ํ•œ ์˜ˆ์—์š”! Flink ๊ฐ™์€ ์‹œ์Šคํ…œ์ด ์—ฌ๋Ÿฌ๋ถ„์˜ ์ทจํ–ฅ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ถ„์„ํ•ด์„œ ์ถ”์ฒœํ•ด์ฃผ๋Š” ๊ฑฐ์ฃ . ๋Œ€๋ฐ• ใ„ทใ„ทใ„ท

Apache Flink ๊ฐœ๋…๋„ Apache Flink ๋ฐ์ดํ„ฐ ์ž…๋ ฅ ์ฒ˜๋ฆฌ๋œ ๊ฒฐ๊ณผ ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ

์œ„์˜ ๊ทธ๋ฆผ์„ ๋ณด์„ธ์š”. Flink๋Š” ๋งˆ์น˜ ๊ฑฐ๋Œ€ํ•œ ๋…น์ƒ‰ ์›์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ๋ฅผ ๋นจ์•„๋“ค์ด๊ณ  ์ฒ˜๋ฆฌํ•ด์„œ ์œ ์šฉํ•œ ์ •๋ณด๋กœ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๊ฑฐ์˜ˆ์š”. ๋ฉ‹์ง€์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜Ž

๐Ÿค” ๊ทผ๋ฐ ์™œ Flink๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ํŠน๋ณ„ํ•œ๋ฐ?

์ž, ์ด์ œ Flink๊ฐ€ ์™œ ๊ทธ๋ ‡๊ฒŒ ํŠน๋ณ„ํ•œ์ง€ ์ž์„ธํžˆ ์•Œ์•„๋ณผ๊นŒ์š”? ์—ฌ๋Ÿฌ๋ถ„, ๋ฒจํŠธ ๋งค์„ธ์š”. ์ง€๊ธˆ๋ถ€ํ„ฐ Flink์˜ ๋งค๋ ฅ ํฌ์ธํŠธ๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์˜ˆ์š”!

  • ์ดˆ๊ณ ์† ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ: Flink๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๊ด‘์†์ด์—์š”! ๐Ÿš€ ์ดˆ๋‹น ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋‹ˆ, ์ƒ์ƒ์ด ๊ฐ€๋‚˜์š”?
  • ์ •ํ™•์„ฑ ๋ณด์žฅ: "Exactly-once" ์ฒ˜๋ฆฌ๋ฅผ ์ง€์›ํ•ด์„œ ๋ฐ์ดํ„ฐ ์†์‹ค์ด๋‚˜ ์ค‘๋ณต ์—†์ด ์ •ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์š”. ์™„๋ฒฝ์ฃผ์˜์ž๋“ค์˜ ์ตœ์•  ๊ธฐ๋Šฅ์ด์ฃ ! โœจ
  • ์ƒํƒœ ๊ด€๋ฆฌ: ๋ณต์žกํ•œ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๊ฒŒ ๋ฌด์Šจ ๋ง์ด๋ƒ๊ณ ์š”? ์‰ฝ๊ฒŒ ๋งํ•ด, ๊ณผ๊ฑฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์˜ˆ์š”!
  • ๋‹ค์–‘ํ•œ ์‹œ๊ฐ„ ๊ฐœ๋… ์ง€์›: ์ด๋ฒคํŠธ ์‹œ๊ฐ„, ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ๋“ฑ ๋‹ค์–‘ํ•œ ์‹œ๊ฐ„ ๊ฐœ๋…์„ ์ง€์›ํ•ด์š”. ์‹œ๊ฐ„ ์—ฌํ–‰๋„ ๊ฐ€๋Šฅํ•˜๋‹ค๊ตฌ์š”? ใ…‹ใ…‹ใ…‹
  • ํ’๋ถ€ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ์†Œ์Šค์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ์ปค๋„ฅํ„ฐ, ๋ณต์žกํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋“ฑ์ด ์žˆ์–ด์š”. ๋งˆ์น˜ ๋ ˆ๊ณ  ๋ธ”๋ก์ฒ˜๋Ÿผ ์กฐ๋ฆฝํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ฃ !

์™€์šฐ! ์ด ์ •๋„๋ฉด Flink๊ฐ€ ์™œ ์ธ๊ธฐ ์žˆ๋Š”์ง€ ์•Œ ๊ฒƒ ๊ฐ™์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜‰

๐Ÿ› ๏ธ Flink๋กœ ๋ญ˜ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?

์ž, ์ด์ œ Flink๋กœ ๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ์š”. ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ์ƒ๋ ฅ์„ ์ž๊ทนํ•ด๋ณผ ์‹œ๊ฐ„์ด์—์š”!

๐ŸŒŸ ์‹ค์‹œ๊ฐ„ ์ถ”์ฒœ ์‹œ์Šคํ…œ

์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ์—์„œ ๊ณ ๊ฐ์˜ ํ–‰๋™์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ถ„์„ํ•ด์„œ ๋งž์ถคํ˜• ์ƒํ’ˆ์„ ์ถ”์ฒœํ•  ์ˆ˜ ์žˆ์–ด์š”. "์ด ์ƒํ’ˆ์„ ๋ณธ ์‚ฌ๋žŒ๋“ค์ด ์ด๋Ÿฐ ๊ฒƒ๋„ ๋ดค์–ด์š”~" ์ด๋Ÿฐ ๊ฑฐ ๋งŽ์ด ๋ณด์…จ์ฃ ? ๊ทธ๊ฒŒ ๋ฐ”๋กœ Flink์˜ ํž˜์ด์—์š”!

๐Ÿšจ ์‹ค์‹œ๊ฐ„ ์‚ฌ๊ธฐ ํƒ์ง€

๊ธˆ์œต ๊ฑฐ๋ž˜์—์„œ ์ด์ƒํ•œ ํŒจํ„ด์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋ˆ„๊ตฐ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์นด๋“œ๋กœ ์ด์ƒํ•œ ๊ณณ์—์„œ ๊ฒฐ์ œํ•˜๋ ค๊ณ  ํ•  ๋•Œ, ๋ฐ”๋กœ ์•Œ๋ฆผ์ด ์˜ค๋Š” ๊ทธ๋Ÿฐ ๊ฑฐ์š”!

๐Ÿ“Š ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ

๋น„์ฆˆ๋‹ˆ์Šค ์„ฑ๊ณผ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ๋Š” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”. ๋งˆ์น˜ ์ฃผ์‹ ์ฐจํŠธ์ฒ˜๋Ÿผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€ํ•˜๋Š” ๊ทธ๋ž˜ํ”„๋ฅผ ์ƒ์ƒํ•ด๋ณด์„ธ์š”!

์ด๋Ÿฐ ์‹์œผ๋กœ Flink๋Š” ์ •๋ง ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์—์„œ ํ™œ์šฉ๋  ์ˆ˜ ์žˆ์–ด์š”. ์—ฌ๋Ÿฌ๋ถ„์˜ ์•„์ด๋””์–ด์— ๋”ฐ๋ผ ๋ฌด๊ถ๋ฌด์ง„ํ•œ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์ฃ !

๐Ÿ—๏ธ Flink๋กœ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ Flink๋กœ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•ด๋ณผ๊นŒ์š”? ์—ฌ๋Ÿฌ๋ถ„, ๊ธด์žฅํ•˜์ง€ ๋งˆ์„ธ์š”. ์ฒœ์ฒœํžˆ, ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผ์™€ ๋ณด์„ธ์š”!

1. ํ™˜๊ฒฝ ์„ค์ •

๋จผ์ € Flink๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ด์š”. ๋‹คํ–‰ํžˆ Flink๋Š” ์„ค์น˜๊ฐ€ ์•„์ฃผ ๊ฐ„๋‹จํ•ด์š”!


# Flink ๋‹ค์šด๋กœ๋“œ
wget https://downloads.apache.org/flink/flink-1.14.0/flink-1.14.0-bin-scala_2.11.tgz

# ์••์ถ• ํ•ด์ œ
tar xzf flink-1.14.0-bin-scala_2.11.tgz

# Flink ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™
cd flink-1.14.0

์งœ์ž”~ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Flink ์„ค์น˜ ๋! ๋„ˆ๋ฌด ์‰ฝ์ฃ ? ใ…‹ใ…‹ใ…‹

2. Flink ์‹คํ–‰ํ•˜๊ธฐ

์ด์ œ Flink๋ฅผ ์‹คํ–‰ํ•ด๋ณผ๊นŒ์š”?


# Flink ํด๋Ÿฌ์Šคํ„ฐ ์‹œ์ž‘
./bin/start-cluster.sh

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Flink ํด๋Ÿฌ์Šคํ„ฐ๊ฐ€ ์‹œ์ž‘๋ผ์š”. ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8081๋กœ ์ ‘์†ํ•˜๋ฉด Flink์˜ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”. ๋ฉ‹์ง€์ฃ ? ๐Ÿ˜Ž

3. ๊ฐ„๋‹จํ•œ Flink ํ”„๋กœ๊ทธ๋žจ ์ž‘์„ฑํ•˜๊ธฐ

์ž, ์ด์ œ ์ง„์งœ Flink ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•ด๋ณผ ๊ฑฐ์˜ˆ์š”. ๊ธด์žฅ๋˜๋‚˜์š”? ๊ฑฑ์ • ๋งˆ์„ธ์š”, ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋กœ ์‹œ์ž‘ํ•  ๊ฑฐ์˜ˆ์š”!


import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.datastream.DataStream;

public class SimpleFlinkJob {
    public static void main(String[] args) throws Exception {
        // ์‹คํ–‰ ํ™˜๊ฒฝ ์ƒ์„ฑ
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ƒ์„ฑ (์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํžˆ 1๋ถ€ํ„ฐ 5๊นŒ์ง€์˜ ์ˆซ์ž๋ฅผ ์‚ฌ์šฉ)
        DataStream<integer> numbers = env.fromElements(1, 2, 3, 4, 5);

        // ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ: ๊ฐ ์ˆซ์ž๋ฅผ 2๋ฐฐ๋กœ ๋งŒ๋“ค๊ธฐ
        DataStream<integer> doubled = numbers.map(n -> n * 2);

        // ๊ฒฐ๊ณผ ์ถœ๋ ฅ
        doubled.print();

        // ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰
        env.execute("Simple Flink Job");
    }
}
</integer></integer>

์šฐ์™€~ ์ฒซ ๋ฒˆ์งธ Flink ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ–ˆ์–ด์š”! ๐ŸŽ‰ ์ด ํ”„๋กœ๊ทธ๋žจ์€ ๋ญ˜ ํ•˜๋Š” ๊ฑธ๊นŒ์š”? ๊ฐ„๋‹จํ•ด์š”. 1๋ถ€ํ„ฐ 5๊นŒ์ง€์˜ ์ˆซ์ž๋ฅผ ๋ฐ›์•„์„œ ๊ฐ๊ฐ์„ 2๋ฐฐ๋กœ ๋งŒ๋“ค์–ด ์ถœ๋ ฅํ•˜๋Š” ๊ฑฐ์˜ˆ์š”. ์‹คํ–‰ํ•˜๋ฉด 2, 4, 6, 8, 10์ด ์ถœ๋ ฅ๋  ๊ฑฐ์˜ˆ์š”!

4. ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌํ•˜๊ธฐ

์ž, ์ด์ œ ์กฐ๊ธˆ ๋” ์‹ค์ „์ ์ธ ์˜ˆ์ œ๋ฅผ ๋ณผ๊นŒ์š”? ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด๋ณผ ๊ฑฐ์˜ˆ์š”!


import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.windowing.time.Time;

public class RealtimeProcessingJob {
    public static void main(String[] args) throws Exception {
        // ์‹คํ–‰ ํ™˜๊ฒฝ ์ƒ์„ฑ
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ƒ์„ฑ (์‹ค์ œ๋กœ๋Š” Kafka๋‚˜ ๋‹ค๋ฅธ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์–ด์š”)
        DataStream<string> inputStream = env.socketTextStream("localhost", 9999);

        // ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ: 5์ดˆ ๋™์•ˆ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์•„์„œ ๋‹จ์–ด ์ˆ˜ ์„ธ๊ธฐ
        DataStream<wordcount> wordCounts = inputStream
            .flatMap((String line, Collector<wordcount> out) -> {
                for (String word : line.split("\\s")) {
                    out.collect(new WordCount(word, 1L));
                }
            })
            .keyBy("word")
            .timeWindow(Time.seconds(5))
            .sum("count");

        // ๊ฒฐ๊ณผ ์ถœ๋ ฅ
        wordCounts.print();

        // ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰
        env.execute("Realtime Word Count");
    }

    public static class WordCount {
        public String word;
        public long count;

        public WordCount() {}

        public WordCount(String word, long count) {
            this.word = word;
            this.count = count;
        }

        @Override
        public String toString() {
            return word + ": " + count;
        }
    }
}
</wordcount></wordcount></string>

์šฐ์™€~ ์ด์ œ ์ง„์งœ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์—ˆ์–ด์š”! ๐Ÿ‘ ์ด ํ”„๋กœ๊ทธ๋žจ์€ ๋ญ˜ ํ•˜๋Š” ๊ฑธ๊นŒ์š”? ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”:

  • ๋จผ์ €, ๋กœ์ปฌํ˜ธ์ŠคํŠธ์˜ 9999 ํฌํŠธ์—์„œ ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์š”. (์‹ค์ œ๋กœ๋Š” Kafka ๊ฐ™์€ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์–ด์š”)
  • ๋ฐ›์•„์˜จ ํ…์ŠคํŠธ๋ฅผ ๋‹จ์–ด๋กœ ๋‚˜๋ˆ„๊ณ , ๊ฐ ๋‹จ์–ด๋งˆ๋‹ค ์นด์šดํŠธ๋ฅผ 1๋กœ ์„ค์ •ํ•ด์š”.
  • 5์ดˆ ๋™์•ˆ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์•„์„œ ๊ฐ ๋‹จ์–ด์˜ ์ถœํ˜„ ํšŸ์ˆ˜๋ฅผ ์„ธ์š”.
  • ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•ด์š”.

์ด ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๊ณ , ๋‹ค๋ฅธ ํ„ฐ๋ฏธ๋„์—์„œ nc -lk 9999 ๋ช…๋ น์–ด๋กœ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด, 5์ดˆ๋งˆ๋‹ค ๊ฐ ๋‹จ์–ด์˜ ์ถœํ˜„ ํšŸ์ˆ˜๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”. ์ •๋ง ๋ฉ‹์ง€์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜ƒ

๐Ÿš€ Flink์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค

์ž, ์ด์ œ ๊ธฐ๋ณธ์ ์ธ ๊ฒƒ๋“ค์€ ์•Œ์•˜์œผ๋‹ˆ Flink์˜ ๋” ๋ฉ‹์ง„ ๊ธฐ๋Šฅ๋“ค์„ ์‚ดํŽด๋ณผ๊นŒ์š”? ์—ฌ๋Ÿฌ๋ถ„, ์ค€๋น„๋˜์…จ๋‚˜์š”? ์ง€๊ธˆ๋ถ€ํ„ฐ Flink์˜ ์ˆจ๊ฒจ์ง„ ๋ณด๋ฌผ๋“ค์„ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์˜ˆ์š”! ๐Ÿดโ€โ˜ ๏ธ

1. ์ƒํƒœ ๊ด€๋ฆฌ (State Management)

Flink์˜ ์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์€ ์ •๋ง ๋Œ€๋‹จํ•ด์š”. ๋ณต์žกํ•œ ์—ฐ์‚ฐ์„ ํ•  ๋•Œ ์ด์ „ ๋ฐ์ดํ„ฐ์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์ž–์•„์š”? Flink๋Š” ์ด๋Ÿฐ ์ƒํ™ฉ์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”!


import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.util.Collector;

public class AverageCalculator extends RichFlatMapFunction<integer double> {
    private transient ValueState<tuple2 integer>> sum;

    @Override
    public void open(Configuration config) {
        ValueStateDescriptor<tuple2 integer>> descriptor =
            new ValueStateDescriptor<>("average", TypeInformation.of(new TypeHint<tuple2 integer>>() {}));
        sum = getRuntimeContext().getState(descriptor);
    }

    @Override
    public void flatMap(Integer input, Collector<double> out) throws Exception {
        Tuple2<integer integer> currentSum = sum.value();
        if (currentSum == null) {
            currentSum = new Tuple2<>(0, 0);
        }
        currentSum.f0 += input;
        currentSum.f1 += 1;
        sum.update(currentSum);

        if (currentSum.f1 >= 10) {
            out.collect((double) currentSum.f0 / currentSum.f1);
            sum.clear();
        }
    }
}
</integer></double></tuple2></tuple2></tuple2></integer>

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

2. ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ (Time-based Processing)

Flink๋Š” ์‹œ๊ฐ„ ๊ฐœ๋…์„ ์ •๋ง ์ž˜ ๋‹ค๋ค„์š”. ์ด๋ฒคํŠธ ์‹œ๊ฐ„(Event Time)๊ณผ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„(Processing Time)์„ ๊ตฌ๋ถ„ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ฃ . ์ด๊ฒŒ ์™œ ์ค‘์š”ํ•˜๋ƒ๊ณ ์š”? ์‹ค์ œ ์„ธ๊ณ„์—์„œ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐœ์ƒํ•œ ์‹œ๊ฐ„๊ณผ ์ฒ˜๋ฆฌ๋˜๋Š” ์‹œ๊ฐ„์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”!


import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.windowing.time.Time;

public class TimeBasedProcessing {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        
        // ์ด๋ฒคํŠธ ์‹œ๊ฐ„ ์‚ฌ์šฉ ์„ค์ •
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        DataStream<myevent> events = env.addSource(new MyEventSource())
            .assignTimestampsAndWatermarks(
                new BoundedOutOfOrdernessTimestampExtractor<myevent>(Time.seconds(10)) {
                    @Override
                    public long extractTimestamp(MyEvent event) {
                        return event.getTimestamp();
                    }
                }
            );

        events
            .keyBy(event -> event.getKey())
            .timeWindow(Time.minutes(5))
            .reduce((e1, e2) -> e1.getValue() > e2.getValue() ? e1 : e2)
            .print();

        env.execute("Time-based Processing Example");
    }
}
</myevent></myevent>

์šฐ์™€~ ์ด ์ฝ”๋“œ ์ข€ ๋ฉ‹์ง„๋ฐ์š”? ๐Ÿ˜ ์ด ํ”„๋กœ๊ทธ๋žจ์€ 5๋ถ„ ๋‹จ์œ„๋กœ ๊ฐ ํ‚ค๋ณ„๋กœ ๊ฐ€์žฅ ํฐ ๊ฐ’์„ ๊ฐ€์ง„ ์ด๋ฒคํŠธ๋ฅผ ์ฐพ์•„๋‚ด์š”. ๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฑด, ๋ฐ์ดํ„ฐ๊ฐ€ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•ด๋„ (์ตœ๋Œ€ 10์ดˆ๊นŒ์ง€) ์˜ฌ๋ฐ”๋ฅธ ์‹œ๊ฐ„๋Œ€์— ํฌํ•จ์‹œ์ผœ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ฑฐ์˜ˆ์š”. ์‹ค์ œ ์„ธ๊ณ„์˜ ๋ณต์žกํ•œ ์ƒํ™ฉ์„ ์ •ํ™•ํ•˜๊ฒŒ ๋ชจ๋ธ๋งํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑฐ์ฃ !

3. ์ฒดํฌํฌ์ธํŒ…๊ณผ ์žฅ์•  ๋ณต๊ตฌ (Checkpointing and Fault Tolerance)

Flink์˜ ๋˜ ๋‹ค๋ฅธ ๊ฐ•์ ์€ ๋ฐ”๋กœ ์žฅ์•  ๋Œ€์‘ ๋Šฅ๋ ฅ์ด์—์š”. ์ฒดํฌํฌ์ธํŒ…์ด๋ผ๋Š” ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ , ๋งŒ์•ฝ ์‹œ์Šคํ…œ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ๋„ ๋งˆ์ง€๋ง‰ ์ฒดํฌํฌ์ธํŠธ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์™„์ „ ๋Œ€๋ฐ•์ด์ฃ ? ๐Ÿ˜ฒ


StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

// ์ฒดํฌํฌ์ธํŒ… ์„ค์ •
env.enableCheckpointing(5000); // 5์ดˆ๋งˆ๋‹ค ์ฒดํฌํฌ์ธํŠธ ์ƒ์„ฑ
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
env.getCheckpointConfig().setCheckpointTimeout(60000);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
env.getCheckpointConfig().enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

// ์ƒํƒœ ๋ฐฑ์—”๋“œ ์„ค์ •
env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));

์ด ์„ค์ •์œผ๋กœ Flink๋Š” 5์ดˆ๋งˆ๋‹ค ์ฒดํฌํฌ์ธํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์ •ํ™•ํžˆ ํ•œ ๋ฒˆ(Exactly Once) ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅํ•ด์š”. ๋˜ํ•œ ์ฒดํฌํฌ์ธํŠธ๋ฅผ HDFS์— ์ €์žฅํ•ด์„œ ์‹œ์Šคํ…œ์ด ์™„์ „ํžˆ ๋‹ค์šด๋˜์–ด๋„ ๋ณต๊ตฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”. ์™„์ „ ์•ˆ์ „ํ•˜์ฃ ? ๐Ÿ‘

4. ๋ณต์žกํ•œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ (Complex Event Processing)

Flink๋Š” ๋‹จ์ˆœํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋„˜์–ด์„œ ๋ณต์žกํ•œ ์ด๋ฒคํŠธ ํŒจํ„ด์„ ๊ฐ์ง€ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๊ฑธ CEP(Complex Event Processing)๋ผ๊ณ  ํ•˜๋Š”๋ฐ, ์ •๋ง ๊ฐ•๋ ฅํ•ด์š”!


import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternStream;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;

DataStream<event> input = ...

Pattern<event> pattern = Pattern.<event>begin("start")
    .where(new SimpleCondition<event>() {
        @Override
        public boolean filter(Event event) {
            return event.getName().equals("start");
        }
    })
    .next("middle")
    .where(new SimpleCondition<event>() {
        @Override
        public boolean filter(Event event) {
            return event.getName().equals("middle");
        }
    })
    .followedBy("end")
    .where(new SimpleCondition<event>() {
        @Override
        public boolean filter(Event event) {
            return event.getName().equals("end");
        }
    })
    .within(Time.seconds(10));

PatternStream<event> patternStream = CEP.pattern(input, pattern);

DataStream<alert> result = patternStream.select(
    (Map<string list>> pattern) -> {
        return new Alert("Pattern Detected: " + pattern);
    }
);
</string></alert></event></event></event></event></event></event></event>

์ด ์ฝ”๋“œ๋Š” ๋ญ˜ ํ•˜๋Š” ๊ฑธ๊นŒ์š”? ์Œ... ์ƒ์ƒํ•ด๋ณด์„ธ์š”. ์—ฌ๋Ÿฌ๋ถ„์ด ๋ณด์•ˆ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค๊ณ  ์žˆ๋‹ค๊ณ  ํ•ด์š”. ๊ทธ๋Ÿฐ๋ฐ ํŠน์ • ํŒจํ„ด์˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•ดํ‚น ์‹œ๋„์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ์•Œ์•˜์–ด์š”. ์ด ์ฝ”๋“œ๋Š” ๋ฐ”๋กœ ๊ทธ๋Ÿฐ ํŒจํ„ด์„ ๊ฐ์ง€ํ•˜๋Š” ๊ฑฐ์˜ˆ์š”! "start" ์ด๋ฒคํŠธ, ๊ทธ ๋‹ค์Œ์— "middle" ์ด๋ฒคํŠธ, ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ "end" ์ด๋ฒคํŠธ๊ฐ€ 10์ดˆ ์ด๋‚ด์— ๋ฐœ์ƒํ•˜๋ฉด ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ๊ฑฐ์ฃ . ์™„์ „ ์ฒฉ๋ณด ์˜ํ™” ๊ฐ™์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜Ž

๐ŸŒŸ Flink์˜ ์‹ค์ œ ์‚ฌ์šฉ ์‚ฌ๋ก€

์ž, ์ด์ œ Flink๊ฐ€ ์–ผ๋งˆ๋‚˜ ๋Œ€๋‹จํ•œ์ง€ ์•Œ๊ฒŒ ๋˜์…จ์ฃ ? ๊ทธ๋Ÿผ ์ด์ œ ์‹ค์ œ๋กœ ์–ด๋–ค ๊ธฐ์—…๋“ค์ด Flink๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณผ๊นŒ์š”? ์—ฌ๋Ÿฌ๋ถ„, ๋†€๋ผ์ง€ ๋งˆ์„ธ์š”. ์ •๋ง ๋Œ€๋‹จํ•œ ๊ธฐ์—…๋“ค์ด Flink๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ฑฐ๋“ ์š”! ๐Ÿ˜ฎ

1. ์•Œ๋ฆฌ๋ฐ”๋ฐ” (Alibaba)

์ค‘๊ตญ์˜ ๊ฑฐ๋Œ€ ์ „์ž์ƒ๊ฑฐ๋ž˜ ๊ธฐ์—… ์•Œ๋ฆฌ๋ฐ”๋ฐ”๋Š” Flink๋ฅผ ๋Œ€๊ทœ๋ชจ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์š”. ํŠนํžˆ '๊ด‘๊ตฐ์ œ'๋ผ๋Š” ๋Œ€๊ทœ๋ชจ ์‡ผํ•‘ ์ถ•์ œ ๋•Œ Flink์˜ ์ง„๊ฐ€๊ฐ€ ๋ฐœํœ˜๋œ๋‹ค๊ณ  ํ•ด์š”.

๐Ÿ›’ ์•Œ๋ฆฌ๋ฐ”๋ฐ”์˜ Flink ํ™œ์šฉ

  • ์‹ค์‹œ๊ฐ„ ๊ฑฐ๋ž˜ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์‚ฌ๊ธฐ ๊ฑฐ๋ž˜ ํƒ์ง€
  • ์‹ค์‹œ๊ฐ„ ์žฌ๊ณ  ๊ด€๋ฆฌ
  • ๊ฐœ์ธํ™”๋œ ์ƒํ’ˆ ์ถ”์ฒœ

์•Œ๋ฆฌ๋ฐ”๋ฐ”๋Š” Flink๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ดˆ๋‹น ์ˆ˜์–ต ๊ฑด์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค๊ณ  ํ•ด์š”. ์™€... ์ƒ์ƒ์ด ๊ฐ€๋‚˜์š”? ๊ทธ ์—„์ฒญ๋‚œ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค๋‹ˆ! ๐Ÿคฏ

2. ์šฐ๋ฒ„ (Uber)

์ฐจ๋Ÿ‰ ๊ณต์œ  ์„œ๋น„์Šค๋กœ ์œ ๋ช…ํ•œ ์šฐ๋ฒ„๋„ Flink์˜ ์—ด๋ ฌํ•œ ํŒฌ์ด์—์š”. ์šฐ๋ฒ„๋Š” Flink๋ฅผ ์‚ฌ์šฉํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฐจ๋Ÿ‰๊ณผ ์Šน๊ฐ์„ ๋งค์นญํ•˜๊ณ , ๋‹ค์–‘ํ•œ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•ด์š”.

๐Ÿš— ์šฐ๋ฒ„์˜ Flink ํ™œ์šฉ

  • ์‹ค์‹œ๊ฐ„ ์ฐจ๋Ÿ‰-์Šน๊ฐ ๋งค์นญ
  • ๋™์  ๊ฐ€๊ฒฉ ์ฑ…์ •
  • ์šด์ „์ž ํ–‰๋™ ๋ถ„์„
  • ์„œ๋น„์Šค ํ’ˆ์งˆ ๋ชจ๋‹ˆํ„ฐ๋ง

์šฐ๋ฒ„๋Š” Flink๋ฅผ ํ†ตํ•ด ์ˆ˜๋ฐฑ๋งŒ ๋ช…์˜ ์šด์ „์ž์™€ ์Šน๊ฐ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—ฐ๊ฒฐํ•˜๊ณ  ์žˆ์–ด์š”. ์—ฌ๋Ÿฌ๋ถ„์ด ์šฐ๋ฒ„๋ฅผ ์ด์šฉํ•  ๋•Œ๋งˆ๋‹ค, ๊ทธ ๋’ค์—์„œ๋Š” Flink๊ฐ€ ์—ด์‹ฌํžˆ ์ผํ•˜๊ณ  ์žˆ๋Š” ๊ฑฐ์ฃ ! ๐Ÿ˜„

3. ๋„ทํ”Œ๋ฆญ์Šค (Netflix)

์ŠคํŠธ๋ฆฌ๋ฐ ์„œ๋น„์Šค์˜ ์™•, ๋„ทํ”Œ๋ฆญ์Šค๋„ Flink๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์š”. ๋„ทํ”Œ๋ฆญ์Šค๋Š” Flink๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๊ณ , ์ฝ˜ํ…์ธ  ์ถ”์ฒœ ์‹œ์Šคํ…œ์„ ๊ฐ•ํ™”ํ•˜๊ณ  ์žˆ์ฃ .

๐ŸŽฌ ๋„ทํ”Œ๋ฆญ์Šค์˜ Flink ํ™œ์šฉ

  • ์‹ค์‹œ๊ฐ„ ์‹œ์ฒญ ํŒจํ„ด ๋ถ„์„
  • ๊ฐœ์ธํ™”๋œ ์ฝ˜ํ…์ธ  ์ถ”์ฒœ
  • ์ŠคํŠธ๋ฆฌ๋ฐ ํ’ˆ์งˆ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ ๋ฐ ๋Œ€์‘

๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ทจํ–ฅ์„ ๋„ˆ๋ฌด ์ž˜ ์•Œ๊ณ  ์žˆ๋‹ค๊ณ  ๋Š๋‚€ ์  ์žˆ๋‚˜์š”? ๊ทธ ๋น„๊ฒฐ์ด ๋ฐ”๋กœ Flink์˜ˆ์š”! Flink๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์‹œ์ฒญ ๊ธฐ๋ก์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ถ„์„ํ•ด์„œ ์ตœ์ ์˜ ์ฝ˜ํ…์ธ ๋ฅผ ์ถ”์ฒœํ•ด์ฃผ๋Š” ๊ฑฐ์ฃ . ์™„์ „ ์‹ ๊ธฐํ•˜์ง€ ์•Š๋‚˜์š”? ๐Ÿฟ

4. ๋ผ์ธ (LINE)