Kafka์—์„œ ์ˆ˜์‹ ๋œ ์ด๋ฒคํŠธ ์žฌ์ฒ˜๋ฆฌ

Kafka์—์„œ ์ˆ˜์‹ ๋œ ์ด๋ฒคํŠธ ์žฌ์ฒ˜๋ฆฌ

ํ—ค์ด ํ•˜๋ธŒ๋ฅด.

์ตœ๊ทผ์— ๋‚˜๋Š” ์ž์‹ ์˜ ๊ฒฝํ—˜์„ ๊ณต์œ ํ–ˆ๋‹ค Kafka Producer์™€ Consumer๊ฐ€ ๋ณด์žฅ๋œ ์ „๋‹ฌ์— ๋” ๊ฐ€๊นŒ์›Œ์ง€๋„๋ก ํŒ€์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ์ด ๊ธ€์—์„œ๋Š” ์™ธ๋ถ€ ์‹œ์Šคํ…œ์„ ์ผ์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜์–ด Kafka์—์„œ ์ˆ˜์‹ ํ•œ ์ด๋ฒคํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๋‹ค์‹œ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ์„ค๋ช…ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

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

์นดํ”„์นด ์ด์ „์˜ ์ƒํ™ฉ์€ ์–ด๋• ๋Š”๊ฐ€

์ด ํ”„๋กœ์ ํŠธ์˜ ์ดˆ๊ธฐ ๋‹จ๊ณ„์—์„œ๋Š” ๋น„๋™๊ธฐ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ์„ ์œ„ํ•ด IBM MQ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์„œ๋น„์Šค ์šด์˜ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ˆ˜์‹ ๋œ ๋ฉ”์‹œ์ง€๋Š” ์ถ”๊ฐ€ ์ˆ˜๋™ ๊ตฌ๋ฌธ ๋ถ„์„์„ ์œ„ํ•ด ๋ฐ๋“œ ๋ ˆํ„ฐ ํ(DLQ)์— ๋ณด๊ด€๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. DLQ๋Š” ์ˆ˜์‹  ๋Œ€๊ธฐ์—ด ์˜†์— ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฉฐ, ๋ฉ”์‹œ์ง€ ์ „์†ก์€ IBM MQ ๋‚ด๋ถ€์—์„œ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์˜ค๋ฅ˜๊ฐ€ ์ผ์‹œ์ ์ด๊ณ  ์ด๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ(์˜ˆ: HTTP ํ˜ธ์ถœ์—์„œ ResourceAccessException์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ MongoDb ์š”์ฒญ์—์„œ MongoTimeoutException์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ) ์žฌ์‹œ๋„ ์ „๋žต์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์˜ ๋ถ„๊ธฐ์™€ ๊ด€๊ณ„์—†์ด ์›๋ž˜ ๋ฉ”์‹œ์ง€๋Š” ์ง€์—ฐ ์ „์†ก์„ ์œ„ํ•ด ์‹œ์Šคํ…œ ๋Œ€๊ธฐ์—ด๋กœ ์ „์†ก๋˜๊ฑฐ๋‚˜, ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ค์‹œ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ๋ณ„๋„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์žฌ์‹œ๋„ ๋ฒˆํ˜ธ๊ฐ€ ๋ฉ”์‹œ์ง€ ํ—ค๋”์— ๊ธฐ๋ก๋˜๋ฉฐ, ์ด๋Š” ์ง€์—ฐ ๊ฐ„๊ฒฉ์ด๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ˆ˜์ค€ ์ „๋žต์˜ ๋์— ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ์ „๋žต์˜ ๋์— ๋„๋‹ฌํ–ˆ์ง€๋งŒ ์™ธ๋ถ€ ์‹œ์Šคํ…œ์„ ์—ฌ์ „ํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๋ฉ”์‹œ์ง€๋Š” ์ˆ˜๋™ ๊ตฌ๋ฌธ ๋ถ„์„์„ ์œ„ํ•ด DLQ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

์†”๋ฃจ์…˜ ๊ฒ€์ƒ‰

์ธํ„ฐ๋„ท์„ ๊ฒ€์ƒ‰ํ•œ ํ›„, ๋‹ค์Œ์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ๊ฒฐ์ •. ๊ฐ„๋‹จํžˆ ๋งํ•ด, ๊ฐ ์ง€์—ฐ ๊ฐ„๊ฒฉ์— ๋Œ€ํ•œ ์ฃผ์ œ๋ฅผ ๋งŒ๋“ค๊ณ , ํ•„์š”ํ•œ ์ง€์—ฐ ์‹œ๊ฐ„์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธก์˜ ์†Œ๋น„์ž๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ œ์•ˆ๋ฉ๋‹ˆ๋‹ค.

Kafka์—์„œ ์ˆ˜์‹ ๋œ ์ด๋ฒคํŠธ ์žฌ์ฒ˜๋ฆฌ

๊ธ์ •์ ์ธ ๋ฆฌ๋ทฐ๊ฐ€ ๋งŽ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์ œ ์ƒ๊ฐ์—๋Š” ์ „์ ์œผ๋กœ ์„ฑ๊ณต์ ์ด์ง€๋Š” ์•Š์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ฒซ์งธ, ๊ฐœ๋ฐœ์ž๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ ์‚ฌํ•ญ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„ ์„ค๋ช…๋œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ๋งŽ์€ ์‹œ๊ฐ„์„ ํ• ์• ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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

์ด์ œ Spring์ด ์ œ๊ณตํ•˜๋Š” ๋ฉ”์‹œ์ง€ ์žฌ์ฒ˜๋ฆฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ ํŠนํžˆ Spring-Kafka๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์‹œ์ง€ ์žฌ์ฒ˜๋ฆฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Spring-kafka๋Š” ๋‹ค์–‘ํ•œ BackOffPolicy๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ถ”์ƒํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” spring-retry์— ๋Œ€ํ•œ ์ „์ด์  ์ข…์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋งค์šฐ ์œ ์—ฐํ•œ ๋„๊ตฌ์ด์ง€๋งŒ, ๊ฐ€์žฅ ํฐ ๋‹จ์ ์€ ๋‹ค์‹œ ๋ณด๋‚ผ ๋ฉ”์‹œ์ง€๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์—…๋ฐ์ดํŠธ๋‚˜ ์ž‘์—… ์ค‘ ์˜ค๋ฅ˜๋กœ ์ธํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋ฉด ์žฌ์ฒ˜๋ฆฌ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋ชจ๋“  ๋ฉ”์‹œ์ง€๊ฐ€ ์†์‹ค๋ฉ๋‹ˆ๋‹ค. ์ด ์ง€์ ์€ ์šฐ๋ฆฌ ์‹œ์Šคํ…œ์— ๋งค์šฐ ์ค‘์š”ํ•˜๋ฏ€๋กœ ๋” ์ด์ƒ ๊ณ ๋ คํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

spring-kafka ์ž์ฒด๋Š” ContainerAwareErrorHandler์˜ ์—ฌ๋Ÿฌ ๊ตฌํ˜„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด SeekToCurrentErrorHandler์ด๋ฅผ ํ†ตํ•ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ ์˜คํ”„์…‹์„ ์ด๋™ํ•˜์ง€ ์•Š๊ณ ๋„ ๋‚˜์ค‘์— ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. spring-kafka 2.3๋ถ€ํ„ฐ BackOffPolicy๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ์žฌ์ฒ˜๋ฆฌ๋œ ๋ฉ”์‹œ์ง€๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•œ ํ›„์—๋„ ์œ ์ง€๋˜์ง€๋งŒ ์—ฌ์ „ํžˆ DLQ ๋ฉ”์ปค๋‹ˆ์ฆ˜์€ ์—†์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ 2019๋…„ ์ดˆ์— ์„ ํƒํ•œ ์˜ต์…˜์œผ๋กœ, DLQ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ๊ณ  ๋‚™๊ด€์ ์œผ๋กœ ๋ฏฟ์—ˆ์Šต๋‹ˆ๋‹ค(์šฐ๋ฆฌ๋Š” ์šด์ด ์ข‹์•˜๊ณ  ์‹ค์ œ๋กœ ๊ทธ๋Ÿฌํ•œ ์žฌ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ์ด ์žˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•œ ๋ช‡ ๋‹ฌ ๋™์•ˆ DLQ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค). ์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜๋กœ ์ธํ•ด SeekToCurrentErrorHandler๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€ ์˜ค๋ฅ˜๋Š” ๋กœ๊ทธ์— ์ธ์‡„๋˜์–ด ์˜คํ”„์…‹์ด ๋ฐœ์ƒํ–ˆ์œผ๋ฉฐ, ์ฒ˜๋ฆฌ๋Š” ๋‹ค์Œ ๋ฉ”์‹œ์ง€๋กœ ๊ณ„์†๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ตœ์ข… ๊ฒฐ์ •

SeekToCurrentErrorHandler ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์œผ๋กœ ์ธํ•ด ์šฐ๋ฆฌ๋Š” ๋ฉ”์‹œ์ง€ ์žฌ์ „์†ก์„ ์œ„ํ•œ ์ž์ฒด ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ฐœ๋ฐœํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์šฐ์„ , ์šฐ๋ฆฌ๋Š” ๊ธฐ์กด ๊ฒฝํ—˜์„ ํ™œ์šฉํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋…ผ๋ฆฌ์— ๋”ฐ๋ผ ์ด๋ฅผ ํ™•์žฅํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ์„ ํ˜• ๋…ผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ ์žฌ์‹œ๋„ ์ „๋žต์—์„œ ์ง€์ •ํ•œ ์งง์€ ์‹œ๊ฐ„ ๋‚ด์— ์ƒˆ ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ๋ฅผ ์ค‘์ง€ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์Šต๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ ์žฌ์‹œ๋„ ์ „๋žต์„ ์‹œํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์ผ ์ง€์ ์„ ์›ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ด ๋‹จ์ผ ์ง€์ ์—๋Š” ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹ ๋ชจ๋‘์— ๋Œ€ํ•œ DLQ ๊ธฐ๋Šฅ์ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์žฌ์‹œ๋„ ์ „๋žต ์ž์ฒด๋Š” ์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋‹ค์Œ ๊ฐ„๊ฒฉ์„ ์–ป๋Š” ์—ญํ• ์„ ํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ €์žฅ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์„ ํ˜• ๋…ผ๋ฆฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์†Œ๋น„์ž ์ค‘์ง€

spring-kafka๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ Consumer๋ฅผ ์ค‘์ง€ํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

public void pauseListenerContainer(MessageListenerContainer listenerContainer, 
                                   Instant retryAt) {
        if (nonNull(retryAt) && listenerContainer.isRunning()) {
            listenerContainer.stop();
            taskScheduler.schedule(() -> listenerContainer.start(), retryAt);
            return;
        }
        // to DLQ
    }

์ด ์˜ˆ์—์„œ retryAt์€ MessageListenerContainer๊ฐ€ ์•„์ง ์‹คํ–‰ ์ค‘์ด๋ฉด ๋‹ค์‹œ ์‹œ์ž‘ํ•  ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค. ์žฌ์‹œ์ž‘์€ TaskScheduler์—์„œ ์‹œ์ž‘๋œ ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ์—์„œ ๋ฐœ์ƒํ•˜๋ฉฐ, ์ด์— ๋Œ€ํ•œ ๊ตฌํ˜„๋„ Spring์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

retryAt์˜ ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ฝœ๋ฐฑ ์นด์šดํ„ฐ์˜ ๊ฐ’์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์นด์šดํ„ฐ ๊ฐ’์— ๋”ฐ๋ผ ์žฌ์‹œ๋„ ์ „๋žต์˜ ํ˜„์žฌ ์ง€์—ฐ ๊ฐ„๊ฒฉ์ด ๊ฒ€์ƒ‰๋ฉ๋‹ˆ๋‹ค. ์ „๋žต์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž์ฒด์—์„œ ์„ ์–ธ๋˜๋ฉฐ, ์ด๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด JSON ํ˜•์‹์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.
  3. JSON ๋ฐฐ์—ด์—์„œ ์ฐพ์€ ๊ฐ„๊ฒฉ์—๋Š” ์ฒ˜๋ฆฌ๋ฅผ ๋ฐ˜๋ณตํ•ด์•ผ ํ•˜๋Š” ์ดˆ ์ˆ˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ดˆ ์ˆ˜๋Š” ํ˜„์žฌ ์‹œ๊ฐ„์— ๋”ํ•ด์ ธ retryAt ๊ฐ’์„ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค.
  4. ๊ฐ„๊ฒฉ์„ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ retryAt ๊ฐ’์€ null์ด๊ณ  ๋ฉ”์‹œ์ง€๋Š” ์ˆ˜๋™ ๊ตฌ๋ฌธ ๋ถ„์„์„ ์œ„ํ•ด DLQ๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค.

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

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

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

๋น„๊ฒฐ์ •์  ๋…ผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ณ„๋„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

๋‹ค์Œ์€ RETRY_AT ์‹œ๊ฐ„์— ๋„๋‹ฌํ•˜๋ฉด DESTINATION ์ฃผ์ œ๋กœ ๋‹ค์‹œ ์ „์†ก๋˜๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ํ•ด๋‹น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(Retryer)์— ์ „์†กํ•˜๋Š” ์ฝ”๋“œ์˜ ์˜ˆ์ž…๋‹ˆ๋‹ค.


public <K, V> void retry(ConsumerRecord<K, V> record, String retryToTopic, 
                         Instant retryAt, String counter, String groupId, Exception e) {
        Headers headers = ofNullable(record.headers()).orElse(new RecordHeaders());
        List<Header> arrayOfHeaders = 
            new ArrayList<>(Arrays.asList(headers.toArray()));
        updateHeader(arrayOfHeaders, GROUP_ID, groupId::getBytes);
        updateHeader(arrayOfHeaders, DESTINATION, retryToTopic::getBytes);
        updateHeader(arrayOfHeaders, ORIGINAL_PARTITION, 
                     () -> Integer.toString(record.partition()).getBytes());
        if (nonNull(retryAt)) {
            updateHeader(arrayOfHeaders, COUNTER, counter::getBytes);
            updateHeader(arrayOfHeaders, SEND_TO, "retry"::getBytes);
            updateHeader(arrayOfHeaders, RETRY_AT, retryAt.toString()::getBytes);
        } else {
            updateHeader(arrayOfHeaders, REASON, 
                         ExceptionUtils.getStackTrace(e)::getBytes);
            updateHeader(arrayOfHeaders, SEND_TO, "backout"::getBytes);
        }
        ProducerRecord<K, V> messageToSend =
            new ProducerRecord<>(retryTopic, null, null, record.key(), record.value(), arrayOfHeaders);
        kafkaTemplate.send(messageToSend);
    }

์ด ์˜ˆ๋ฅผ ๋ณด๋ฉด ๋งŽ์€ ์ •๋ณด๊ฐ€ ํ—ค๋”๋ฅผ ํ†ตํ•ด ์ „์†ก๋œ๋‹ค๋Š” ๊ฒƒ์ด ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค. RETRY_AT ๊ฐ’์€ ์†Œ๋น„์ž ์ค‘์ง€ ์žฌ์‹œ๋„ ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. DESTINATION ๋ฐ RETRY_AT ์™ธ์—๋„ ๋‹ค์Œ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

  • GROUP_ID๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜์—ฌ ์ˆ˜๋™ ๋ถ„์„๊ณผ ๋ณด๋‹ค ์‰ฌ์šด ๊ฒ€์ƒ‰์„ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ORIGINAL_PARTITION์„ ์‚ฌ์šฉํ•˜์—ฌ ์žฌ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๋™์ผํ•œ Consumer๋ฅผ ์œ ์ง€ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” null์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ๊ฒฝ์šฐ ์ƒˆ ํŒŒํ‹ฐ์…˜์€ ์›๋ณธ ๋ฉ”์‹œ์ง€์˜ record.key() ํ‚ค๋กœ ์–ป์–ด์ง‘๋‹ˆ๋‹ค.
  • ์žฌ์‹œ๋„ ์ „๋žต์— ๋”ฐ๋ผ COUNTER ๊ฐ’์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • SEND_TO๋Š” RETRY_AT์— ๋„๋‹ฌํ•˜๋ฉด ์žฌ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ์ง€ ์•„๋‹ˆ๋ฉด DLQ์— ๋„ฃ์„์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒ์ˆ˜์ž…๋‹ˆ๋‹ค.
  • ์ด์œ  - ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ๊ฐ€ ์ค‘๋‹จ๋œ ์ด์œ .

Retryer๋Š” PostgreSQL์—์„œ ์žฌ์ „์†ก ๋ฐ ์ˆ˜๋™ ๊ตฌ๋ฌธ ๋ถ„์„์„ ์œ„ํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ด๋จธ๋Š” RETRY_AT๊ฐ€ ๋ฐœ์ƒํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์ฐพ๊ณ  record.key() ํ‚ค์™€ ํ•จ๊ป˜ DESTINATION ํ† ํ”ฝ์˜ ORIGINAL_PARTITION ํŒŒํ‹ฐ์…˜์œผ๋กœ ๋‹ค์‹œ ๋ณด๋‚ด๋Š” ์ž‘์—…์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์ „์†ก๋œ ๋ฉ”์‹œ์ง€๋Š” PostgreSQL์—์„œ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€์˜ ์ˆ˜๋™ ๊ตฌ๋ฌธ ๋ถ„์„์€ REST API๋ฅผ ํ†ตํ•ด Retryer์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋Š” ๊ฐ„๋‹จํ•œ UI์—์„œ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ์ฃผ์š” ๊ธฐ๋Šฅ์œผ๋กœ๋Š” DLQ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ค์‹œ ๋ณด๋‚ด๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๊ณ , ์˜ค๋ฅ˜ ์ •๋ณด๋ฅผ ๋ณด๊ณ , ์˜ค๋ฅ˜ ์ด๋ฆ„์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ์•ก์„ธ์Šค ์ œ์–ด๊ฐ€ ํ™œ์„ฑํ™”๋˜์—ˆ์œผ๋ฏ€๋กœ Retryer๊ฐ€ ์ˆ˜์‹ ํ•˜๋Š” ์ฃผ์ œ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์ถ”๊ฐ€๋กœ ์š”์ฒญํ•˜๊ณ  Retryer๊ฐ€ DESTINATION ์ฃผ์ œ์— ์“ธ ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋ถˆํŽธํ•˜์ง€๋งŒ ์ฃผ์ œ๋ณ„ ๊ฐ„๊ฒฉ ๋ฐฉ์‹๊ณผ ๋‹ฌ๋ฆฌ ๋ณธ๊ฒฉ์ ์ธ DLQ์™€ ์ด๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ UI๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ ์†Œ๋น„์ž ๊ทธ๋ฃน์ด ๋“ค์–ด์˜ค๋Š” ์ฃผ์ œ๋ฅผ ์ฝ๊ณ  ํ•ด๋‹น ๊ทธ๋ฃน์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„œ๋กœ ๋‹ค๋ฅธ ๋…ผ๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ค‘ ํ•˜๋‚˜์—์„œ Retryer๋ฅผ ํ†ตํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ค์‹œ ์‹œ๋„ํ•˜๋ฉด ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋„ ์ค‘๋ณต์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์žฌ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ณ„๋„์˜ ์ฃผ์ œ๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜์‹  ๋ฐ ์žฌ์‹œ๋„ ์ฃผ์ œ๋Š” ๋™์ผํ•œ ์†Œ๋น„์ž๊ฐ€ ์•„๋ฌด๋Ÿฐ ์ œํ•œ ์—†์ด ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Kafka์—์„œ ์ˆ˜์‹ ๋œ ์ด๋ฒคํŠธ ์žฌ์ฒ˜๋ฆฌ

๊ธฐ๋ณธ์ ์œผ๋กœ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์ง€๋งŒ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํ”„๋ง ํด๋ผ์šฐ๋“œ ๋„ทํ”Œ๋ฆญ์Šค ๋˜๋Š” ์ƒˆ๋กœ์šด ์Šคํ”„๋ง ํด๋ผ์šฐ๋“œ ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ์™ธ๋ถ€ ์„œ๋น„์Šค์˜ ํ˜ธ์ถœ ์‚ฌ์ดํŠธ๋ฅผ ์ ์ ˆํ•œ ์ถ”์ƒํ™”๋กœ ๋ž˜ํ•‘ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ „๋žต์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์นธ๋ง‰์ด ํŒจํ„ด๋„ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, spring-cloud-netflix์—์„œ๋Š” ์Šค๋ ˆ๋“œ ํ’€์ด๋‚˜ ์„ธ๋งˆํฌ์–ด๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถœ๋ ฅ

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

์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฃผ์š” ์žฅ์  ์ค‘ ํ•˜๋‚˜๋Š” ๋™์ผํ•œ Kafka ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ์‹คํ–‰๋˜๋Š” ์™ธ๋ถ€ ์‹œ์Šคํ…œ์—์„œ๋„ ํฐ ์ˆ˜์ • ์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค! ์ด๋Ÿฌํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์žฌ์‹œ๋„ ์ฃผ์ œ์— ์•ก์„ธ์Šคํ•˜๊ณ , ๋ช‡ ๊ฐœ์˜ Kafka ํ—ค๋”๋ฅผ ์ž‘์„ฑํ•˜๊ณ , Retryer์— ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€์ ์ธ ์ธํ”„๋ผ๋ฅผ ๊ตฌ์ถ•ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Retryer๋กœ ์ „์†ก๋˜๋Š” ๋ฉ”์‹œ์ง€ ์ˆ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ์„ ํ˜• ๋…ผ๋ฆฌ๋ฅผ ์ ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ ํƒํ•˜๊ณ  Consumer๋ฅผ ์ค‘์ง€ํ•˜์—ฌ ํ•ด๋‹น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์žฌ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

DDoS ๋ณดํ˜ธ, VPS VDS ์„œ๋ฒ„๊ฐ€ ์žˆ๋Š” ์‚ฌ์ดํŠธ๋ฅผ ์œ„ํ•œ ์•ˆ์ •์ ์ธ ํ˜ธ์ŠคํŒ… ๊ตฌ์ž… ๐Ÿ”ฅ DDoS ๊ณต๊ฒฉ ๋ฐฉ์ง€ ๊ธฐ๋Šฅ์ด ํƒ‘์žฌ๋œ ์•ˆ์ •์ ์ธ ์›น์‚ฌ์ดํŠธ ํ˜ธ์ŠคํŒ…, VPS ๋ฐ VDS ์„œ๋ฒ„๋ฅผ ๊ตฌ๋งคํ•˜์„ธ์š” | ProHoster