๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋ฐฑ์—”๋“œ

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_MSA(Microservice Architecture)] MSA ํŠน์ง•๊ณผ Spring Cloud

by alswlfl 2026. 7. 1.

 

MSA(Microservice Architecture)

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

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

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

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

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

๋ชจ๋†€๋ฆฌํ‹ฑ ์•„ํ‚คํ…์ฒ˜๋Š” ํ•˜๋‚˜์˜ ํฐ ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ ๊ตฌ์„ฑ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ, ๋ชจ๋“  ๊ธฐ๋Šฅ์ด ํ•˜๋‚˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์— ํฌํ•จ๋˜์–ด ์žˆ์Œ

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

์žฅ์ 

  • ๊ฐ„๋‹จํ•œ ๋ฐฐํฌ: ๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋ฒ ์ด์Šค์— ํฌํ•จ๋˜์–ด ์žˆ์–ด ๋ฐฐํฌ๊ฐ€ ๋‹จ์ˆœ
  • ๋‹จ์ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์‰ฝ๊ฒŒ ์œ ์ง€ ๊ฐ€๋Šฅ

๋‹จ์ 

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

MSA

MSA๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ ์„œ๋น„์Šค๋กœ ๊ตฌ์„ฑ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ, ๊ฐ ์„œ๋น„์Šค๋Š” ํŠน์ • ๋น„์ฆˆ๋‹ˆ์Šค ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•จ

MSA

์žฅ์ 

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

๋‹จ์ 

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

 

๏นก์žฅ๋‹จ์ ์„ ์ž˜ ํŒŒ์•…ํ•ด์„œ ์ ํ•ฉํ•œ ์•„ํ‚คํ…์ฒ˜ ๋ฐฉ์‹์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•จ

๋ณดํ†ต ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ๋Š” ๋น ๋ฅธ ๋žœ๋”ฉ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋†€๋ฆฌํ‹ฑ์œผ๋กœ ๊ฐœ๋ฐœํ•˜๋Š”๊ฒŒ ์ข‹๊ณ , ์ ์  ์„œ๋น„์Šค ์‚ฌ์ด์ฆˆ๊ฐ€ ์ปค์กŒ์„ ๋•Œ MSA ์ „ํ™˜ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€๋„ ์ข‹์Œ


Spring Cloud

Spring Cloud๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐœ๋ฐœ์„ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ๋„๊ตฌ์™€ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ์˜ ํ™•์žฅ์œผ๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์šด์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์›€

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

  • ์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ๋””์Šค์ปค๋ฒ„๋ฆฌ: Eureka, Consul, Zookeeper
  • ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: Ribbon, Spring Cloud LoadBalancer
  • ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค: Hystrix, Resilience4j
  • API ๊ฒŒ์ดํŠธ์›จ์ด: Zuul, Spring Cloud Gateway
  • ๊ตฌ์„ฑ ๊ด€๋ฆฌ: Spring Cloud Config
  • ๋ถ„์‚ฐ ์ถ”์ : Spring Cloud Sleuth, Zipkin
  • ๋ฉ”์‹œ์ง•: Spring Cloud Stream
๋”๋ณด๊ธฐ

์‹ค์ œ๋กœ ํ˜„์—…์—์„œ๋Š” ์•„๋งˆ Hystrix๋‚˜ Zuul๋กœ ๊ฐœ๋ฐœ๋œ ์ƒํƒœ๊ฐ€ ๋งŽ์Œ.

Spring์€ ๊ณ„์†ํ•ด์„œ ๋ฒ„์ „์ด ์—…๋ฐ์ดํŠธ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ˜„์žฌ Spring ๋ฒ„์ „์— ๋งž๋Š” ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•ด์•ผ ํ•˜์ง€๋งŒ, ์‹ค์ œ ํ˜„์—…์—์„œ๋Š” ๋ฒ„์ „์„ ์˜ฌ๋ฆด ๋•Œ ๊ณ ๋ ค ์‚ฌํ•ญ์ด ๋งŽ์ด ๋•Œ๋ฌธ์— ์–ด๋ ค์›€์ด ์กด์žฌํ•ด์„œ ๋ฒ„์ „์—…์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์—†์Œ


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

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

1) Eureka(์œ ๋ ˆ์นด)

๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ ์„œ๋ฒ„๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ๊ฐ ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌ

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

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

1) Ribbon

๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋กœ, ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๊ฐ„์˜ ๋ถ€ํ•˜๋ฅผ ๋ถ„์‚ฐ

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

์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค(Circuit Breaker)

1) Hystrix

๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ์‹คํŒจ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์‹œ์Šคํ…œ์˜ ์ „์ฒด์ ์ธ ์•ˆ์ •์„ฑ์„ ์œ ์ง€

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

2) Resilience4j

์ž๋ฐ” ๊ธฐ๋ฐ˜์˜ ๊ฒฝ๋Ÿ‰ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ๋„ทํ”„๋ฆญ์Šค Hystrix์˜ ๋Œ€์•ˆ์œผ๋กœ ๊ฐœ๋ฐœ๋จ

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

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

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

1) Zuul

๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ API ๊ฒŒ์ดํŠธ์›จ์ด๋กœ, ๋ชจ๋“  ์„œ๋น„์Šค ์š”์ฒญ์„ ์ค‘์•™์—์„œ ๊ด€๋ฆฌ

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

2) Cloud gateway

์Šคํ”„๋ง ํด๋ผ์šฐ๋“œ์—์„œ ์ œ๊ณตํ•˜๋Š” API ๊ฒŒ์ดํŠธ์›จ์ด๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ํ•„์ˆ˜์ ์ธ ์—ญํ• 

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

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

1) Spring Cloud Config

๋ถ„์‚ฐ๋œ ํ™˜๊ฒฝ์—์„œ ์ค‘์•™ ์ง‘์ค‘์‹ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์ œ๊ณต

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

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

Netflix๋Š” Spring Cloud๋ฅผ ํ™œ์šฉํ•ด์„œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋กœ ์ „ํ™˜ํ•จ

  • ์„œ๋น„์Šค ๋ถ„๋ฆฌ: ๊ธฐ์กด ๋ชจ๋†€๋ฆฌ์‹ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋กœ ๋ถ„๋ฆฌ
  • ์ž๋™ํ™” ๋„๊ตฌ ๋„์ž…: CI/CD ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•ด ์ฝ”๋“œ์˜ ๋นŒ๋“œ, ํ…Œ์ŠคํŠธ, ๋ฐฐํฌ ๊ณผ์ • ์ž๋™ํ™”ํ•ด ๊ฐœ๋ฐœ ์†๋„ ํ–ฅ์ƒ
  • ์ž์ฒด ๋„๊ตฌ ๊ฐœ๋ฐœ: Hystrix, Eureka, Ribbon ๋“ฑ์˜ ๋„๊ตฌ๋ฅผ ๊ฐœ๋ฐœํ•ด ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ , ์žฅ์•  ๋ณต๊ตฌ, ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ๋“ฑ์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
  • ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ ํ™œ์šฉ: AWS์™€ ๊ฐ™์€ ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ๋ฅผ ํ™œ์šฉํ•ด ์„œ๋น„์Šค ํ™•์žฅ์„ฑ ๋†’์ž„

MSA๋ฅผ ํ™œ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค๊ณ„

์š”๊ตฌ์‚ฌํ•ญ ๋ฐ ๋กœ์ง

  • ์š”๊ตฌ์‚ฌํ•ญ: ์ฃผ๋ฌธ์ด ๋“ค์–ด์˜ค๋ฉด ์ฃผ๋ฌธ ํ™•์ธํ•˜๊ณ  ์ฒ˜๋ฆฌ
  • ํ๋ฆ„: ์ฃผ๋ฌธ ํ™•์ธ → ์ƒํ’ˆ ํ™•์ธ → ์œ ์ € ํ™•์ธ
  • ์ฆ‰, ์ด 3๊ฐœ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ•„์š”ํ•จ! Order ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, Product ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, User ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

์š”๊ตฌ์‚ฌํ•ญ ํ๋ฆ„

1. Ribbon์„ ํ†ตํ•œ ์„œ๋น„์Šค ๊ฐ„์˜ ํ†ต์‹ 

Ribbon์„ ํ†ตํ•œ ์„œ๋น„์Šค ํ†ต์‹ 

๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ•˜๋‚˜๋งŒ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ,

ex) Order ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Product ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์š”์ฒญ ์‹œ ๋‘ ๊ฐœ์˜ Product ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค ์ค‘ ์–ด๋””๋กœ ํ˜ธ์ถœํ•ด์•ผ ํ• ์ง€ Ribbon์—์„œ ํ•ด์ฃผ๊ณ  ์‹ค์ œ ํ˜ธ์ถœ์€ Feign Client๋ฅผ ํ†ตํ•ด์„œ ์ด๋ฃจ์–ด์ง

 

2. Eureka ์„œ๋ฒ„ ๊ตฌ์ถ•

ํŠน์ • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ˜ธ์ถœํ•  ๋•Œ ์œ„์น˜๋ฅผ ์•Œ์•„์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— Eureka ์„œ๋ฒ„ ๊ตฌ์ถ• ํ›„, ๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„ ๋“ฑ๋ก

Eureka ์„œ๋ฒ„ ๊ตฌ์ถ•

ex) Order ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Product ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ˜ธ์ถœํ• ์ง€ User๋ฅผ ํ˜ธ์ถœํ• ์ง€ Host๋ฅผ ์•Œ์•„์•ผํ•จ

์ฆ‰ Order ์„œ๋ฒ„์—์„œ Product ์„œ๋ฒ„ ํ˜ธ์ถœํ•  ๋•Œ, Eureka ์„œ๋ฒ„์— ๋“ค์–ด๊ฐ€์„œ ์ •๋ณด ํ™•์ธํ•˜๋ฉด ๋ˆ„๊ตด ํ˜ธ์ถœํ• ์ง€ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ์Œ

  • Eureka ์„œ๋ฒ„์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์„œ๋น„์Šค ์•ฑ๋“ค์€ Eureka ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋จ

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

์–ด๋–ค ์‚ฌ์šฉ์ž๊ฐ€ ์ฃผ๋ฌธ์„ ํ™•์ธํ•  ๋•Œ๋Š” Order ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ˜ธ์ถœํ•˜๊ณ , ์ƒํ’ˆ ๋“ฑ๋กํ•  ๋•Œ๋Š” Product ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ˜ธ์ถœํ•˜๋Š” ๋“ฑ Order, Product, User๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ํ˜ธ์ŠคํŠธ๋กœ ํ˜ธ์ถœํ•ด์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€ ์กด์žฌํ•จ

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์•ž๋‹จ์— API ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ๋‘ฌ์„œ ๊ฐ ์š”์ฒญ์— ๋งž๋Š” ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋ƒ„

 

๋˜ํ•œ, ๋กœ๊ทธ์ธ๋œ ํšŒ์›๋งŒ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ API ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ๋กœ๊ทธ์ธ์— ๋Œ€ํ•ด ์ฒดํฌํ•˜๋ฉด, ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋’ค๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ณ  Block ์‹œ์ผœ ์š”์ฒญ์„ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์Œ → API ๊ฒŒ์ดํŠธ์›จ์ด์˜ Filter์—์„œ ๋กœ๊ทธ์ธ ์ฒดํฌ ์ˆ˜ํ–‰

API ๊ฒŒ์ดํŠธ ์ถ”๊ฐ€

4. Resilience4j ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋ฅผ ํ†ตํ•œ Failover ์ฒ˜๋ฆฌ

Order ์„œ๋ฒ„๊ฐ€ User ์„œ๋ฒ„๋ฅผ ํ˜ธ์ถœํ•œ ํ›„ Product ์„œ๋ฒ„๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํ๋ฆ„์ธ ์ƒํ™ฉ์—์„œ User ์„œ๋ฒ„ ํ˜ธ์ถœ์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ, ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค์ธ Resilience4j๋ฅผ ํ†ตํ•ด Product ์„œ๋ฒ„ ํ˜ธ์ถœ๊นŒ์ง€ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ User ์„œ๋ฒ„์—์„œ Block ์‹œํ‚ค๊ธฐ

Resilience4j ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋ฅผ ํ†ตํ•œ Failover ์ฒ˜๋ฆฌ

 

5. yml ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” Config ์„œ๋ฒ„ ์ถ”๊ฐ€

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋งˆ๋‹ค ๊ฐ๊ฐ์˜ yml ํŒŒ์ผ์„ ๊ฐ๊ฐ ๋„ฃ์–ด์ฃผ๋ฉด, ์ˆ˜์ •์ด ํ•„์š”ํ•  ๋•Œ ์ „๋ถ€ ์ˆ˜์ •ํ•ด ์ค˜์•ผํ•จ. ์ด๋Ÿฌํ•œ ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด yml ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” Config ์„œ๋ฒ„ ์ถ”๊ฐ€

  • yml ํŒŒ์ผ์ด ์ˆ˜์ •๋˜์—ˆ์„ ๋•Œ, ๊ฐ ์„œ๋น„์Šค์— ์•Œ๋ ค์ฃผ๋ฉด ๋ชจ๋“  ์„œ๋น„์Šค๊ฐ€ ๋ฐ”๋กœ ์—…๋ฐ์ดํŠธ๋จ

Config ์„œ๋ฒ„ ์ถ”๊ฐ€

 

6. ๋ถ„์‚ฐ ์ถ”์  Zipkin์œผ๋กœ ์š”์ฒญ ํ˜ธ์ถœ ํŒŒ์•…

์ฃผ๋ฌธ → ์ƒํ’ˆ → ํšŒ์› ์ˆœ์œผ๋กœ ํ˜ธ์ถœ์ด ์ง„ํ–‰๋  ๋•Œ, ๊ฐ ์•ฑ๋“ค์ด ํ˜ธ์ถœ๋˜๋Š”์ง€ ์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์—†์Œ! ๋ถ„์‚ฐ ์ถ”์  Zipkin์„ ํ†ตํ•ด์„œ ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ ์–ด๋–ป๊ฒŒ ํ˜ธ์ถœ๋˜๋Š”์ง€ ํŒŒ์•…

๋ถ„์‚ฐ ์ถ”์  ์ถ”๊ฐ€


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

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

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

Eureka๋ž€?

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

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

  • Eureka ์„œ๋ฒ„๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์ค‘์•™ ์„œ๋ฒ„๋กœ, application.properties ํ˜น์€ application.yml ํŒŒ์ผ์—์„œ ์ง„ํ–‰
server:
  port: 8761

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

 

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

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

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  # ๋ฆฌ์Šค ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„

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

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

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

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

ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Eureka ์„œ๋ฒ„์—์„œ ํ•„์š”ํ•œ ์„œ๋น„์Šค์˜ ์œ„์น˜๋ฅผ ์กฐํšŒํ•จ

  1. RestTemplate์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
    • Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ @LoadBalanced ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด RestTemplate์— ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  2. FeignClient๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
    • Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ FeignClient๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ์„œ๋น„์Šค ํ˜ธ์ถœ ์ˆ˜ํ–‰ํ•จ
@SpringBootApplication
@EnableFeignClients
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
@FeignClient(name = "my-service")
public interface MyServiceClient {

    @GetMapping("/api/data")
    String getData();
}
@RestController
public class MyFeignClientController {

    @Autowired
    private MyServiceClient myServiceClient;

    @GetMapping("/get-data-feign")
    public String getDataWithFeignClient() {
        return myServiceClient.getData();
    }
}

 

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

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

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

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

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

์‹ค์Šต

์œ ๋ ˆ์นด ์„œ๋ฒ„ ํ•˜๋‚˜์— ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ์ธ์Šคํ„ด์Šค 2๊ฐœ ์—ฐ๊ฒฐํ•ด๋ณด๊ธฐ

์‹ค์Šต ๋‚ด์šฉ

1. Spring Initializr๋ฅผ ํ†ตํ•ด ์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

2. Eureka Server ์„ค์ •

# application.properties ์„ค์ •

spring.application.name=server

server.port=19090

# ์œ ๋ ˆ์นด ์„œ๋ฒ„์— ๋ณธ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋“ฑ๋กํ• ์ง€ ๊ฒฐ์ •
# ์„œ๋ฒ„์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ๋กœ ์ธ์‹๋˜์ง€ ์•Š๊ฒŒ false๋กœ ์„ค์ •
eureka.client.register-with-eureka=false

# ์œ ๋ ˆ์นด ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ฌ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๋ณ€์ˆ˜
eureka.client.fetch-registry=false

eureka.instance.hostname=localhost

# ์œ ๋ ˆ์นด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์œ ๋ ˆ์นด ์„œ๋ฒ„์™€ ํ†ต์‹ ์„ ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” URL ์ง€์ •
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
package com.spring_cloud.eureka.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer // ์ถ”๊ฐ€ํ•ด์ค˜์•ผ์ง€ ์œ ๋ ˆ์นด ์„œ๋ฒ„๋กœ ๋™์ž‘ํ•จ
public class ServerApplication {

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

}

 

3. Eureka Client ์„ค์ •(๋‘ ๊ณณ ๋ชจ๋‘ ์ถ”๊ฐ€)

spring.application.name=first

server.port=19091

eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
spring.application.name=second

server.port=19092

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

Eureka Server(19090ํฌํŠธ) ์ ‘์† ํ™”๋ฉด

4. ๋‘ Eureka Client ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํ›„ ๋‹ค์‹œ ์ ‘์†

Eureka Server(19090ํฌํŠธ) ์ ‘์† ํ™”๋ฉด


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

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

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

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

FeignClient

FeignClient๋Š” Spring Cloud์—์„œ ์ œ๊ณตํ•˜๋Š” HTTP ํด๋ผ์ด์–ธํŠธ๋กœ, ์„ ์–ธ์ ์œผ๋กœ RESTful ์›น ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Œ

๋˜ํ•œ, Eureka์™€ ๊ฐ™์€ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์™€ ์—ฐ๋™ํ•˜์—ฌ ๋™์ ์œผ๋กœ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ์กฐํšŒํ•˜๊ณ  ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ์ˆ˜ํ–‰ํ•จ

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

Ribbon

Ribbon์€ ๋„ทํ”Œ๋ฆญ์Šค๊ฐ€ ๊ฐœ๋ฐœํ•œ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ๊ฐ„์˜ ๋ถ€ํ•˜๋ฅผ ๋ถ„์‚ฐํ•จ

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

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

FeignClient์™€ Ribbon ์„ค์ •

1. ๊ธฐ๋ณธ ์„ค์ •

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

 

2. 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 ์—ฐ๋™

1. Eureka ์„ค์ •

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

 

2. 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 ์ธ์Šคํ„ด์Šค ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ด ํ˜ธ์ถœํ•จ(์ด ๊ณผ์ •์—์„œ RR ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‚ฌ์šฉํ•ด ์ˆœ์ฐจ์ ์œผ๋กœ ๋ถ„๋ฐฐํ•จ)

4) ์‘๋‹ต ์ฒ˜๋ฆฌ: ์„ ํƒ๋œ Product ์ธ์Šคํ„ด์Šค์—์„œ ์‘๋‹ต์„ ๋ฐ›์•„ Order ์„œ๋น„์Šค์— ๋ฐ˜ํ™˜ํ•˜๊ณ , ์ตœ์ข…์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ์‘๋‹ต์„ ์ „๋‹ฌํ•จ


์‹ค์Šต

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

์‹ค์Šต ๋‚ด์šฉ

1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

  • Product ํ”„๋กœ์ ํŠธ์™€ Order ํ”„๋กœ์ ํŠธ ์ถ”๊ฐ€(์œ ๋ ˆ์นด ์„œ๋ฒ„๋Š” ์œ„์—์„œ ๋งŒ๋“ ๊ฑฐ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ)

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

2. Product ์„œ๋ฒ„ ์ƒ์„ฑ

package com.spring_cloud.eureka.client.product;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients // FeignClient ํ™œ์„ฑํ™”
public class ProductApplication {

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

}
package com.spring_cloud.eureka.client.product;

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 {

    // application.properties์— ์ž‘์„ฑํ•œ server.port ๊ฐ€์ ธ์˜ค๊ธฐ
    @Value("${server.port}")
    private String serverPort; // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํฌํŠธ

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable("id") String id) {
        return "Product " + id + " info!!! From port :" + serverPort;
    }
}
// application.yml ์„ค์ • ์ถ”๊ฐ€
spring:
  application:
    name: product-service

server:
  port: 19092

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/
  • Product ์„œ๋ฒ„๋ฅผ 19092, 19093, 19094 ์ด 3๊ฐœ์˜ ํฌํŠธ์— ์‹คํ–‰ํ•ด์•ผํ•จ(์ธํ…”๋ฆฌ์ œ์ด ๋ฐฉ๋ฒ•)

์ธํ…”๋ฆฌ์ œ์ด์—์„œ ํฌํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ
์œ ๋ ˆ์นด ์„œ๋ฒ„ ์‹คํ–‰, Product ์„œ๋ฒ„

3. Order ์„œ๋ฒ„ ์ƒ์„ฑ

@SpringBootApplication
@EnableFeignClients // ์ถ”๊ฐ€
public class OrderApplication {

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

}
  • Product ์„œ๋ฒ„ ํ˜ธ์ถœ ์ฝ”๋“œ ์ž‘์„ฑ ํ•„์š”
package com.spring_cloud.eureka.client.order;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "product-service") // Eureka ์„œ๋ฒ„์—์„œ 'product-service' ์‹๋ณ„์ž๋กœ ์ฐพ์•„์„œ ํ˜ธ์ถœ
public interface ProductClient {
    @GetMapping("/product/{id}")
    String getProduct(@PathVariable("id") String id);
}
  • OrderService์—์„œ Product ํ˜ธ์ถœํ•œ ํ›„ String ์กฐํ•ฉ ํ›„ ๋‹ค์‹œ Controller๋กœ ์‘๋‹ต ๋ฐ˜ํ™˜
package com.spring_cloud.eureka.client.order;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OrderService {

    // @RequiredArgsConstructor ์• ๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ฃผ์ž…๋ฐ›๊ธฐ
    private final ProductClient productClient;

    public String getProductInfo(String productId) {
        return productClient.getProduct(productId);
    }

    public String getOrder(String orderId) {
        if(orderId.equals("1")){
            String productId = "2";
            String productInfo = getProductInfo(productId);
            return "Your order is " + orderId + " and " + productInfo;
        }
        return "Not exist order...";
    }
}
package com.spring_cloud.eureka.client.order;

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 OrderController {
    
    private final OrderService orderService;

    @GetMapping("/order/{orderId}")
    public String getOrder(@PathVariable("orderId") String orderId) {
        return orderService.getOrder(orderId);
    }
}

์œ ๋ ˆ์นด ์„œ๋ฒ„
์ฃผ๋ฌธ ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ(์‹คํŒจ ์˜ˆ์‹œ)
์ฃผ๋ฌธ ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ(์‹คํŒจ ์˜ˆ์‹œ)

  • ์–ด๋А ํฌํŠธ๋ฅผ ๊ฐ€์ง„ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜ค๋Š”์ง€ ํ™•์ธ ๊ฐ€๋Šฅํ•จ. ์ด๋•Œ ๋ผ์šด๋“œ ๋กœ๋นˆ ๋ฐฉ์‹์œผ๋กœ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ ์ˆ˜ํ–‰๋จ

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

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

Resilience4j

Resilience4j๋Š” ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ์‹คํŒจ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์‹œ์Šคํ…œ์„ ์•ˆ์ •์„ฑ์„ ์œ ์ง€ํ•จ

๋‹ค์–‘ํ•œ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์žฅ์•  ๊ฒฉ๋ฆฌ ๋ฐ ๋น ๋ฅธ ์‹คํŒจ๋ฅผ ํ†ตํ•ด ๋ณต์›๋ ฅ์„ ๋†’์ž„

 

1. ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ์ธ ํด๋กœ์ฆˆ๋“œ, ์˜คํ”ˆ, ํ•˜ํ”„-์˜คํ”ˆ ์ƒํƒœ๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ ์‹คํŒจ๋ฅผ ๊ด€๋ฆฌ

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

2. Fallback: ํ˜ธ์ถœ ์‹คํŒจ ์‹œ ๋Œ€์ฒด ๋กœ์ง์„ ์ œ๊ณตํ•ด ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ ํ™•๋ณด

3. ๋ชจ๋‹ˆํ„ฐ๋ง: ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ๋„๊ตฌ ์ œ๊ณต

Resilience4j ์„ค์ •

  • Resilience4j๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ
# resilience4j ์˜์กด์„ฑ์€ spring starter์—์„œ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. 
# ”io.github.resilience4j:resilience4j-spring-boot3:2.2.0” ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. 
# ”boot3” ์ž„์„ ์ฃผ์˜ ํ•˜์„ธ์š”.
dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
	  implementation 'org.springframework.boot:spring-boot-starter-aop'
}
  • application.yml ํŒŒ์ผ์— Resilience4j ์„ค์ • ์ถ”๊ฐ€
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 ๋ฉ”์„œ๋“œ๋Š” ์™ธ๋ถ€ ์„œ๋น„์Šค ํ˜ธ์ถœ์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ๋Œ€์ฒด ๋กœ์ง์„ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ

@Service
public class MyService {

    @CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
    public String myMethod() {
        // ์™ธ๋ถ€ ์„œ๋น„์Šค ํ˜ธ์ถœ
        return externalService.call();
    }

    public String fallbackMethod(Throwable t) {
        return "Fallback response";
    }
}

 

์žฅ์ 

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

Resilience4j ๋Œ€์‹œ๋ณด๋“œ

Resilience4j ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค์˜ ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Œ

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

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

  • Resilience4j๋Š” Spring Cloud Netflix ํŒจํ‚ค์ง€์˜ ์ผ๋ถ€๋กœ, Eureka์™€ Ribbon ๋“ฑ ๋‹ค๋ฅธ Spring Cloud ๊ตฌ์„ฑ ์š”์†Œ์™€ ์‰ฝ๊ฒŒ ํ†ตํ•ฉ ๊ฐ€๋Šฅ
  • Spring Cloud์˜ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์™€ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ์„ ํ™œ์šฉํ•ด ๋”์šฑ ์•ˆ์ •์ ์ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์ถ• ๊ฐ€๋Šฅ

์‹ค์Šต

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

 

1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋ฐ ์˜์กด์„ฑ ์ถ”๊ฐ€

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.1.0'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.spring-cloud.resilience4j'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

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()
}

 

2. Product ๊ด€๋ จ ํŒŒ์ผ ์ƒ์„ฑ(Product, ProductController, ProductService)

package com.spring_cloud.resilience4j.sample.products;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    private String id;
    private String title;
}
package com.spring_cloud.resilience4j.sample.products;

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 String id) {
        return productService.getProductDetails(id);
    }
}
package com.spring_cloud.resilience4j.sample.products;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ProductService {


    public Product getProductDetails(String productId) {
        return new Product(productId, "Sample Product");
    }
}

 

3. ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ถ”๊ฐ€

  • ProductService ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— '์ƒํ’ˆ ์•„์ด๋”” 111์„ ํ˜ธ์ถœ' ์‹œ ์—๋Ÿฌ ๋ฐœ์ƒ์„ ์œ„ํ•œ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ถ”๊ฐ€
package com.spring_cloud.resilience4j.sample.products;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ProductService {

    @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
    public Product getProductDetails(String productId) {
        
        if("111".equals(productId)){
            throw new RuntimeException("Empty response body");
        }
        
        return new Product(productId, "Sample Product");
    }
    
    public Product fallbackGetProductDetails(String productId, Throwable t) {
        return new Product(productId, "Fallback Product");
    }
}

 

4. 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

์ •์ƒ ์š”์ฒญ ์‹œ
์‹คํŒจ ์š”์ฒญ ์‹œ(Fallback ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ)->์ผ์ • ์‹œ๊ฐ„ ์ง€๋‚œ ํ›„ ๋‹ค์‹œ ํด๋กœ์ฆˆ๋“œ ์ƒํƒœ๋จ

 

5. ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€ํ•ด ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ ํ™•์ธ

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
    public void registerEventListener() {
        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(name = "productService", fallbackMethod = "fallbackGetProductDetails")
    public Product getProductDetails(String productId) {
        log.info("###Fetching product details for productId: {}", productId);
        if ("111".equals(productId)) {
            log.warn("###Received empty body for productId: {}", productId);
            throw new RuntimeException("Empty response body");
        }
        return new Product(
            productId,
            "Sample Product"
        );
    }

    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: ...           |
    // +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+


   
}

์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ ํ™•์ธ

6. ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค๋กœ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ •๋ณด ํ™•์ธ ๊ฐ€๋Šฅ

  • http://localhost:19090/actuator/prometheus

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


API ๊ฒŒ์ดํŠธ์›จ์ด(Spring Cloud Gateway)

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

Spring Cloud Gateway

Spring ํ”„๋กœ์ ํŠธ์˜ ์ผํ™˜์œผ๋กœ ๊ฐœ๋ฐœ๋œ API ๊ฒŒ์ดํŠธ์›จ์ด๋กœ, ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ์ ์ ˆํ•œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…ํ•˜๊ณ  ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•จ

Spring Cloud Netflix ํŒจํ‚ค์ง€์˜ ์ผ๋ถ€๋กœ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋จ

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

Spring Cloud Gateway ์„ค์ •

  • Spring Cloud Gateway ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์˜์กด์„ฑ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ
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'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…, lb๋Š” ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ ๋œปํ•จ
          predicates:
            - Path=/users/** # /users/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ
        - id: orders-service  # ๋ผ์šฐํŠธ ์‹๋ณ„์ž
          uri: lb://orders-service  # 'orders-service'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…
          predicates:
            - Path=/orders/** #/orders/** ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ด ๋ผ์šฐํŠธ๋กœ ์ฒ˜๋ฆฌ

# Gateway๋ฅผ eureka client๋กœ ๋“ฑ๋กํ•ด์„œ eureka server๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค ์œ„์น˜ ํ™•์ธ
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

 

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

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

  • Global Filter: ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์ž‘๋™ํ•˜๋Š” ํ•„ํ„ฐ
  • Gateway Filter: ํŠน์ • ๋ผ์šฐํŠธ์—๋งŒ ์ ์šฉ๋˜๋Š” ํ•„ํ„ฐ

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

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

 

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

  • Mono: ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ 0 ๋˜๋Š” 1๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
    - Mono<Void>๋Š” ์•„๋ฌด ๋ฐ์ดํ„ฐ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ์„ ์˜๋ฏธ
  • ServerWebExchange: HTTP ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์บก์Аํ™”ํ•œ ๊ฐ์ฒด
    - exchange.getRequest()๋กœ HTTP ์š”์ฒญ ๊ฐ€์ ธ์˜ด
    - exchange.getResponse()๋กœ HTTP ์‘๋‹ต ๊ฐ€์ ธ์˜ด
  • GatewayFilterChain: ์—ฌ๋Ÿฌ ํ•„ํ„ฐ๋ฅผ ์ฒด์ธ์ฒ˜๋Ÿผ ์—ฐ๊ฒฐ
    - chain.filter(exchange)๋Š” ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ ์ „๋‹ฌ

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

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

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // ์š”์ฒญ ๋กœ๊น…
        System.out.println("Request: " + exchange.getRequest().getPath());
        return chain.filter(exchange);
    }

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

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // ์‘๋‹ต ๋กœ๊น…
            System.out.println("Response Status: " + exchange.getResponse().getStatusCode());
        }));
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

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

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

์‹ค์Šต

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

์‹ค์Šต ๋‚ด์šฉ

1. ๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํฌํŠธ ๋ฒˆํ˜ธ ์ˆ˜์ •

Eureka ์„œ๋ฒ„์™€ Order ์„œ๋น„์Šค 1๊ฐœ, Product ์„œ๋น„์Šค 2๊ฐœ

 

2. Gateway ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋ฐ 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์„ ์ง€์ •

 

3. Filter ์ƒ์„ฑ

package com.spring_cloud.eureka.client.gateway;

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());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        logger.info("Pre Filter: Request URI: "+request.getURI());
        return chain.filter(exchange); // ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์ „๋‹ฌ
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE; // ์ตœ์ƒ์œ„ ์šฐ์„ ์ˆœ์œ„
    }
}
package com.spring_cloud.eureka.client.gateway;

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.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.logging.Logger;

public class CustomPostFilter implements GlobalFilter, Ordered {
    private static final Logger logger = Logger.getLogger(CustomPreFilter.class.getName());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            logger.info("Post Filter : Response status code is " + response.getStatusCode());
        }));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

๊ฒŒ์ดํŠธ์›จ์ด ์ถ”๊ฐ€
๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ํ†ตํ•ด ๊ฐ ์„œ๋น„์Šค๋กœ ์ ‘๊ทผ
๊ฒŒ์ดํŠธ์›จ์ด ํ•„ํ„ฐ


๋ณด์•ˆ ๊ตฌ์„ฑ(OAuth2 + JWT)

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

OAuth2

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

JWT(JSON Web Token)

JWT๋Š” JSON ํ˜•์‹์˜ ์ž๊ฐ€ ํฌํ•จ๋œ ํ† ํฐ์œผ๋กœ, ํด๋ ˆ์ž„(claim)์„ ํฌํ•จํ•ด ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด ์ „๋‹ฌํ•จ
- JWT = ํ—ค๋” + ํŽ˜์ด๋กœ๋“œ + ์„œ๋ช…
- JWT๋Š” ์•”ํ˜ธํ™”๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ๊ณผ ์ถœ์ฒ˜ ๋ณด์žฅ
  • ์ž๊ฐ€ ํฌํ•จ: ํ† ํฐ ์ž์ฒด์— ๋ชจ๋“  ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด ๋ณ„๋„์˜ ์ƒํƒœ ์ €์žฅ์ด ํ•„์š” ์—†์Œ
  • ๊ฐ„๊ฒฐ์„ฑ: ์งง๊ณ  ๊ฐ„๊ฒฐํ•œ ๋ฌธ์ž์—ด๋กœ, URL, ํ—ค๋” ๋“ฑ์— ์‰ฝ๊ฒŒ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Œ
  • ์„œ๋ช… ๋ฐ ์•”ํ˜ธํ™”: ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ๊ณผ ์ธ์ฆ์„ ๋ณด์žฅํ•จ
    • JWT๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ค‘๊ฐ„์— ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์•ˆ๋˜์—ˆ๋Š”์ง€๋ฅผ ๋ฉ”์ธ์œผ๋กœ ํ™•์ธ!
    • ํ† ํฐ์˜ ๋‚ด๋ถ€์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ณด๋‹ค๋Š” ํ† ํฐ ๋ณ€์กฐ ์—ฌ๋ถ€๊ฐ€ ์ค‘์ !
    • JWT ํ† ํฐ์ด ๋ณ€์กฐ๊ฐ€ ์ด๋ฃจ์–ด์ง€๋ฉด ํ†ต๊ณผ๊ฐ€ ์•ˆ๋จ

์‹ค์Šต

๊ฒŒ์ดํŠธ์›จ์ด์˜ Pre ํ•„ํ„ฐ์—์„œ JWT ์ธ์ฆ์„ ์ง„ํ–‰
- ๊ฒŒ์ดํŠธ์›จ์ด: 19091
- product ์„œ๋น„์Šค: 19093(์ด ์„œ๋น„์Šค๋งŒ ํ™œ์šฉ)
- auth ์„œ๋น„์Šค: 19095

1) 'http://localhost:19091/auth/signin?user_id=admin_1'๋กœ ์š”์ฒญ์ด ๊ฒŒ์ดํŠธ์›จ์ด๋กœ ์™”์„ ๋•Œ, user_id๋กœ ํ† ํฐ ์ƒ์„ฑ ํ›„ ๋ฐ˜ํ™˜
2) ํด๋ผ์ด์–ธํŠธ๋Š” ํ† ํฐ์„ ํ™œ์šฉํ•ด Product๋กœ ํ˜ธ์ถœ
3) ๊ฒŒ์ดํŠธ์›จ์ด๋กœ ๋จผ์ € ๋“ค์–ด์™€ ํ† ํฐ์œผ๋กœ ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ ํ™•์ธ
4) ํ†ต๊ณผํ•˜๋ฉด Product ์„œ๋น„์Šค๋กœ ์ด๋™

๋งŒ์•ฝ ๊ถŒํ•œ์„ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๊ฒŒ์ดํŠธ์›จ์ด → auth ํ˜ธ์ถœ ํ•„์š”
- ๊ธฐ๋ณธ์ ์ธ User: Gateway → Product
- ๊ถŒํ•œ์„ ๊ฐ€์ง„ User: Gateway → auth ์š”์ฒญ ํ›„ ์‘๋‹ต → Gateway → Product

์‹ค์Šต ๋‚ด์šฉ

1. auth ์„œ๋น„์Šค ์ƒ์„ฑ ํ›„ build.gradle, application.yml ์ˆ˜์ •

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

implementation 'io.jsonwebtoken:jjwt:0.12.6' // dependencies์— ์ถ”๊ฐ€
spring:
  application:
    name: auth-service

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

service:
  jwt:
    access-expiration: 3600000
    secret-key: {secret-key}

server:
  port: 19095

 

2. JWT Config ํŒŒ์ผ ์ƒ์„ฑ

package com.spring_cloud.eureka.client.auth;

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())
                // ์š”์ฒญ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
                .authorizeHttpRequests(authorize -> authorize
                        // /auth/signIn ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ๋กœ๋Š” ์ธ์ฆ ์—†์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
                        .requestMatchers("/auth/signIn").permitAll()
                        // ๊ทธ ์™ธ์˜ ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
                        .anyRequest().authenticated()
                )
                // ์„ธ์…˜ ๊ด€๋ฆฌ ์ •์ฑ…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก STATELESS๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                );

        // ์„ค์ •๋œ ๋ณด์•ˆ ํ•„ํ„ฐ ์ฒด์ธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        return http.build();
    }
}

 

3. Service ์ƒ์„ฑ(ํ† ํฐ ๋ฐœ๊ธ‰)

package com.spring_cloud.eureka.client.auth;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
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;

    public AuthService(@Value("${service.jwt.secret-key}") String secretKey) {
        this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
    }

    // access token ์ƒ์„ฑ
    public String createAccessToken(String user_id) {
        return Jwts.builder()
                .claim("user_id", user_id)
                .issuer(issuer) // ํ† ํฐ ๋ฐœ๊ธ‰์ž
                .issuedAt(new Date((System.currentTimeMillis())))
                .expiration(new Date(System.currentTimeMillis() + accessExpiration))
                .signWith(secretKey, Jwts.SIG.HS512)
                .compact();
    }
}

 

4. Controller ์ƒ์„ฑ

package com.spring_cloud.eureka.client.auth;

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;
    
    // ResponseEntity<?>: ?๋Š” ์™€์ผ๋“œ ์นด๋“œ
    @GetMapping("/auth/signIn")
    public ResponseEntity<?> createAuthToken(@RequestParam String user_id) {
        return ResponseEntity.ok(new AuthResponse(authService.createAccessToken(user_id)));
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class AuthResponse {
        private String access_token;
    }
}

Eureka ์„œ๋ฒ„์— auth ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค ์ถ”๊ฐ€
auth ์„œ๋น„์Šค ํšŒ์›๊ฐ€์ž… API ํ˜ธ์ถœ ํ›„ ํ† ํฐ ๋ฐ˜ํ™˜

 

5. Gateway์— ํ† ํฐ ์ฒดํฌ ๊ธฐ๋Šฅ ์ถ”๊ฐ€(ํ•„ํ„ฐ)

server:
  port: 19091  # ๊ฒŒ์ดํŠธ์›จ์ด ์„œ๋น„์Šค๊ฐ€ ์‹คํ–‰๋  ํฌํŠธ ๋ฒˆํ˜ธ

spring:
  main:
    web-application-type: reactive  # Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฆฌ์•กํ‹ฐ๋ธŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์„ค์ •๋จ
  application:
    name: gateway-service  # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„์„ 'gateway-service'๋กœ ์„ค์ •
  cloud:
    gateway:
      server:
        webflux:
          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:
    access-expiration: 3600000
    secret-key: secret-key
// build.gradle ๋‚ด dependencies์— ์ถ”๊ฐ€
implementation 'io.jsonwebtoken:jjwt:0.12.6'
package com.spring_cloud.eureka.client.gateway;

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;

import javax.crypto.SecretKey;

@Component
@Slf4j
public class LocalJwtAuthenticationFilter implements GlobalFilter {

    @Value("${service.jwt.secret-key}")
    private String secretKey;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // /auth/signIn์œผ๋กœ ๋“ค์–ด์˜จ URI๋Š” ํ•„ํ„ฐ ์ˆ˜ํ–‰ X
        String path = exchange.getRequest().getURI().getPath();
        if(path.equals("/auth/signIn")) {
            return chain.filter(exchange);
        }

        // ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        String token = extractToken(exchange);

        // ํ† ํฐ ๊ฒ€์ฆ ์‹คํŒจ
        if(token == null || !validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }

    // ํ—ค๋”์—์„œ ํ† ํฐ ์ถ”์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ
    private String extractToken(ServerWebExchange exchange) {
        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 {
            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;
        }
    }
}

๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ํ†ตํ•œ signIn ํ˜ธ์ถœ
ํ† ํฐ์„ ํ™œ์šฉํ•œ ๊ฒŒ์ดํŠธ์›จ์ด ์ธ์ฆ ์ฒ˜๋ฆฌ


Config(Spring Cloud Config)

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

Spring Cloud 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);
    }
}
  • 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 ํด๋ผ์ด์–ธํŠธ ์„ค์ •

  • build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
}
  • 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 ์„œ๋ฒ„๋Š” ํ™˜๊ฒฝ๋ณ„๋กœ ๋‹ค๋ฅธ ์„ค์ • ํŒŒ์ผ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Œ
  • ex) application-dev.yml, application-prod.yml ํŒŒ์ผ์„ Git ์ €์žฅ์†Œ์— ์ €์žฅํ•ด ํ™˜๊ฒฝ๋ณ„ ์„ค์ • ๊ด€๋ฆฌํ•จ
  • Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ํ”„๋กœํ•„์„ ์‚ฌ์šฉํ•ด ํ™˜๊ฒฝ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Œ
// Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ”„๋กœํ•„์„ ์‚ฌ์šฉํ•ด ํ™˜๊ฒฝ ๊ตฌ๋ถ„
spring:
  profiles:
    active: dev

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

์‹ค์‹œ๊ฐ„ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ์„ ๋ฐ˜์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์กด์žฌํ•จ

  • Spring Cloud Bus ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ์ˆ˜๋™์œผ๋กœ /actuator/refresh ์—”๋“œํฌ์ธํŠธ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•
  • Spring Boot DevTools ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  • Git ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

๊ฐ ๋ฐฉ๋ฒ•์€ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์ ˆํžˆ ์„ ํƒํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

 

Spring Cloud Bus

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

 

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

1) /actuator/refresh ์•ค๋“œํฌ์ธํŠธ ์‚ฌ์šฉ

Spring Cloud Bus๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ˆ˜๋™์œผ๋กœ ์„ค์ •์„ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ์Œ. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” Spring Actuator์˜ /actuator/refresh ์•ค๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

  1. Config ์„œ๋ฒ„์—์„œ ์„ค์ • ํŒŒ์ผ ๋ณ€๊ฒฝ
  2. ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ /actuator/refresh ์•ค๋“œํฌ์ธํŠธ๋ฅผ POST ์š”์ฒญ์œผ๋กœ ํ˜ธ์ถœํ•ด ๋ณ€๊ฒฝ๋œ ์„ค์ • ๋ฐ˜์˜

์ด ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜์ง€๋งŒ, ๊ฐ ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ˆ˜๋™์œผ๋กœ ์•ค๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•จ

 

2) Spring Boot DevTools ์‚ฌ์šฉ

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Œ. ์ด๋Š” classpath ๋‚ด์˜ ํŒŒ์ผ ๋ณ€๊ฒฝ๋„ ํฌํ•จ๋จ

 

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

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

์‹ค์Šต

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

- ์‹ค์Šต์—์„œ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“œ ์‚ฌ์šฉ
๋กœ์ปฌ ํ™˜๊ฒฝ(๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“œ)์—์„œ๋Š” ์„ค์ • ๋ณ€๊ฒฝ ํ›„ ๋ฐ˜์˜ํ•˜๋ ค๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ์‹œ์ž‘ํ•ด์•ผ ํ•จ

ํ˜„์—…์—์„œ๋Š” ์ง์ ‘ Config ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ๋ณด๋‹ค๋Š”, ์ด๋ฏธ ๊ตฌ์ถ•๋œ Config ์„œ๋ฒ„๋ฅผ ์—ฐ๋™ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ผ๋ฐ˜์ ์ผ ๊ฒƒ์ž„

์‹ค์Šต ๋‚ด์šฉ

1. Config-server ์ƒ์„ฑ ๋ฐ ํ•„์š” ์„ค์ •

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

package com.spring_cloud.eureka.client.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer // ์ถ”๊ฐ€
public class ConfigApplication {

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

}
// application.yml
server:
  port: 18080

spring:
  profiles:
    active: native
  application:
    name: config-server
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config-repo # config-repo๋ฅผ resource ํด๋”๋กœ ์ง€์ •

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

 

2. product์— ์ „๋‹ฌํ•  yml ํŒŒ์ผ ์„ ์–ธ

// resources > config-repo > product-service.yml
server:
  port: 19093

message: "product-service message"
// resources > config-repo > product-service-local.yml
server:
  port: 19083

message: "product-service-local message"

 

3. product ์„œ๋น„์Šค ์ˆ˜์ •(Config ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด)

// ์˜์กด์„ฑ ์ถ”๊ฐ€
implementation 'org.springframework.cloud:spring-cloud-starter-config'
// application.yml
spring:
  application:
    name: product-service
  profiles:
    active: local
  config:
    import: "configserver:"
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server

server:
  port: 0

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

message: "default message"
package com.spring_cloud.eureka.client.product;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    // application.properties์— ์ž‘์„ฑํ•œ server.port ๊ฐ€์ ธ์˜ค๊ธฐ
    @Value("${server.port}")
    private String serverPort; // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํฌํŠธ
    
    @Value("${message}")
    private String message;

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

product ์„œ๋น„์Šค ์‹คํ–‰(19083 ํฌํŠธ)
product ์„œ๋น„์Šค ํ˜ธ์ถœ

 

4. Config ๋ฉ”์‹œ์ง€ ๋ณ€๊ฒฝ ์‹œ Product ์„œ๋น„์Šค์—๋„ ๋ฉ”์‹œ์ง€ ๋ณ€๊ฒฝ ๋ฐ˜์˜

// product ์„œ๋น„์Šค application.yml ํŒŒ์ผ์— refresh ์ถ”๊ฐ€

...

management:
  endpoints:
    web:
      exposure:
        include: refresh


message: "default message"
package com.spring_cloud.eureka.client.product;

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.RestController;

@RefreshScope // value๋ฅผ ๊ฐ’์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉ ์ค‘์ด๊ธฐ ๋•Œ๋ฌธ์—, refresh ๋ณ€๊ฒฝ ์‹œ ํ•ด๋‹น ๊ฐ’๋„ ์—…๋ฐ์ดํŠธ
@RestController
public class ProductController {

    // application.properties์— ์ž‘์„ฑํ•œ server.port ๊ฐ€์ ธ์˜ค๊ธฐ
    @Value("${server.port}")
    private String serverPort; // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํฌํŠธ

    @Value("${message}")
    private String message;

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

config ์„œ๋ฒ„์— ์žˆ๋Š” ํŒŒ์ผ ์ˆ˜์ • ํ›„ ๋‹ค์‹œ ์‹คํ–‰

  • 'http://localhost:19083/actuator/refresh' ์ˆ˜๋™ ์‹คํ–‰ ํ›„, ์—…๋ฐ์ดํŠธ ๋ฐ˜์˜ ํ™•์ธ

Config ๋ณ€๊ฒฝ ๋ฐ˜์˜ ํ™•์ธ


๋ถ„์‚ฐ ์ถ”์ (Spring Cloud Sleuth) ๋ฐ ๋กœ๊น…(Zipkin)

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

Micrometer

Spring ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋งคํŠธ๋ฆญ์„ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ,

๊ฐ ์„œ๋น„์Šค์˜ ์„ฑ๋Šฅ ์ง€ํ‘œ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ , Prometheus, Grafana ๋“ฑ๊ณผ ์—ฐ๋™ํ•ด ์‹œ๊ฐํ™” ๊ฐ€๋Šฅํ•จ

๋ถ„์‚ฐ ์ถ”์ ์„ ์œ„ํ•œ ๊ธฐ๋Šฅ๋„ ์ œ๊ณตํ•ด ์„œ๋น„์Šค ๊ฐ„์˜ ํ˜ธ์ถœ ํ๋ฆ„์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Œ

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

Zipkin

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

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

์„œ๋ฒ„ ์„ค์ •

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

 

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

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

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

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

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

Zipkin ๋‚ด ์„œ๋น„์Šค ํ˜ธ์ถœ ํ๋ฆ„ ์˜ˆ์‹œ


์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜์™€ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ(Spring Cloud Stream)

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

์žฅ์ 

1. ๋А์Šจํ•œ ๊ฒฐํ•ฉ

  • ์„œ๋น„์Šค ๊ฐ„์˜ ๊ฐ•ํ•œ ์ข…์†์„ฑ์„ ์ œ๊ฑฐํ•ด ๋…๋ฆฝ์ ์ธ ๊ฐœ๋ฐœ๊ณผ ๋ฐฐํฌ ๊ฐ€๋Šฅ
  • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ†ต์‹ ์„ ํ†ตํ•ด ์„œ๋น„์Šค ๊ฐ„์˜ ๊ฒฐํ•ฉ๋„ ๋‚ฎ์ถค

2. ํ™•์žฅ์„ฑ

  • ์ˆ˜ํ‰ ํ™•์žฅ์ด ์šฉ์ดํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ์—์„œ ์œ ์šฉ
  • ์ด๋ฒคํŠธ ํ”„๋กœ๋“€์„œ์™€ ์ปจ์Šˆ๋จธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ํ™•์žฅ ๊ฐ€๋Šฅ

3. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

  • ์ด๋ฒคํŠธ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด ์‹œ์Šคํ…œ์˜ ์‘๋‹ต์„ฑ ํ–ฅ์ƒ
  • ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋‹จ์ 

1. ๋ณต์žก์„ฑ ์ฆ๊ฐ€

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

2. ์žฅ์•  ์ „ํŒŒ

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

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

1. ์ด๋ฒคํŠธ ์†Œ์Šค: ์‚ฌ์šฉ์ž๊ฐ€ ์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ์—์„œ ์ฃผ๋ฌธ

  • ์ฃผ๋ฌธ ์„œ๋น„์Šค๊ฐ€ '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œํ‚ด

2. ์ด๋ฒคํŠธ ๋ฒ„์Šค: Kafka๋‚˜ RabbitMQ์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๊ฐ€ '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌ

3. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ:

  • ์žฌ๊ณ  ์„œ๋น„์Šค: '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•ด ์žฌ๊ณ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์—…๋ฐ์ดํŠธ
  • ๋ฐฐ์†ก ์„œ๋น„์Šค: '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•ด ๋ฐฐ์†ก ์ค€๋น„ ์‹œ์ž‘
  • ๊ฒฐ์ œ ์„œ๋น„์Šค: '์ฃผ๋ฌธ ์ƒ์„ฑ' ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•ด ๊ฒฐ์ œ ์ฒ˜๋ฆฌ

Spring Cloud Stream

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

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

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค ๋™์ž‘ ํ๋ฆ„


์ฟ ๋ฒ„๋„คํ‹ฐ์Šค(Kubernetes)

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค(Kubernetes)๋Š” ์ปจํ…Œ์ด๋„ˆํ™”๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐฐํฌ, ํ™•์žฅ, ์šด์˜์„ ์ž๋™ํ™”ํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”Œ๋žซํผ์œผ๋กœ, Google์—์„œ ๊ฐœ๋ฐœํ•˜๊ณ  ํ˜„์žฌ๋Š” CNCF(Cloud Native Computing Foundation)์—์„œ ๊ด€๋ฆฌํ•จ
- ์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋„๊ตฌ๋กœ, ๋‹ค์ˆ˜์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
  • ์ปจํ…Œ์ด๋„ˆ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ทธ ์ข…์†์„ฑ์„ ํ•จ๊ป˜ ํŒจํ‚ค์ง•ํ•œ ๊ฐ€์ƒํ™”๋œ ํ™˜๊ฒฝ
  • Pod: ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ ์‹คํ–‰๋˜๋Š” ์ตœ์†Œ ๋‹จ์œ„์˜ ๋ฐฐํฌ ๊ฐ์ฒด๋กœ, ํ•˜๋‚˜ ์ด์ƒ์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํฌํ•จ
  • ๋…ธ๋“œ(Node): ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํด๋Ÿฌ์Šคํ„ฐ์—์„œ Pod๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋ฌผ๋ฆฌ์  ๋˜๋Š” ๊ฐ€์ƒ ๋จธ์‹ 
  • ํด๋Ÿฌ์Šคํ„ฐ: ์—ฌ๋Ÿฌ ๋…ธ๋“œ๋กœ ๊ตฌ์„ฑ๋œ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ์ง‘ํ•ฉ
  • ๋„ค์ž„์ŠคํŽ˜์ด์Šค(Namespace): ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์—์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๋Š” ๋‹จ์œ„