Back-End/Spring

MSA (Microservice Architecture)

Kr1 2026. 4. 14. 12:58

 

 

๐Ÿ—จ๏ธ MSA

MSA ๊ฐœ์š”

MSA๋ž€?

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

 

๋ชจ๋†€๋ฆฌํ‹ฑ ์•„ํ‚คํ…์ฒ˜์™€์˜ ๋น„๊ต

๋ชจ๋†€๋ฆฌํ‹ฑ ์•„ํ‚คํ…์ฒ˜

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

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

 

MSA์˜ ์žฅ๋‹จ์ 

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

 

๐Ÿ—จ๏ธ Spring Cloud

Spring Cloud ๊ฐœ์š”

Spring Cloud๋ž€?

  • ์ •์˜
    • Spring Cloud๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐœ๋ฐœ์„ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ๋„๊ตฌ์™€ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ์˜ ํ™•์žฅ
    • ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์šด์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์›€
  • ์ฃผ์š” ๊ธฐ๋Šฅ
    1. ์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ๋””์Šค์ปค๋ฒ„๋ฆฌ: Eureka, Consul, Zookeeper
      • ์„œ๋น„์Šค ์œ„์น˜ ํƒ์ƒ‰: ๋™์ ์œผ๋กœ ๋ณ€ํ•˜๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค์˜ ์œ„์น˜(IP, ํฌํŠธ)๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ๊ด€๋ฆฌํ•˜๋Š” '์ „ํ™”๋ฒˆํ˜ธ๋ถ€' ์—ญํ•  
    2. ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: Ribbon, Spring Cloud LoadBalancer
      • ํŠธ๋ž˜ํ”ฝ ๋ถ€ํ•˜ ๋ถ„์‚ฐ: ํŠน์ • ์„œ๋ฒ„์— ๋ถ€ํ•˜๊ฐ€ ์ง‘์ค‘๋˜์ง€ ์•Š๋„๋ก ์—ฌ๋Ÿฌ ๋Œ€์˜ ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค์— ํŠธ๋ž˜ํ”ฝ์„ ๊ท ๋“ฑํ•˜๊ฒŒ ๋ถ„์‚ฐ์‹œ์ผœ ์ฃผ๋Š” ๊ธฐ์ˆ  
    3. ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค: Hystrix, Resilience4j
      • ์žฅ์•  ์ „ํŒŒ ์ฐจ๋‹จ: ํŠน์ • ์„œ๋น„์Šค์— ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ์„ ์ฐจ๋‹จํ•˜์—ฌ ์ „์ฒด ์‹œ์Šคํ…œ์œผ๋กœ ์žฅ์• ๊ฐ€ ์ „ํŒŒ๋˜๋Š” ๊ฒƒ์„ ๋ง‰๊ณ  ์šฐํšŒ๋กœ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์•ˆ์ „์žฅ์น˜
    4. API ๊ฒŒ์ดํŠธ์›จ์ด: Zuul, Spring Cloud Gateway
      • ํ†ตํ•ฉ ๊ด€๋ฌธ ๊ด€๋ฆฌ: ์‹œ์Šคํ…œ์˜ ๋‹จ์ผ ์ง„์ž…์ ์œผ๋กœ์„œ ์ธ์ฆ/์ธ๊ฐ€ ์ฒ˜๋ฆฌ, ์š”์ฒญ ๋ผ์šฐํŒ…, ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ, ๋กœ๊น… ๋“ฑ์„ ๊ณตํ†ต์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฌธ์ง€๊ธฐ ์—ญํ• 
    5. ๊ตฌ์„ฑ ๊ด€๋ฆฌ: Spring Cloud Config
      • ํ™˜๊ฒฝ ์„ค์ • ์ค‘์•™ํ™”: ์„œ๋ฒ„๋ฅผ ์žฌ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ ๋„ ๋ถ„์‚ฐ๋œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋“ค์˜ ์„ค์ •๊ฐ’(properties, yaml)์„ ์ค‘์•™ ์„œ๋ฒ„์—์„œ ํ•œ ๋ฒˆ์— ๊ด€๋ฆฌํ•˜๊ณ  ๋ณ€๊ฒฝํ•˜๋Š” ๊ธฐ๋Šฅ
    6. ๋ถ„์‚ฐ ์ถ”์ : Spring Cloud Sleuth, Zipkin
      • ์ฒ˜๋ฆฌ ๊ฒฝ๋กœ ์‹œ๊ฐํ™”: ์—ฌ๋Ÿฌ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ๊ฑฐ์ณ ์ฒ˜๋ฆฌ๋˜๋Š” ํ•˜๋‚˜์˜ ์š”์ฒญ(Request)์˜ ํ๋ฆ„๊ณผ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์„ ์‹œ๊ฐํ™”ํ•˜์—ฌ ์žฅ์•  ์ง€์ ์„ ํŒŒ์•…ํ•˜๋Š” ๊ธฐ์ˆ 
    7. ๋ฉ”์‹œ์ง•: Spring Cloud Stream
      • ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ: Kafka๋‚˜ RabbitMQ ๊ฐ™์€ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค ๊ฐ„ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•˜๋„๋ก ๋•๋Š” ์ถ”์ƒํ™” ๋ ˆ์ด์–ด
ํ˜„์—…์—์„œ๋Š” ๋ฒ„์ „ ์—…๋ฐ์ดํŠธ ์‹œ์˜ ์•ˆ์ •์„ฑ ๋ฌธ์ œ๋‚˜ ๊ธฐ์กด ๋ ˆ๊ฑฐ์‹œ ์‹œ์Šคํ…œ๊ณผ์˜ ํ˜ธํ™˜์„ฑ ๋•Œ๋ฌธ์— ์—ฌ์ „ํžˆ Hystrix๋‚˜ Zuul์„ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์ด ๋งŽ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ตœ์‹  ํ‘œ์ค€์ธ Spring Cloud Gateway๋‚˜ Resilience4j๊ฐ€ ์ด๋“ค๊ณผ ๊ธฐ๋Šฅ์ ์œผ๋กœ ์œ ์‚ฌํ•œ ์—ญํ• ์„ ํ•œ๋‹ค๋Š” ์ ์„ ์ดํ•ดํ•˜๊ณ , ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์œ ์—ฐํ•˜๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

Spring Cloud ์ฃผ์š” ๋ชจ๋“ˆ

์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ๋””์Šค์ปค๋ฒ„๋ฆฌ

๐Ÿ“Œ Eureka

  • ๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ ์„œ๋ฒ„๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ๊ฐ ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌ
  • ์ฃผ์š” ํŠน์ง•:
    • ์„œ๋น„์Šค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ: ๋ชจ๋“  ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค์˜ ์œ„์น˜๋ฅผ ์ €์žฅํ•˜๋Š” ์ค‘์•™ ์ €์žฅ์†Œ
    • ํ—ฌ์Šค ์ฒดํฌ(Health check): ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค์˜ ์ƒํƒœ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ™•์ธํ•˜์—ฌ ๊ฐ€์šฉ์„ฑ์„ ๋ณด์žฅ

๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ

๐Ÿ“Œ Ribbon

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

์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค

๐Ÿ“Œ Hystrix

  • ๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ์‹คํŒจ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์‹œ์Šคํ…œ์˜ ์ „์ฒด์ ์ธ ์•ˆ์ •์„ฑ์„ ์œ ์ง€
  • ์ฃผ์š” ํŠน์ง•:
    • ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ: ํด๋กœ์ฆˆ๋“œ, ์˜คํ”ˆ, ํ•˜ํ”„-์˜คํ”ˆ ์ƒํƒœ๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ ์‹คํŒจ๋ฅผ ๊ด€๋ฆฌ
    • Failback: ํ˜ธ์ถœ ์‹คํŒจ ์‹œ ๋Œ€์ฒด ๋กœ์ง์„ ์ œ๊ณตํ•˜์—ฌ ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ ํ™•๋ณด
    • ๋ชจ๋‹ˆํ„ฐ๋ง: Hystrix Dashboard๋ฅผ ํ†ตํ•ด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง

๐Ÿ“Œ Resilience4j

  • Resilience4j๋Š” ์ž๋ฐ” ๊ธฐ๋ฐ˜์˜ ๊ฒฝ๋Ÿ‰ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ๋„ทํ”Œ๋ฆญ์Šค Hystrix์˜ ๋Œ€์•ˆ์œผ๋กœ ๊ฐœ๋ฐœ
  • ์ฃผ์š” ํŠน์ง•:
    • ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค: ํ˜ธ์ถœ ์‹คํŒจ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์„œํ‚ท์„ ์—ด์–ด ์ถ”๊ฐ€์ ์ธ ํ˜ธ์ถœ์„ ์ฐจ๋‹จํ•˜์—ฌ ์‹œ์Šคํ…œ์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ž„ 
    • Failback: ํ˜ธ์ถœ ์‹คํŒจ ์‹œ ๋Œ€์ฒด ๋กœ์ง์„ ์‹คํ–‰ํ•˜์—ฌ ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ์„ ์œ ์ง€
    • ํƒ€์ž„์•„์›ƒ ์„ค์ •: ํ˜ธ์ถœ์˜ ์‘๋‹ต ์‹œ๊ฐ„์„ ์„ค์ •ํ•˜์—ฌ ๋А๋ฆฐ ์„œ๋น„์Šค ํ˜ธ์ถœ์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์Œ
    • ์žฌ์‹œ๋„: ์žฌ์‹œ๋„ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜์—ฌ ์ผ์‹œ์ ์ธ ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ ๋“ฑ์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์Œ

 

Spring Cloud ๊ตฌ์„ฑ ์š”์†Œ์˜ ํ™œ์šฉ

API ๊ฒŒ์ดํŠธ์›จ์ด

๐Ÿ“Œ Zuul

  • ๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ API ๊ฒŒ์ดํŠธ์›จ์ด๋กœ, ๋ชจ๋“  ์„œ๋น„์Šค ์š”์ฒญ์„ ์ค‘์•™์—์„œ ๊ด€๋ฆฌ
  • ์ฃผ์š” ํŠน์ง•:
    • ๋ผ์šฐํŒ…: ์š”์ฒญ URL์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์„œ๋น„์Šค๋กœ ์š”์ฒญ ์ „๋‹ฌ
    • ํ•„ํ„ฐ: ์š”์ฒญ ์ „ํ›„์— ๋‹ค์–‘ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํ•„ํ„ฐ ์ฒด์ธ ์ œ๊ณต
    • ๋ชจ๋‹ˆํ„ฐ๋ง: ์š”์ฒญ ๋กœ๊ทธ ๋ฐ ๋ฉ”ํŠธ๋ฆญ์„ ํ†ตํ•ด ์„œ๋น„์Šค ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ์Œ

๐Ÿ“Œ Cloud Gateway

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

 

๊ตฌ์„ฑ ๊ด€๋ฆฌ 

๐Ÿ“Œ Spring Cloud Config

  • Spring Cloud Config๋Š” ๋ถ„์‚ฐ๋œ ํ™˜๊ฒฝ์—์„œ ์ค‘์•™ ์ง‘์ค‘์‹ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์ œ๊ณต
  • ์ฃผ์š” ํŠน์ง•:
    • Config ์„œ๋ฒ„: ์ค‘์•™์—์„œ ์„ค์ • ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•˜๊ณ  ๊ฐ ์„œ๋น„์Šค์— ์ œ๊ณต
    • Config ํด๋ผ์ด์–ธํŠธ: Config ์„œ๋ฒ„์—์„œ ์„ค์ •์„ ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค
    • ์„ค์ •๊ฐฑ์‹ : ์„ค์ • ๋ณ€๊ฒฝ ์‹œ ์„œ๋น„์Šค ์žฌ์‹œ์ž‘ ์—†์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ˜์˜

 

Spring Cloud์˜ ์ ์šฉ ์‚ฌ๋ก€

Netflix

  • ๋ฐฐ๊ฒฝ
    • ๋„ทํ”Œ๋ฆญ์Šค๋Š” 2000๋…„๋Œ€ ํ›„๋ฐ˜ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ์žฅ์• ๋ฅผ ํ†ตํ•ด ์‹ฌ๊ฐํ•œ ์„œ๋น„์Šค ์žฅ์• ๋ฅผ ๊ฒช๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
    • ์‚ฌ์šฉ์ž๊ฐ€ ๊ธ‰๊ฒฉํžˆ ์ฆ๊ฐ€ ํ•จ์— ๋”ฐ๋ผ ๊ธฐ์กด ๋ชจ๋†€๋ฆฌํ‹ฑ ์•„ํ‚คํ…์ฒ˜๋กœ๋Š” ๋น ๋ฅด๊ฒŒ ์ฆ๊ฐ€ํ•˜๋Š” ํŠธ๋ž˜ํ”ฝ๊ณผ ์‚ฌ์šฉ์ž ์š”๊ตฌ๋ฅผ ๊ฐ๋‹นํ•˜๊ธฐ ์–ด๋ ค์›Œ์กŒ์Šต๋‹ˆ๋‹ค.
    • ๋˜ํ•œ ์‹ ๋ขฐ์„ฑ ๋†’๊ณ  ์ˆ˜ํ‰ ํ™•์žฅ์ด ๊ฐ€๋Šฅํ•œ ํด๋ผ์šฐ๋“œ ์‹œ์Šคํ…œ์œผ๋กœ ์ด์ „ํ•  ํ•„์š”์„ฑ์„ ๋А๊ผˆ์Šต๋‹ˆ๋‹ค.
    • ํญ๋ฐœ์ ์ธ ์‚ฌ์šฉ์ž ์ฆ๊ฐ€, ๋นˆ๋ฒˆํ•œ ์„œ๋น„์Šค ์žฅ์• , ์ธํ”„๋ผ ํ™•์žฅ์˜ ์–ด๋ ค์›€, ๋น ๋ฅธ ๊ธฐ๋Šฅ ๋ฐฐํฌ์˜ ํ•„์š”์„ฑ, ํด๋ผ์šฐ๋“œ ์ „ํ™˜ ๋“ฑ์˜ ๊ณ„๊ธฐ๋กœ ์ธํ•ด MSA ์ „ํ™˜์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • MSA๋กœ์˜ ์ „ํ™˜ ์ด์œ 
    • ํ™•์žฅ์„ฑ(Scalability): ๋„ทํ”Œ๋ฆญ์Šค๋Š” ๊ธ€๋กœ๋ฒŒ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ˆ˜๋ฐฑ๋งŒ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ”„๋ผ๊ฐ€ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.
    • ์‹ ๋ขฐ์„ฑ(Reliability): ํ•œ ๋ถ€๋ถ„์˜ ์žฅ์• ๊ฐ€ ์ „์ฒด ์‹œ์Šคํ…œ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.
    • ๊ฐœ๋ฐœ ์†๋„(Speed of Development): ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๋น ๋ฅด๊ฒŒ ๋ฐฐํฌํ•˜๊ณ , ๋…๋ฆฝ์ ์ธ ํŒ€์ด ๋™์‹œ์— ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ „ํ™˜ ๊ณผ์ •
    • ์„œ๋น„์Šค ๋ถ„๋ฆฌ: ๋„ทํ”Œ๋ฆญ์Šค๋Š” ๊ธฐ์กด ๋ชจ๋†€๋ฆฌ์‹ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
    • ์ž๋™ํ™” ๋„๊ตฌ ๋„์ž…: CI/CD ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•˜์—ฌ ์ฝ”๋“œ์˜ ๋นŒ๋“œ, ํ…Œ์ŠคํŠธ, ๋ฐฐํฌ ๊ณผ์ •์„ ์ž๋™ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ ์†๋„๋ฅผ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.
    • ์ž์ฒด ๋„๊ตฌ ๊ฐœ๋ฐœ: ๋„ทํ”Œ๋ฆญ์Šค๋Š” Hystrix, Eureka, Ribbon ๋“ฑ์˜ ๋„๊ตฌ๋ฅผ ๊ฐœ๋ฐœํ•˜์—ฌ ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ , ์žฅ์•  ๋ณต๊ตฌ, ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ๋“ฑ์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
    • ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ ํ™œ์šฉ: AWS์™€ ๊ฐ™์€ ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์„œ๋น„์Šค ํ™•์žฅ์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ
    • ํ–ฅ์ƒ๋œ ํ™•์žฅ์„ฑ: ๋„ทํ”Œ๋ฆญ์Šค๋Š” MSA ์ „ํ™˜ ์ดํ›„ ์ˆ˜์ฒœ ๊ฐœ์˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•˜๋ฉฐ, ๊ธ€๋กœ๋ฒŒ ์‚ฌ์šฉ์ž ์ฆ๊ฐ€์— ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
    • ๋†’์€ ๊ฐ€์šฉ์„ฑ: ์„œ๋น„์Šค ์žฅ์•  ์‹œ ๋‹ค๋ฅธ ์„œ๋น„์Šค์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๊ณ  ๋…๋ฆฝ์ ์œผ๋กœ ๋ณต๊ตฌํ•  ์ˆ˜ ์žˆ์–ด, ์‚ฌ์šฉ์ž์—๊ฒŒ ์ง€์†์ ์ธ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
    • ๋น ๋ฅธ ๋ฐฐํฌ ์ฃผ๊ธฐ: ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์‹ ์†ํ•˜๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด, ์‚ฌ์šฉ์ž ์š”๊ตฌ์— ๋น ๋ฅด๊ฒŒ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

 

*์ฐธ๊ณ  Spring Initializr์‚ฌ์ดํŠธ

https://start.spring.io/

์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ์— ํ•„์š”ํ•œ Spring Could์˜ ์ฃผ์š” ๊ธฐ๋Šฅ ์•Œ์•„๋ณด๊ธฐ 

  1. ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์˜ค๋”, ํ”„๋กœ๋•ํŠธ, ์œ ์ € ์„ธ ๊ฐ€์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ•„์š”ํ•˜๋ฉฐ, ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์ด ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.
  2. ์„œ๋น„์Šค ์ค‘๋ณต๊ณผ ๋ถ€ํ•˜ ๋ถ„์‚ฐ์„ ์œ„ํ•ด ๋ฆฌ๋ณธ(Ribbon)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ผ์šด๋“œ ๋กœ๋นˆ ๋ฐฉ์‹์˜ Load Balancing์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  3. ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์œ ๋ ˆ์นด(Eureka) ์„œ๋ฒ„๋ฅผ ๋‘๊ณ , ๊ฐ ์„œ๋น„์Šค๋Š” ์œ ๋ ˆ์นด ํด๋ผ์ด์–ธํŠธ๋กœ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค.
  4. ์™ธ๋ถ€ ์š”์ฒญ์€ API ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ํ†ตํ•ด ์ ‘์ˆ˜๋˜๊ณ , ์—”๋“œํฌ์ธํŠธ์— ๋”ฐ๋ผ ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค๋กœ ์š”์ฒญ์ด ๋ถ„๋ฐฐ๋ฉ๋‹ˆ๋‹ค.
  5. ๋กœ๊ทธ์ธ ๋“ฑ ์ธ์ฆ ์š”๊ตฌ๋Š” API ๊ฒŒ์ดํŠธ์›จ์ด์˜ ํ•„ํ„ฐ ๊ธฐ๋Šฅ์—์„œ ์ฒ˜๋ฆฌํ•˜์—ฌ, ์ธ์ฆ๋˜์ง€ ์•Š์€ ์š”์ฒญ์€ ์„œ๋น„์Šค ๋‚ด๋ถ€๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  6. ์„œ๋น„์Šค ์š”์ฒญ ๊ณผ์ •์—์„œ ์žฅ์•  ๋ฐœ์ƒ ์‹œ, Resilience4j Circuit Breaker๋ฅผ ํ™œ์šฉํ•ด ์‹คํŒจ ์ „ํŒŒ์™€ ๋‹ค์–‘ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.
  7. ์„ค์ • ํŒŒ์ผ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ณ„๋„์˜ ์ปจํ”ผ๊ทธ(Config) ์„œ๋ฒ„๋ฅผ ๋‘๊ณ , ๋ชจ๋“  ์„œ๋น„์Šค๊ฐ€ ์ค‘์•™ ์„œ๋ฒ„์—์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์„ค์ •์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.
  8. ์ง‘ํ‚จ(Zipkin) ๋“ฑ ๋ถ„์‚ฐ ์ถ”์  ๋„๊ตฌ๋กœ ๊ฐ ์„œ๋น„์Šค ๊ฐ„ ํ˜ธ์ถœ ๊ฒฝ๋กœ์™€ ์ „์ฒด ํ๋ฆ„์„ ์ง๊ด€์ ์œผ๋กœ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  9. ์‹œ์Šคํ…œ ์„ค๊ณ„ ๋ฐ ์„ค๋ช… ๋Šฅ๋ ฅ ๊ฐ•ํ™”๋ฅผ ์œ„ํ•ด ๊ตฌ์„ฑ๋„ ์ž‘์„ฑ์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ์ง์ ‘ ๊ทธ๋ฆผ์„ ๊ทธ๋ฆด ๊ฒƒ์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค

 

๐Ÿ—จ๏ธ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ

๊ตฌ์„ฑ ์š”์†Œ ์—ญํ•  (๊ฐ„๋‹จ ์š”์•ฝ) ๋น„์œ  ์ •ํ™•ํ•œ ๊ธฐ์ˆ ์  ์ •์˜ (Precise Meaning)
@EnableFeignClients Feign ํ™œ์„ฑํ™” ๋ฒ„ํŠผ ๊ณต์žฅ ๊ฐ€๋™ ์Šค์œ„์น˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ @FeignClient๊ฐ€ ๋ถ™์€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ฐพ์•„ Bean(๊ฐ์ฒด)์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ์Šค์บ” ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•จ.
@FeignClient ํ˜ธ์ถœํ•  ๋Œ€์ƒ ์ง€์ • ๋ฆฌ๋ชจ์ปจ์˜ ์ฑ„๋„ ์™ธ๋ถ€ ์„œ๋น„์Šค์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•œ HTTP ํด๋ผ์ด์–ธํŠธ๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ์ •์˜ํ•˜๋ฉฐ, name ์†์„ฑ์„ ํ†ตํ•ด Eureka์— ๋“ฑ๋ก๋œ ์„œ๋น„์Šค ID์™€ ๋งคํ•‘ํ•จ.
Eureka Client ์„œ๋น„์Šค ์ฃผ์†Œ๋ก ๊ด€๋ฆฌ ์ตœ์‹  ์ „ํ™”๋ฒˆํ˜ธ๋ถ€ Eureka ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ „์ฒด ์„œ๋น„์Šค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(์ฃผ์†Œ ๋ชฉ๋ก)๋ฅผ ๋กœ์ปฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚ด๋ ค๋ฐ›์•„ ์œ ์ง€ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ์—์ด์ „ํŠธ.
Ribbon (๋˜๋Š” LoadBalancer) ์ธ์Šคํ„ด์Šค ์„ ํƒ ์ „ํ™” ๊ฑธ ์ƒ๋Œ€ ์„ ํƒ Eureka Client๊ฐ€ ๊ฐ€์ ธ์˜จ ๋ชฉ๋ก ์ค‘ ์–ด๋–ค ์„œ๋ฒ„ ์ฃผ์†Œ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ์ง€ ์•Œ๊ณ ๋ฆฌ์ฆ˜(Round Robin ๋“ฑ)์— ๋”ฐ๋ผ ๊ฒฐ์ •ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ.

 

์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ ๊ฐœ์š”

์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๋ž€?

  • ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ๊ฐ ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์ฐพ์•„์ฃผ๋Š” ๊ธฐ๋Šฅ
  • ๊ฐ ์„œ๋น„์Šค๋Š” ๋“ฑ๋ก ์„œ๋ฒ„์— ์ž์‹ ์˜ ์œ„์น˜๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ๋‹ค๋ฅธ ์„œ๋น„์Šค๋Š” ์ด๋ฅผ ์กฐํšŒํ•˜์—ฌ ํ†ต์‹ 
  • ์ฃผ์š” ๊ธฐ๋Šฅ์œผ๋กœ๋Š” ์„œ๋น„์Šค ๋“ฑ๋ก, ์„œ๋น„์Šค ์กฐํšŒ, ํ—ฌ์Šค ์ฒดํฌ ๋“ฑ์ด ์žˆ์Œ

 

Eureka ๊ฐœ์š”

Eureka๋ž€?

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

Eureka ์„œ๋ฒ„ ์„ค์ •

  • Eureka ์„œ๋ฒ„๋Š” ์„œ๋น„์Šค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์ค‘์•™ ์„œ๋ฒ„
  • ์„œ๋ฒ„ ์„ค์ • ํŒŒ์ผ ์˜ˆ์‹œ:
server:
  port: 8761

eureka:
  client:
    register-with-eureka: false  # ๋‹ค๋ฅธ Eureka ์„œ๋ฒ„์— ์ด ์„œ๋ฒ„๋ฅผ ๋“ฑ๋กํ•˜์ง€ ์•Š์Œ
    fetch-registry: false  # ๋‹ค๋ฅธ Eureka ์„œ๋ฒ„์˜ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š์Œ
  server:
    enable-self-preservation: false  # ์ž๊ธฐ ๋ณดํ˜ธ ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”
  • ํ•ด๋‹น ์„ค์ •์„ ํ†ตํ•ด Eureka ์„œ๋ฒ„๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋„๋ก ์ค€๋น„

 

Eureka ํด๋ผ์ด์–ธํŠธ ์„ค์ •

  • ๊ฐ ์„œ๋น„์Šค๋Š” Eureka ์„œ๋ฒ„์— ์ž์‹ ์„ ๋“ฑ๋กํ•ด์•ผ ํ•จ
  • spring-cloud-starter-netflix-eureka-client ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„๋งŒ ์„ค์ •ํŒŒ์ผ์— ์žˆ์œผ๋ฉด Eureka์— ๋“ฑ๋ก๋จ
  • ํด๋ผ์ด์–ธํŠธ ์„ค์ • ํŒŒ์ผ ์˜ˆ์‹œ:
spring:
  application:
    name: my-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/  # Eureka ์„œ๋ฒ„ URL
    register-with-eureka: true  # Eureka ์„œ๋ฒ„์— ๋“ฑ๋ก
    fetch-registry: true  # Eureka ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
  instance:
    hostname: localhost  # ํด๋ผ์ด์–ธํŠธ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„
    prefer-ip-address: true  # IP ์ฃผ์†Œ ์‚ฌ์šฉ ์„ ํ˜ธ
    lease-renewal-interval-in-seconds: 30  # ๋ฆฌ์Šค ๊ฐฑ์‹  ๊ฐ„๊ฒฉ
    lease-expiration-duration-in-seconds: 90  # ๋ฆฌ์Šค ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„

 

์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ๋””์Šค์ปค๋ฒ„๋ฆฌ

์„œ๋น„์Šค ๋“ฑ๋ก

  • ๊ฐ ์„œ๋น„์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Eureka ์„œ๋ฒ„์— ์ž์‹ ์˜ ์œ„์น˜๋ฅผ ๋“ฑ๋ก
  • spring-cloud-starter-netflix-eureka-client ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„๋งŒ ์„ค์ •ํŒŒ์ผ์— ์žˆ์œผ๋ฉด Eureka์— ๋“ฑ๋ก๋จ

์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ

  • ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Eureka ์„œ๋ฒ„์—์„œ ํ•„์š”ํ•œ ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ์กฐํšŒ
  • RestTemplate์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
    • ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Eureka ์„œ๋ฒ„์—์„œ ํ•„์š”ํ•œ ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
    • Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ @LoadBalanced ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ RestTemplate์— ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    @SpringBootApplication
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    
        // [ํ•ต์‹ฌ] RestTemplate์— ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ์„ค์ •
        // Eureka์— ๋“ฑ๋ก๋œ '์„œ๋น„์Šค ์ด๋ฆ„'์„ ์‹ค์ œ IP ์ฃผ์†Œ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•จ
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    @RestController
    public class MyRestTemplateController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/get-data-rest")
        public String getDataWithRestTemplate() {
            // [์ค‘์š”] ์‹ค์ œ IP/ํฌํŠธ ๋Œ€์‹  Eureka์— ๋“ฑ๋ก๋œ '์„œ๋น„์Šค ์ด๋ฆ„(my-service)'์„ ์‚ฌ์šฉ
            // ๋‚ด๋ถ€์ ์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๊ฐ€ ์ด ์ด๋ฆ„์„ ๋ณด๊ณ  ๊ฐ€์šฉ ์„œ๋ฒ„ ๋ฆฌ์ŠคํŠธ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•จ
            String serviceUrl = "<http://my-service/api/data>";
            // GET ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ String ํด๋ž˜์Šค ํ˜•ํƒœ๋กœ ๋ฐ›์•„์˜ด
            return restTemplate.getForObject(serviceUrl, String.class);
        }
    }
  • FeignClient๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
    • ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Eureka ์„œ๋ฒ„์—์„œ ํ•„์š”ํ•œ ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
    • Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ FeignClient๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„ํŽธํ•˜๊ฒŒ ์„œ๋น„์Šค ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
@SpringBootApplication
// [ํ•„์ˆ˜] ํ•ด๋‹น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ FeignClient ์‚ฌ์šฉ์„ ํ™œ์„ฑํ™”ํ•จ
@EnableFeignClients
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
// [ํ•ต์‹ฌ] 'my-service'๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง„ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๊ฒ ๋‹ค๊ณ  ์„ ์–ธ
@FeignClient(name = "my-service")
public interface MyServiceClient {

    // ์™ธ๋ถ€ ์„œ๋ฒ„์˜ "/api/data" ๊ฒฝ๋กœ๋กœ GET ์š”์ฒญ์„ ๋งคํ•‘
    // ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ๋งŒ์œผ๋กœ ์›๊ฒฉ ์„œ๋ฒ„์˜ API๊ฐ€ ์‹คํ–‰๋จ
    @GetMapping("/api/data")
    String getData();
}
  • name = "my-service": ํ˜ธ์ถœํ•  ์ƒ๋Œ€๋ฐฉ์˜ ์ด๋ฆ„ (์œ ๋ ˆ์นด ๋“ฑ๋ก ๋ช…์นญ)
  • @GetMapping("/api/data"): ์ƒ๋Œ€๋ฐฉ ์„œ๋ฒ„์— ์ž‘์„ฑ๋œ ์‹ค์ œ API ์ฃผ์†Œ
  • ์›๊ฒฉ ์„œ๋ฒ„ (Remote Server): ์‹ค์ œ๋กœ ์ผ์„ ์ฒ˜๋ฆฌํ•ด์„œ ์‘๋‹ต์„ ์ค„ ์ƒ๋Œ€๋ฐฉ ์„œ๋น„์Šค
๐Ÿ“Œ @FeignClient name ์†์„ฑ ํ•ต์‹ฌ ์ •๋ฆฌ
  1. 1. name์˜ ๋ณธ์งˆ
    • ์ •์˜: ์œ ๋ ˆ์นด(Eureka) ์„œ๋ฒ„์— ๋“ฑ๋ก๋œ ๋Œ€์ƒ ์„œ๋น„์Šค์˜ ๊ณ ์œ  ID.
    • ์—ญํ• : "์–ด๋–ค ๋…€์„๋“ค์„ ๋ถ€๋ฅผ ๊ฒƒ์ธ๊ฐ€?"๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๊ทธ๋ฃน ์ด๋ฆ„.
  2. ๋™์ž‘ ํ”„๋กœ์„ธ์Šค (3๋‹จ๊ณ„)
    1. ๋ชฉ๋ก Fetch (์ „์ฒด ์กฐํšŒ)
      • Feign์ด ์œ ๋ ˆ์นด์—๊ฒŒ "my-service ์ฃผ์†Œ ๋‹ค ์ค˜!"๋ผ๊ณ  ์š”์ฒญ.
      • ์œ ๋ ˆ์นด๋Š” ํ•ด๋‹น ์ด๋ฆ„์„ ๊ฐ€์ง„ ๋ชจ๋“  ์ธ์Šคํ„ด์Šค(A, B, C)์˜ ์ฃผ์†Œ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜.
    2. ์บ์‹ฑ (๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ)
      • ์šฐ๋ฆฌ ์„œ๋ฒ„(ํ˜ธ์ถœ์ž)๋Š” ๋ฐ›์€ ์ฃผ์†Œ๋ก์„ ์ž๊ธฐ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•ด ๋‘๊ณ  ๊ด€๋ฆฌํ•จ.
    3. ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ (์„ ํƒ ํ˜ธ์ถœ)
      • Ribbon์ด ๋ฉ”๋ชจ๋ฆฌ ์† ๋ชฉ๋ก ์ค‘ ๋”ฑ ํ•˜๋‚˜๋ฅผ ์„ ํƒ (๊ธฐ๋ณธ: ๋ผ์šด๋“œ ๋กœ๋นˆ).
    4. ๊ฒฐ์ •๋œ ์„œ๋ฒ„ ์ฃผ์†Œ๋กœ๋งŒ ์ตœ์ข… HTTP ์š”์ฒญ์„ ๋ณด๋ƒ„.

์Šคํ”„๋ง์€ ์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ณด๊ณ  ์ด๋ ‡๊ฒŒ ํ•ด์„ํ•ฉ๋‹ˆ๋‹ค:

"์•„ํ•˜! ๊ฐœ๋ฐœ์ž๊ฐ€ getData()๋ผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ๋‚˜๋Š” my-service๋ผ๋Š” ์„œ๋ฒ„ ์ฃผ์†Œ๋ฅผ ์ฐพ์•„์„œ ๊ทธ ๋’ค์— /api/data๋ฅผ ๋ถ™์ด๊ณ  GET ๋ฐฉ์‹์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ๋˜๋Š”๊ตฌ๋‚˜!"
  • โœ๏ธ ํ•„๊ธฐ์šฉ ์š”์•ฝ
    • Controller์˜ @GetMapping: ์‚ฌ์šฉ์ž๊ฐ€ ๋‚˜๋ฅผ ๋ถ€๋ฅด๋Š” ์ฃผ์†Œ
    • FeignClient์˜ @GetMapping: ๋‚ด๊ฐ€ ๋‹ค๋ฅธ ์„œ๋ฒ„๋ฅผ ๋ถ€๋ฅด๋Š” ์ฃผ์†Œ (๊ทœ๊ฒฉ ์ •์˜)
    • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์“ฐ๋Š” ์ด์œ : "์–ด๋–ค ์ฃผ์†Œ๋กœ, ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋ณด๋‚ผ์ง€"๋งŒ ์ ์–ด๋‘๋ฉด, ๋ณต์žกํ•œ ํ†ต์‹  ์ฝ”๋“œ(URL ์—ฐ๊ฒฐ, ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋“ฑ)๋ฅผ ์Šคํ”„๋ง์ด ๋Œ€์‹  ์งœ์ฃผ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
@RestController
public class MyFeignClientController {

    @Autowired
    private MyServiceClient myServiceClient;

    @GetMapping("/get-data-feign")
    public String getDataWithFeignClient() {
        // [์žฅ์ ] ๋งˆ์น˜ ๊ฐ™์€ ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ๋กœ์ปฌ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋“ฏ์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ
        // ๋‚ด๋ถ€์ ์œผ๋กœ Eureka ์กฐํšŒ + ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์ด ๋ชจ๋‘ ์ˆ˜ํ–‰๋จ
        return myServiceClient.getData();
    }
}
  • ์ž…๊ตฌ ์—ด๊ธฐ: @GetMapping("/get-data-feign")
    • ์‚ฌ์šฉ์ž๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์— http://localhost:8080/get-data-feign์ด๋ผ๊ณ  ์ž…๋ ฅํ•˜๊ณ  ์—”ํ„ฐ๋ฅผ ์น˜๋ฉด ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ๋ช…๋ น ์ „๋‹ฌ: return myServiceClient.getData();
    • ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ฐ›์€ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ "์ž, ์ด์ œ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘” FeignClient ๋ฆฌ๋ชจ์ปจ์„ ๋ˆŒ๋Ÿฌ์„œ ๋‹ค๋ฅธ ์„œ๋ฒ„(my-service)์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€!"๋ผ๊ณ  ์‹œํ‚ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ ๋ฐฐ๋‹ฌ:
    • ๋‹ค๋ฅธ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ์ž(๋ธŒ๋ผ์šฐ์ €)์—๊ฒŒ ํ™”๋ฉด์œผ๋กœ ๋ณด์—ฌ์ฃผ๋ฉฐ ๋งˆ๋ฌด๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

 

ํ—ฌ์Šค ์ฒดํฌ ๋ฐ ์žฅ์•  ์ฒ˜๋ฆฌ

  • ํ—ฌ์Šค ์ฒดํฌ
    • Eureka ์„œ๋ฒ„๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์—ฌ ๊ฐ€์šฉ์„ฑ์„ ์œ ์ง€
    • ๊ธฐ๋ณธ ํ—ฌ์Šค ์ฒดํฌ ์—”๋“œํฌ์ธํŠธ /actuator/health๋ฅผ ์‚ฌ์šฉ
  • ์žฅ์•  ์ฒ˜๋ฆฌ
    • ์„œ๋น„์Šค ์žฅ์•  ์‹œ Eureka ์„œ๋ฒ„๋Š” ํ•ด๋‹น ์ธ์Šคํ„ด์Šค๋ฅผ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์—์„œ ์ œ๊ฑฐํ•˜์—ฌ ๋‹ค๋ฅธ ์„œ๋น„์Šค์˜ ์ ‘๊ทผ์„ ์ฐจ๋‹จ

 

Eureka์˜ ๊ณ ๊ฐ€์šฉ์„ฑ ๊ตฌ์„ฑ

ํด๋Ÿฌ์Šคํ„ฐ ๊ตฌ์„ฑ

  • Eureka ์„œ๋ฒ„์˜ ๊ณ ๊ฐ€์šฉ์„ฑ์„ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Œ
  • ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ๊ณ ๊ฐ€์šฉ์„ฑ์„ ์œ ์ง€ํ•˜๋ฉฐ, ๊ฐ ์ธ์Šคํ„ด์Šค๋Š” ์„œ๋กœ๋ฅผ ํ”ผ์–ด๋กœ ๋“ฑ๋กํ•˜์—ฌ ์ƒํ˜ธ ๋ฐฑ์—…
  • ์„ค์ • ํŒŒ์ผ ์˜ˆ์‹œ:
eureka:
  client:
    service-url:
      defaultZone: http://eureka-peer1:8761/eureka/,http://eureka-peer2:8761/eureka/
  • Eureka ์„œ๋ฒ„๋ฅผ ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค๋กœ ๊ตฌ์„ฑํ•  ๋•Œ ๊ฐ ์„œ๋ฒ„์˜ ํ”ผ์–ด ์„ค์ •์„ ํ†ตํ•ด ์„œ๋กœ๋ฅผ ์ธ์‹ํ•˜๊ณ  ๋ฐฑ์—…ํ•  ์ˆ˜ ์žˆ์Œ

 

์‹ค์Šต

๐Ÿ’ก Eureka server ํ•˜๋‚˜์— ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ์ธ์Šคํ„ด์Šค 2๊ฐœ๋ฅผ ์—ฐ๊ฒฐํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • Eureka server
    • start.spring.io ์— ์ ‘์†ํ•˜์—ฌ Eureka Service ๋””ํŽœ๋˜์‹œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
  • service instance
    • start.spring.io ์— ์ ‘์†ํ•˜์—ฌ Eureka Discovery Client ๋””ํŽœ๋˜์‹œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
    • Artifact ๋ฅผ first, second ๋กœ ๋ฐ”๊ฟ”์„œ GENERATE ํ•˜์—ฌ 2๊ฐœ๋ฅผ ๋‹ค์šด
ServerApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class ServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServerApplication.class, args);
	}

}
src/resource/application.properties [server]
spring.application.name=server

server.port=19090

# ์œ ๋ ˆ์นด ์„œ๋ฒ„์— ์ž์‹ ์„ ๋“ฑ๋กํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
# true๋กœ ์„ค์ •ํ•˜๋ฉด ์œ ๋ ˆ์นด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์œ ๋ ˆ์นด ์„œ๋ฒ„์— ์ž์‹ ์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
# ์œ ๋ ˆ์นด ์„œ๋ฒ„์—์„œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ false๋กœ ์„ค์ •ํ•˜์—ฌ, ์„œ๋ฒ„๊ฐ€ ์ž๊ธฐ ์ž์‹ ์„ ๋“ฑ๋กํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
eureka.client.register-with-eureka=false

# ์œ ๋ ˆ์นด ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ฌ์ง€ ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
# true๋กœ ์„ค์ •ํ•˜๋ฉด ์œ ๋ ˆ์นด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์œ ๋ ˆ์นด ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋‹ค๋ฅธ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
# ์œ ๋ ˆ์นด ์„œ๋ฒ„์—์„œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ false๋กœ ์„ค์ •ํ•˜์—ฌ, ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
eureka.client.fetch-registry=false

# ์œ ๋ ˆ์นด ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค์˜ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
# ์œ ๋ ˆ์นด ์„œ๋ฒ„๊ฐ€ ์ž์‹ ์˜ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ๋‹ค๋ฅธ ์„œ๋น„์Šค์— ์•Œ๋ฆด ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
eureka.instance.hostname=localhost

# ์œ ๋ ˆ์นด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์œ ๋ ˆ์นด ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ ์„œ๋น„์Šค URL์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
# ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์œ ๋ ˆ์นด ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜๊ณ  ๋“ฑ๋กํ•˜๊ฑฐ๋‚˜ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉํ•  URL์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
src/resource/application.properties [first]
spring.application.name=first

server.port=19091

# ์œ ๋ ˆ์นด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์œ ๋ ˆ์นด ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ ์„œ๋น„์Šค URL์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
# ์œ ๋ ˆ์นด ์„œ๋ฒ„์˜ ํฌํŠธ์™€ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ์ •ํ™•ํžˆ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
src/resource/application.properties [second]
spring.application.name=second

server.port=19092

# ์œ ๋ ˆ์นด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์œ ๋ ˆ์นด ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ ์„œ๋น„์Šค URL์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
# ์œ ๋ ˆ์นด ์„œ๋ฒ„์˜ ํฌํŠธ์™€ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ์ •ํ™•ํžˆ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/

 

  • Run
    • ์œ ๋ ˆ์นด ์„œ๋ฒ„ → first → second ์ˆœ์œผ๋กœ ์‹คํ–‰
    • http://localhost:19090/ ์œผ๋กœ ์ ‘์†ํ•˜๋ฉด ๋‘ ๊ฐœ์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ

 

๐Ÿ—จ๏ธ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ๊ฐœ์š”

๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์ด๋ž€?

  • ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์€ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ์„ ์—ฌ๋Ÿฌ ์„œ๋ฒ„๋กœ ๋ถ„์‚ฐ์‹œ์ผœ ์„œ๋ฒ„์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ณ , ์‹œ์Šคํ…œ์˜ ์„ฑ๋Šฅ๊ณผ ๊ฐ€์šฉ์„ฑ์„ ๋†’์ด๋Š” ๊ธฐ์ˆ 
  • ์„œ๋ฒ„ ๊ฐ„ ํŠธ๋ž˜ํ”ฝ์„ ๊ณ ๋ฅด๊ฒŒ ๋ถ„๋ฐฐํ•˜์—ฌ ํŠน์ • ์„œ๋ฒ„์— ๋ถ€ํ•˜๊ฐ€ ์ง‘์ค‘๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€
  • ์ข…๋ฅ˜: ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ, ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์ด๋ž€?

  • ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ ‘ ์—ฌ๋Ÿฌ ์„œ๋ฒ„ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹
  • ํด๋ผ์ด์–ธํŠธ๋Š” ์„œ๋ฒ„์˜ ๋ชฉ๋ก์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰

 

FeignClient ๊ฐœ์š”

FeignClient๋ž€?

  • FeignClient๋Š” Spring Cloud์—์„œ ์ œ๊ณตํ•˜๋Š” HTTP ํด๋ผ์ด์–ธํŠธ๋กœ, ์„ ์–ธ์ ์œผ๋กœ RESTful ์›น ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Œ
  • Eureka์™€ ๊ฐ™์€ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์™€ ์—ฐ๋™ํ•˜์—ฌ ๋™์ ์œผ๋กœ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ์กฐํšŒํ•˜๊ณ  ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰
FeignClient๋Š” ๋‹ค๋ฅธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ REST API๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•œ HTTP ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋ฅผ, ์ธํ„ฐํŽ˜์ด์Šค ์„ ์–ธ๋งŒ์œผ๋กœ ์•Œ์•„์„œ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋งค์šฐ ํŽธ๋ฆฌํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, @FeignClient(name = "product-service") ๋ผ๊ณ  ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋ฉด, Spring Cloud๋Š” Eureka(์„œ๋น„์Šค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ)๋ฅผ ํ†ตํ•ด product-service๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง„ ์„œ๋ฒ„๋“ค์˜ ์‹ค์ œ IP ์ฃผ์†Œ์™€ ํฌํŠธ๋ฅผ ์•Œ์•„๋ƒ…๋‹ˆ๋‹ค. ๋งŒ์•ฝ product-service ์„œ๋ฒ„๊ฐ€ 3๋Œ€ ๋–  ์žˆ๋‹ค๋ฉด, FeignClient๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ(Spring Cloud LoadBalancer)์™€ ์—ฐ๋™๋˜์–ด 3๋Œ€์˜ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ์ ์ ˆํžˆ ๋ถ„์‚ฐ์‹œ์ผœ ์ค๋‹ˆ๋‹ค.
์šฉ์–ด ํ•ต์‹ฌ ์˜๋ฏธ ํ•œ ์ค„ ์š”์•ฝ
HTTP ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ๋Œ€๋ฆฌ์ธ ์„œ๋ฒ„๊ฐ€ ๋‹ค๋ฅธ ์„œ๋ฒ„์˜ API๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ
์„ ์–ธ์  ํ˜ธ์ถœ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ ๊ตฌ์ฒด์ ์ธ ๋กœ์ง ๊ตฌํ˜„ ์—†์ด ์ธํ„ฐํŽ˜์ด์Šค์™€ ์–ด๋…ธํ…Œ์ด์…˜๋งŒ์œผ๋กœ ํ†ต์‹ ํ•˜๋Š” ๋ฐฉ์‹
FeignClient ์Šค๋งˆํŠธํ•œ ํ†ต์‹  ๋„๊ตฌ ์ธํ„ฐํŽ˜์ด์Šค ์„ ์–ธ๋งŒ์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๊นŒ์ง€ ์•Œ์•„์„œ ํ•ด์ฃผ๋Š” HTTP ํด๋ผ์ด์–ธํŠธ

FeignClient์˜ ์ฃผ์š” ํŠน์ง•

  • ์„ ์–ธ์  HTTP ํด๋ผ์ด์–ธํŠธ: ์ธํ„ฐํŽ˜์ด์Šค์™€ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ REST API๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Œ
  • Eureka ์—ฐ๋™: Eureka์™€ ํ†ตํ•ฉํ•˜์—ฌ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก์„ ๋™์ ์œผ๋กœ ์กฐํšŒํ•˜๊ณ  ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰
  • ์ž๋™ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: Ribbon์ด ํ†ตํ•ฉ๋˜์–ด ์žˆ์–ด ์ž๋™์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰

 

Ribbon ๊ฐœ์š”

Ribbon์ด๋ž€?

  • ๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๊ฐ„์˜ ๋ถ€ํ•˜๋ฅผ ๋ถ„์‚ฐ
  • ๋‹ค์–‘ํ•œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ง€์›ํ•˜๋ฉฐ, Eureka์™€ ๊ฐ™์€ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์™€ ์—ฐ๋™ํ•˜์—ฌ ์‚ฌ์šฉ

Ribbon์˜ ์ฃผ์š” ํŠน์ง•

  • ์„œ๋ฒ„ ๋ฆฌ์ŠคํŠธ ์ œ๊ณต์ž: Eureka ๋“ฑ์œผ๋กœ๋ถ€ํ„ฐ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๋ฆฌ์ŠคํŠธ๋ฅผ ์ œ๊ณต๋ฐ›์•„ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์— ์‚ฌ์šฉ
  • ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜: ๋ผ์šด๋“œ ๋กœ๋นˆ, ๊ฐ€์ค‘์น˜ ๊ธฐ๋ฐ˜ ๋“ฑ ๋‹ค์–‘ํ•œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ง€์›
  • Failover: ์š”์ฒญ ์‹คํŒจ ์‹œ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค๋กœ ์ž๋™ ์ „ํ™˜

 

FeignClient์™€ Ribbon ์„ค์ •

๊ธฐ๋ณธ ์„ค์ •

  • FeignClient์™€ Ribbon์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ
build.gradle ํŒŒ์ผ ์˜ˆ์‹œ:
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •:
@SpringBootApplication
@EnableFeignClients
public class MyServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyServiceApplication.class, args);
    }
}

 

FeignClient ์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑ

  • FeignClient ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์„œ๋น„์Šค ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰
์˜ˆ์‹œ ์ฝ”๋“œ
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "my-service")
public interface MyServiceClient {

    @GetMapping("/endpoint")
    String getResponse(@RequestParam(name = "param") String param);
}

 

๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜

๋ผ์šด๋“œ ๋กœ๋นˆ

  • ๋ผ์šด๋“œ ๋กœ๋นˆ: ๊ฐ ์„œ๋ฒ„์— ์ˆœ์ฐจ์ ์œผ๋กœ ์š”์ฒญ์„ ๋ถ„๋ฐฐํ•˜๋Š” ๋ฐฉ์‹
  • ๊ฐ„๋‹จํ•˜๊ณ  ๊ณตํ‰ํ•˜๊ฒŒ ํŠธ๋ž˜ํ”ฝ์„ ๋ถ„์‚ฐ

๊ฐ€์ค‘์น˜ ๊ธฐ๋ฐ˜ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ

  • ๊ฐ€์ค‘์น˜ ๊ธฐ๋ฐ˜ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: ๊ฐ ์„œ๋ฒ„์— ๊ฐ€์ค‘์น˜๋ฅผ ๋ถ€์—ฌํ•˜๊ณ , ๊ฐ€์ค‘์น˜์— ๋น„๋ก€ํ•˜์—ฌ ์š”์ฒญ์„ ๋ถ„๋ฐฐํ•˜๋Š” ๋ฐฉ์‹
  • ์„œ๋ฒ„์˜ ์„ฑ๋Šฅ์ด๋‚˜ ๋„คํŠธ์›Œํฌ ์ƒํƒœ์— ๋”ฐ๋ผ ๊ฐ€์ค‘์น˜๋ฅผ ์กฐ์ ˆ

๊ธฐํƒ€ ์•Œ๊ณ ๋ฆฌ์ฆ˜

  • ์ตœ์†Œ ์—ฐ๊ฒฐ: ํ˜„์žฌ ์—ฐ๊ฒฐ๋œ ํด๋ผ์ด์–ธํŠธ ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ์ ์€ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹
  • ์‘๋‹ต ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜: ์„œ๋ฒ„์˜ ์‘๋‹ต ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ๊ฐ€์žฅ ๋น ๋ฅธ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹

 

FeignClient์™€ Eureka ์—ฐ๋™

Eureka ์„ค์ •

  • Eureka์™€ FeignClient๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋™์ ์œผ๋กœ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ์กฐํšŒํ•˜์—ฌ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰
application.yml ํŒŒ์ผ ์„ค์ • ์˜ˆ์‹œ:
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

 

FeignClient์™€ Ribbon ๋™์ž‘ ์›๋ฆฌ

  1. ์„œ๋น„์Šค ์ด๋ฆ„: @FeignClient(name = "my-service") ์–ด๋…ธํ…Œ์ด์…˜์€ Eureka์— ๋“ฑ๋ก๋œ ์„œ๋น„์Šค ์ด๋ฆ„์„ ์ฐธ์กฐ
  2. ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ์กฐํšŒ: Eureka ์„œ๋ฒ„์—์„œ my-service๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋“ฑ๋ก๋œ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก์„ ์กฐํšŒ
  3. ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: ์กฐํšŒ๋œ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Ribbon์„ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰
  4. ์š”์ฒญ ๋ถ„๋ฐฐ: ์—ฌ๋Ÿฌ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, Round Robin ๋˜๋Š” ๋‹ค๋ฅธ ์„ค์ •๋œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋ถ„๋ฐฐ
์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ์ƒ๊ฐํ•ด ๋ณด๊ธฐ
  • ๐Ÿ’ก Order ์„œ๋น„์Šค๋Š” Product ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
    • Order ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค: 1๊ฐœ
    • Product ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค: 3๊ฐœ 
  1. Order ์„œ๋น„์Šค ์‹คํ–‰: Order ์„œ๋น„์Šค๊ฐ€ ์‹คํ–‰๋˜๋ฉด Eureka ์„œ๋ฒ„์—์„œ Product ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  2. Product ์„œ๋น„์Šค ํ˜ธ์ถœ: Order ์„œ๋น„์Šค์—์„œ Product ์„œ๋น„์Šค์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด FeignClient๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  3. Ribbon์„ ํ†ตํ•œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: FeignClient๋Š” Ribbon์„ ํ†ตํ•ด 3๊ฐœ์˜ Product ์ธ์Šคํ„ด์Šค ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์—ฌ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ Round Robin ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ๋ถ„๋ฐฐํ•ฉ๋‹ˆ๋‹ค.
  4. ์‘๋‹ต ์ฒ˜๋ฆฌ: ์„ ํƒ๋œ Product ์ธ์Šคํ„ด์Šค์—์„œ ์‘๋‹ต์„ ๋ฐ›์•„ Order ์„œ๋น„์Šค์— ๋ฐ˜ํ™˜ํ•˜๊ณ , ์ตœ์ข…์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ์‘๋‹ต์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

 

FeignClient ์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑ ์˜ˆ์‹œ

  • FeignClient ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์„œ๋น„์Šค ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰
์˜ˆ์‹œ ์ฝ”๋“œ:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @FeignClient: ์ด ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ์ž„์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.
 * name = "my-service": Eureka ์„œ๋ฒ„์— ๋“ฑ๋ก๋œ ๋Œ€์ƒ ์„œ๋น„์Šค์˜ ์ด๋ฆ„์„ ์ ์Šต๋‹ˆ๋‹ค.
 * Ribbon์€ ์ด ์ด๋ฆ„์„ ๋ณด๊ณ  "์–ด๋–ค ์ฃผ์†Œ(IP)๋กœ ๋ณด๋‚ผ์ง€" ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
 */
@FeignClient(name = "my-service")
public interface MyServiceClient {

    /**
     * @GetMapping: ๋Œ€์ƒ ์„œ๋ฒ„(my-service)์˜ "/endpoint"๋ผ๋Š” ๊ฒฝ๋กœ๋กœ GET ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.
     * @RequestParam: ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์ฃผ์†Œ์ฐฝ์— ?param=๊ฐ’ ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ™์—ฌ์„œ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
     * ๋ฐ˜ํ™˜ ํƒ€์ž…(String): ์ƒ๋Œ€๋ฐฉ ์„œ๋ฒ„๊ฐ€ ์‘๋‹ต์œผ๋กœ ๋Œ๋ ค์ค„ ๋ฐ์ดํ„ฐ ํ˜•์‹์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
     */
    @GetMapping("/endpoint")
    String getResponse(@RequestParam(name = "param") String param);
}

์„œ๋น„์Šค ์‚ฌ์šฉ ์˜ˆ์ œ

  • FeignClient๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ์„œ๋น„์Šค ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰
์˜ˆ์‹œ ์ฝ”๋“œ:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    // ์œ„์—์„œ ๋งŒ๋“  MyServiceClient๋ฅผ ์Šคํ”„๋ง์ด ์ž๋™์œผ๋กœ ๊ตฌํ˜„ํ•ด์„œ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค(DI).
    @Autowired
    private MyServiceClient myServiceClient;

    /**
     * ํด๋ผ์ด์–ธํŠธ(์‚ฌ์šฉ์ž ๋ธŒ๋ผ์šฐ์ € ๋“ฑ)๊ฐ€ ์ด ์„œ๋ฒ„์˜ "/call-service"๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
     */
    @GetMapping("/call-service")
    public String callService(@RequestParam String param) {
        
        /*
         * [ํ•ต์‹ฌ ๋™์ž‘ ๊ณผ์ •]
         * 1. myServiceClient.getResponse(param)๋ฅผ ์‹คํ–‰ํ•˜๋ฉด,
         * 2. Feign์ด "my-service"๋ผ๋Š” ์ด๋ฆ„์„ ๋ณด๊ณ  Eureka์—์„œ ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
         * 3. Ribbon์ด ๋ผ์šด๋“œ ๋กœ๋นˆ ๋ฐฉ์‹์œผ๋กœ ์ธ์Šคํ„ด์Šค ํ•˜๋‚˜๋ฅผ ๊ณ ๋ฆ…๋‹ˆ๋‹ค.
         * 4. ์„ ํƒ๋œ ์ธ์Šคํ„ด์Šค๋กœ HTTP ์š”์ฒญ์„ ์Š~ ๋ณด๋‚ด๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์™€์„œ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.
         */
        return myServiceClient.getResponse(param);
    }
}

 

์‹ค์Šต

๐Ÿ’ก ์œ ๋ ˆ์นด ์„œ๋ฒ„ ํ•˜๋‚˜์— ์ฃผ๋ฌธ ์ธ์Šคํ„ด์Šค 1๊ฐœ์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์˜ ํฌํŠธ๋งŒ ๋‹ค๋ฅธ ์ƒํ’ˆ ์ธ์Šคํ„ด์Šค 3๊ฐœ๋ฅผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ƒํ’ˆ์„ ์š”์ฒญ(http://localhost:19091/order/1) ํ•˜๋ฉด ์‘๋‹ตํ•˜๋Š” ์ธ์Šคํ„ด์Šค์˜ ํฌํŠธ๋ฅผ ๋ฐ›์•„์„œ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ผ์šด๋“œ๋กœ๋นˆ์œผ๋กœ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ์ด ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

 

Eureka Server

  • ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ (Eureka) ๊ฐ•์˜์—์„œ ์ง„ํ–‰ํ–ˆ๋˜ ์œ ๋ ˆ์นด ์„œ๋ฒ„๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

Product instance

  • ๐Ÿ’ก Product๋Š” ์š”์ฒญ์ด ์˜ค๋ฉด ์•„๋ž˜์˜ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ”Product {productId} info!!!! From port : ${serverPort}”
ProductApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class ProductApplication {

	public static void main(String[] args) {
		SpringApplication.run(ProductApplication.class, args);
	}

}
ProductController.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    @Value("${server.port}") // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰ ์ค‘์ธ ํฌํŠธ๋ฅผ ์ฃผ์ž…๋ฐ›์Šต๋‹ˆ๋‹ค.
    private String serverPort;

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable String id) {
        return "Product " + id + " info!!!!! From port : " + serverPort ;
    }

    
}
resources/application.yml (application.properties ํŒŒ์ผ์€ ์‚ญ์ œ)
spring:
  application:
    name: product-service
server:
  port: 19092
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/
  • 19092,19093,19094 ํฌํŠธ์— ๊ฐ™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ํ•˜๊ธฐ
    • ์ธํ…”๋ฆฌ์ œ์ด์˜ ์ƒ๋‹จ ๋ฉ”๋‰ด์—์„œ ์‹คํ–‰ > ๊ตฌ์„ฑ ํŽธ์ง‘์œผ๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.
    • ProductApplication ์˜ ์ด๋ฆ„์„ ProdcutApplication:19092๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    • ์šฐ์ธก ์ƒ๋‹จ์˜ ๋ณต์‚ฌ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ProductApplication์„ ๋‘ ๊ฐœ ๋” ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด 19093,19094๋กœ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ต์…˜์ˆ˜์ •์„ ํด๋ฆญํ•˜์—ฌ VM์˜ต์…˜ ์ถ”๊ฐ€๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
    • ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด -Dserver.port=19093 ์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. 19094์—๋„ ๊ฐ™์€ ์ž‘์—…์„ ํ•ด์ค๋‹ˆ๋‹ค.

 

Order instance

๐Ÿ’ก Order๋Š” ์š”์ฒญ์ด ์˜ค๋ฉด product๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ƒํ’ˆ์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์˜ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
”Your order {orderId} product info => ${ํ”„๋กœ๋•ํŠธ์—์„œ ๋ฐ›์€ ๋ฌธ์ž์—ด}”

์ฃผ๋ฌธ์€ ์ฃผ๋ฌธ์•„์ด๋””๊ฐ€ 1์ธ ์ฃผ๋ฌธ๋งŒ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 1 ์ฃผ๋ฌธ์€ 112๋ฒˆ ์ƒํ’ˆ์„ ํ˜ธ์ถœํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • start.spring.io ์— ์ ‘์†ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค (๋””ํŽœ๋˜์‹œ๋Š” ์ด๋ฏธ์ง€ ์ฐธ๊ณ 

OrderApplication.java
  • Feign ๊ธฐ๋Šฅ ํ™œ์„ฑํ™” (OrderApplication.java)
    • Logic: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋  ๋•Œ ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ๋ถ€์—ฌํ•˜๋Š” ๊ฐ€์žฅ ์ฒซ ๋ฒˆ์งธ ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.
    • Hint: ์ด ํŒŒ์ผ์€ ์ผ์ข…์˜ '๋ฉ”์ธ ์Šค์œ„์น˜'์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์Šค์œ„์น˜๋ฅผ ์ผœ์ง€ ์•Š์œผ๋ฉด ์•„๋ฌด๋ฆฌ ๋ฆฌ๋ชจ์ปจ(@FeignClient)์„ ์ž˜ ๋งŒ๋“ค์–ด๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
// [ํ•ต์‹ฌ] ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ @FeignClient๊ฐ€ ๋ถ™์€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ฐพ์•„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
@EnableFeignClients
public class OrderApplication {

	public static void main(String[] args) {
		SpringApplication.run(OrderApplication.class, args);
	}

}
OrderController.java
  • ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ์ ‘์ˆ˜ (OrderController.java)
    • Logic: ์™ธ๋ถ€(์‚ฌ์šฉ์ž)๋กœ๋ถ€ํ„ฐ /order/{orderId} ํ˜•ํƒœ์˜ ์š”์ฒญ์„ ๊ฐ€์žฅ ๋จผ์ € ๋ฐ›์•„๋‚ด๋Š” ์ฐฝ๊ตฌ ์—ญํ• ์ž…๋‹ˆ๋‹ค.
    • Hint: ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋ณต์žกํ•œ ๋กœ์ง์„ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ , ๋“ค์–ด์˜จ ์š”์ฒญ์„ OrderService๋กœ ํ† ์Šคํ•˜๋Š” ์—ญํ• ๋งŒ ๊น”๋”ํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// ์ด ํด๋ž˜์Šค๊ฐ€ REST API ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์ž„์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.
@RestController
/ final์ด ๋ถ™์€ ํ•„๋“œ(orderService)์˜ ์ƒ์„ฑ์ž๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์–ด ์˜์กด์„ฑ์„ ์ฃผ์ž…(DI)ํ•ฉ๋‹ˆ๋‹ค.
@RequiredArgsConstructor
public class OrderController {

    // ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•  ์„œ๋น„์Šค ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. (Lombok์— ์˜ํ•ด ์ž๋™ ์ฃผ์ž…๋จ)
    private final OrderService orderService;
	
    // GET ๋ฉ”์„œ๋“œ๋กœ "/order/1" ๊ฐ™์€ ํ˜•ํƒœ์˜ URL ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
    @GetMapping("/order/{orderId}")
    // URL ๊ฒฝ๋กœ์— ์žˆ๋Š” {orderId} ๊ฐ’์„ ๋ณ€์ˆ˜ orderId๋กœ ์™ ๋นผ์˜ต๋‹ˆ๋‹ค.
    public String getOrder(@PathVariable String orderId) {
    	// ์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต ๋ฐ ์กฐ๋ฆฝ์€ ์„œ๋น„์Šค ๊ณ„์ธต์œผ๋กœ ๋„˜๊ฒจ์„œ ์ฒ˜๋ฆฌํ•œ ๋’ค ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        return orderService.getOrder(orderId);
    }
    
}
ProdcutClient.java (ํด๋ž˜์Šค๊ฐ€ ์•„๋‹Œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ƒ์„ฑ)
  • ์›๊ฒฉ ์„œ๋น„์Šค ํ˜ธ์ถœ ์ •์˜ (ProductClient.java)
    • Logic: ๋Œ€์ƒ ์„œ๋น„์Šค์˜ ์ด๋ฆ„๊ณผ API ๋ช…์„ธ๋ฅผ ์„ ์–ธํ•˜์—ฌ, ๋ณต์žกํ•œ HTTP ํ†ต์‹  ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ์›๊ฒฉ ์„œ๋ฒ„๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์œ ๋ ˆ์นด ์„œ๋ฒ„์— ์žˆ๋Š” product-service์—๊ฒŒ ์–ด๋–ค ๊ฒฝ๋กœ๋กœ, ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ฒจ์ฃผ๋ฉฐ ์š”์ฒญํ• ์ง€ ์ •์˜ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.
    • Hint: ์ด ์ฝ”๋“œ๋Š” "ํ†ต์‹  ๋ช…์„ธ์„œ"์ž…๋‹ˆ๋‹ค. ์ด ๋ช…์„ธ์„œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์Šคํ”„๋ง์ด ๋Ÿฐํƒ€์ž„(์‹คํ–‰ ์‹œ์ )์— ์‹ค์ œ ํ†ต์‹ ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ๊ตฌํ˜„( { } ๋ธ”๋ก )ํ•  ํ•„์š” ์—†์ด ์„ ์–ธ๋งŒ ํ•ด๋‘๋ฉด, ๋Ÿฐํƒ€์ž„์— ์Šคํ”„๋ง๊ณผ Feign์ด ์•Œ์•„์„œ ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. 
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// [Logic 1: ๋Œ€์ƒ ์„œ๋น„์Šค ์ง€์ • ๋ฐ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ์ค€๋น„]
// ์œ ๋ ˆ์นด ์บ์‹œ์—์„œ "product-service"๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋“ฑ๋ก๋œ ์„œ๋ฒ„ ๋ชฉ๋ก์„ ์ฐพ์Šต๋‹ˆ๋‹ค.
// ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค Ribbon์ด ์ด ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์—ฌ ์ตœ์ข… ๋ชฉ์ ์ง€(Target URL)๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
@FeignClient(name = "product-service")
public interface ProductClient {
    
    // [Logic 2: HTTP ์š”์ฒญ ๊ทœ๊ฒฉ ์ •์˜]
    // ์„ ํƒ๋œ ๋Œ€์ƒ ์„œ๋ฒ„์—๊ฒŒ ๋ณด๋‚ผ HTTP ๋ฉ”์„œ๋“œ(GET)์™€ ๊ฒฝ๋กœ("/product/{id}")๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
    @GetMapping("/product/{id}")
    String getProduct(
            // [Logic 3: ์š”์ฒญ ๋ฐ์ดํ„ฐ ๋งตํ•‘]
            // ์šฐ๋ฆฌ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์งค ๋•Œ getProduct("112") ๋ผ๊ณ  ํ˜ธ์ถœํ•˜๋ฉด,
            // ํŒŒ๋ผ๋ฏธํ„ฐ "112"๊ฐ€ ์œ„ ๊ฒฝ๋กœ์˜ {id}์— ๋งตํ•‘๋˜์–ด ์ตœ์ข…์ ์œผ๋กœ ํƒ€๊ฒŸ ์„œ๋ฒ„์˜ "/product/112" ๊ฒฝ๋กœ๋กœ HTTP ์š”์ฒญ์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค.
            @PathVariable("id") String id 
    );
}

 

๐Ÿ’ก ๋‚ด๋ถ€ ์‹คํ–‰ ํ๋ฆ„ (Under the Hood)

  • ์šฐ๋ฆฌ๊ฐ€ ๋‚˜์ค‘์— OrderService์—์„œ productClient.getProduct("112")๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฒŒ์–ด์ง€๋Š” ์ผ์€ ๋”ฑ 3๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.
    1. ํƒ€๊ฒŸ ์„ ์ •: Feign์ด @FeignClient(name="product-service")๋ฅผ ๋ณด๊ณ , Ribbon์„ ํ†ตํ•ด ํ˜ธ์ถœํ•  ์„œ๋ฒ„ 1๊ฐœ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
    2. URL ์กฐ๋ฆฝ: ์„ ํƒ๋œ ์„œ๋ฒ„์˜ ๊ธฐ๋ณธ ์ฃผ์†Œ์— @GetMapping์˜ ๊ฒฝ๋กœ์™€ @PathVariable์˜ ๊ฐ’์„ ํ•ฉ์ณ์„œ ์™„์ „ํ•œ URL์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. (์˜ˆ: http://{์„ ํƒ๋œ์„œ๋ฒ„์ฃผ์†Œ}/product/112)
    3. ์š”์ฒญ ๋ฐ ์‘๋‹ต: ์กฐ๋ฆฝ๋œ URL๋กœ ์‹ค์ œ HTTP GET ์š”์ฒญ์„ ์ „์†กํ•˜๊ณ , ๋Œ€์ƒ ์„œ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌํ•œ ๊ฒฐ๊ณผ๊ฐ’(String)์„ ๋ฐ˜ํ™˜๋ฐ›์Šต๋‹ˆ๋‹ค.
OrderService.java
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฒ˜๋ฆฌ ๋ฐ ์›๊ฒฉ ํ˜ธ์ถœ (OrderService.java)
    • Logic (๋ชฉ์ ): ์ปจํŠธ๋กค๋Ÿฌ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ ์š”์ฒญ์„ ๋ถ„์„ํ•˜๊ณ , ์™ธ๋ถ€ ์„œ๋น„์Šค(Product)์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•  ๊ฒฝ์šฐ FeignClient๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ๋’ค ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์กฐ๋ฆฝํ•ฉ๋‹ˆ๋‹ค. Hint: ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ ์—…๋ฌด(์กฐ๊ฑด ํ™•์ธ, ํƒ€ ์„œ๋ฒ„ ํ†ต์‹ , ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต)๋Š” ๋ชจ๋‘ ์ด Service ๊ณ„์ธต์—์„œ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

// [Logic 1: ์„œ๋น„์Šค ๊ณ„์ธต ์„ ์–ธ]
// ์ด ํด๋ž˜์Šค๊ฐ€ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ž„์„ ์Šคํ”„๋ง์— ์•Œ๋ฆฝ๋‹ˆ๋‹ค.
@Service 
// [Logic 2: ์˜์กด์„ฑ ์ž๋™ ์ฃผ์ž… (DI)]
// ์Šคํ”„๋ง์ด ์‹คํ–‰๋  ๋•Œ, ๋ฉ”๋ชจ๋ฆฌ์— ์ƒ์„ฑ๋œ ProductClient ๊ตฌํ˜„์ฒด๋ฅผ ์ด๊ณณ์— ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐํ•ด ์ค๋‹ˆ๋‹ค.
@RequiredArgsConstructor 
public class OrderService {

    // ์™ธ๋ถ€ ์„œ๋น„์Šค(Product)์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด ์ •์˜ํ•ด๋‘” FeignClient ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.
    private final ProductClient productClient;

    // [Logic 3: ์™ธ๋ถ€ ํ†ต์‹  ๋ฉ”์„œ๋“œ ๋ถ„๋ฆฌ]
    // Product ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ณ„๋„์˜ ๋ฉ”์„œ๋“œ๋กœ ๋นผ๋‘์–ด ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.
    // ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ˆœ๊ฐ„, ๋‚ด๋ถ€์ ์œผ๋กœ [์œ ๋ ˆ์นด ์ฃผ์†Œ ๊ฒ€์ƒ‰ -> ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ -> HTTP ์š”์ฒญ]์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    public String getProductInfo(String productId) {
        return productClient.getProduct(productId);
    }

    // [Logic 4: ํ•ต์‹ฌ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ๋กœ์ง]
    // ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋„˜์–ด์˜จ ์ฃผ๋ฌธ ๋ฒˆํ˜ธ(orderId)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    public String getOrder(String orderId) {
        
        // ์กฐ๊ฑด: ์ „๋‹ฌ๋ฐ›์€ ์ฃผ๋ฌธ ๋ฒˆํ˜ธ๊ฐ€ "1"์ผ ๋•Œ๋งŒ ์ •์ƒ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
        if(orderId.equals("1")) {
            
            // 1๋ฒˆ ์ฃผ๋ฌธ์— ํ•„์š”ํ•œ ํƒ€๊ฒŸ ์ƒํ’ˆ ๋ฒˆํ˜ธ๋ฅผ "2"๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
            String productId = "2"; 
            
            // ๋ถ„๋ฆฌํ•ด๋‘” ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ์™ธ๋ถ€(Product) ์„œ๋ฒ„์—์„œ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
            String productInfo = getProductInfo(productId);
            
            // [Logic 5: ์ตœ์ข… ์‘๋‹ต ๋ฐ์ดํ„ฐ ์กฐ๋ฆฝ]
            // ํ˜„์žฌ ์„œ๋ฒ„๊ฐ€ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ(orderId)์™€ ์™ธ๋ถ€ ์„œ๋ฒ„์—์„œ ์‘๋‹ต๋ฐ›์€ ๋ฐ์ดํ„ฐ(productInfo)๋ฅผ ํ•ฉ์ณ์„œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
            return "Your order is " + orderId + " and " + productInfo;
        }
        
        // "1"๋ฒˆ ์ด์™ธ์˜ ์ฃผ๋ฌธ์ด ๋“ค์–ด์™”์„ ๋•Œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.
        return "Not exist order...";
    }
}
application.yml
spring:
  application:
    name: order-service
server:
  port: 19091
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

 

Run

  • ์œ ๋ ˆ์นด ์„œ๋ฒ„ → order → product(3๊ฐœ ๋ชจ๋‘) ์ˆœ์œผ๋กœ ์‹คํ–‰
  • http://localhost:19090/ ์œผ๋กœ ์ ‘์†ํ•˜๋ฉด ์ธ์Šคํ„ด์Šค๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ
  • PRODUCT-SERVICE์— ํฌํŠธ๊ฐ€ 19092,19093,19094 3๊ฐœ๊ฐ€ ๋– ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ

 

ํ™•์ธ

  • http://localhost:19091/order/1 ์— ์ ‘์†ํ•  ๋•Œ๋งˆ๋‹ค ํ…์ŠคํŠธ์˜ ํฌํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Œ ์ด๋ฅผ ํ†ตํ•ด ์š”์ฒญ๋งˆ๋‹ค ๋ผ์šด๋“œ๋กœ๋นˆ์œผ๋กœ ๋™์ž‘ํ•จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ

 

๐ŸŽฏ [์ด์ •๋ฆฌ] Spring Cloud ๊ธฐ๋ฐ˜ MSA ํ†ต์‹  ๊ตฌํ˜„ (Eureka + FeignClient)

  • ์ง€๊ธˆ๊นŒ์ง€ ์šฐ๋ฆฌ๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜(MSA) ํ™˜๊ฒฝ์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์„œ๋น„์Šค๊ฐ€ ์–ด๋–ป๊ฒŒ ์„œ๋กœ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ณ , ๋ถ€ํ•˜๋ฅผ ๋ถ„์‚ฐํ•˜๋ฉฐ ํ†ต์‹ ํ•˜๋Š”์ง€ ์ง์ ‘ ๊ตฌํ˜„ํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ „์ฒด์ ์ธ ํ๋ฆ„๊ณผ ํŒŒ์ผ๋ณ„ ์„ค๊ณ„๋„๋ฅผ ์š”์•ฝํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๐ŸŒŸ ์ „์ฒด ์•„ํ‚คํ…์ฒ˜ ๋™์ž‘ ํ๋ฆ„
  1. ์„œ๋น„์Šค ๋“ฑ๋ก (Eureka Registry): Product ์„œ๋น„์Šค์™€ Order ์„œ๋น„์Šค๋Š” ๊ฐ๊ฐ์˜ ํ™˜๊ฒฝ ์„ค์ •(application.yml)์„ ํ†ตํ•ด ์ž์‹ ๋“ค์˜ ์ด๋ฆ„๊ณผ ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ์œ ๋ ˆ์นด(Eureka) ์„œ๋ฒ„์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
  2. ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค ์‹คํ–‰: ํŠธ๋ž˜ํ”ฝ ๋ถ„์‚ฐ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด Product ์„œ๋น„์Šค๋ฅผ ์ธํ…”๋ฆฌ์ œ์ด์˜ ์‹คํ–‰ ๊ตฌ์„ฑ์„ ํ™œ์šฉํ•˜์—ฌ ์„œ๋กœ ๋‹ค๋ฅธ 3๊ฐœ์˜ ํฌํŠธ(19092, 19093, 19094)๋กœ ๋™์‹œ์— ๋„์›๋‹ˆ๋‹ค.
  3. ์„ ์–ธ์  ์›๊ฒฉ ํ˜ธ์ถœ (Feign Client): Order ์„œ๋น„์Šค ๋‚ด๋ถ€์— Product ์„œ๋น„์Šค์˜ ์ •๋ณด๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ํ†ต์‹  ์ฝ”๋“œ ์—†์ด ์–ด๋…ธํ…Œ์ด์…˜๋งŒ์œผ๋กœ ์›๊ฒฉ ํ˜ธ์ถœ ์ค€๋น„๋ฅผ ๋งˆ์นฉ๋‹ˆ๋‹ค.
  4. ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ (Round-Robin): ์‚ฌ์šฉ์ž๊ฐ€ Order ์„œ๋น„์Šค๋กœ ์ฃผ๋ฌธ์„ ์š”์ฒญํ•˜๋ฉด, Order ์„œ๋น„์Šค๋Š” ์œ ๋ ˆ์นด์—์„œ ๋ฐ›์•„์˜จ Product ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ด ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด๋•Œ ๋ผ์šด๋“œ ๋กœ๋นˆ(Round-Robin) ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ์ž‘๋™ํ•˜์—ฌ ํŠธ๋ž˜ํ”ฝ์„ ๊ณตํ‰ํ•˜๊ฒŒ ๋ถ„๋ฐฐํ•ฉ๋‹ˆ๋‹ค.
  5. ๋™์ž‘ ํ™•์ธ: ์ตœ์ข… ์‘๋‹ต ๋ฉ”์‹œ์ง€์— ํฌํ•จ๋œ Product ์„œ๋ฒ„์˜ ํฌํŠธ ๋ฒˆํ˜ธ๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจํ•  ๋•Œ๋งˆ๋‹ค ์ˆœ์ฐจ์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ํ†ตํ•ด, ๋ถ„์‚ฐ ํ˜ธ์ถœ์ด ์ •์ƒ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ์Œ์„ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค.
๐Ÿ“‚ ํŒŒ์ผ๋ณ„ ํ•ต์‹ฌ ์—ญํ•  ์„ค๊ณ„๋„
  • ๐Ÿ›’ [Product Service] - ๋ฐ์ดํ„ฐ ์ œ๊ณต์ž
    • ์š”์ฒญ์„ ๋ฐ›์œผ๋ฉด ์ž์‹ ์˜ ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•œ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์ž…๋‹ˆ๋‹ค.
      1. application.yml: ๋‚ด ์„œ๋น„์Šค ์ด๋ฆ„(product-service)์„ ์ง€์ •ํ•˜๊ณ , ์‹คํ–‰ํ•  ํฌํŠธ์™€ ์œ ๋ ˆ์นด ์„œ๋ฒ„ ์ฃผ์†Œ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ํ˜ธ์  ํŒŒ๊ธฐ.
      2. ProductApplication.java: ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰.
      3. ProductController.java: ์‹ค์ œ ์š”์ฒญ์„ ๋ฐ›์•„ "๋‚˜๋Š” OOO๋ฒˆ ํฌํŠธ์—์„œ ์ผํ•˜๋Š” Product ์„œ๋ฒ„์•ผ!"๋ผ๊ณ  ์‘๋‹ต์„ ๋‚ด๋ ค์ฃผ๋Š” ์ฐฝ๊ตฌ.
  • ๐Ÿ“ฆ [Order Service] - ๋ฐ์ดํ„ฐ ์†Œ๋น„์ž
    • ์‚ฌ์šฉ์ž์˜ ์ฃผ๋ฌธ์„ ๋ฐ›๊ณ , ํ•„์š”์‹œ Product ์„œ๋น„์Šค์— ํ†ต์‹ ์„ ์š”์ฒญํ•˜๋Š” ์—ญํ• ์ž…๋‹ˆ๋‹ค.
      1. application.yml: ๋‚ด ์„œ๋น„์Šค ์ด๋ฆ„(order-service)๊ณผ ํฌํŠธ(19091)๋ฅผ ์„ค์ •ํ•˜๊ณ  ์œ ๋ ˆ์นด์— ๋“ฑ๋ก.
      2. OrderApplication.java: @EnableFeignClients๋ฅผ ๋ถ™์—ฌ ๋‹ค๋ฅธ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ๋ฉ”์ธ ์Šค์œ„์น˜.
      3. ProductClient.java: ์œ ๋ ˆ์นด์— product-service๋กœ ๋“ฑ๋ก๋œ ์„œ๋ฒ„๋ฅผ ์ฐพ์•„๊ฐ€๊ธฐ ์œ„ํ•œ ์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ์„œ (๋ฆฌ๋ชจ์ปจ ์—ญํ• ).
      4. OrderService.java: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ํ•ต์‹ฌ. "์ฃผ๋ฌธ ๋ฒˆํ˜ธ 1๋ฒˆ์ด ๋“ค์–ด์˜ค๋ฉด, ProductClient๋ฅผ ํ†ตํ•ด 2๋ฒˆ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ ์กฐํ•ฉํ•ด๋ผ!"๋ผ๊ณ  ์ง€์‹œํ•˜๋Š” ๋‘๋‡Œ.
      5. OrderController.java: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž(ํด๋ผ์ด์–ธํŠธ)๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์ดˆ์˜ API ์ง„์ž…์ .
โœ… ์ตœ์ข… ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒฐ๊ณผ
  • ๋ชจ๋“  ์„œ๋ฒ„๋ฅผ ๋„์šด ํ›„ ๋ธŒ๋ผ์šฐ์ €๋‚˜ Postman์—์„œ http://localhost:19091/order/1์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•ด ๋ด…๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ ๋ฌธ์ž์—ด ๋์— ์ฐํžˆ๋Š” ํฌํŠธ ๋ฒˆํ˜ธ(19092 → 19093 → 19094)๊ฐ€ ๊ณ„์† ๋ฐ”๋€๋‹ค๋ฉด, ์œ ๋ ˆ์นด๋ฅผ ํ†ตํ•œ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์™€ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์ด ์™„๋ฒฝํ•˜๊ฒŒ ์„ฑ๊ณตํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค! ๐Ÿš€

 

๐Ÿ—จ๏ธ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค

์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๊ฐœ์š”

์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋ž€?

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

 

Resilience4j ๊ฐœ์š”

Resilience4j๋ž€?

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

Resilience4j์˜ ์ฃผ์š” ํŠน์ง•

  • ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ: ํด๋กœ์ฆˆ๋“œ, ์˜คํ”ˆ, ํ•˜ํ”„-์˜คํ”ˆ ์ƒํƒœ๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ ์‹คํŒจ๋ฅผ ๊ด€๋ฆฌ
    1. ํด๋กœ์ฆˆ๋“œ(Closed):
      • ๊ธฐ๋ณธ ์ƒํƒœ๋กœ, ๋ชจ๋“  ์š”์ฒญ์„ ํ†ต๊ณผ์‹œํ‚ต๋‹ˆ๋‹ค.
      • ์ด ์ƒํƒœ์—์„œ ํ˜ธ์ถœ์ด ์‹คํŒจํ•˜๋ฉด ์‹คํŒจ ์นด์šดํ„ฐ๊ฐ€ ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
      • ์‹คํŒจ์œจ์ด ์„ค์ •๋œ ์ž„๊ณ„๊ฐ’(์˜ˆ: 50%)์„ ์ดˆ๊ณผํ•˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
      • ์˜ˆ์‹œ: ์ตœ๊ทผ 5๋ฒˆ์˜ ํ˜ธ์ถœ ์ค‘ 3๋ฒˆ์ด ์‹คํŒจํ•˜์—ฌ ์‹คํŒจ์œจ์ด 60%์— ๋„๋‹ฌํ•˜๋ฉด ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
    2. ์˜คํ”ˆ(Open):
      • ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๋ฉด ๋ชจ๋“  ์š”์ฒญ์„ ์ฆ‰์‹œ ์‹คํŒจ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
      • ์ด ์ƒํƒœ์—์„œ ์š”์ฒญ์ด ์‹คํŒจํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ์—๋Ÿฌ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
      • ์„ค์ •๋œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„, ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋Š” ํ•˜ํ”„-์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
      • ์˜ˆ์‹œ: ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๊ณ  20์ดˆ ๋™์•ˆ ๋ชจ๋“  ์š”์ฒญ์ด ์ฐจ๋‹จ๋ฉ๋‹ˆ๋‹ค.
    3. ํ•˜ํ”„-์˜คํ”ˆ(Half-Open):
      • ์˜คํ”ˆ ์ƒํƒœ์—์„œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋Š” ํ•˜ํ”„-์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
      • ํ•˜ํ”„-์˜คํ”ˆ ์ƒํƒœ์—์„œ๋Š” ์ œํ•œ๋œ ์ˆ˜์˜ ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜์—ฌ ์‹œ์Šคํ…œ์ด ์ •์ƒ ์ƒํƒœ๋กœ ๋ณต๊ตฌ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
      • ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋Š” ํด๋กœ์ฆˆ๋“œ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
      • ์š”์ฒญ์ด ๋‹ค์‹œ ์‹คํŒจํ•˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋Š” ๋‹ค์‹œ ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
      • ์˜ˆ์‹œ: ํ•˜ํ”„-์˜คํ”ˆ ์ƒํƒœ์—์„œ 3๊ฐœ์˜ ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜๊ณ , ๋ชจ๋‘ ์„ฑ๊ณตํ•˜๋ฉด ํด๋กœ์ฆˆ๋“œ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ๋‹ค์‹œ ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
  • Fallback: ํ˜ธ์ถœ ์‹คํŒจ ์‹œ ๋Œ€์ฒด ๋กœ์ง์„ ์ œ๊ณตํ•˜์—ฌ ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ ํ™•๋ณด
  • ๋ชจ๋‹ˆํ„ฐ๋ง: ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ๋„๊ตฌ ์ œ๊ณต

 

Resilience4j ์„ค์ •

๊ธฐ๋ณธ ์„ค์ •

๐Ÿ’ก resilience4j ์˜์กด์„ฑ์€ spring starter์—์„œ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
”io.github.resilience4j:resilience4j-spring-boot3:2.2.0” ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ”boot3” ์ž„์„ ์ฃผ์˜ํ•˜์„ธ์š”.
  • Resilience4j๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
build.gradle ํŒŒ์ผ ์˜ˆ์‹œ:
dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
	  implementation 'org.springframework.boot:spring-boot-starter-aop'
}

Resilience4j ์„ค์ • ํŒŒ์ผ

  • Resilience4j์˜ ์„ค์ •์€ application.yml ํŒŒ์ผ์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ์‹œ ์„ค์ • ํŒŒ์ผ:
resilience4j:
  circuitbreaker:
    configs:
      default:  # ๊ธฐ๋ณธ ๊ตฌ์„ฑ ์ด๋ฆ„
        registerHealthIndicator: true  # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ—ฌ์Šค ์ฒดํฌ์— ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ชจ๋‹ˆํ„ฐ๋ง ๊ฐ€๋Šฅ
        # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํƒ€์ž…์„ ์„ค์ •
        # COUNT_BASED: ๋งˆ์ง€๋ง‰ N๋ฒˆ์˜ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ฒฐ์ •
        # TIME_BASED: ๋งˆ์ง€๋ง‰ N์ดˆ ๋™์•ˆ์˜ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ฒฐ์ •
        slidingWindowType: COUNT_BASED  # ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํƒ€์ž…์„ ํ˜ธ์ถœ ์ˆ˜ ๊ธฐ๋ฐ˜(COUNT_BASED)์œผ๋กœ ์„ค์ •
        # ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํฌ๊ธฐ๋ฅผ ์„ค์ •
        # COUNT_BASED์ผ ๊ฒฝ์šฐ: ์ตœ๊ทผ N๋ฒˆ์˜ ํ˜ธ์ถœ์„ ์ €์žฅ
        # TIME_BASED์ผ ๊ฒฝ์šฐ: ์ตœ๊ทผ N์ดˆ ๋™์•ˆ์˜ ํ˜ธ์ถœ์„ ์ €์žฅ
        slidingWindowSize: 5  # ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํฌ๊ธฐ๋ฅผ 5๋ฒˆ์˜ ํ˜ธ์ถœ๋กœ ์„ค์ •
        minimumNumberOfCalls: 5  # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ํ˜ธ์ถœ ์ˆ˜๋ฅผ 5๋กœ ์„ค์ •
        slowCallRateThreshold: 100  # ๋А๋ฆฐ ํ˜ธ์ถœ์˜ ๋น„์œจ์ด ์ด ์ž„๊ณ„๊ฐ’(100%)์„ ์ดˆ๊ณผํ•˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘
        slowCallDurationThreshold: 60000  # ๋А๋ฆฐ ํ˜ธ์ถœ์˜ ๊ธฐ์ค€ ์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ)์œผ๋กœ, 60์ดˆ ์ด์ƒ ๊ฑธ๋ฆฌ๋ฉด ๋А๋ฆฐ ํ˜ธ์ถœ๋กœ ๊ฐ„์ฃผ
        failureRateThreshold: 50  # ์‹คํŒจ์œจ์ด ์ด ์ž„๊ณ„๊ฐ’(50%)์„ ์ดˆ๊ณผํ•˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘
        permittedNumberOfCallsInHalfOpenState: 3  # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ Half-open ์ƒํƒœ์—์„œ ํ—ˆ์šฉํ•˜๋Š” ์ตœ๋Œ€ ํ˜ธ์ถœ ์ˆ˜๋ฅผ 3์œผ๋กœ ์„ค์ •
        # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ Open ์ƒํƒœ์—์„œ Half-open ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๊ธฐ ์ „์— ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„
        waitDurationInOpenState: 20s  # Open ์ƒํƒœ์—์„œ Half-open ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๊ธฐ ์ „์— ๋Œ€๊ธฐํ•˜๋Š” ์‹œ๊ฐ„์„ 20์ดˆ๋กœ ์„ค์ •

 

Fallback ๋ฉ”์ปค๋‹ˆ์ฆ˜

Fallback ์„ค์ •

  • Fallback ๋ฉ”์„œ๋“œ๋Š” ์™ธ๋ถ€ ์„œ๋น„์Šค ํ˜ธ์ถœ์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ๋Œ€์ฒด ๋กœ์ง์„ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
์˜ˆ์‹œ ์ฝ”๋“œ:
@Service
public class MyService {

    /**
     * @CircuitBreaker: ์™ธ๋ถ€ ํ˜ธ์ถœ ์žฅ์•  ์‹œ ์‹œ์Šคํ…œ์„ ๋ณดํ˜ธํ•˜๋Š” ์ฐจ๋‹จ๊ธฐ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
     * - name: ์„ค์ • ํŒŒ์ผ(yml)์—์„œ ์ •์˜ํ•œ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค์˜ ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค.
     * - fallbackMethod: ์žฅ์•  ๋ฐœ์ƒ(์—๋Ÿฌ, ํƒ€์ž„์•„์›ƒ ๋“ฑ) ์‹œ ํ˜ธ์ถœํ•  ๋Œ€์ฒด ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
     */
    @CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
    public String myMethod() {
        // ์™ธ๋ถ€ ์„œ๋น„์Šค ํ˜ธ์ถœ ๋กœ์ง (์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์ง€์ )
        return externalService.call();
    }

    /**
     * Fallback ๋ฉ”์„œ๋“œ: ์›๋ณธ ๋ฉ”์„œ๋“œ ์‹คํŒจ ์‹œ ์‹คํ–‰๋˜๋Š” ๋Œ€์ฒด ๋กœ์ง์ž…๋‹ˆ๋‹ค.
     * - ์›๋ณธ ๋ฉ”์„œ๋“œ์™€ ๋ฐ˜ํ™˜ ํƒ€์ž…์ด ๋ฐ˜๋“œ์‹œ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (String)
     * - ํŒŒ๋ผ๋ฏธํ„ฐ ๋งˆ์ง€๋ง‰์— Throwable(์˜ˆ์™ธ ๊ฐ์ฒด)์„ ํฌํ•จํ•ด์•ผ ์—๋Ÿฌ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
     */
    public String fallbackMethod(Throwable t) {
        // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฐ˜ํ™˜ํ•  ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’์ด๋‚˜ ๋ฉ”์‹œ์ง€
        return "Fallback response";
    }
}

 

Fallback์˜ ์žฅ์ 

  • ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ด๊ณ , ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ผ์ •ํ•œ ์‘๋‹ต์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์žฅ์• ๊ฐ€ ๋‹ค๋ฅธ ์„œ๋น„์Šค์— ์ „ํŒŒ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

 

Resilience4j Dashboard

Resilience4j Dashboard ์„ค์ •

  • Resilience4j Dashboard๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค์˜ ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
build.gradle ํŒŒ์ผ ์˜ˆ์‹œ:
dependencies {
    implementation 'io.github.resilience4j:resilience4j-micrometer'
    implementation 'io.micrometer:micrometer-registry-prometheus'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
  • ์„ค์ •์€ application.yml ํŒŒ์ผ์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ์‹œ ์ฝ”๋“œ:
management:
  endpoints:
    web:
      exposure:
        include: prometheus
  prometheus:
    metrics:
      export:
        enabled: true
  • http://${hostname}:${port}/actuator/prometheus ์— ์ ‘์†ํ•˜์—ฌ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค ํ•ญ๋ชฉ์„ ํ™•์ธ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

Resilience4j Dashboard ์‚ฌ์šฉ

  • Prometheus์™€ Grafana๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Resilience4j ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค์˜ ์ƒํƒœ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Prometheus๋ฅผ ํ†ตํ•ด ์ˆ˜์ง‘๋œ ๋ฉ”ํŠธ๋ฆญ์„ Grafana ๋Œ€์‹œ๋ณด๋“œ์—์„œ ์‹œ๊ฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Resilience4j์™€ Spring Cloud ์—ฐ๋™

Spring Cloud์™€์˜ ํ†ตํ•ฉ

  • Resilience4j๋Š” Spring Cloud Netflix ํŒจํ‚ค์ง€์˜ ์ผ๋ถ€๋กœ, Eureka์™€ Ribbon ๋“ฑ ๋‹ค๋ฅธ Spring Cloud ๊ตฌ์„ฑ ์š”์†Œ์™€ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Spring Cloud์˜ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์™€ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ํ™œ์šฉํ•˜์—ฌ ๋”์šฑ ์•ˆ์ •์ ์ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
spring:
  application:
    name: my-service
  cloud:
    circuitbreaker:
      resilience4j:
        enabled: true

 

์‹ค์Šต

๐Ÿ’ก ์ด๋ฒˆ ์‹ค์Šต์—์„œ ์œ ๋ ˆ์นด๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ƒํ’ˆ์„ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์„ ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ƒํ’ˆ ์•„์ด๋”” 111์„ ํ˜ธ์ถœํ•˜๋ฉด ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ fallbackMethod๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด๋ฒคํŠธ๋ฆฌ์Šค๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค์˜ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋ฐ ์ฝ”๋“œ ์ž‘์„ฑ

  • start.spring.io ์— ์ ‘์†ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. (๋””ํŽœ๋˜์‹œ๋Š” ์ด๋ฏธ์ง€ ์ฐธ๊ณ )
    • โ— ์ฃผ์˜: starter์—์„œ resilience4j ๋””ํŽœ๋˜์‹œ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. starter์—์„œ ์ถ”๊ฐ€ํ•˜๋ฉด “org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j”๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ถ”์ƒํ™” ๊ณ„์ธต์„ ํ†ตํ•ด Resilience4j๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์šฐ๋ฆฌ๋Š” ์ง์ ‘ resilience4j๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด build.gradle์— “io.github.resilience4j:resilience4j-spring-boot3:2.2.0”๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

build.gradle์˜ dependencies์— ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. (์•„๋ž˜ ์ฝ”๋“œ๋Š” ์™„์„ฑ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.)
dependencies {
	implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
	implementation 'org.springframework.boot:spring-boot-starter-aop'
	
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}


tasks.named('test'){
		useJUnitPlatform()
}
java/com/spring_cloud/resilience4j/sample/products/Product.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {

    private String id;
    private String title;

}
java/com/spring_cloud/resilience4j/sample/products/ProductController.java
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;


    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id") String id) {
        return productService.getProductDetails(id);
    }
}
java/com/spring_cloud/resilience4j/sample/products/ProductService.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ProductService {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private final CircuitBreakerRegistry circuitBreakerRegistry;
	
    // @PostConstruct: ์„œ๋น„์Šค๊ฐ€ ์‹คํ–‰๋˜์ž๋งˆ์ž ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์‹œํ•˜๋„๋ก ๋“ฑ๋ก
    @PostConstruct
    public void registerEventListener() {
    	// "productService"๋ผ๋Š” ์ด๋ฆ„์˜ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋กœ๊ทธ์— ๋‚จ๊น๋‹ˆ๋‹ค.
        circuitBreakerRegistry.circuitBreaker("productService").getEventPublisher()
            .onStateTransition(event -> log.info("#######CircuitBreaker State Transition: {}", event)) // ์ƒํƒœ ์ „ํ™˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
            .onFailureRateExceeded(event -> log.info("#######CircuitBreaker Failure Rate Exceeded: {}", event)) // ์‹คํŒจ์œจ ์ดˆ๊ณผ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
            .onCallNotPermitted(event -> log.info("#######CircuitBreaker Call Not Permitted: {}", event)) // ํ˜ธ์ถœ ์ฐจ๋‹จ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
            .onError(event -> log.info("#######CircuitBreaker Error: {}", event)); // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
    }

    // @CircuitBreaker: ์žฅ์•  ์‹œ fallbackMethod๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •
    @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
    public Product getProductDetails(String productId) {
        log.info("###Fetching product details for productId: {}", productId);
        
        // ํŠน์ • ์•„์ด๋””(111) ํ˜ธ์ถœ ์‹œ ์–ต์ง€๋กœ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํ…Œ์ŠคํŠธ
        if ("111".equals(productId)) {
            log.warn("###Received empty body for productId: {}", productId);
            throw new RuntimeException("Empty response body");
        }
        return new Product(
            productId,
            "Sample Product"
        );
    }

    // Fallback ๋ฉ”์„œ๋“œ: ์—๋Ÿฌ๊ฐ€ ๋‚˜๊ฑฐ๋‚˜ ์„œํ‚ท์ด ์—ด๋ ค์žˆ์„ ๋•Œ ๋Œ€์‹  ์‹คํ–‰๋จ
    // ๊ทœ์น™: ์›๋ž˜ ๋ฉ”์„œ๋“œ์™€ ๋ฆฌํ„ด ํƒ€์ž…(Product)์ด ๊ฐ™์•„์•ผ ํ•˜๋ฉฐ, ๋งˆ์ง€๋ง‰์— Throwable์„ ๋ฐ›์•„์•ผ ํ•จ
    public Product fallbackGetProductDetails(String productId, Throwable t) {
        log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
        return new Product(
            productId,
            "Fallback Product"
        );
    }

    
    // ์ด๋ฒคํŠธ ์„ค๋ช… ํ‘œ
    // +---------------------------+-------------------------------------------------+--------------------------------------------+
    // | ์ด๋ฒคํŠธ                      | ์„ค๋ช…                                             | ๋กœ๊ทธ ์ถœ๋ ฅ                                    |
    // +---------------------------+-------------------------------------------------+--------------------------------------------+
    // | ์ƒํƒœ ์ „ํ™˜ (Closed -> Open)   | ์—ฐ์†๋œ ์‹คํŒจ๋กœ ์ธํ•ด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๋ฉด ๋ฐœ์ƒ  | CircuitBreaker State Transition: ...       |
    // | ์‹คํŒจ์œจ ์ดˆ๊ณผ                  | ์„ค์ •๋œ ์‹คํŒจ์œจ ์ž„๊ณ„์น˜๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ๋ฐœ์ƒ                     | CircuitBreaker Failure Rate Exceeded: ...  |
    // | ํ˜ธ์ถœ ์ฐจ๋‹จ                    | ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์˜คํ”ˆ ์ƒํƒœ์ผ ๋•Œ ํ˜ธ์ถœ์ด ์ฐจ๋‹จ๋˜๋ฉด ๋ฐœ์ƒ         | CircuitBreaker Call Not Permitted: ...     |
    // | ์˜ค๋ฅ˜ ๋ฐœ์ƒ                    | ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœ์ด ์‹คํŒจํ•˜๋ฉด ๋ฐœ์ƒ               | CircuitBreaker Error: ...                  |
    // +---------------------------+-------------------------------------------------+--------------------------------------------+


    // +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
    // | ์ด๋ฒคํŠธ                                    | ์„ค๋ช…                                        | ๋กœ๊ทธ ์ถœ๋ ฅ                                                         |
    // +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
    // | ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ                                | ์ œํ’ˆ ์ •๋ณด๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ                | ###Fetching product details for productId: ...                  |
    // | (์„ฑ๊ณต ์‹œ) ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœ ์„ฑ๊ณต        | ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ด ์„ฑ๊ณตํ•˜์—ฌ ์ •์ƒ์ ์ธ ์‘๋‹ต์„ ๋ฐ˜ํ™˜          |                                                                 |
    // | (์‹คํŒจ ์‹œ) ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœ ์‹คํŒจ        | ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ด ์‹คํŒจํ•˜์—ฌ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ                 | #######CircuitBreaker Error: ...                                |
    // | (์‹คํŒจ ์‹œ) ์‹คํŒจ ํšŸ์ˆ˜ ์ฆ๊ฐ€                      | ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์‹คํŒจ ํšŸ์ˆ˜๋ฅผ ์ฆ๊ฐ€์‹œํ‚ด               |                                                                 |
    // | (์‹คํŒจ์œจ ์ดˆ๊ณผ ์‹œ) ์‹คํŒจ์œจ ์ดˆ๊ณผ                   | ์„ค์ •๋œ ์‹คํŒจ์œจ ์ž„๊ณ„์น˜๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ๋ฐœ์ƒ               | #######CircuitBreaker Failure Rate Exceeded: ...                |
    // | (์‹คํŒจ์œจ ์ดˆ๊ณผ ์‹œ) ์ƒํƒœ ์ „ํ™˜ (Closed -> Open)   | ์—ฐ์†๋œ ์‹คํŒจ๋กœ ์ธํ•ด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์˜คํ”ˆ ์ƒํƒœ๋กœ ์ „ํ™˜๋จ   | #######CircuitBreaker State Transition: Closed -> Open at ...  |
    // | (์˜คํ”ˆ ์ƒํƒœ ์‹œ) ํ˜ธ์ถœ ์ฐจ๋‹จ                      | ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ์˜คํ”ˆ ์ƒํƒœ์ผ ๋•Œ ํ˜ธ์ถœ์ด ์ฐจ๋‹จ๋จ         | #######CircuitBreaker Call Not Permitted: ...                   |
    // | (์˜คํ”ˆ ์ƒํƒœ ์‹œ) ํด๋ฐฑ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ                 | ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ด ์ฐจ๋‹จ๋  ๊ฒฝ์šฐ ํด๋ฐฑ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ          | ####Fallback triggered for productId: ... due to: ...           |
    // +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+


   
}
resources/application.yml
spring:
  application:
    name: sample

server:
  port: 19090

resilience4j:
  circuitbreaker:
    configs:
      default:  # ๊ธฐ๋ณธ ๊ตฌ์„ฑ ์ด๋ฆ„
        registerHealthIndicator: true  # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ—ฌ์Šค ์ฒดํฌ์— ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ชจ๋‹ˆํ„ฐ๋ง ๊ฐ€๋Šฅ
        # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํƒ€์ž…์„ ์„ค์ •
        # COUNT_BASED: ๋งˆ์ง€๋ง‰ N๋ฒˆ์˜ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ฒฐ์ •
        # TIME_BASED: ๋งˆ์ง€๋ง‰ N์ดˆ ๋™์•ˆ์˜ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ฒฐ์ •
        slidingWindowType: COUNT_BASED  # ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํƒ€์ž…์„ ํ˜ธ์ถœ ์ˆ˜ ๊ธฐ๋ฐ˜(COUNT_BASED)์œผ๋กœ ์„ค์ •
        # ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํฌ๊ธฐ๋ฅผ ์„ค์ •
        # COUNT_BASED์ผ ๊ฒฝ์šฐ: ์ตœ๊ทผ N๋ฒˆ์˜ ํ˜ธ์ถœ์„ ์ €์žฅ
        # TIME_BASED์ผ ๊ฒฝ์šฐ: ์ตœ๊ทผ N์ดˆ ๋™์•ˆ์˜ ํ˜ธ์ถœ์„ ์ €์žฅ
        slidingWindowSize: 5  # ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ์˜ ํฌ๊ธฐ๋ฅผ 5๋ฒˆ์˜ ํ˜ธ์ถœ๋กœ ์„ค์ •
        minimumNumberOfCalls: 5  # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ํ˜ธ์ถœ ์ˆ˜๋ฅผ 5๋กœ ์„ค์ •
        slowCallRateThreshold: 100  # ๋А๋ฆฐ ํ˜ธ์ถœ์˜ ๋น„์œจ์ด ์ด ์ž„๊ณ„๊ฐ’(100%)์„ ์ดˆ๊ณผํ•˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘
        slowCallDurationThreshold: 60000  # ๋А๋ฆฐ ํ˜ธ์ถœ์˜ ๊ธฐ์ค€ ์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ)์œผ๋กœ, 60์ดˆ ์ด์ƒ ๊ฑธ๋ฆฌ๋ฉด ๋А๋ฆฐ ํ˜ธ์ถœ๋กœ ๊ฐ„์ฃผ
        failureRateThreshold: 50  # ์‹คํŒจ์œจ์ด ์ด ์ž„๊ณ„๊ฐ’(50%)์„ ์ดˆ๊ณผํ•˜๋ฉด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ ๋™์ž‘
        permittedNumberOfCallsInHalfOpenState: 3  # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ Half-open ์ƒํƒœ์—์„œ ํ—ˆ์šฉํ•˜๋Š” ์ตœ๋Œ€ ํ˜ธ์ถœ ์ˆ˜๋ฅผ 3์œผ๋กœ ์„ค์ •
        # ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๊ฐ€ Open ์ƒํƒœ์—์„œ Half-open ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๊ธฐ ์ „์— ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„
        waitDurationInOpenState: 20s  # Open ์ƒํƒœ์—์„œ Half-open ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๊ธฐ ์ „์— ๋Œ€๊ธฐํ•˜๋Š” ์‹œ๊ฐ„์„ 20์ดˆ๋กœ ์„ค์ •

management:
  endpoints:
    web:
      exposure:
        include: prometheus
  prometheus:
    metrics:
      export:
        enabled: true

 

Run

  • http://localhost:19090/products/11๋ฅผ 3๋ฒˆ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • http://localhost:19090/products/111์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด์„œ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๋˜ํ•œ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค๊ฐ€ Open ์ƒํƒœ๊ฐ€ ๋˜๋ฉด getProductDetails ํ•จ์ˆ˜๋ฅผ ํƒ€์ง€์•Š๊ณ  ๋ฐ”๋กœ fallbackGetProductDetails ๋กœ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

  • http://localhost:19090/actuator/prometheus๋กœ ์ ‘์†ํ•˜๋ฉด ์ธ์Šคํ„ด์Šค์˜ ์ƒํƒœ ๊ฐ’๋“ค์ด ๋‚˜์˜ต๋‹ˆ๋‹ค.
  • ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ€๋‹ค๋ณด๋ฉด ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค ๊ด€๋ จ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค์—์„œ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๐Ÿ“Š ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค(Prometheus): ์„œ๋ฒ„์˜ ์‹ค์‹œ๊ฐ„ ์ƒํƒœ ์ •๋ณด(๋ฉ”ํŠธ๋ฆญ)๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ˆ˜์ง‘ํ•˜๊ณ  ์ €์žฅํ•˜์—ฌ, ์‹œ์Šคํ…œ์— ๋ฌธ์ œ๊ฐ€ ์—†๋Š”์ง€ ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ฃผ๋Š” ์˜คํ”ˆ์†Œ์Šค ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

 

๐Ÿ—จ๏ธ Api GW

๊ฒŒ์ดํŠธ์›จ์ด ๊ฐœ์š”

API ๊ฒŒ์ดํŠธ์›จ์ด๋ž€?

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

API ๊ฒŒ์ดํŠธ์›จ์ด์˜ ์ฃผ์š” ๊ธฐ๋Šฅ

  • ๋ผ์šฐํŒ…: ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ์ ์ ˆํ•œ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌ
  • ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ: ์š”์ฒญ์˜ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ์„ ๊ฒ€์ฆ
  • ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: ์—ฌ๋Ÿฌ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๊ฐ„์˜ ๋ถ€ํ•˜ ๋ถ„์‚ฐ
  • ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋กœ๊น…: ์š”์ฒญ ๋ฐ ์‘๋‹ต์„ ๋กœ๊น…ํ•˜๊ณ  ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์š”์ฒญ ๋ฐ ์‘๋‹ต ๋ณ€ํ™˜: ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ํ•„ํ„ฐ๋ง

 

Spring Cloud Gateway ๊ฐœ์š”

Spring Cloud Gateway๋ž€?

  • Spring Cloud Gateway๋Š” Spring ํ”„๋กœ์ ํŠธ์˜ ์ผํ™˜์œผ๋กœ ๊ฐœ๋ฐœ๋œ API ๊ฒŒ์ดํŠธ์›จ์ด๋กœ, ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ์ ์ ˆํ•œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…ํ•˜๊ณ  ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • Spring Cloud Netflix ํŒจํ‚ค์ง€์˜ ์ผ๋ถ€๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

Spring Cloud Gateway์˜ ์ฃผ์š” ํŠน์ง•

  • ๋™์  ๋ผ์šฐํŒ…: ์š”์ฒญ์˜ URL ํŒจํ„ด์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ๋ผ์šฐํŒ…
  • ํ•„ํ„ฐ๋ง: ์š”์ฒญ ์ „ํ›„์— ๋‹ค์–‘ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํ•„ํ„ฐ ์ฒด์ธ ์ œ๊ณต
  • ๋ชจ๋‹ˆํ„ฐ๋ง: ์š”์ฒญ ๋กœ๊ทธ ๋ฐ ๋ฉ”ํŠธ๋ฆญ์„ ํ†ตํ•ด ์„œ๋น„์Šค ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ๋ณด์•ˆ: ์š”์ฒญ์˜ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ฒ€์ฆ

 

Spring Cloud Gateway ์„ค์ •

๊ธฐ๋ณธ ์„ค์ •

  • Spring Cloud Gateway๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
build.gradle ํŒŒ์ผ ์˜ˆ์‹œ:
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
	  implementation 'org.springframework.boot:spring-boot-starter-actuator'
	  implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	  implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}

๋ผ์šฐํŒ… ์„ค์ •

  • application.yml ํŒŒ์ผ์—์„œ ๋ผ์šฐํŒ… ์„ค์ •์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ์‹œ ์„ค์ • ํŒŒ์ผ:
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  # ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ๋ผ์šฐํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ์„ค์ •
      routes:
        - id: users-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://users-service # 'users-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/users/** # /users/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ
        - id: orders-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://orders-service  # 'orders-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/orders/** #/orders/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

 

Spring Cloud Gateway ํ•„ํ„ฐ๋ง

ํ•„ํ„ฐ ์ข…๋ฅ˜

  • Global Filter: ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์ž‘๋™ํ•˜๋Š” ํ•„ํ„ฐ
  • Gateway Filter: ํŠน์ • ๋ผ์šฐํŠธ์—๋งŒ ์ ์šฉ๋˜๋Š” ํ•„ํ„ฐ
    • ํŠน์ • ๋ผ์šฐํŠธ ์„ค์ •(yml)์— ๋ช…์‹œ์ ์œผ๋กœ ๋“ฑ๋ก๋œ ํŠน์ • ์„œ๋น„์Šค ์š”์ฒญ์—๋งŒ ์„ ๋ณ„์ ์œผ๋กœ ์ ์šฉ๋˜๋Š” ํ•„ํ„ฐ์ž…๋‹ˆ๋‹ค.  
    • ๋ผ์šฐํŠธ (Route): ์œ ์ž…๋œ ์š”์ฒญ์˜ ๋ชฉ์ ์ง€ ์ฃผ์†Œ(URI)์™€ ์กฐ๊ฑด(Predicate)์„ ์ •์˜ํ•˜์—ฌ ํŠน์ • ์„œ๋น„์Šค๋กœ ์—ฐ๊ฒฐํ•ด ์ฃผ๋Š” ๊ฒฝ๋กœ ๋งคํ•‘ ๊ทœ์น™์ž…๋‹ˆ๋‹ค.

ํ•„ํ„ฐ ๊ตฌํ˜„

  • ํ•„ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด GlobalFilter ๋˜๋Š” GatewayFilter ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , filter ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

ํ•„ํ„ฐ ์ฃผ์š” ๊ฐ์ฒด

  • Mono
    • Mono๋Š” ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ 0 ๋˜๋Š” 1๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
      1. ๋น„๋™๊ธฐ ์ž‘์—…์ด ๋๋‚ฌ์„ ๋•Œ '๊ฒฐ๊ณผ๊ฐ’ 1๊ฐœ๋ฅผ ๋ฐ˜ํ™˜'ํ•˜๊ฑฐ๋‚˜, ํ˜น์€ ๋ฐ์ดํ„ฐ ์—†์ด '์ž‘์—… ์™„๋ฃŒ ์‹ ํ˜ธ'๋งŒ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋น„๋™๊ธฐ ์ „์šฉ ๋ฆฌํ„ด ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
      2. ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ (Reactive Programming): ํŠน์ • ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ (Non-blocking), ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋ณ€ํ™”ํ•  ๋•Œ๋งˆ๋‹ค ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์‘ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
    • Mono<Void>๋Š” ์•„๋ฌด ๋ฐ์ดํ„ฐ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  • ServerWebExchange
    • ServerWebExchange๋Š” HTTP ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์บก์Аํ™”ํ•œ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
    • exchange.getRequest()๋กœ HTTP ์š”์ฒญ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    • exchange.getResponse()๋กœ HTTP ์‘๋‹ต์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • GatewayFilterChain
    • GatewayFilterChain์€ ์—ฌ๋Ÿฌ ํ•„ํ„ฐ๋ฅผ ์ฒด์ธ์ฒ˜๋Ÿผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
    • chain.filter(exchange)๋Š” ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

 

ํ•„ํ„ฐ ์‹œ์ ๋ณ„ ์ข…๋ฅ˜

Pre ํ•„ํ„ฐ

  • Pre ํ•„ํ„ฐ๋Š” ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋˜๊ธฐ ์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Pre ํ•„ํ„ฐ์—์„œ๋Š” ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ  ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ๋‹ค์Œ, ์ฒด์ธ์˜ ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ, ์ถ”๊ฐ€์ ์ธ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— then ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
@Component
public class PreFilter implements GlobalFilter, Ordered {

    /**
     * filter: ๋ชจ๋“  ๋ผ์šฐํŠธ ์š”์ฒญ ์‹œ ์‹คํ–‰๋˜๋Š” ํ•„ํ„ฐ ๋กœ์ง์ž…๋‹ˆ๋‹ค.
     * @param exchange: ํ˜„์žฌ ์š”์ฒญ์˜ HTTP Request ๋ฐ Response๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ํ†ตํ•ฉ ๊ฐ์ฒด
     * @param chain: ํ•„ํ„ฐ ์ฒด์ธ ๊ฐ์ฒด๋กœ, ํ˜„์žฌ ํ•„ํ„ฐ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚œ ๋’ค ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์ œ์–ด๊ถŒ์„ ๋„˜๊น๋‹ˆ๋‹ค.
     * @return Mono<Void>: ๋น„๋™๊ธฐ ์ž‘์—…์˜ ์™„๋ฃŒ ์‹ ํ˜ธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // [Logic] ํ˜„์žฌ ์š”์ฒญ์˜ URI ๊ฒฝ๋กœ๋ฅผ ์ถ”์ถœํ•˜์—ฌ ์ฝ˜์†”์— ๋กœ๊น…
        System.out.println("Request Path: " + exchange.getRequest().getPath());

        // [Chain] ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์„ ์ „๋‹ฌ (๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ฒด์ธ ์—ฐ๊ฒฐ)
        return chain.filter(exchange);
    }

    /**
     * getOrder: ์—ฌ๋Ÿฌ ํ•„ํ„ฐ ๊ฐ„์˜ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
     * - ๋ฐ˜ํ™˜ ๊ฐ’์ด ๋‚ฎ์„์ˆ˜๋ก ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์Šต๋‹ˆ๋‹ค. (์Œ์ˆ˜ ๊ฐ€๋Šฅ)
     * - ์—ฌ๊ธฐ์„œ๋Š” -1๋กœ ์„ค์ •ํ•˜์—ฌ ๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰๋˜๋„๋ก ์ง€์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
     */
    @Override
    public int getOrder() {
        return -1;
    }
}
  • ServerWebExchange: ์Šคํ”„๋ง ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ™˜๊ฒฝ(WebFlux)์—์„œ HTTP ์š”์ฒญ(Request)๊ณผ ์‘๋‹ต(Response)์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ๊ด€๋ฆฌํ•˜๋Š” ์ปจํ…์ŠคํŠธ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
  • GatewayFilterChain: ์—ฌ๋Ÿฌ ํ•„ํ„ฐ๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ์—ฐ๊ฒฐ๋œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. chain.filter(exchange)๋ฅผ ํ˜ธ์ถœํ•ด์•ผ๋งŒ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์ด ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.
  • Ordered: ํ•„ํ„ฐ์˜ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ์ „์—ญ ํ•„ํ„ฐ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ผ ๋•Œ, ๋ณด์•ˆ์ด๋‚˜ ๋กœ๊น…์ฒ˜๋Ÿผ ๋จผ์ € ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ์ž‘์—…์— ๋‚ฎ์€ ์ˆซ์ž(์˜ˆ: -1)๋ฅผ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

 

Post ํ•„ํ„ฐ

  • Post ํ•„ํ„ฐ๋Š” ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋œ ํ›„, ์‘๋‹ต์ด ๋ฐ˜ํ™˜๋˜๊ธฐ ์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. Post ํ•„ํ„ฐ์—์„œ๋Š” ์ฒด์ธ์˜ ๋‹ค์Œ ํ•„ํ„ฐ๊ฐ€ ์™„๋ฃŒ๋œ ํ›„์— ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ chain.filter(exchange)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋‹ค์Œ ํ•„ํ„ฐ๋ฅผ ์‹คํ–‰ํ•œ ํ›„, then ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์ด ์™„๋ฃŒ๋œ ํ›„์— ์‹คํ–‰ํ•  ์ž‘์—…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
@Component
public class PostFilter implements GlobalFilter, Ordered {

    /**
     * filter: ์š”์ฒญ์ด ํ•˜์œ„ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌ๋œ ํ›„, ์‘๋‹ต์ด ๋Œ์•„์˜ฌ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
     * @param exchange: ์š”์ฒญ/์‘๋‹ต ์ปจํ…์ŠคํŠธ ๊ฐ์ฒด
     * @param chain: ํ•„ํ„ฐ ์ฒด์ธ ์ œ์–ด ๊ฐ์ฒด
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // [Logic] chain.filter(exchange)๊ฐ€ ์™„๋ฃŒ๋œ ํ›„(.then) ์‹คํ–‰ํ•  ๋กœ์ง์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
        // ์ด ๊ตฌ์กฐ ๋•๋ถ„์— "์š”์ฒญ -> ํ•˜์œ„ ์„œ๋น„์Šค ํ˜ธ์ถœ -> ์‘๋‹ต" ๊ณผ์ • ์ดํ›„์— ํ•„ํ„ฐ๊ฐ€ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            
            // [Post Logic] ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ถ”์ถœํ•˜์—ฌ ๋กœ๊ทธ๋กœ ์ถœ๋ ฅ
            System.out.println("Response Status: " + exchange.getResponse().getStatusCode());
            
        }));
    }

    /**
     * getOrder: ํ•„ํ„ฐ์˜ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
     * Pre Filter ๋‹จ๊ณ„์—์„œ๋Š” ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์„์ˆ˜๋ก ๋จผ์ € ์‹คํ–‰๋˜์ง€๋งŒ, 
     * Post Filter ๋‹จ๊ณ„(then ์ดํ›„)์—์„œ๋Š” ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์„์ˆ˜๋ก ๊ฐ€์žฅ ๋‚˜์ค‘์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
     */
    @Override
    public int getOrder() {
        return -1;
    }
}
  • .then(...): ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์•ž์„  ์ž‘์—…(chain.filter)์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋œ ์งํ›„์— ์‹คํ–‰ํ•  ํ›„์† ์ž‘์—…์„ ์ง€์ •ํ•˜๋Š” ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์— ์ž‘์„ฑ๋œ ์ฝ”๋“œ๊ฐ€ ๋ฐ”๋กœ Post Filter ์—ญํ• ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • Mono.fromRunnable(() -> { ... }): ๋ฆฌํ„ดํ•  ๋ฐ์ดํ„ฐ๋Š” ์—†์ง€๋งŒ, ํŠน์ • ๋กœ์ง(๋กœ๊ทธ ์ถœ๋ ฅ ๋“ฑ)์„ ๋น„๋™๊ธฐ ํ๋ฆ„ ์•ˆ์—์„œ ์‹คํ–‰์‹œํ‚ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
  • exchange.getResponse(): ์š”์ฒญ์ด ์•„๋‹Œ, ์ตœ์ข…์ ์œผ๋กœ ๊ฐ€๊ณต๋˜์–ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋‚˜๊ฐˆ ์‘๋‹ต ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜์—ฌ ์ƒํƒœ ์ฝ”๋“œ๋‚˜ ํ—ค๋” ์ •๋ณด๋ฅผ ์ฝ์–ด์˜ฌ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

 

Spring Cloud์™€์˜ ํ†ตํ•ฉ

  • Spring Cloud Gateway๋Š” Spring Cloud Netflix ํŒจํ‚ค์ง€์˜ ์ผ๋ถ€๋กœ, Eureka์™€ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Eureka๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ์กฐํšŒํ•˜์—ฌ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๊ณผ ๋ผ์šฐํŒ…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Zuul(Spring Boot 2)

  • ๋Œ€๋ถ€๋ถ„์˜ ํšŒ์‚ฌ์—์„œ ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ ์‹œ์Šคํ…œ์€ ์Šคํ”„๋ง ๋ถ€ํŠธ 2๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Zuul์„ ์•Œ๊ณ  ๊ฐ€๋Š” ๊ฒƒ์ด ์ข‹์„๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ ๋ณด๋‹ค๋Š” ๊ธฐ๋Šฅ์— ์ง‘์ค‘ํ•˜์„ธ์š”. ”ํด๋ผ์šฐ๋“œ ๊ฒŒ์ดํŠธ์›จ์ด์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๊ตฌ๋‚˜” ๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ๋„˜์–ด๊ฐ€๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Zuul ์„ค์ •

  • Spring Boot 2์—์„œ๋Š” Zuul์„ ์‚ฌ์šฉํ•˜์—ฌ API ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
build.gradle ํŒŒ์ผ ์˜ˆ์‹œ:
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •:
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}

๋ผ์šฐํŒ… ์„ค์ •

  • application.yml ํŒŒ์ผ์—์„œ ๋ผ์šฐํŒ… ์„ค์ •์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ์‹œ ์„ค์ • ํŒŒ์ผ:
zuul:
  routes:
    users-service:
      path: /users/**
      serviceId: users-service
    orders-service:
      path: /orders/**
      serviceId: orders-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

 

ํ•„ํ„ฐ ์„ค์ •

  • Zuul ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ ์ „ํ›„์— ๋‹ค์–‘ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ์‹œ ์ฝ”๋“œ:
@Component
public class PreFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        // ์š”์ฒญ ๋กœ๊น…
        System.out.println(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
        return null;
    }
}

 

์‹ค์Šต

๐Ÿ’ก ํด๋ผ์šฐ๋“œ ๊ฒŒ์ดํŠธ์›จ์ด + ์œ ๋ ˆ์นด + Order ์ธ์Šคํ„ด์Šค(1๊ฐœ) + Product ์ธ์Šคํ„ด์Šค(2๊ฐœ) ๋กœ ์ง„ํ–‰ํ•ด๋ด…๋‹ˆ๋‹ค.

 

๐Ÿ—๏ธ ์ „์ฒด ์‹œ์Šคํ…œ ๊ตฌ์กฐ
  1. Eureka Server (19090): ๋ชจ๋“  ์„œ๋น„์Šค(Order, Product, Gateway)์˜ ์ฃผ์†Œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ „ํ™”๋ฒˆํ˜ธ๋ถ€ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  2. Order Service (19092): /order ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์‹ค์ œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.
  3. Product Service (19093, 19094): /product ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ํฌํŠธ๊ฐ€ ๋‘ ๊ฐœ๋ผ ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ(๋ถ„์‚ฐ ์ฒ˜๋ฆฌ)์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  4. Gateway (19091): ๋ชจ๋“  ์š”์ฒญ์ด ํ†ต๊ณผํ•˜๋Š” ๋‹จ์ผ ์ง„์ž…์ ์ด๋ฉฐ, ํ•„ํ„ฐ๋ฅผ ํ†ตํ•ด ์š”์ฒญ/์‘๋‹ต์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

 

Eureka ๋ฐ Order, Product

  • “๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ” ์‹ค์Šต์—์„œ์˜ ์œ ๋ ˆ์นด ์„œ๋ฒ„์™€ Order, Product๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•˜์—ฌ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

์ฃผ๋ฌธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
OrderContorller.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @GetMapping
    public String getOrder() {
        return "Order details";
    }
}
application.yml
server:
  port: 19092

spring:
  application:
    name: order-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

 

์ƒํ’ˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
ProductContorller.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    @Value("${server.port}") // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰ ์ค‘์ธ ํฌํŠธ๋ฅผ ์ฃผ์ž…๋ฐ›์Šต๋‹ˆ๋‹ค.
    private String serverPort;

    @GetMapping("/product")
    public String getProduct() {
        return "Product info!!!!! From port : " + serverPort;
    }
}
application.yml
server:
  port: 19093

spring:
  application:
    name: product-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka
  • ์ƒํ’ˆ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ํ™•์ธ์„ ์œ„ํ•ด 2๊ฐœ์˜ ํฌํŠธ(19093.19094)์— ์‹คํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

[์ถ”๊ฐ€์ •๋ณด] ์‚ฌ์šฉ์ค‘์ธ ํฌํŠธ ์ •์ง€์‹œํ‚ค๊ธฐ

์œˆ๋„์šฐ
  • CMD ์ฐฝ์„ ์—ด๊ณ  ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. ๋’ค์— ์ˆซ์ž๋Š” ์ •์ง€์‹œํ‚ค๊ณ ์ž ํ•˜๋Š” ํฌํŠธ์ž…๋‹ˆ๋‹ค.
netstat -a -o | findstr 19093
  • ๊ทธ๋Ÿฌ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ด๋‹น ํฌํŠธ์˜ ํ”„๋กœ์„ธ์Šค ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. ๋ฆฌ์ŠคํŠธ์˜ ๋งˆ์ง€๋ง‰์ด PID ์ด๋ฉฐ ์ด ์•„์ด๋“œ๋ฅผ ํ†ตํ•ด ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์ •์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
TCP     0.0.0.0:19093    0.0.0.0:0   LISTENING   2340
  • ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ํ”„๋กœ์„ธ์Šค๋ฅผ ์ •์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ •์ง€ ๋ช…๋ น์–ด์˜ ์ˆซ์ž๋Š” ์œ„์—์„œ ์กฐํšŒํ•œ PID ์ž…๋‹ˆ๋‹ค.
taskkill /f /pid 2340

 

๊ฒŒ์ดํŠธ ์›จ์ด

  • start.spring.io ์—์„œ ์•„๋ž˜์™€ ๊ฐ™์€ ๋””ํŽœ๋˜์‹œ๋ฅผ ์ถ”๊ฐ€ ํ›„ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“‘ ํ•ต์‹ฌ ์ฝ”๋“œ ์š”์•ฝ
  • GlobalFilter: ์–ด๋–ค ์„œ๋น„์Šค๋กœ ๊ฐ€๋“  ๋ฌด์กฐ๊ฑด ๊ฑฐ์ณ์•ผ ํ•˜๋Š” ํ•„ํ„ฐ์ž„์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • Ordered: ํ•„ํ„ฐ๋“ค์ด ์—ฌ๋Ÿฌ ๊ฐœ์ผ ๋•Œ ์‹คํ–‰๋  ์ˆœ์„œ๋ฅผ ์ˆซ์ž๋กœ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. (์ž‘์€ ์ˆซ์ž๊ฐ€ ๋จผ์ € ์‹คํ–‰)
  • lb:// (Load Balancer): ์œ ๋ ˆ์นด์— ๋“ฑ๋ก๋œ ์„œ๋น„์Šค ์ด๋ฆ„์„ ์ฐพ์•„ ์ž๋™์œผ๋กœ ํŠธ๋ž˜ํ”ฝ์„ ๋ถ„์‚ฐํ•˜๋Š” ๋ฌธ๋ฒ•์ž…๋‹ˆ๋‹ค.
  • then(): ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ "์„œ๋น„์Šค๋ฅผ ๊ฐ”๋‹ค ์˜จ ๋‹ค์Œ์— ์‹คํ–‰ํ•ด๋ผ"๋ผ๊ณ  ์‹œ์ ์„ ์ง€์ •ํ•˜๋Š” ํ•ต์‹ฌ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.

[๊ฒŒ์ดํŠธ์›จ์ด ์ž…์žฅ -> ๋กœ๊ทธ ๊ธฐ๋ก -> ์œ ๋ ˆ์นด ์ฃผ์†Œ ์กฐํšŒ -> ์‹ค์ œ ์„œ๋น„์Šค ํ˜ธ์ถœ -> ๋Œ์•„์˜ค๋ฉด์„œ ๋กœ๊ทธ ๊ธฐ๋ก -> ์‘๋‹ต ์™„๋ฃŒ]์˜ ๊ณผ์ •์„ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

CustomPreFilter.java
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.logging.Logger;

@Component
public class CustomPreFilter implements GlobalFilter, Ordered {

    private static final Logger logger = Logger.getLogger(CustomPreFilter.class.getName());

    /**
     * filter: ๊ฒŒ์ดํŠธ์›จ์ด๋กœ ๋“ค์–ด์˜จ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์‹คํ–‰๋˜๋Š” ๋กœ์ง์ž…๋‹ˆ๋‹ค.
     * @param exchange: HTTP ์š”์ฒญ(Request)๊ณผ ์‘๋‹ต(Response)์„ ํฌํ•จํ•˜๋Š” ์›น ์ปจํ…์ŠคํŠธ
     * @param chain: ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์„ ๋„˜๊ฒจ์ฃผ๋Š” ํ•„ํ„ฐ ์ฒด์ธ ์ œ์–ด ๊ฐ์ฒด
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // [Logic] ํ˜„์žฌ ์š”์ฒญ(Request) ๊ฐ์ฒด๋ฅผ ์ถ”์ถœ
        ServerHttpRequest request = exchange.getRequest();
        
        // [Logging] ์š”์ฒญ์˜ ๋ชฉ์ ์ง€ URI๋ฅผ ๋กœ๊ทธ๋กœ ๋‚จ๊น€ (Pre-Processing)
        logger.info("Pre Filter: Request URI is " + request.getURI());

        // [Chain] ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์ œ์–ด๊ถŒ์„ ๋„˜๊น€ (๋น„๋™๊ธฐ ๋…ผ๋ธ”๋กœํ‚น ์ฒ˜๋ฆฌ)
        return chain.filter(exchange);
    }

    /**
     * getOrder: ํ•„ํ„ฐ์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
     * - Ordered.HIGHEST_PRECEDENCE: ๊ฐ€์žฅ ๋‚ฎ์€ ์ •์ˆ˜๊ฐ’(Integer.MIN_VALUE)์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ 
     * ์ „์ฒด ํ•„ํ„ฐ ์ฒด์ธ ์ค‘ ๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
  • ๐Ÿ“‘ ํ•ต์‹ฌ ๊ฐ์ฒด ๋ฐ ์„ค์ • ๋ช…์„ธ
    • ServerHttpRequest: ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋‚ธ HTTP ์š”์ฒญ ์ •๋ณด(URI, Header, Method ๋“ฑ)์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
    • Ordered.HIGHEST_PRECEDENCE: ํ•„ํ„ฐ ์ˆœ์„œ๋ฅผ ์ˆซ์ž๋กœ ์ผ์ผ์ด ์ง€์ •ํ•˜๋Š” ๋Œ€์‹ , ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ตœ์šฐ์„ ์ˆœ์œ„ ์ƒ์ˆซ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.
    • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: ๋ฆฌํ„ด ํƒ€์ž…์ธ Mono<Void>๋Š” ํ•ด๋‹น ํ•„ํ„ฐ์˜ ๋กœ์ง์ด ๋๋‚ฌ์Œ์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์•Œ๋ฆฌ๊ณ  ๋‹ค์Œ ํ•„ํ„ฐ(chain.filter)๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
  • โœ… ์š”์•ฝ
    • ๋ชฉ์ : ์œ ๋ ˆ์นด์— ๋“ฑ๋ก๋œ Order/Product ์„œ๋น„์Šค๋กœ ์š”์ฒญ์ด ์ „๋‹ฌ๋˜๊ธฐ ์ „, ๋ชจ๋“  ์š”์ฒญ์˜ URI๋ฅผ ๊ฐ์‹œํ•˜๊ณ  ๋กœ๊น…ํ•ฉ๋‹ˆ๋‹ค.
    • ์ˆœ์„œ: HIGHEST_PRECEDENCE๋ฅผ ํ†ตํ•ด ๊ฒŒ์ดํŠธ์›จ์ด ๋‚ด๋ถ€์˜ ์ˆ˜๋งŽ์€ ํ•„ํ„ฐ ์ค‘ ๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
    • ํ™•์žฅ์„ฑ: ์ด ํ•„ํ„ฐ ๋‚ด๋ถ€์— ํŠน์ • ํ—ค๋” ๊ฒ€์ฆ์ด๋‚˜ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ณตํ†ต ๋ณด์•ˆ ํ•„ํ„ฐ๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
CustomPostFilter.java
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.logging.Logger;

@Component
public class CustomPostFilter implements GlobalFilter, Ordered {

    private static final Logger logger = Logger.getLogger(CustomPostFilter.class.getName());

    /**
     * filter: ํ•˜์œ„ ์„œ๋น„์Šค๋กœ๋ถ€ํ„ฐ ์‘๋‹ต์ด ๋Œ์•„์˜จ ํ›„ ์‹คํ–‰๋˜๋Š” ๋กœ์ง์ž…๋‹ˆ๋‹ค.
     * @param exchange: ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ํฌํ•จํ•˜๋Š” ์ปจํ…์ŠคํŠธ ๊ฐ์ฒด
     * @param chain: ํ•„ํ„ฐ ์ฒด์ธ ์ œ์–ด ๊ฐ์ฒด
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
        // [Logic] chain.filter(exchange)๊ฐ€ ์™„๋ฃŒ๋œ ํ›„(.then) ์‹คํ–‰ํ•  ์ž‘์—…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
        // ๋น„๋™๊ธฐ ํ๋ฆ„์ƒ '์‘๋‹ต' ๋‹จ๊ณ„์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            
            // [Response Status] ์ตœ์ข… ์‘๋‹ต ๊ฐ์ฒด์—์„œ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
            ServerHttpResponse response = exchange.getResponse();
            
            // [Logging] ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋กœ๊ทธ๋กœ ๋‚จ๊น๋‹ˆ๋‹ค. (Post-Processing)
            logger.info("Post Filter: Response status code is " + response.getStatusCode());
            
        }));
    }

    /**
     * getOrder: ํ•„ํ„ฐ์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
     * - Ordered.LOWEST_PRECEDENCE: ๊ฐ€์žฅ ๋†’์€ ์ •์ˆ˜๊ฐ’(Integer.MAX_VALUE)์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ
     * ์ „์ฒด ํ•„ํ„ฐ ์ฒด์ธ ์ค‘ ๊ฐ€์žฅ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. (๊ฐ€์žฅ ๋‚˜์ค‘์— ์‹คํ–‰)
     */
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
  • ๐Ÿ“‘ ํ•ต์‹ฌ ๋ฌธ๋ฒ• ๋ฐ ๋™์ž‘ ๋ช…์„ธ
    • .then(Mono.fromRunnable(...)): ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•ต์‹ฌ ์—ฐ์‚ฐ์ž๋กœ, ํ•˜์œ„ ์„œ๋น„์Šค(Order, Product ๋“ฑ)์˜ ์ฒ˜๋ฆฌ๊ฐ€ ์™„์ „ํžˆ ๋๋‚œ ์‹œ์ ์— ํŠน์ • ๋กœ์ง์„ ์‹คํ–‰์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • ServerHttpResponse: ์š”์ฒญ์ด ์•„๋‹Œ, ์ตœ์ข…์ ์œผ๋กœ ๊ฐ€๊ณต๋˜์–ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋‚˜๊ฐˆ ์‘๋‹ต ์ •๋ณด(Status Code, Headers ๋“ฑ)๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
    • Ordered.LOWEST_PRECEDENCE: ์ด ํ•„ํ„ฐ๋ฅผ ์ฒด์ธ์˜ ๊ฐ€์žฅ ๋์— ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์„œ๋น„์Šค ๋กœ์ง๊ณผ ๋‹ค๋ฅธ ํ•„ํ„ฐ๋“ค์˜ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚œ ํ›„ ์ตœ์ข…์ ์œผ๋กœ ์‘๋‹ต ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ธฐ์— ์ ํ•ฉํ•œ ์„ค์ •์ž…๋‹ˆ๋‹ค.
  • โœ… ์š”์•ฝ
    1. ๋™์ž‘ ์‹œ์ : ์„œ๋น„์Šค์˜ ์‘๋‹ต์ด ๊ฒŒ์ดํŠธ์›จ์ด์— ๋„์ฐฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†ก๋˜๊ธฐ ์ง์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
    2. ๊ตฌํ˜„ ํŠน์ง•: then ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์˜ '์™„๋ฃŒ ์ดํ›„' ์‹œ์ ์„ ์žก๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.
    3. ์‹ค์Šต ์˜๋ฏธ: ์ด ํ•„ํ„ฐ๊นŒ์ง€ ํ†ต๊ณผํ•ด์•ผ ์‚ฌ์šฉ์ž๋Š” ์ตœ์ข…์ ์œผ๋กœ Product๋‚˜ Order์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
application.yml
server:
  port: 19091  # ๊ฒŒ์ดํŠธ์›จ์ด ์„œ๋น„์Šค๊ฐ€ ์‹คํ–‰๋  ํฌํŠธ ๋ฒˆํ˜ธ

spring:
  main:
    web-application-type: reactive  # Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฆฌ์•กํ‹ฐ๋ธŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์„ค์ •๋จ
  application:
    name: gateway-service  # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„์„ 'gateway-service'๋กœ ์„ค์ •
  cloud:
    gateway:
      routes:  # Spring Cloud Gateway์˜ ๋ผ์šฐํŒ… ์„ค์ •
        - id: order-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://order-service  # 'order-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/order/**  # /order/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ
        - id: product-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://product-service  # 'product-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/product/**  # /product/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ
      discovery:
        locator:
          enabled: true  # ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ๋ผ์šฐํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ์„ค์ •

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/  # Eureka ์„œ๋ฒ„์˜ URL์„ ์ง€์ •

 

โš™๏ธ Gateway YAML ํ•ต์‹ฌ ์„ค์ • ์š”์•ฝ

 

1. routes (๋ผ์šฐํŒ… ๊ทœ์น™)

  • id: ํ•ด๋‹น ๋ผ์šฐํŒ… ๊ทœ์น™์˜ ๊ณ ์œ  ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์šฉ ์ด๋ฆ„์ด๋ผ๊ณ  ๋ณด์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • predicates (์กฐ๊ฑด): "์–ด๋–ค ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ ์ด ๊ทœ์น™์„ ์ ์šฉํ•  ๊ฒƒ์ธ๊ฐ€?"๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
    • - Path=/order/**: ์ ‘์†ํ•œ URL์ด /order/๋กœ ์‹œ์ž‘ํ•˜๋ฉด ์ด ๋ผ์šฐํŠธ(Order ์„œ๋น„์Šค)๋กœ ๋ณด๋‚ด๊ฒ ๋‹ค๋Š” ๋งค์นญ ์กฐ๊ฑด์ž…๋‹ˆ๋‹ค.

2. uri: lb:// (๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ ๋ฐ ๋ชฉ์ ์ง€)

  • lb:// (Load Balancer): ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ๋ชฉ์ ์ง€ ์ฃผ์†Œ๋ฅผ http://192.168... ์ฒ˜๋Ÿผ ๊ณ ์ • IP๋กœ ์ ์ง€ ์•Š๊ณ , ์„œ๋น„์Šค ์ด๋ฆ„์œผ๋กœ ์ ์Šต๋‹ˆ๋‹ค.
  • ์ž‘๋™ ์›๋ฆฌ: ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์œ ๋ ˆ์นด(Eureka) ์„œ๋ฒ„์— ๋ฌผ์–ด๋ณด๊ณ , ํ•ด๋‹น ์ด๋ฆ„์„ ๊ฐ€์ง„ ์ธ์Šคํ„ด์Šค(์˜ˆ: Product 1, Product 2) ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ณจ๋ผ ์š”์ฒญ์„ ๋ถ„์‚ฐํ•ด์„œ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

3. discovery.locator.enabled: true

  • ์œ ๋ ˆ์นด ์„œ๋ฒ„์— ๋“ฑ๋ก๋œ ์„œ๋น„์Šค๋“ค์˜ ๋ชฉ๋ก์„ ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์‹œํ•˜๋ฉฐ, ์ƒˆ๋กœ์šด ์„œ๋น„์Šค๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ๋งˆ๋‹ค ๋™์ ์œผ๋กœ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ํ—ˆ์šฉํ•˜๋Š” ์„ค์ •์ž…๋‹ˆ๋‹ค.

4. eureka.client.service-url.defaultZone

  • ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์„œ๋น„์Šค๋“ค์˜ ์‹ค์ œ ์ฃผ์†Œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์œ ๋ ˆ์นด ์„œ๋ฒ„์˜ ์œ„์น˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ์„ค์ •์ด ์žˆ์–ด์•ผ ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์œ ๋ ˆ์นด๋ผ๋Š” '์ „ํ™”๋ฒˆํ˜ธ๋ถ€'๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๐Ÿ“‘ ํ•œ ์ค„ ํ•ต์‹ฌ ์š”์•ฝ
  • "๊ฒŒ์ดํŠธ์›จ์ด๋Š” predicates ์กฐ๊ฑด์— ๋งž๋Š” ์š”์ฒญ์ด ์˜ค๋ฉด, ์œ ๋ ˆ์นด์—์„œ lb:// ๋’ค์— ์ ํžŒ ์„œ๋น„์Šค ์ด๋ฆ„์„ ์ฐพ์•„ ํ•ด๋‹น ์ฃผ์†Œ๋กœ ์š”์ฒญ์„ ํ† ์Šคํ•œ๋‹ค."

 

๐Ÿ”„ ์š”์ฒญ ์ฒ˜๋ฆฌ ํ๋ฆ„ (Step-by-Step)
  • ์‚ฌ์šฉ์ž๊ฐ€ http://localhost:19091/product๋กœ ์š”์ฒญ์„ ๋ณด๋ƒˆ์„ ๋•Œ์˜ ๋™์ž‘ ์ˆœ์„œ์ž…๋‹ˆ๋‹ค.

1๋‹จ๊ณ„: Gateway ์ง„์ž… ๋ฐ ์‚ฌ์ „ ํ•„ํ„ฐ ์‹คํ–‰

  • ๊ฐ€์žฅ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ CustomPreFilter๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ์˜ exchange.getRequest().getURI() ๋กœ์ง์„ ํ†ตํ•ด "์–ด๋–ค ์ฃผ์†Œ๋กœ ์š”์ฒญ์ด ์™”๋Š”์ง€" ๋กœ๊ทธ๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค.
  • chain.filter(exchange)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋ฐ”ํ†ต์„ ๋„˜๊น๋‹ˆ๋‹ค.

2๋‹จ๊ณ„: ๋ผ์šฐํŒ… ๋ฐ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ (YML ์„ค์ • ๊ธฐ์ค€)

  • application.yml์˜ predicates ์„ค์ •์— ๋”ฐ๋ผ /product/** ๊ฒฝ๋กœ์ž„์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • uri: lb://product-service ์„ค์ •์„ ๋ณด๊ณ  ์œ ๋ ˆ์นด์—๊ฒŒ "Product ์„œ๋น„์Šค ์ฃผ์†Œ ํ•˜๋‚˜ ์ค˜"๋ผ๊ณ  ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
  • ์œ ๋ ˆ์นด๋กœ๋ถ€ํ„ฐ 19093 ๋˜๋Š” 19094 ํฌํŠธ๋ฅผ ๋ฐ›์•„ ํ•ด๋‹น ์ธ์Šคํ„ด์Šค๋กœ ์š”์ฒญ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

3๋‹จ๊ณ„: ์‹ค์ œ ์„œ๋น„์Šค(Microservice) ์‹คํ–‰

  • Product ์„œ๋น„์Šค์˜ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋˜์–ด "Product info!!!!! From port : 1909x" ๋ฌธ์ž์—ด์„ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

4๋‹จ๊ณ„: ์‚ฌํ›„ ํ•„ํ„ฐ ์‹คํ–‰ (๋ณต๊ท€ ๊ธธ)

  • ์‘๋‹ต์ด ๊ฒŒ์ดํŠธ์›จ์ด๋กœ ๋Œ์•„์˜ค๋ฉด, ์•„๊นŒ ์˜ˆ์•ฝํ•ด๋‘” CustomPostFilter์˜ .then() ๋‚ด๋ถ€ ๋กœ์ง์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • exchange.getResponse().getStatusCode()๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์‘๋‹ตํ–ˆ๋Š”์ง€(200 OK ๋“ฑ) ๋กœ๊ทธ๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค.

5๋‹จ๊ณ„: ์ตœ์ข… ์‘๋‹ต

  • ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฌผ์„ ์ „๋‹ฌํ•˜๋ฉฐ ํ†ต์‹ ์ด ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

 

Run

  • ์œ ๋ ˆ์นด ์„œ๋ฒ„ ⇒ ๊ฒŒ์ดํŠธ์›จ์ด ⇒ ์ฃผ๋ฌธ ⇒ ์ƒํ’ˆ ์ˆœ์œผ๋กœ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • http://localhost:19090์— ์ ‘์†ํ•˜์—ฌ ๊ฐ ์ธ์Šคํ„ด์Šค๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • http://localhost:19091/order ๋กœ ์ ‘์†ํ•˜์—ฌ ๊ฒŒ์ดํŠธ์›จ์ด์—์„œ order ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • http://localhost:19091/product ๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ํ˜ธ์ถœ ํ•˜๋ฉด์„œ ํฌํŠธ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ์ด ๋™์ž‘ํ•จ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

  • ๊ฒŒ์ดํŠธ์›จ์ด์˜ ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด ํ˜ธ์ถœ ํ• ๋•Œ๋งˆ๋‹ค ํ•„ํ„ฐ๊ฐ€ ๋™์ž‘ํ•˜๋Š”๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ—จ๏ธ ๋ณด์•ˆ๊ตฌ์„ฑ

๋ณด์•ˆ ๊ฐœ์š”

๋ณด์•ˆ์˜ ์ค‘์š”์„ฑ

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

 

OAuth2 ๊ฐœ์š”

OAuth2๋ž€?

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

OAuth2์˜ ์ฃผ์š” ๊ฐœ๋…

  • Authorization Code Grant: ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ก์„ธ์Šค ํ† ํฐ์„ ์–ป๋Š” ๋ฐฉ์‹
  • Implicit Grant: ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ง์ ‘ ์•ก์„ธ์Šค ํ† ํฐ์„ ์–ป๋Š” ๋ฐฉ์‹
  • Resource Owner Password Credentials Grant: ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ก์„ธ์Šค ํ† ํฐ์„ ์–ป๋Š” ๋ฐฉ์‹
  • Client Credentials Grant: ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ž์‹ ์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์‚ฌ์šฉํ•˜์—ฌ ์•ก์„ธ์Šค ํ† ํฐ์„ ์–ป๋Š” ๋ฐฉ์‹

 

JWT ๊ฐœ์š”

JWT๋ž€?

  • JWT(JSON Web Token)๋Š” JSON ํ˜•์‹์˜ ์ž๊ฐ€ ํฌํ•จ๋œ ํ† ํฐ์œผ๋กœ, ํด๋ ˆ์ž„(claim)์„ ํฌํ•จํ•˜์—ฌ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  • JWT๋Š” ์„ธ ๋ถ€๋ถ„์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค: ํ—ค๋”, ํŽ˜์ด๋กœ๋“œ, ์„œ๋ช…
  • JWT๋Š” ์•”ํ˜ธํ™”๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ๊ณผ ์ถœ์ฒ˜๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
  • https://jwt.io/

JWT์˜ ์ฃผ์š” ํŠน์ง•

  • ์ž๊ฐ€ ํฌํ•จ: ํ† ํฐ ์ž์ฒด์— ๋ชจ๋“  ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด ๋ณ„๋„์˜ ์ƒํƒœ ์ €์žฅ์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.
  • ๊ฐ„๊ฒฐ์„ฑ: ์งง๊ณ  ๊ฐ„๊ฒฐํ•œ ๋ฌธ์ž์—ด๋กœ, URL, ํ—ค๋” ๋“ฑ์— ์‰ฝ๊ฒŒ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„œ๋ช… ๋ฐ ์•”ํ˜ธํ™”: ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ๊ณผ ์ธ์ฆ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

 

์‹ค์Šต

  • โ— ์ด๋ฒˆ ๊ฐ•์˜์—์„œ๋Š” ์‹ค์Šต์„ ํ†ตํ•ด ํด๋ผ์šฐ๋“œ ๊ฒŒ์ดํŠธ์›จ์ด์˜ Pre ํ•„ํ„ฐ์—์„œ JWT ์ธ์ฆ์„ ์ง„ํ–‰ํ•ด๋ด…๋‹ˆ๋‹ค. ์šฐ์„  “์Šคํ”„๋ง ํด๋ผ์šฐ๋“œ ๊ฒŒ์ดํŠธ์›จ์ด”์—์„œ ํ•™์Šตํ•œ ๋ชจ๋“  ํ”„๋กœ์ ํŠธ๋ฅผ ๋ณต์‚ฌํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
  • ์—ฌ๊ธฐ์— Auth Service ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ํด๋ผ์šฐ๋“œ ๊ฒŒ์ดํŠธ์›จ์ด์— Pre ํ•„ํ„ฐ๋ฅผ ํ•˜๋‚˜ ๋” ์ƒ์„ฑํ•˜์—ฌ ๋กœ๊ทธ์ธ์„ ์ฒดํฌ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
  • ์•„๋ž˜ ์ด๋ฏธ์ง€์˜ ์ ์„ ๋งŒ ์‹คํ–‰ํ•˜์—ฌ ํ™•์ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
  • PostMan ๋˜๋Š” ํฌ๋กฌ ์ต์Šคํ…์…˜ ์ค‘ Talend API Tester๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ•์˜์—์„œ๋Š” Talend๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

Auth Service

  • ๋กœ๊ทธ์ธ์„ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•˜๋ฉด ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๊ณ  ์ด ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ Gateway๋ฅผ ํ˜ธ์ถœ ํ•ฉ๋‹ˆ๋‹ค.
start.spring.io ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

build.gradle ํŒŒ์ผ์˜ ๋””ํŽœ๋˜์‹œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ( jwt ์ถ”๊ฐ€ )
dependencies {
	implementation 'io.jsonwebtoken:jjwt:0.12.6'
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	testImplementation 'io.projectreactor:reactor-test'
	
}
application.yml
spring:
  application:
    name: auth-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

service:
  jwt:
    access-expiration: 3600000
    secret-key: "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"

server:
  port: 19095
AuthConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class AuthConfig {

    // SecurityFilterChain ๋นˆ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” Spring Security์˜ ๋ณด์•ˆ ํ•„ํ„ฐ ์ฒด์ธ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // CSRF ๋ณดํ˜ธ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. CSRF ๋ณดํ˜ธ๋Š” ์ฃผ๋กœ ๋ธŒ๋ผ์šฐ์ € ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•˜๋Š” ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
            .csrf(csrf -> csrf.disable())
            // ์š”์ฒญ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
            .authorizeRequests(authorize -> authorize
                // /auth/signIn ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ๋กœ๋Š” ์ธ์ฆ ์—†์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
                .requestMatchers("/auth/signIn").permitAll()
                // ๊ทธ ์™ธ์˜ ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
                .anyRequest().authenticated()
            )
            // ์„ธ์…˜ ๊ด€๋ฆฌ ์ •์ฑ…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก STATELESS๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );

        // ์„ค์ •๋œ ๋ณด์•ˆ ํ•„ํ„ฐ ์ฒด์ธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        return http.build();
    }
}
AuthService.java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.util.Date;

@Service
public class AuthService {

    @Value("${spring.application.name}")
    private String issuer;

    @Value("${service.jwt.access-expiration}")
    private Long accessExpiration;

    private final SecretKey secretKey;

    /**
     * AuthService ์ƒ์„ฑ์ž.
     * Base64 URL ์ธ์ฝ”๋”ฉ๋œ ๋น„๋ฐ€ ํ‚ค๋ฅผ ๋””์ฝ”๋”ฉํ•˜์—ฌ HMAC-SHA ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ์ ํ•ฉํ•œ SecretKey ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
     *
     * @param secretKey Base64 URL ์ธ์ฝ”๋”ฉ๋œ ๋น„๋ฐ€ ํ‚ค
     */
    public AuthService(@Value("${service.jwt.secret-key}") String secretKey) {
        this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
    }

    /**
     * ์‚ฌ์šฉ์ž ID๋ฅผ ๋ฐ›์•„ JWT ์•ก์„ธ์Šค ํ† ํฐ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
     *
     * @param user_id ์‚ฌ์šฉ์ž ID
     * @return ์ƒ์„ฑ๋œ JWT ์•ก์„ธ์Šค ํ† ํฐ
     */
    public String createAccessToken(String user_id) {
        return Jwts.builder()
                // ์‚ฌ์šฉ์ž ID๋ฅผ ํด๋ ˆ์ž„์œผ๋กœ ์„ค์ •
                .claim("user_id", user_id)
                .claim("role", "ADMIN")
                // JWT ๋ฐœํ–‰์ž๋ฅผ ์„ค์ •
                .issuer(issuer)
                // JWT ๋ฐœํ–‰ ์‹œ๊ฐ„์„ ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ ์„ค์ •
                .issuedAt(new Date(System.currentTimeMillis()))
                // JWT ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ์„ค์ •
                .expiration(new Date(System.currentTimeMillis() + accessExpiration))
                // SecretKey๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HMAC-SHA512 ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์„œ๋ช…
                .signWith(secretKey, io.jsonwebtoken.SignatureAlgorithm.HS512)
                // JWT ๋ฌธ์ž์—ด๋กœ ์ปดํŒฉํŠธํ•˜๊ฒŒ ๋ณ€ํ™˜
                .compact();
    }
}
AuthController.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class AuthController {

    private final AuthService authService;

    /**
     * ์‚ฌ์šฉ์ž ID๋ฅผ ๋ฐ›์•„ JWT ์•ก์„ธ์Šค ํ† ํฐ์„ ์ƒ์„ฑํ•˜์—ฌ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.
     *
     * @param user_id ์‚ฌ์šฉ์ž ID
     * @return JWT ์•ก์„ธ์Šค ํ† ํฐ์„ ํฌํ•จํ•œ AuthResponse ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
     */
    @GetMapping("/auth/signIn")
    public ResponseEntity<?> createAuthenticationToken(@RequestParam String user_id){
        return ResponseEntity.ok(new AuthResponse(authService.createAccessToken(user_id)));
    }

    /**
     * JWT ์•ก์„ธ์Šค ํ† ํฐ์„ ํฌํ•จํ•˜๋Š” ์‘๋‹ต ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class AuthResponse {
        private String access_token;

    }
}

 

Cloud Gateway

  • ๊ธฐ์กด ๊ฒŒ์ดํŠธ์›จ์ด ์ฝ”๋“œ์— JWT์ธ์ฆ ๋ฐ auth-service ๋ผ์šฐํŒ… ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
build.gradle ํŒŒ์ผ์— ํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ( jwt ์ถ”๊ฐ€ )
dependencies {
	implementation 'io.jsonwebtoken:jjwt:0.12.6'
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'io.projectreactor:reactor-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
application.yml ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
server:
  port: 19091  # ๊ฒŒ์ดํŠธ์›จ์ด ์„œ๋น„์Šค๊ฐ€ ์‹คํ–‰๋  ํฌํŠธ ๋ฒˆํ˜ธ

spring:
  main:
    web-application-type: reactive  # Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฆฌ์•กํ‹ฐ๋ธŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์„ค์ •๋จ
  application:
    name: gateway-service  # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„์„ 'gateway-service'๋กœ ์„ค์ •
  cloud:
    gateway:
      routes:  # Spring Cloud Gateway์˜ ๋ผ์šฐํŒ… ์„ค์ •
        - id: order-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://order-service  # 'order-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/order/**  # /order/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ
        - id: product-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://product-service  # 'product-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/product/**  # /product/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ
        - id: auth-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://auth-service  # 'auth-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/auth/signIn  # /auth/signIn ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ
      discovery:
        locator:
          enabled: true  # ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ๋ผ์šฐํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ์„ค์ •

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/  # Eureka ์„œ๋ฒ„์˜ URL์„ ์ง€์ •
      

service:
  jwt:
    secret-key: "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"
LocalJwtAuthenticationFilter.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class LocalJwtAuthenticationFilter implements GlobalFilter {

    @Value("${service.jwt.secret-key}")
    private String secretKey; // ์„ค์ • ํŒŒ์ผ(yml)์—์„œ ๊ฐ€์ ธ์˜จ ์•”ํ˜ธํ™” ํ‚ค

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. ์ ‘์†ํ•œ ๊ฒฝ๋กœ(Path) ์ถ”์ถœ
        String path = exchange.getRequest().getURI().getPath();

        // 2. ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ์ฒ˜๋ฆฌ: ๋กœ๊ทธ์ธ ๊ฒฝ๋กœ๋Š” ํ† ํฐ ๊ฒ€์‚ฌ ์—†์ด ํ†ต๊ณผ์‹œํ‚ด
        if (path.equals("/auth/signIn")) {
            return chain.filter(exchange); 
        }

        // 3. ํ—ค๋”์—์„œ ํ† ํฐ ์ถ”์ถœ
        String token = extractToken(exchange);

        // 4. ํ† ํฐ์ด ์—†๊ฑฐ๋‚˜ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด 401(Unauthorized) ์—๋Ÿฌ ๋ฐ˜ํ™˜
        if (token == null || !validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); // ์—ฌ๊ธฐ์„œ ์š”์ฒญ ์ค‘๋‹จ
        }

        // 5. ๊ฒ€์ฆ ์„ฑ๊ณต ์‹œ ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์ด๋™
        return chain.filter(exchange);
    }

    // [ํ† ํฐ ์ถ”์ถœ ๋ฉ”์„œ๋“œ]
    private String extractToken(ServerWebExchange exchange) {
        // Authorization ํ—ค๋”์—์„œ "Bearer "๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฌธ์ž์—ด์„ ์ฐพ์Œ
        String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7); // "Bearer " ๋’ท๋ถ€๋ถ„(ํ† ํฐ ์‹ค์ œ๊ฐ’)๋งŒ ์ž˜๋ผ์„œ ๋ฐ˜ํ™˜
        }
        return null;
    }

    // [ํ† ํฐ ๊ฒ€์ฆ ๋ฉ”์„œ๋“œ]
    private boolean validateToken(String token) {
        try {
            // ์•”ํ˜ธํ™” ํ‚ค ์ƒ์„ฑ ๋ฐ ํ† ํฐ ํ•ด์„(Parsing)
            SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
            Jws<Claims> claimsJws = Jwts.parser()
                    .verifyWith(key)
                    .build().parseSignedClaims(token);
            
            log.info("#####ํ† ํฐ ๋‚ด๋ถ€ ์ •๋ณด(Payload) :: " + claimsJws.getPayload().toString());
            return true; // ํ•ด์„์— ์„ฑ๊ณตํ•˜๋ฉด ์œ ํšจํ•œ ํ† ํฐ
        } catch (Exception e) {
            return false; // ์œ„์กฐ๋˜์—ˆ๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ํ† ํฐ์ธ ๊ฒฝ์šฐ ์—๋Ÿฌ ๋ฐœ์ƒ -> false ๋ฐ˜ํ™˜
        }
    }
}
  • ServerWebExchange์™€ Path ์ถ”์ถœ
    • ServerWebExchange exchange: HTTP ์š”์ฒญ(Request)๊ณผ ์‘๋‹ต(Response) ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๋‹ด๊ณ  ์žˆ๋Š” ํ†ตํ•ฉ ๋ฐ”๊ตฌ๋‹ˆ์ž…๋‹ˆ๋‹ค. ๊ฒŒ์ดํŠธ์›จ์ด๋Š” ๋น„๋™๊ธฐ ๋ฐฉ์‹(WebFlux)์„ ์“ฐ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด์˜ HttpServletRequest ๋Œ€์‹  ์ด๊ฑธ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • exchange.getRequest().getURI().getPath():
      1. getRequest(): ๋ฐ”๊ตฌ๋‹ˆ์—์„œ ์š”์ฒญ ์ •๋ณด๋ฅผ ๊บผ๋ƒ…๋‹ˆ๋‹ค.
      2. getURI(): ์š”์ฒญ๋œ ์ „์ฒด ์ฃผ์†Œ ์ •๋ณด(์˜ˆ: http://localhost:19091/auth/signIn)๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
      3. getPath(): ๊ทธ์ค‘์—์„œ ํ˜ธ์ŠคํŠธ์™€ ํฌํŠธ๋ฅผ ์ œ์™ธํ•œ ์ˆœ์ˆ˜ ๊ฒฝ๋กœ์ธ /auth/signIn๋งŒ ๋ฌธ์ž์—ด๋กœ ๋ฝ‘์•„๋ƒ…๋‹ˆ๋‹ค. ํ•„ํ„ฐ์—์„œ "์–ด๋–ค ์„œ๋น„์Šค๋กœ ๊ฐ€๋ ค๋Š” ์š”์ฒญ์ธ๊ฐ€?"๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ธฐ์ค€์ด ๋ฉ๋‹ˆ๋‹ค.
  • extractToken (Bearer ํ† ํฐ ์ฒ˜๋ฆฌ)
    • HTTP ํ‘œ์ค€ ๊ทœ์•ฝ์ƒ JWT ํ† ํฐ์€ ํ—ค๋”์— Authorization: Bearer <ํ† ํฐ๊ฐ’> ํ˜•ํƒœ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
    • substring(7)์„ ํ•˜๋Š” ์ด์œ ๋Š” ์•ž์˜ Bearer (์ด 7์ž)๋ฅผ ๋–ผ์–ด๋‚ด๊ณ  ์ˆœ์ˆ˜ํ•œ ํ† ํฐ ๋ฐ์ดํ„ฐ๋งŒ ์–ป๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.
  • validateToken (JWT ๊ฒ€์ฆ ์›๋ฆฌ)
    • SecretKey: ์„œ๋ฒ„๊ฐ€ ํ† ํฐ์„ ๋งŒ๋“ค ๋•Œ ์ผ๋˜ ๋น„๋ฐ€๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค. ์ด ํ‚ค๊ฐ€ ์žˆ์–ด์•ผ๋งŒ ํ† ํฐ์ด ์œ„์กฐ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • parseSignedClaims(token): ์•”ํ˜ธํ™”๋œ ํ† ํฐ์„ ํ’€์–ด์„œ ๋‚ด์šฉ์„ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ† ํฐ ์œ ํ†ต๊ธฐํ•œ์ด ์ง€๋‚ฌ๊ฑฐ๋‚˜ ๋ˆ„๊ตฐ๊ฐ€ ๋‚ด์šฉ์„ ์ˆ˜์ •ํ–ˆ๋‹ค๋ฉด ์—ฌ๊ธฐ์„œ ๋ฐ”๋กœ ์˜ˆ์™ธ(Exception)๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

RUN

์œ ๋ ˆ์นด ์„œ๋ฒ„ ⇒ ๊ฒŒ์ดํŠธ์›จ์ด⇒ ์ธ์ฆ ⇒ ์ƒํ’ˆ ์ˆœ์œผ๋กœ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • http://localhost:19090์— ์ ‘์†ํ•˜์—ฌ ๊ฐ ์ธ์Šคํ„ด์Šค๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒŒ์ดํŠธ์›จ์ด์—์„œ ์ƒํ’ˆ์„ ์š”์ฒญํ•ด ๋ด…๋‹ˆ๋‹ค. 401 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒŒ์ดํŠธ์›จ์ด์—์„œ ๋กœ๊ทธ์ธ์„ ์š”์ฒญํ•˜์—ฌ ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์•„๋ด…๋‹ˆ๋‹ค.
  • ํ•ด๋‹น ํ† ํฐ์„ ์ƒํ’ˆ์š”์ฒญ์— ํ—ค๋”์— ๋„ฃ์–ด์„œ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
    • ํ•ด๋‹น ํ† ํฐ์„ ์ƒํ’ˆ์š”์ฒญ์— ํ—ค๋”์— ๋„ฃ์–ด์„œ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
    • ์š”์ฒญ์„ ํ†ตํ•ด ์ •์ƒ์ ์œผ๋กœ ์‘๋‹ต์ด ์˜ค๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Bearer ๋ž€?
  • Bearer๋Š” OAuth 2.0 ํ”„๋กœํ† ์ฝœ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ธ์ฆ ํ† ํฐ ์œ ํ˜• ์ค‘ ํ•˜๋‚˜๋กœ, ์•ก์„ธ์Šค ํ† ํฐ์„ ํ†ตํ•ด ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. Bearer ํ† ํฐ์€ ์š”์ฒญ ํ—ค๋”์— ํฌํ•จ๋˜์–ด ์„œ๋ฒ„์— ์ „๋‹ฌ๋˜๋ฉฐ, ์„œ๋ฒ„๋Š” ์ด๋ฅผ ๊ฒ€์ฆํ•˜์—ฌ ์š”์ฒญ์ด ์œ ํšจํ•œ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ๋ฒ•: ํด๋ผ์ด์–ธํŠธ๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ›์€ Bearer ํ† ํฐ์„ HTTP ์š”์ฒญ ํ—ค๋”์— ํฌํ•จ์‹œํ‚ค๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„ ์ธก ๊ฒ€์ฆ: ์„œ๋ฒ„๋Š” ์ด ํ† ํฐ์„ ๊ฒ€์ฆํ•˜์—ฌ ์š”์ฒญ์ด ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํ† ํฐ์˜ ์œ ํšจ์„ฑ, ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋“ฑ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณด์•ˆ: Bearer ํ† ํฐ์€ HTTPS๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํ† ํฐ์ด ์ „์†ก ์ค‘์— ๋„๋‚œ๋‹นํ•˜์ง€ ์•Š๋„๋ก ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ—จ๏ธ Config

Spring Cloud Config ๊ฐœ์š”

Spring Cloud Config๋ž€?

  • Spring Cloud Config๋Š” ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ํ™˜๊ฒฝ์—์„œ ์ค‘์•™ ์ง‘์ค‘์‹ ๊ตฌ์„ฑ ๊ด€๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ค์ •์„ ์ค‘์•™์—์„œ ๊ด€๋ฆฌํ•˜๊ณ , ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Git, ํŒŒ์ผ ์‹œ์Šคํ…œ, JDBC ๋“ฑ ๋‹ค์–‘ํ•œ ์ €์žฅ์†Œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ๊ธฐ๋Šฅ

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

 

Spring Cloud Config ์„œ๋ฒ„ ์„ค์ •

Config ์„œ๋ฒ„ ๊ตฌ์„ฑ

  • Config ์„œ๋ฒ„๋Š” ์„ค์ • ํŒŒ์ผ์„ ์ €์žฅํ•˜๊ณ  ์ œ๊ณตํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • build.gradle ํŒŒ์ผ์— ํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-config-server'
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •

  • Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Config ์„œ๋ฒ„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

์„ค์ • ํŒŒ์ผ ๊ตฌ์„ฑ

  • application.yml ํŒŒ์ผ์—์„œ Config ์„œ๋ฒ„์˜ ์„ค์ •์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/my-config-repo/config-repo
          clone-on-start: true

 

Spring Cloud Config ํด๋ผ์ด์–ธํŠธ ์„ค์ •

ํด๋ผ์ด์–ธํŠธ ๊ตฌ์„ฑ

  • Config ํด๋ผ์ด์–ธํŠธ๋Š” Config ์„œ๋ฒ„์—์„œ ์„ค์ •์„ ๋ฐ›์•„์˜ค๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • build.gradle ํŒŒ์ผ์— ํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
}

์„ค์ • ํŒŒ์ผ ๊ตฌ์„ฑ

  • ํด๋ผ์ด์–ธํŠธ์˜ application.yml ํŒŒ์ผ์—์„œ Config ์„œ๋ฒ„์˜ ์„ค์ •์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
spring:
  application:
    name: my-config-client
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
     
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

 

ํ™˜๊ฒฝ๋ณ„ ๊ตฌ์„ฑ ๊ด€๋ฆฌ

ํ™˜๊ฒฝ๋ณ„ ์„ค์ • ํŒŒ์ผ

  • Config ์„œ๋ฒ„๋Š” ํ™˜๊ฒฝ๋ณ„๋กœ ๋‹ค๋ฅธ ์„ค์ • ํŒŒ์ผ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ๋ฅผ ๋“ค์–ด, application-dev.yml, application-prod.yml ํŒŒ์ผ์„ Git ์ €์žฅ์†Œ์— ์ €์žฅํ•˜์—ฌ ํ™˜๊ฒฝ๋ณ„ ์„ค์ •์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœํ•„ ์‚ฌ์šฉ

  • Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ”„๋กœํ•„์„ ์‚ฌ์šฉํ•˜์—ฌ ํ™˜๊ฒฝ์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
spring:
  profiles:
    active: dev

 

์‹ค์‹œ๊ฐ„ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ

โ— ์‹ค์‹œ๊ฐ„ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ์„ ๋ฐ˜์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Spring Cloud Bus๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•, ์ˆ˜๋™์œผ๋กœ /actuator/refresh ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•, Spring Boot DevTools๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  Git ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ ๋ฐฉ๋ฒ•์€ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์ ˆํžˆ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Spring Cloud Bus๋Š” ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์„ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์„ค์ • ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ „ํŒŒํ•˜๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•˜๋ฉฐ, Git ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ค์ • ํŒŒ์ผ์˜ ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Spring Boot DevTools๋Š” ์ฃผ๋กœ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

Spring Cloud Bus

  • Spring Cloud Bus๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ค์ • ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ(RabbitMQ ๋˜๋Š” Kafka ๋“ฑ)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ „ํŒŒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์ˆ˜๋™ ๊ตฌ์„ฑ ๊ฐฑ์‹ 

  • /actuator/refresh ์—”๋“œํฌ์ธํŠธ ์‚ฌ์šฉ
    • Spring Cloud Bus๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ˆ˜๋™์œผ๋กœ ์„ค์ •์„ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด Spring Actuator์˜ /actuator/refresh ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„ค์ • ๊ฐฑ์‹  ์ ˆ์ฐจ
    1. Config ์„œ๋ฒ„์—์„œ ์„ค์ • ํŒŒ์ผ์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    2. ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ /actuator/refresh ์—”๋“œํฌ์ธํŠธ๋ฅผ POST ์š”์ฒญ์œผ๋กœ ํ˜ธ์ถœํ•˜์—ฌ ๋ณ€๊ฒฝ๋œ ์„ค์ •์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
์ด ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜์ง€๋งŒ, ๊ฐ ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ˆ˜๋™์œผ๋กœ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Spring Boot DevTools ์‚ฌ์šฉ
    • Spring Boot DevTools๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” classpath ๋‚ด์˜ ํŒŒ์ผ ๋ณ€๊ฒฝ๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

 

Git ์ €์žฅ์†Œ ์‚ฌ์šฉ

  • Spring Cloud Config ์„œ๋ฒ„๊ฐ€ Git ์ €์žฅ์†Œ์—์„œ ์„ค์ • ํŒŒ์ผ์„ ์ฝ์–ด์˜ค๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์„ค์ • ํŒŒ์ผ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์‰ฝ๊ฒŒ ๋ฐ˜์˜ํ•˜๊ณ , ์—ฌ๋Ÿฌ ์„œ๋น„์Šค ๊ฐ„์— ์ผ๊ด€๋œ ๊ตฌ์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

์‹ค์Šต

โ— ์ปจํ”ผ๊ทธ ์„œ๋ฒ„๋ฅผ ์ƒ์„ฑํ•˜๊ณ  product ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด local ์—์„œ ๋™์ž‘ํ•  ๋•Œ ํฌํŠธ ์ •๋ณด ๋ฐ ๋ฉ”์‹œ์ง€๋ฅผ ์ปจํ”ผ๊ทธ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค ์ปจํ”ผ๊ทธ ์„œ๋ฒ„์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ product ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ message๊ฐ€ ๊ฐฑ์‹ ๋˜๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

 

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

 

Config-server

start.spring.io ์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

 

1๏ธโƒฃ Config-Server (์„ค์ • ๊ด€๋ฆฌ ์„œ๋ฒ„)

  • ์ด ์„œ๋ฒ„๋Š” ๋‹ค๋ฅธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋“ค์—๊ฒŒ ์„ค์ • ์ •๋ณด(ํฌํŠธ, ๋ฉ”์‹œ์ง€ ๋“ฑ)๋ฅผ ์ค‘์•™์—์„œ ๋ฐฐํฌํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
ConfigApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer // ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ค์ •(Config) ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์„œ๋ฒ„๋กœ ์ž‘๋™ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
public class ConfigApplication {

	public static void main(String[] args) {
		SpringApplication.run(ConfigApplication.class, args);
	}

}
resources/appication.yml
server:
  port: 18080

spring:
  profiles:
    active: native # [ํ•ต์‹ฌ] ์™ธ๋ถ€ Git ์ €์žฅ์†Œ๊ฐ€ ์•„๋‹Œ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด๋ถ€(๋กœ์ปฌ ํŒŒ์ผ ์‹œ์Šคํ…œ)์—์„œ ์„ค์ • ํŒŒ์ผ์„ ์ฝ์–ด์˜ค๋„๋ก ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  application:
    name: config-server
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config-repo  # ์„ค์ • ํŒŒ์ผ๋“ค์ด ๋ชจ์—ฌ์žˆ๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. (src/main/resources/config-repo)



eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/ # ์„ค์ • ์„œ๋ฒ„ ์ž์‹ ๋„ ์œ ๋ ˆ์นด์— ๋“ฑ๋ก๋˜์–ด ๋‹ค๋ฅธ ์„œ๋น„์Šค๋“ค์ด ์ฐพ์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

 

  • classpath: ์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ์—์„œ src/main/resources ํด๋”๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ธฐ๋ณธ ์•ฝ์†์–ด์ž…๋‹ˆ๋‹ค.
  • /config-repo: ๊ทธ ๋ฐ‘์— ์ง์ ‘ ๋งŒ๋“  ํด๋” ์ด๋ฆ„์ด์ฃ .
    • ์ฆ‰, ์„œ๋ฒ„์—๊ฒŒ "๋‚ด๊ฐ€ ํŒŒ์ผ๋“ค ์ € ํด๋”์— ๋‹ค ๋ชจ์•„๋†จ์œผ๋‹ˆ๊นŒ, ์•ž์œผ๋กœ ํด๋ผ์ด์–ธํŠธ(Product ๋“ฑ)๊ฐ€ ์ž๊ธฐ ์„ค์ • ํŒŒ์ผ ๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•˜๋ฉด ๋ฌด์กฐ๊ฑด ์ € ํด๋” ์•ˆ์„ ๋’ค์ ธ์„œ ์ฐพ์•„์ค˜" ๋ผ๊ณ  ๋ฃฐ์„ ์„ธ์›Œ๋‘” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

 

2๏ธโƒฃ Config-Repo (์„ค์ • ํŒŒ์ผ ์ €์žฅ์†Œ)

  • ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ด๋ฆ„๊ณผ ํ”„๋กœํ•„(ํ™˜๊ฒฝ)์— ๋”ฐ๋ผ ์–ด๋–ค ํŒŒ์ผ์„ ์ฝ์„์ง€ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค. ๊ทœ์น™์€ {application-name}-{profile}.yml ์ž…๋‹ˆ๋‹ค.
resources ์•ˆ์— config-repo๋ผ๋Š” ํด๋”๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ์•„๋ž˜์˜ ๋‘ ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • product-service.yml (๊ธฐ๋ณธ ์„ค์ •)
server:
  port: 19093

message: "product-service message"
  • product-service-local.yml (local ํ”„๋กœํ•„ ์ „์šฉ ์„ค์ •)
server:
  port: 19083 # ํด๋ผ์ด์–ธํŠธ๊ฐ€ 'local' ํ™˜๊ฒฝ์œผ๋กœ ์‹คํ–‰๋˜๋ฉด ์ด ํฌํŠธ(19083)์™€ ๋ฉ”์‹œ์ง€๊ฐ€ ์šฐ์„  ์ ์šฉ๋˜์–ด ๋ฎ์–ด์”Œ์›Œ์ง‘๋‹ˆ๋‹ค.

message: "product-service-local message"

 

Product-service

3๏ธโƒฃ Product-Service (์„ค์ •์„ ์ œ๊ณต๋ฐ›๋Š” ํด๋ผ์ด์–ธํŠธ)

build-gradle์˜ ๋””ํŽœ๋˜์‹œ์— config ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
dependencies {
    // Actuator: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์„ค์ •๊ฐ’ ๋™์  ๊ฐฑ์‹ (/refresh) ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
    // Config Client: ์„ค์ • ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์„ค์ •๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.
	implementation 'org.springframework.cloud:spring-cloud-starter-config'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
application.yml
server:
  port: 0  # ์ž„์‹œ ํฌํŠธ์ž…๋‹ˆ๋‹ค. Config ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ํฌํŠธ(19083)๋กœ ์ตœ์ข… ๋ฎ์–ด์”Œ์›Œ์ง‘๋‹ˆ๋‹ค.

spring:
  profiles:
    active: local # 'local' ํ”„๋กœํ•„์„ ํ™œ์„ฑํ™”ํ•˜์—ฌ Config ์„œ๋ฒ„์˜ product-service-local.yml ํŒŒ์ผ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
  application:
    name: product-service # Config ์„œ๋ฒ„์—์„œ ์ฐพ์„ ํŒŒ์ผ๋ช…์˜ ๊ธฐ์ค€์ด ๋ฉ๋‹ˆ๋‹ค.
  config:
    import: "configserver:" # Spring Boot 2.4 ์ดํ›„ ๋ฐฉ์‹. ์„ค์ • ์„œ๋ฒ„์—์„œ ์„ค์ •์„ ๊ฐ€์ ธ์˜ค๋ผ๋Š” ๋ช…์‹œ์  ์„ ์–ธ์ž…๋‹ˆ๋‹ค.
  cloud:
    config:
      discovery:
        enabled: true # ์„ค์ • ์„œ๋ฒ„์˜ IP ์ฃผ์†Œ๋ฅผ ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ์•Š๊ณ , ์œ ๋ ˆ์นด๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ์ฐพ์•„๋ƒ…๋‹ˆ๋‹ค.
        service-id: config-server # ์œ ๋ ˆ์นด์— ๋“ฑ๋ก๋œ ์„ค์ • ์„œ๋ฒ„์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.

management:
  endpoints:
    web:
      exposure:
        include: refresh # /actuator/refresh ์—”๋“œํฌ์ธํŠธ๋ฅผ ์—ด์–ด, ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์—†์ด ์„ค์ •๊ฐ’์„ ๋‹ค์‹œ ์ฝ์–ด์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

message: "default message" # Config ์„œ๋ฒ„ ์ ‘์† ์‹คํŒจ ์‹œ ์‚ฌ์šฉ๋  ๊ธฐ๋ณธ๊ฐ’(Fallback)์ž…๋‹ˆ๋‹ค.

 

 

  • ์Šคํ”„๋ง ํด๋ผ์šฐ๋“œ ์ปจํ”ผ๊ทธ์—๋Š” ์ „ ์„ธ๊ณ„ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ ˆ๋Œ€ ๊ทœ์น™(๊ณต์‹)์ด ํ•˜๋‚˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„ค์ •์„ ์š”๊ตฌํ•˜๋ฉด, ์ปจํ”ผ๊ทธ ์„œ๋ฒ„๋Š” ๋ฌด์กฐ๊ฑด ์•„๋ž˜ ๊ณต์‹์œผ๋กœ ํŒŒ์ผ ์ด๋ฆ„์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
    • ํƒ์ƒ‰ ๊ณต์‹: {application-name}-{profile}.yml
  • ์„œ๋ฒ„ ๊ฐ„์˜ ๋Œ€ํ™” (์ž‘๋™ ์›๋ฆฌ): Product ์„œ๋ฒ„๊ฐ€ ์ฒ˜์Œ ์ผœ์งˆ ๋•Œ, ์ž์‹ ์ด ๊ฐ€์ง„ application.yml์„ ์ฝ๊ณ  Config ์„œ๋ฒ„์—๊ฒŒ ์ด๋ ‡๊ฒŒ ์†Œ๋ฆฌ์นฉ๋‹ˆ๋‹ค.
    • Product ์„œ๋ฒ„: "์•ผ ์ปจํ”ผ๊ทธ ์„œ๋ฒ„! ๋‚ด ์ด๋ฆ„(application.name)์€ product-service๊ณ , ๋‚˜ ์ง€๊ธˆ local ํ™˜๊ฒฝ(profiles.active)์œผ๋กœ ์‹คํ–‰๋์–ด! ๋‚ด ์„ค์ • ํŒŒ์ผ ๋‚ด๋†”!"
    • Config ์„œ๋ฒ„: "์•Œ์•˜์–ด, ์ž ๊น๋งŒ. ๊ณต์‹์— ๋Œ€์ž…ํ•ด๋ณด์ž... product-service + - + local + .yml ์ด๋‹ˆ๊นŒ... ์˜ค์ผ€์ด! ๋‚ด ํด๋”(config-repo)์—์„œ product-service-local.yml ํŒŒ์ผ์„ ์ฐพ์•„์„œ ๋„ˆํ•œํ…Œ ๋ณด๋‚ด์ค„๊ฒŒ!"

 

ProductController.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * @RefreshScope ์• ๋…ธํ…Œ์ด์…˜์€ Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋นˆ์ด ์„ค์ • ๋ณ€๊ฒฝ์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
 * ๊ธฐ๋ณธ์ ์œผ๋กœ Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋นˆ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋  ๋•Œ ์ดˆ๊ธฐํ™”๋˜๊ณ , ์„ค์ • ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋”๋ผ๋„ ํ•ด๋‹น ๋นˆ์€ ๊ฐฑ์‹ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
 * ์ด ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด /actuator/refresh ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์„ค์ • ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋™์ ์œผ๋กœ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 */
@RefreshScope
@RestController
@RequestMapping("/product")
public class ProductController {

    @Value("${server.port}") // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰ ์ค‘์ธ ํฌํŠธ๋ฅผ ์ฃผ์ž…๋ฐ›์Šต๋‹ˆ๋‹ค.
    private String serverPort;

    @Value("${message}") // Config ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜จ 'message' ๊ฐ’์„ ์ฃผ์ž…๋ฐ›์Šต๋‹ˆ๋‹ค.
    private String message;

    @GetMapping
    public String getProduct() {
        return "Product detail from PORT : " + serverPort + " and message : " + this.message ;
    }
}

 

RUN

  • ์œ ๋ ˆ์นด ์„œ๋ฒ„ > ์ปจํ”ผ๊ทธ ์„œ๋ฒ„ > ์ƒํ’ˆ ์ˆœ์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
์ƒํ’ˆ์ด ์‹คํ–‰๋ ๋•Œ ๋กœ๊ทธ์˜ ํฌํŠธ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. 19083 ์œผ๋กœ ํ• ๋‹น๋จ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

http://localhost:19083/product ์„ ํ˜ธ์ถœํ•˜๋ฉด ํฌํŠธ์™€ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

config-server ์˜ product-service-local.yml ํŒŒ์ผ์˜ message๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  config-server ๋ฅผ ์žฌ์‹œ์ž‘ ํ•ฉ๋‹ˆ๋‹ค.
server:
  port: 19083

message: "product-service-local message updated"
talend api tester๋ฅผ ์‹คํ–‰ํ•˜๊ณ  http://localhost:19083/actuator/refresh ๋กœ post ์š”์ฒญ์„ ํ•ฉ๋‹ˆ๋‹ค. ์‘๋‹ต์œผ๋กœ ๋ฉ”์‹œ์ง€๊ฐ€ ์—…๋ฐ์ดํŠธ ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"Product ์„œ๋ฒ„์•ผ, ๋ฐฉ๊ธˆ ์„ค์ • ํŒŒ์ผ ๋ฐ”๋€Œ์—ˆ์œผ๋‹ˆ Config ์„œ๋ฒ„์— ๋‹ค์‹œ ์ ‘์†ํ•ด์„œ ๊ฐ’๋งŒ ์™ ๋นผ์™€!"

๋‹ค์‹œ http://localhost:19083/product๋ฅผ ํ˜ธ์ถœ ํ•˜๋ฉด ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

*์ฐธ๊ณ  http://localhost:18080/product-service/local ๋กœ ์ ‘์†ํ•˜๋ฉด product-service์˜ ์„ค์ • ๊ฐ’๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ์„ค์ •๊ฐ’๋“ค๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

 

 

๐Ÿ“‘ ํ•ต์‹ฌ ๊ฐœ๋… ์š”์•ฝ

  1. ์„ค์ • ํŒŒ์ผ ๋งค์นญ ๊ทœ์น™ (Naming Convention)
    • spring.application.name = product-service
    • spring.profiles.active = local
    • ์œ„ ๋‘ ๊ฐ€์ง€ ์„ค์ •์ด ๊ฒฐํ•ฉ๋˜์–ด, Config ์„œ๋ฒ„์— ์žˆ๋Š” product-service-local.yml ํŒŒ์ผ์„ ์ตœ์šฐ์„ ์œผ๋กœ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค.
  2. ์œ ๋ ˆ์นด(Eureka) ์—ฐ๋™์˜ ์žฅ์  (discovery.enabled: true)
    • ๊ธฐ์กด์—๋Š” ํด๋ผ์ด์–ธํŠธ YAML์— uri: http://localhost:18080 ์ฒ˜๋Ÿผ ์„ค์ • ์„œ๋ฒ„ ์ฃผ์†Œ๋ฅผ ์ง์ ‘ ์ ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.
    • ์œ ๋ ˆ์นด๋ฅผ ์—ฐ๋™ํ•˜๋ฉด service-id: config-server ์ด๋ฆ„๋งŒ์œผ๋กœ ์ฃผ์†Œ๋ฅผ ์ฐพ์•„๋‚ด๋ฏ€๋กœ, ์„ค์ • ์„œ๋ฒ„์˜ IP๋‚˜ ํฌํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  3. ๋™์  ์„ค์ • ๊ฐฑ์‹  (@RefreshScope + Actuator)
    • ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์„œ๋ฒ„๋ฅผ ๋‚ด๋ฆฌ์ง€ ์•Š์•„๋„, product-service-local.yml์˜ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•œ ๋’ค ํด๋ผ์ด์–ธํŠธ ์ธก์— /actuator/refresh POST ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ProductController์˜ message ๋ณ€์ˆ˜ ๊ฐ’์ด ์ฆ‰์‹œ ๋ฐ”๋€๋‹ˆ๋‹ค. (์ด๊ฒƒ์ด MSA์—์„œ Config Server๋ฅผ ์“ฐ๋Š” ๊ฐ€์žฅ ํฐ ์ด์œ ์ž…๋‹ˆ๋‹ค.)

 

๐Ÿ—จ๏ธ ๋ถ„์‚ฐ์ถ”์  

๋ถ„์‚ฐ ์ถ”์ 

๋ถ„์‚ฐ ์ถ”์ ์ด๋ž€?

  • ๋ถ„์‚ฐ ์ถ”์ ์€ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ ์„œ๋น„์Šค ๊ฐ„์˜ ์š”์ฒญ ํ๋ฆ„์„ ์ถ”์ ํ•˜๊ณ  ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
  • ๊ฐ ์„œ๋น„์Šค์˜ ํ˜ธ์ถœ ๊ด€๊ณ„์™€ ์„ฑ๋Šฅ์„ ์‹œ๊ฐํ™”ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ์ง„๋‹จํ•˜๊ณ  ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์š” ๊ฐœ๋…: ํŠธ๋ ˆ์ด์Šค(Trace), ์ŠคํŒฌ(Span), ์ปจํ…์ŠคํŠธ(Context)
    1. ํŠธ๋ ˆ์ด์Šค(Trace) : ํŠธ๋ ˆ์ด์Šค๋Š” ํ•˜๋‚˜์˜ ์š”์ฒญ์ด ์‹œ์ž‘๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๊ฐ ์„œ๋น„์Šค๋ฅผ ๊ฑฐ์น˜๋Š” ์ „์ฒด ํ๋ฆ„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค
      • ํ•˜๋‚˜์˜ ํŠธ๋ ˆ์ด์Šค๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ŠคํŒฌ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค
      • ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌ๋  ๋•Œ, ๊ฐ ์„œ๋น„์Šค ํ˜ธ์ถœ์ด ํŠธ๋ ˆ์ด์Šค์˜ ์ผ๋ถ€๋กœ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค
      • ํŠธ๋ ˆ์ด์Šค ID๋Š” ๊ฐ ์ŠคํŒฌ์— ๊ณตํ†ต์œผ๋กœ ๋ถ€์—ฌ๋˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์ „์ฒด ์š”์ฒญ ํ๋ฆ„์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
    2. ์ŠคํŒฌ(Span) : ์ŠคํŒฌ์€ ๋ถ„์‚ฐ ์ถ”์ ์—์„œ ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„๋กœ, ํŠน์ • ์„œ๋น„์Šค ๋‚ด์—์„œ์˜ ๊ฐœ๋ณ„ ์ž‘์—… ๋˜๋Š” ์š”์ฒญ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
      • ๊ฐ ์ŠคํŒฌ์€ ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ์ข…๋ฃŒ ์‹œ๊ฐ„์„ ๊ธฐ๋กํ•˜์—ฌ ์ž‘์—…์˜ ์ง€์† ์‹œ๊ฐ„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
      • ์ŠคํŒฌ์€ ๊ณ ์œ ํ•œ ์ŠคํŒฌ ID๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์ด๋Š” ํŠธ๋ ˆ์ด์Šค ID์™€ ํ•จ๊ป˜ ํŠน์ • ์ž‘์—…์„ ์‹๋ณ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
      • ์ŠคํŒฌ์€ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.
      • ์ŠคํŒฌ์—๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(ํƒœ๊ทธ, ๋กœ๊ทธ, ์ด๋ฒคํŠธ ๋“ฑ)๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ƒ์„ธํ•œ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    3. ์ปจํ…์ŠคํŠธ(Context) : ์ปจํ…์ŠคํŠธ๋Š” ์š”์ฒญ์ด ์„œ๋น„์Šค ๊ฐ„์— ์ „๋‹ฌ๋  ๋•Œ ํ•จ๊ป˜ ์ „ํŒŒ๋˜์–ด, ๊ฐ ์„œ๋น„์Šค๊ฐ€ ์š”์ฒญ์˜ ์ „์ฒด ํ๋ฆ„์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
      • ์ปจํ…์ŠคํŠธ๋Š” ํŠธ๋ ˆ์ด์Šค ID, ์ŠคํŒฌ ID, ๋ถ€๋ชจ ์ŠคํŒฌ ID ๋“ฑ์˜ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์—ฌ ๊ฐ ์„œ๋น„์Šค๊ฐ€ ์š”์ฒญ์˜ ์ถœ์ฒ˜์™€ ๊ฒฝ๋กœ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.
      • ์„œ๋น„์Šค ํ˜ธ์ถœ ๊ฐ„์— ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•จ์œผ๋กœ์จ, ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ์ „์ฒด์—์„œ ์ผ๊ด€๋œ ์ถ”์ ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

์™œ ๋ถ„์‚ฐ ์ถ”์ ์ด ํ•„์š”ํ•œ๊ฐ€?

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

 

Micrometer

Micrometer๋ž€?

  • Micrometer๋Š” Spring ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ฉ”ํŠธ๋ฆญ์„ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.
    • ๋ฉ”ํŠธ๋ฆญ(Metric)์€ CPU ์‚ฌ์šฉ๋Ÿ‰์ด๋‚˜ ์—๋Ÿฌ ๋ฐœ์ƒ ํšŸ์ˆ˜์ฒ˜๋Ÿผ, ์„œ๋ฒ„์˜ ํ˜„์žฌ ์ƒํƒœ์™€ ์„ฑ๋Šฅ์„ ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜์ง‘ํ•˜๋Š” '๊ฐ๊ด€์ ์ธ ์ˆซ์ž ๋ฐ์ดํ„ฐ(์ธก์ •๊ฐ’)์ž…๋‹ˆ๋‹ค.
  • ๊ฐ ์„œ๋น„์Šค์˜ ์„ฑ๋Šฅ ์ง€ํ‘œ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ , Prometheus, Grafana ๋“ฑ๊ณผ ์—ฐ๋™ํ•˜์—ฌ ์‹œ๊ฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถ„์‚ฐ ์ถ”์ ์„ ์œ„ํ•œ ๊ธฐ๋Šฅ๋„ ์ œ๊ณตํ•˜์—ฌ ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ํ๋ฆ„์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฃผ์š” ํŠน์ง•

  • ๋‹ค์–‘ํ•œ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‹ค์–‘ํ•œ ์„ฑ๋Šฅ ์ง€ํ‘œ๋ฅผ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์œ ์—ฐํ•œ ์—ฐ๋™: Prometheus, Grafana ๋“ฑ ๋‹ค์–‘ํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ์™€ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ถ”์  ๊ธฐ๋Šฅ: ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ํ๋ฆ„์„ ์ถ”์ ํ•˜์—ฌ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์„ ์ง„๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Zipkin

Zipkin์ด๋ž€?

  • Zipkin์€ ํŠธ๋ ˆ์ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ์‹œ๊ฐํ™”ํ•˜๋Š” ๋ถ„์‚ฐ ์ถ”์  ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.
  • ๊ฐ ์„œ๋น„์Šค์˜ ํŠธ๋ ˆ์ด์Šค์™€ ์ŠคํŒฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ ํ๋ฆ„์„ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ํŠน์ง•

  • ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ์ €์žฅ: ๊ฐ ์„œ๋น„์Šค์—์„œ ์ „์†ก๋œ ํŠธ๋ ˆ์ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์‹œ๊ฐํ™”: ํŠธ๋ ˆ์ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐํ™”ํ•˜์—ฌ ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ๊ด€๊ณ„๋ฅผ ๋ช…ํ™•ํžˆ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ๋ง: ํŠน์ • ํŠธ๋ ˆ์ด์Šค๋‚˜ ์ŠคํŒฌ์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ํ•„ํ„ฐ๋งํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ์ง„๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Zipkin ์„œ๋ฒ„ ์„ค์ •

Zipkin ์„œ๋ฒ„ ์‹คํ–‰

  • Zipkin ์„œ๋ฒ„๋ฅผ Docker๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
docker run -d -p 9411:9411 openzipkin/zipkin

Zipkin ๋Œ€์‹œ๋ณด๋“œ ์‚ฌ์šฉ

  • Zipkin ๋Œ€์‹œ๋ณด๋“œ์— ์ ‘์†ํ•˜์—ฌ ํŠธ๋ ˆ์ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค:
    • URL: http://localhost:9411
    • ํŠธ๋ ˆ์ด์Šค ๊ฒ€์ƒ‰ ๋ฐ ๋ถ„์„

 

๋ถ„์‚ฐ ์ถ”์  ์˜ˆ์ œ

์„œ๋น„์Šค ํ˜ธ์ถœ ํ๋ฆ„ ์ถ”์ 

  • ์˜ˆ์ œ ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ํ๋ฆ„์„ ์ถ”์ ํ•˜๊ณ , Zipkin ๋Œ€์‹œ๋ณด๋“œ์—์„œ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ ์„œ๋น„์Šค ํ˜ธ์ถœ ์‹œ ํŠธ๋ ˆ์ด์Šค์™€ ์ŠคํŒฌ์ด ์ƒ์„ฑ๋˜๊ณ , Zipkin ์„œ๋ฒ„๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค.

์„ฑ๋Šฅ ๋ณ‘๋ชฉ ์ง„๋‹จ

  • Zipkin ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ํ†ตํ•ด ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์ด ๋ฐœ์ƒํ•˜๋Š” ๋ถ€๋ถ„์„ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ ์ŠคํŒฌ์˜ ์†Œ์š” ์‹œ๊ฐ„๊ณผ ํ˜ธ์ถœ ๊ด€๊ณ„๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์ง„๋‹จํ•˜๊ณ  ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

 

์‹ค์Šต

  • ์ด ์‹ค์Šต์€ MSA ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ๊ณจ์น˜ ์•„ํ”ˆ ๋ฌธ์ œ์ธ "์š”์ฒญ ์ถ”์ (Distributed Tracing)"์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ Zipkin(์ง‘ํ‚จ) ์—ฐ๋™ ์‹ค์Šต์ž…๋‹ˆ๋‹ค. ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ๋Š” ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ ํ•˜๋‚˜๊ฐ€ ์—ฌ๋Ÿฌ ์„œ๋ฒ„(Order -> Product ๋“ฑ)๋ฅผ ๊ฑฐ์น˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ค‘๊ฐ„์— ์—๋Ÿฌ๊ฐ€ ๋‚˜๊ฑฐ๋‚˜ ๋А๋ ค์กŒ์„ ๋•Œ ์–ด๋А ์„œ๋ฒ„์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋Š”์ง€ ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Product-service

build.gradle ํŒŒ์ผ ๋””ํŽœ๋˜์‹œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
    // Micrometer Tracing (Brave): ์Šคํ”„๋ง ๋ถ€ํŠธ 3.x์—์„œ ๋ถ„์‚ฐ ์ถ”์ ์„ ๋‹ด๋‹นํ•˜๋Š” ํ•ต์‹ฌ ์—”์ง„์ž…๋‹ˆ๋‹ค.
	implementation 'io.micrometer:micrometer-tracing-bridge-brave'
    // Feign Micrometer: Order๊ฐ€ Product๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, HTTP ํ—ค๋”์— '์ถ”์  ID(Trace ID)'๋ฅผ ๋ชฐ๋ž˜ ๋ผ์›Œ ๋„ฃ์–ด ํ๋ฆ„์ด ๋Š๊ธฐ์ง€ ์•Š๊ฒŒ ์—ฐ๊ฒฐํ•ด ์ฃผ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
	implementation 'io.github.openfeign:feign-micrometer'
    // Zipkin Reporter: Brave ์—”์ง„์ด ์ˆ˜์ง‘ํ•œ ์ถ”์  ๋ฐ์ดํ„ฐ(๋ฉ”ํŠธ๋ฆญ)๋ฅผ Zipkin ์„œ๋ฒ„๋กœ ์ „์†ก(Report)ํ•˜๋Š” ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.
	implementation 'io.zipkin.reporter2:zipkin-reporter-brave'

	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
application.yml
spring:
  application:
    name: product-service
server:
  port: 19092
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

management:
  zipkin:
    tracing:
      endpoint: "http://localhost:9411/api/v2/spans" # Zipkin ์„œ๋ฒ„๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ธฐ ์œ„ํ•ด ์—ด์–ด๋‘” API ์ฃผ์†Œ์ž…๋‹ˆ๋‹ค.
  tracing:
    sampling:
      probability: 1.0 # ์ƒ˜ํ”Œ๋ง ํ™•๋ฅ . 1.0์€ 100%๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  ์š”์ฒญ์„ ์ถ”์ ํ•˜๊ฒ ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.

Order-service

build.gradle ํŒŒ์ผ ๋””ํŽœ๋˜์‹œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'io.micrometer:micrometer-tracing-bridge-brave'
	implementation 'io.github.openfeign:feign-micrometer'
	implementation 'io.zipkin.reporter2:zipkin-reporter-brave'

	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
application.yml
spring:
  application:
    name: order-service
server:
  port: 19091
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

management:
  zipkin:
    tracing:
      endpoint: "http://localhost:9411/api/v2/spans"
  tracing:
    sampling:
      probability: 1.0

Zipkin

  • Zipkin ๋„์ปค ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰ ์ฝ”๋“œ๋ฅผ ์ปค๋งจ๋“œ๋ฅผ ์ผœ์„œ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.
docker run -d -p 9411:9411 openzipkin/zipkin

RUN

  • Eureka Server > Order > Product ์ˆœ์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • http://localhost:19091/order/1 ๋ฅผ ์ ‘์†ํ•ด๋ด…๋‹ˆ๋‹ค. ์ด์ „์˜ ์‘๋‹ต๊ณผ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  • http://localhost:9411/zipkin/ ๋กœ ์ ‘์† ํ›„์— RUN QUERY๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋‚˜์˜ค๋ฉฐ Spans 3 ์ธ ํ•ญ๋ชฉ์˜ SHOW๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

  • Order-service๊ฐ€ Product-service๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ณผ์ •์ด ํŠธ๋ž˜ํ‚น ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“‘ ํ•ต์‹ฌ ๊ฐœ๋… (Trace์™€ Span)

  • Zipkin ํ™”๋ฉด์„ ๋ณด๋ฉด Trace์™€ Span์ด๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. ์ด ๋‘ ๊ฐ€์ง€๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์ด ๋ถ„์‚ฐ ์ถ”์ ์˜ ์ „๋ถ€์ž…๋‹ˆ๋‹ค.
    1. Trace (ํŠธ๋ ˆ์ด์Šค): ์‚ฌ์šฉ์ž์˜ ์ตœ์ดˆ ์š”์ฒญ๋ถ€ํ„ฐ ์ตœ์ข… ์‘๋‹ต๊นŒ์ง€์˜ ์ „์ฒด ํ๋ฆ„(1๊ฐœ์˜ ํฐ ์ž‘์—…)์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. (๊ณ ์œ ํ•œ Trace ID๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.)
    2. Span (์ŠคํŒฌ): ์ „์ฒด ํ๋ฆ„ ์†์—์„œ ๊ฐ ์„œ๋น„์Šค๊ฐ€ ์ˆ˜ํ–‰ํ•œ ๊ฐœ๋ณ„ ์ž‘์—… ๋‹จ์œ„๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. (๊ฐ์ž ๊ณ ์œ ํ•œ Span ID๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.)
      • ๊ณต์‹: 1๊ฐœ์˜ Trace = N๊ฐœ์˜ Span

๐Ÿ’ก ์™œ Spans ๋ฆฌ์ŠคํŠธ๊ฐ€ '3'์œผ๋กœ ๋‚˜์˜ฌ๊นŒ์š”? (์‹ค์Šต ๊ฒฐ๊ณผ ๋ถ„์„)

  • http://localhost:19091/order/1 ์„ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ Zipkin์— Span์ด 3๊ฐœ ์ฐํžˆ๋Š” ๊ธฐ์ˆ ์ ์ธ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
    1. Span 1 (Order Service): ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์ด Order ์„œ๋ฒ„์— ์ตœ์ดˆ๋กœ ๋„์ฐฉํ•˜์—ฌ ์ฒ˜๋ฆฌ๋ฅผ ์‹œ์ž‘ํ•œ ์ž‘์—….
    2. Span 2 (Feign Client): Order ์„œ๋ฒ„๊ฐ€ Product ์„œ๋ฒ„๋กœ HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด ํ†ต์‹ ์„ ์‹œ๋„ํ•˜๊ณ  ๋Œ€๊ธฐํ•œ ์ž‘์—….
    3. Span 3 (Product Service): Product ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์„ ์ „๋‹ฌ๋ฐ›์•„ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค๊ณ  ์‘๋‹ตํ•œ ์ž‘์—….
์ด 3๊ฐœ์˜ Span์ด ๋ชจ์—ฌ 1๊ฐœ์˜ Trace(์ „์ฒด ํ๋ฆ„)๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉฐ, Zipkin์€ ์ด๋ฅผ ์‹œ๊ฐ„ ์ˆœ์„œ๋Œ€๋กœ ์—ฐ๊ฒฐํ•˜์—ฌ ๊ทธ๋ž˜ํ”„๋กœ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

 

๐Ÿ—จ๏ธ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ

์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜

์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜๋ž€?

  • ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜๋Š” ์‹œ์Šคํ…œ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ(์ƒํƒœ ๋ณ€ํ™”๋‚˜ ํ–‰๋™)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ์„ค๊ณ„ ์Šคํƒ€์ผ์ž…๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์„œ๋น„์Šค ๊ฐ„์˜ ๋А์Šจํ•œ ๊ฒฐํ•ฉ์„ ํ†ตํ•ด ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ๊ฐœ๋…

  • ์ด๋ฒคํŠธ: ์‹œ์Šคํ…œ ๋‚ด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ƒํƒœ ๋ณ€ํ™”๋‚˜ ํ–‰๋™์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.
  • ์ด๋ฒคํŠธ ์†Œ์Šค: ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ด๋ฒคํŠธ ๋ฒ„์Šค์— ์ „๋‹ฌํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฒคํŠธ ๋ฒ„์Šค: ์ด๋ฒคํŠธ ์†Œ์Šค์™€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๊ฐ„์˜ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ์„ ์ค‘๊ฐœํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ์ 

  • ๋А์Šจํ•œ ๊ฒฐํ•ฉ
    • ์„œ๋น„์Šค ๊ฐ„์˜ ๊ฐ•ํ•œ ์ข…์†์„ฑ์„ ์ œ๊ฑฐํ•˜์—ฌ ๋…๋ฆฝ์ ์ธ ๊ฐœ๋ฐœ๊ณผ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅ
    • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ†ต์‹ ์„ ํ†ตํ•ด ์„œ๋น„์Šค ๊ฐ„์˜ ๊ฒฐํ•ฉ๋„ ๋‚ฎ์ถค
  • ํ™•์žฅ์„ฑ
    • ์ˆ˜ํ‰ ํ™•์žฅ์ด ์šฉ์ดํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ์—์„œ ์œ ์šฉ
    • ์ด๋ฒคํŠธ ํ”„๋กœ๋“€์„œ์™€ ์ปจ์Šˆ๋จธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ํ™•์žฅ ๊ฐ€๋Šฅ
      • ๐Ÿ“ข ์ด๋ฒคํŠธ ํ”„๋กœ๋“€์„œ (Producer): ์‹œ์Šคํ…œ์—์„œ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ์ด๋ฅผ ์ด๋ฒคํŠธ ๋ฉ”์‹œ์ง€๋กœ ๋งŒ๋“ค์–ด ๋ธŒ๋กœ์ปค์— ๋ฐœํ–‰(Publish)ํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.
      • ๐Ÿ“ฎ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค (Broker): ํ”„๋กœ๋“€์„œ๊ฐ€ ๋ณด๋‚ธ ์ด๋ฒคํŠธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด๊ด€ํ•˜๊ณ , ํ•„์š”ํ•œ ์ปจ์Šˆ๋จธ๋“ค์—๊ฒŒ ์ „๋‹ฌํ•ด ์ฃผ๋Š” ๋น„๋™๊ธฐ ์ค‘๊ณ„์†Œ(์šฐ์ฒด๊ตญ) ์—ญํ• ์ž…๋‹ˆ๋‹ค." (์˜ˆ: Kafka, RabbitMQ)
      • ๐ŸŽง ์ด๋ฒคํŠธ ์ปจ์Šˆ๋จธ (Consumer): ๋ธŒ๋กœ์ปค๋ฅผ ๊ตฌ๋…(Subscribe)ํ•˜๊ณ  ์žˆ๋‹ค๊ฐ€, ์ด๋ฒคํŠธ๊ฐ€ ๋„์ฐฉํ•˜๋ฉด ์ด๋ฅผ ๊ฐ€์ ธ์™€ ์ž์‹ ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(์•Œ๋ฆผ ๋ฐœ์†ก, DB ์—…๋ฐ์ดํŠธ ๋“ฑ)์„ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
    • ์ด๋ฒคํŠธ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์‹œ์Šคํ…œ์˜ ์‘๋‹ต์„ฑ ํ–ฅ์ƒ
    • ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ ์ตœ์ ํ™”
"๊ฒฐ๊ณผ์ ์œผ๋กœ ๋ธŒ๋กœ์ปค๋ผ๋Š” ์ค‘๊ฐ„ ๋‹ค๋ฆฌ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ”„๋กœ๋“€์„œ์™€ ์ปจ์Šˆ๋จธ๋Š” ์„œ๋กœ๊ฐ€ ๋ˆ„๊ตฐ์ง€ ๋ชฐ๋ผ๋„(๋А์Šจํ•œ ๊ฒฐํ•ฉ) ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ์ž ํŠธ๋ž˜ํ”ฝ์— ๋งž์ถฐ ๋…๋ฆฝ์ ์œผ๋กœ ์Šค์ผ€์ผ ์•„์›ƒ(ํ™•์žฅ)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."

์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜์˜ ๋‹จ์ 

  • ๋ณต์žก์„ฑ ์ฆ๊ฐ€
    • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ†ต์‹ ์œผ๋กœ ์ธํ•ด ์‹œ์Šคํ…œ์˜ ๋ณต์žก์„ฑ ์ฆ๊ฐ€ ๊ฐ€๋Šฅ
    • ์ด๋ฒคํŠธ ํ๋ฆ„๊ณผ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ์„ค๊ณ„ ํ•„์š”
  • ์žฅ์•  ์ „ํŒŒ
    • ์ด๋ฒคํŠธ ์‹คํŒจ ์‹œ ๋‹ค๋ฅธ ์„œ๋น„์Šค๋กœ ์žฅ์• ๊ฐ€ ์ „ํŒŒ๋  ์ˆ˜ ์žˆ์Œ
    • ์ด๋ฒคํŠธ ์žฌ์ฒ˜๋ฆฌ ๋ฐ ์žฅ์•  ๋ณต๊ตฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๊ตฌํ˜„ ํ•„์š”

 

์˜ˆ์‹œ: ์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ

  1. ์ด๋ฒคํŠธ ์†Œ์Šค: ์‚ฌ์šฉ์ž๊ฐ€ ์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ์—์„œ ์ฃผ๋ฌธ์„ ํ•ฉ๋‹ˆ๋‹ค.
    • ์ฃผ๋ฌธ ์„œ๋น„์Šค๊ฐ€ '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  2. ์ด๋ฒคํŠธ ๋ฒ„์Šค: Kafka๋‚˜ RabbitMQ์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๊ฐ€ '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  3. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ:
    • ์žฌ๊ณ  ์„œ๋น„์Šค: '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜์—ฌ ์žฌ๊ณ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
    • ๋ฐฐ์†ก ์„œ๋น„์Šค: '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜์—ฌ ๋ฐฐ์†ก ์ค€๋น„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ฒฐ์ œ ์„œ๋น„์Šค: '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜์—ฌ ๊ฒฐ์ œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.

 

Spring Cloud Stream

Spring Cloud Stream์ด๋ž€?

  • Spring Cloud Stream์€ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.
  • Kafka, RabbitMQ ๋“ฑ์˜ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค์™€ ํ†ตํ•ฉํ•˜์—ฌ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ํ”„๋กœ๋“€์„œ์™€ ์ปจ์Šˆ๋จธ ๊ฐ„์˜ ํ†ต์‹ ์„ ์ถ”์ƒํ™”ํ•˜์—ฌ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ฃผ์š” ํŠน์ง•

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

 

๐Ÿ—จ๏ธ ๋งˆ๋ฌด๋ฆฌ

๋ฌด์กฐ๊ฑด MSA!?

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

 

์ถ”๊ฐ€ ์ •๋ณด “์ฟ ๋ฒ„๋„คํ‹ฐ์Šค”

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ž€?

  • ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค(Kubernetes)๋Š” ์ปจํ…Œ์ด๋„ˆํ™”๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐฐํฌ, ํ™•์žฅ, ์šด์˜์„ ์ž๋™ํ™”ํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค.
    • ์ˆ˜๋งŽ์€ ์ปจํ…Œ์ด๋„ˆ(์•ฑ)๋“ค์ด ์ค‘๋‹จ ์—†์ด ์•ˆ์ •์ ์œผ๋กœ ๋Œ์•„๊ฐ€๋„๋ก ๋ฐฐํฌ, ํ™•์žฅ, ๋ณต๊ตฌ ์ž‘์—…์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” 'ํ†ตํ•ฉ ์šด์˜ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ'
    • ๐Ÿ“ฆ ์ปจํ…Œ์ด๋„ˆ (Container): ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ทธ ์‹คํ–‰์— ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ์„ค์ • ํŒŒ์ผ ๋“ฑ์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ํŒจํ‚ค์ง•ํ•œ '๋…๋ฆฝ์ ์ธ ๊ฐ€์ƒํ™”(์ž์›์„ ๊ฒฉ๋ฆฌํ•œ ๋…๋ฆฝ ์‹คํ–‰ ์˜์—ญ) ์‹คํ–‰ ๋‹จ์œ„'์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ด๋‚˜ ์šด์˜ OS์— ์ข…์†๋˜์ง€ ์•Š๊ณ  ์–ด๋””์„œ๋“  ๋™์ผํ•˜๊ฒŒ ํ”„๋กœ๊ทธ๋žจ์„ ๊ตฌ๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Google์—์„œ ๊ฐœ๋ฐœํ•˜๊ณ  ํ˜„์žฌ๋Š” CNCF(Cloud Native Computing Foundation)์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋„๊ตฌ๋กœ, ๋‹ค์ˆ˜์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๐ŸŽผ ์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋„๊ตฌ (Container Orchestration Tool): "์ˆ˜์‹ญ~์ˆ˜๋งŒ ๊ฐœ์˜ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์•ˆ์ •์ ์œผ๋กœ ์šด์˜๋  ์ˆ˜ ์žˆ๋„๋ก ๋ฐฐํฌ, ์Šค์ผ€์ผ๋ง(ํ™•์žฅ/์ถ•์†Œ), ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ, ์žฅ์•  ๋ณต๊ตฌ ๋“ฑ์˜ '์ „์ฒด ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์ž๋™ํ™”ํ•˜์—ฌ ํ†ตํ•ฉ ๊ด€๋ฆฌ'ํ•˜๋Š” ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

์ฃผ์š” ๊ฐœ๋…

  • ์ปจํ…Œ์ด๋„ˆ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ทธ ์ข…์†์„ฑ์„ ํ•จ๊ป˜ ํŒจํ‚ค์ง•ํ•œ ๊ฐ€์ƒํ™”๋œ ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค.
  • Pod: ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ ์‹คํ–‰๋˜๋Š” ์ตœ์†Œ ๋‹จ์œ„์˜ ๋ฐฐํฌ ๊ฐ์ฒด๋กœ, ํ•˜๋‚˜ ์ด์ƒ์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  • ๋…ธ๋“œ(Node): ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํด๋Ÿฌ์Šคํ„ฐ์—์„œ Pod๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋ฌผ๋ฆฌ์  ๋˜๋Š” ๊ฐ€์ƒ ๋จธ์‹ ์ž…๋‹ˆ๋‹ค.
    • ํด๋Ÿฌ์Šคํ„ฐ๋ผ๋Š” ํŒ€์— ์†Œ์†๋œ ๊ฐœ๋ณ„ ์ปดํ“จํ„ฐ(์„œ๋ฒ„) ํ•œ ๋Œ€
  • ํด๋Ÿฌ์Šคํ„ฐ: ์—ฌ๋Ÿฌ ๋…ธ๋“œ๋กœ ๊ตฌ์„ฑ๋œ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค.
    • ์ด ๋…ธ๋“œ๋“ค์ด ๋ชจ์—ฌ์„œ ๋งŒ๋“œ๋Š” ํŒ€ ์ „์ฒด
  • ๋„ค์ž„์ŠคํŽ˜์ด์Šค(Namespace): ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์—์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๋Š” ๋‹จ์œ„์ž…๋‹ˆ๋‹ค.
๊ฐœ๋ฐœ์ž๊ฐ€ "์ด ์•ฑ ์ข€ ๋„์›Œ์ค˜"๋ผ๊ณ  ํด๋Ÿฌ์Šคํ„ฐ์— ์š”์ฒญํ•˜๋ฉด, ํด๋Ÿฌ์Šคํ„ฐ๋Š” ๋‚ด๋ถ€์— ์—ฐ๊ฒฐ๋œ ์—ฌ๋Ÿฌ ๋Œ€์˜ ๋…ธ๋“œ ์ค‘ ๊ฐ€์žฅ ์—ฌ์œ ๋กœ์šด ๋…€์„์„ ๊ณจ๋ผ ์ž๋™์œผ๋กœ ์•ฑ์„ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์‚ฌ์šฉ์ž๋Š” ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€๋ผ๋Š” ์‚ฌ์‹ค์„ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†์ด, ํด๋Ÿฌ์Šคํ„ฐ๋ผ๋Š” '๊ฑฐ๋Œ€ํ•œ ํ•˜๋‚˜์˜ ์ž์› ๋ฉ์–ด๋ฆฌ'๋งŒ ๋ณด๊ณ  ์ž‘์—…ํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ด์ฃ .

 

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์™€ Spring Cloud ๋น„๊ต

  • ๊ณตํ†ต์ 
    • ํ™•์žฅ์„ฑ: ๋‘˜ ๋‹ค ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์˜ ํ™•์žฅ์„ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ด€๋ฆฌ์„ฑ: ์„œ๋น„์Šค์˜ ๋ฐฐํฌ, ๊ด€๋ฆฌ, ํ™•์žฅ ๋“ฑ์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.
    • ๊ณ ๊ฐ€์šฉ์„ฑ: ์„œ๋น„์Šค์˜ ๊ฐ€์šฉ์„ฑ์„ ๋†’์ด๊ณ , ์žฅ์•  ๋ฐœ์ƒ ์‹œ ์ž๋™ ๋ณต๊ตฌ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฐจ์ด์ 
    1. ์ดˆ์ :
      • Spring Cloud: ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐ„์˜ ํ†ต์‹ , ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ, ๊ตฌ์„ฑ ๊ด€๋ฆฌ ๋“ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์˜ ๋ฌธ์ œ ํ•ด๊ฒฐ์— ์ดˆ์ 
      • ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค: ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ, ๋ฐฐํฌ, ์Šค์ผ€์ผ๋ง ๋“ฑ ์ธํ”„๋ผ ๋ ˆ๋ฒจ์˜ ๋ฌธ์ œ ํ•ด๊ฒฐ์— ์ดˆ์ 
    2. ๊ตฌ์„ฑ ์š”์†Œ:
      • Spring Cloud: Eureka, Ribbon, Zuul, Config Server, Hystrix ๋“ฑ ๋‹ค์–‘ํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํŒจํ„ด์„ ์ง€์›ํ•˜๋Š” ๊ตฌ์„ฑ ์š”์†Œ
        • MSA, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์œ„ ์ดˆ์  
      • ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค: Pod, Deployment, Service, Ingress, ConfigMap, Secret ๋“ฑ ์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์— ํ•„์š”ํ•œ ๊ตฌ์„ฑ ์š”์†Œ
        • ์ธํ”„๋ผ ์ดˆ์  
    3. ๋ฐฐํฌ ๋ฐฉ์‹:
      • Spring Cloud: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ๋‹ค์–‘ํ•œ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค์— ์ง์ ‘ ๋ฐฐํฌ
      • ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค: ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์—์„œ ๋ฐฐํฌ ๋ฐ ๊ด€๋ฆฌ

 

Spring Cloud์™€์˜ ํ†ตํ•ฉ๋„ ๊ฐ€๋Šฅ

  • Spring Cloud Kubernetes
    • Spring Cloud Kubernetes๋Š” Spring Cloud์™€ Kubernetes์˜ ํ†ตํ•ฉ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
    • ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ, ConfigMap, Secrets ๋“ฑ์„ Spring Cloud ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฃผ์š” ๊ธฐ๋Šฅ
    • ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ: Kubernetes API๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ๋™์ ์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ตฌ์„ฑ ๊ด€๋ฆฌ: Kubernetes ConfigMap๊ณผ Secrets์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •์„ ์ค‘์•™์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    • ์ž๋™ํ™”๋œ ๋ฐฐํฌ: CI/CD ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ํ†ตํ•ฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ž๋™ ๋ฐฐํฌ์™€ ๊ด€๋ฆฌ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

 

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ์žฅ์ ๊ณผ ๋‹จ์ 

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

 

์‚ฌ๋ก€

  • ์„ฑ๊ณต ์‚ฌ๋ก€
    • Google: ๋‚ด๋ถ€์ ์œผ๋กœ Borg๋ผ๋Š” ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ Kubernetes๋กœ ๋ฐœ์ „์‹œ์ผœ ์˜คํ”ˆ์†Œ์Šคํ™”
    • Netflix: ๋Œ€๊ทœ๋ชจ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด Kubernetes๋ฅผ ํ™œ์šฉ
    • Airbnb: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ์™€ ๊ด€๋ฆฌ๋ฅผ ์ž๋™ํ™”ํ•˜์—ฌ ์šด์˜ ํšจ์œจ์„ฑ ๊ทน๋Œ€ํ™”
  • ์‹คํŒจ ์‚ฌ๋ก€์™€ ๊ตํ›ˆ
    • ์ดˆ๊ธฐ ์„ค์ •์˜ ๋ณต์žก์„ฑ: ๋งŽ์€ ๊ธฐ์—…๋“ค์ด ์ดˆ๊ธฐ ์„ค์ •์˜ ๋ณต์žก์„ฑ์œผ๋กœ ์ธํ•ด ๋„์ž…์— ์–ด๋ ค์›€์„ ๊ฒช์Œ
    • ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ์˜ ์ค‘์š”์„ฑ: ์ ์ ˆํ•œ ๋ฆฌ์†Œ์Šค ํ• ๋‹น๊ณผ ๊ด€๋ฆฌ๊ฐ€ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์œผ๋ฉด ๋น„์šฉ์ด ๊ธ‰์ฆํ•  ์ˆ˜ ์žˆ์Œ
    • ์ „๋ฌธ์„ฑ ๋ถ€์กฑ: ์ถฉ๋ถ„ํ•œ Kubernetes ์ „๋ฌธ์„ฑ์ด ์—†๋Š” ํŒ€์—์„œ๋Š” ์šด์˜์ƒ์˜ ์–ด๋ ค์›€์„ ๊ฒช์„ ์ˆ˜ ์žˆ์Œ