๐จ๏ธ MSA
MSA ๊ฐ์
MSA๋?
- Microservices Architecture (MSA)
- MSA๋ ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ์๋น์ค๋ก ๋ถ๋ฆฌํ์ฌ ๊ฐ๋ฐ, ๋ฐฐํฌ, ์ ์ง๋ณด์๋ฅผ ์ฉ์ดํ๊ฒ ํ๋ ์ํํธ์จ์ด ์ํคํ ์ฒ ์คํ์ผ
- ๊ฐ ์๋น์ค๋ ํน์ ๋น์ฆ๋์ค ๊ธฐ๋ฅ์ ์ํํ๋ฉฐ, ์๋ก ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌ๋๊ณ ํ์ฅ๋ ์ ์์
- ์๋น์ค ๊ฐ์ ํต์ ์ ์ฃผ๋ก HTTP/HTTPS, ๋ฉ์์ง ํ ๋ฑ์ ํตํด ์ด๋ฃจ์ด์ง
- ์ฃผ์ ํน์ง
- ๋ ๋ฆฝ์ ์ธ ๋ฐฐํฌ ๊ฐ๋ฅ์ฑ: ๊ฐ ์๋น์ค๋ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌํ ์ ์์ผ๋ฉฐ, ๋ค๋ฅธ ์๋น์ค์ ์ํฅ์ ์ฃผ์ง ์๊ณ ์ ๋ฐ์ดํธํ ์ ์์
- ์์ ํ ๊ตฌ์ฑ: ๊ฐ ์๋น์ค๋ ์์ ํ์ด ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐํ๊ณ ๊ด๋ฆฌํ ์ ์์
- ๊ธฐ์ ์คํ์ ๋ค์์ฑ: ๊ฐ ์๋น์ค๋ ์ ์ ํ ๊ธฐ์ ์คํ์ ์์ ๋กญ๊ฒ ์ ํํ ์ ์์
๋ชจ๋๋ฆฌํฑ ์ํคํ ์ฒ์์ ๋น๊ต
๋ชจ๋๋ฆฌํฑ ์ํคํ ์ฒ

- ์ ์:
- ๋ชจ๋๋ฆฌํฑ ์ํคํ ์ฒ๋ ํ๋์ ํฐ ์ฝ๋๋ฒ ์ด์ค(ํน์ ์ํํธ์จ์ด, ์ ํ๋ฆฌ์ผ์ด์ ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋น๋ํ๊ณ ์คํํ๋ ๋ฐ ํ์ํ ์ ์ฒด ์์ค ์ฝ๋์ ์งํฉ)๋ก ๊ตฌ์ฑ๋ ์ ํ๋ฆฌ์ผ์ด์
- ๋ชจ๋ ๊ธฐ๋ฅ์ด ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ๋ด์ ํฌํจ
- ์ฅ์ :
- ๊ฐ๋จํ ๋ฐฐํฌ: ๋ชจ๋ ์ฝ๋๊ฐ ํ๋์ ์ฝ๋๋ฒ ์ด์ค์ ํฌํจ๋์ด ์์ด ๋ฐฐํฌ๊ฐ ๋จ์
- ๋จ์ผ ๋ฐ์ดํฐ๋ฒ ์ด์ค: ํ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ์ฝ๊ฒ ์ ์งํ ์ ์์
- ๋จ์ :
- ํ์ฅ์ฑ ๋ถ์กฑ: ํน์ ๊ธฐ๋ฅ์ ํ์ฅํ๋ ค๋ฉด ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฅํด์ผ ํจ
- ๊ธด ๊ฐ๋ฐ ์ฃผ๊ธฐ: ์์ ๋ณ๊ฒฝ ์ฌํญ๋ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์ ๋ฐฐํฌํด์ผ ํจ
- ์ ์ฐ์ฑ ๋ถ์กฑ: ์๋ก์ด ๊ธฐ์ ๋์ ์ด ์ด๋ ต๊ณ , ํน์ ๋ชจ๋์ ์ข ์์ ์
MSA

- ์ ์:
- MSA๋ ์ฌ๋ฌ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ์๋น์ค๋ก ๊ตฌ์ฑ๋ ์ ํ๋ฆฌ์ผ์ด์
- ๊ฐ ์๋น์ค๋ ํน์ ๋น์ฆ๋์ค ๊ธฐ๋ฅ์ ์ํ
- ์ฅ์ :
- ํ์ฅ์ฑ: ํน์ ์๋น์ค๋ง ํ์ฅํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ ์ ์์
- ๋ ๋ฆฝ์ ๋ฐฐํฌ: ๊ฐ๋ณ ์๋น์ค์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌํ ์ ์์
- ์ ์ฐ์ฑ: ์๋น์ค๋ณ๋ก ์ ํฉํ ๊ธฐ์ ์คํ์ ์ ํํ ์ ์์
- ๋จ์ :
- ๋ณต์ก์ฑ ์ฆ๊ฐ: ์๋น์ค ๊ฐ ํต์ , ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง ๋ฑ์ ๋ณต์ก์ฑ์ด ์ฆ๊ฐ
- ์ด์๋น์ฉ ์ฆ๊ฐ: ๊ฐ ์๋น์ค์ ๋ชจ๋ํฐ๋ง, ๋ก๊น ๋ฑ์ ๊ฐ๋ณ์ ์ผ๋ก ๊ด๋ฆฌํด์ผ ํจ
MSA์ ์ฅ๋จ์

- ์ฅ์
- ํ์ฅ์ฑ: ๊ฐ ์๋น์ค๋ ๋ ๋ฆฝ์ ์ผ๋ก ํ์ฅ ๊ฐ๋ฅ, ํน์ ๊ธฐ๋ฅ์ ๋ํ ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ฉ์ด
- ์ ์ฐ์ฑ: ๋ค์ํ ๊ธฐ์ ์คํ์ ์ฌ์ฉํ์ฌ ์๋น์ค๋ณ ์ต์ ํ ๊ฐ๋ฅ
- ๋ ๋ฆฝ์ ๋ฐฐํฌ: ์๋น์ค๋ณ๋ก ๋ ๋ฆฝ์ ๋ฐฐํฌ๊ฐ ๊ฐ๋ฅํ์ฌ ๋ฐฐํฌ ์ฃผ๊ธฐ๋ฅผ ๋จ์ถ
- ์์ ํ ๊ตฌ์ฑ: ์๋น์ค๋ณ ์์ ํ์ผ๋ก ๊ตฌ์ฑ๋์ด ๋ฏผ์ฒฉํ ๊ฐ๋ฐ ๊ฐ๋ฅ
- ๋จ์
- ๋ณต์ก์ฑ: ์๋น์ค ๊ฐ ํต์ , ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง, ํธ๋์ญ์ ๊ด๋ฆฌ ๋ฑ์ ๋ณต์ก์ฑ์ด ์ฆ๊ฐ
- ์ด์๋น์ฉ: ๊ฐ ์๋น์ค์ ๋ชจ๋ํฐ๋ง, ๋ก๊น , ์ฅ์ ๋์ ๋ฑ์ ๊ฐ๋ณ์ ์ผ๋ก ๊ด๋ฆฌํด์ผ ํ๋ฏ๋ก ์ด์ ๋น์ฉ์ด ์ฆ๊ฐ
- ๋ฐ์ดํฐ ๊ด๋ฆฌ: ๋ถ์ฐ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ์ธํด ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง๊ฐ ์ด๋ ค์ธ ์ ์์
- ๋คํธ์ํฌ ์ง์ฐ: ์๋น์ค ๊ฐ์ ํต์ ์ด ๋คํธ์ํฌ๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋ฏ๋ก ์ง์ฐ ์๊ฐ์ด ๋ฐ์ํ ์ ์์
๐จ๏ธ Spring Cloud
Spring Cloud ๊ฐ์
Spring Cloud๋?
- ์ ์
- Spring Cloud๋ ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ๋ฐ์ ์ํด ๋ค์ํ ๋๊ตฌ์ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ์คํ๋ง ํ๋ ์์ํฌ์ ํ์ฅ
- ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ๋ฅผ ์ฝ๊ฒ ๊ตฌํํ๊ณ ์ด์ํ ์ ์๋๋ก ๋์
- ์ฃผ์ ๊ธฐ๋ฅ
- ์๋น์ค ๋ฑ๋ก ๋ฐ ๋์ค์ปค๋ฒ๋ฆฌ: Eureka, Consul, Zookeeper
- ์๋น์ค ์์น ํ์: ๋์ ์ผ๋ก ๋ณํ๋ ๋ง์ดํฌ๋ก์๋น์ค ์ธ์คํด์ค์ ์์น(IP, ํฌํธ)๋ฅผ ๋ฑ๋กํ๊ณ , ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ฅผ ์ฐพ์๋ผ ์ ์๊ฒ ๊ด๋ฆฌํ๋ '์ ํ๋ฒํธ๋ถ' ์ญํ
- ๋ก๋ ๋ฐธ๋ฐ์ฑ: Ribbon, Spring Cloud LoadBalancer
- ํธ๋ํฝ ๋ถํ ๋ถ์ฐ: ํน์ ์๋ฒ์ ๋ถํ๊ฐ ์ง์ค๋์ง ์๋๋ก ์ฌ๋ฌ ๋์ ์๋ฒ ์ธ์คํด์ค์ ํธ๋ํฝ์ ๊ท ๋ฑํ๊ฒ ๋ถ์ฐ์์ผ ์ฃผ๋ ๊ธฐ์
- ์ํท ๋ธ๋ ์ด์ปค: Hystrix, Resilience4j
- ์ฅ์ ์ ํ ์ฐจ๋จ: ํน์ ์๋น์ค์ ์ฅ์ ๊ฐ ๋ฐ์ํ์ ๋ ํธ์ถ์ ์ฐจ๋จํ์ฌ ์ ์ฒด ์์คํ ์ผ๋ก ์ฅ์ ๊ฐ ์ ํ๋๋ ๊ฒ์ ๋ง๊ณ ์ฐํ๋ก๋ฅผ ์ ๊ณตํ๋ ์์ ์ฅ์น
- API ๊ฒ์ดํธ์จ์ด: Zuul, Spring Cloud Gateway
- ํตํฉ ๊ด๋ฌธ ๊ด๋ฆฌ: ์์คํ ์ ๋จ์ผ ์ง์ ์ ์ผ๋ก์ ์ธ์ฆ/์ธ๊ฐ ์ฒ๋ฆฌ, ์์ฒญ ๋ผ์ฐํ , ๋ก๋ ๋ฐธ๋ฐ์ฑ, ๋ก๊น ๋ฑ์ ๊ณตํต์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฌธ์ง๊ธฐ ์ญํ
- ๊ตฌ์ฑ ๊ด๋ฆฌ: Spring Cloud Config
- ํ๊ฒฝ ์ค์ ์ค์ํ: ์๋ฒ๋ฅผ ์ฌ์์ํ์ง ์๊ณ ๋ ๋ถ์ฐ๋ ๋ง์ดํฌ๋ก์๋น์ค๋ค์ ์ค์ ๊ฐ(properties, yaml)์ ์ค์ ์๋ฒ์์ ํ ๋ฒ์ ๊ด๋ฆฌํ๊ณ ๋ณ๊ฒฝํ๋ ๊ธฐ๋ฅ
- ๋ถ์ฐ ์ถ์ : Spring Cloud Sleuth, Zipkin
- ์ฒ๋ฆฌ ๊ฒฝ๋ก ์๊ฐํ: ์ฌ๋ฌ ๋ง์ดํฌ๋ก์๋น์ค๋ฅผ ๊ฑฐ์ณ ์ฒ๋ฆฌ๋๋ ํ๋์ ์์ฒญ(Request)์ ํ๋ฆ๊ณผ ์ฒ๋ฆฌ ์๊ฐ์ ์๊ฐํํ์ฌ ์ฅ์ ์ง์ ์ ํ์ ํ๋ ๊ธฐ์
- ๋ฉ์์ง: Spring Cloud Stream
- ๋น๋๊ธฐ ๋ฐ์ดํฐ ์ ๋ฌ: Kafka๋ RabbitMQ ๊ฐ์ ๋ฉ์์ง ๋ธ๋ก์ปค๋ฅผ ํตํด ์๋น์ค ๊ฐ ๋น๋๊ธฐ ๋ฐ์ดํฐ ์ ๋ฌ์ ์ฝ๊ฒ ๊ตฌํํ๋๋ก ๋๋ ์ถ์ํ ๋ ์ด์ด
- ์๋น์ค ๋ฑ๋ก ๋ฐ ๋์ค์ปค๋ฒ๋ฆฌ: Eureka, Consul, Zookeeper
ํ์ ์์๋ ๋ฒ์ ์ ๋ฐ์ดํธ ์์ ์์ ์ฑ ๋ฌธ์ ๋ ๊ธฐ์กด ๋ ๊ฑฐ์ ์์คํ ๊ณผ์ ํธํ์ฑ ๋๋ฌธ์ ์ฌ์ ํ 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์ฌ์ดํธ
์ฃผ๋ฌธ ์ฒ๋ฆฌ ์์คํ ์ ํ์ํ Spring Could์ ์ฃผ์ ๊ธฐ๋ฅ ์์๋ณด๊ธฐ


- ์ฃผ๋ฌธ ์ฒ๋ฆฌ๋ฅผ ์ํด ์ค๋, ํ๋ก๋ํธ, ์ ์ ์ธ ๊ฐ์ง ์ ํ๋ฆฌ์ผ์ด์ ์ด ํ์ํ๋ฉฐ, ์๋น์ค ๊ฐ ํต์ ์ด ํ์์ ์ ๋๋ค.
- ์๋น์ค ์ค๋ณต๊ณผ ๋ถํ ๋ถ์ฐ์ ์ํด ๋ฆฌ๋ณธ(Ribbon)์ ์ฌ์ฉํ์ฌ ๋ผ์ด๋ ๋ก๋น ๋ฐฉ์์ Load Balancing์ ์ ์ฉํฉ๋๋ค.
- ์๋น์ค์ ์์น๋ฅผ ๋์ ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ์ํด ์ ๋ ์นด(Eureka) ์๋ฒ๋ฅผ ๋๊ณ , ๊ฐ ์๋น์ค๋ ์ ๋ ์นด ํด๋ผ์ด์ธํธ๋ก ๋ฑ๋ก๋ฉ๋๋ค.
- ์ธ๋ถ ์์ฒญ์ API ๊ฒ์ดํธ์จ์ด๋ฅผ ํตํด ์ ์๋๊ณ , ์๋ํฌ์ธํธ์ ๋ฐ๋ผ ๊ฐ๊ฐ์ ์๋น์ค๋ก ์์ฒญ์ด ๋ถ๋ฐฐ๋ฉ๋๋ค.
- ๋ก๊ทธ์ธ ๋ฑ ์ธ์ฆ ์๊ตฌ๋ API ๊ฒ์ดํธ์จ์ด์ ํํฐ ๊ธฐ๋ฅ์์ ์ฒ๋ฆฌํ์ฌ, ์ธ์ฆ๋์ง ์์ ์์ฒญ์ ์๋น์ค ๋ด๋ถ๋ก ์ ๋ฌํ์ง ์์ต๋๋ค.
- ์๋น์ค ์์ฒญ ๊ณผ์ ์์ ์ฅ์ ๋ฐ์ ์, Resilience4j Circuit Breaker๋ฅผ ํ์ฉํด ์คํจ ์ ํ์ ๋ค์ํ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํฉ๋๋ค.
- ์ค์ ํ์ผ ๊ด๋ฆฌ๋ฅผ ์ํด ๋ณ๋์ ์ปจํผ๊ทธ(Config) ์๋ฒ๋ฅผ ๋๊ณ , ๋ชจ๋ ์๋น์ค๊ฐ ์ค์ ์๋ฒ์์ ์ค์๊ฐ์ผ๋ก ์ค์ ์ ๊ณต์ ํฉ๋๋ค.
- ์งํจ(Zipkin) ๋ฑ ๋ถ์ฐ ์ถ์ ๋๊ตฌ๋ก ๊ฐ ์๋น์ค ๊ฐ ํธ์ถ ๊ฒฝ๋ก์ ์ ์ฒด ํ๋ฆ์ ์ง๊ด์ ์ผ๋ก ํ์ ํ ์ ์์ต๋๋ค.
- ์์คํ ์ค๊ณ ๋ฐ ์ค๋ช ๋ฅ๋ ฅ ๊ฐํ๋ฅผ ์ํด ๊ตฌ์ฑ๋ ์์ฑ์ ์ค์์ฑ์ ๊ฐ์กฐํ๋ฉฐ, ์ง์ ๊ทธ๋ฆผ์ ๊ทธ๋ฆด ๊ฒ์ ์ ์ํฉ๋๋ค
๐จ๏ธ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ
| ๊ตฌ์ฑ ์์ | ์ญํ (๊ฐ๋จ ์์ฝ) | ๋น์ | ์ ํํ ๊ธฐ์ ์ ์ ์ (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. name์ ๋ณธ์ง
- ์ ์: ์ ๋ ์นด(Eureka) ์๋ฒ์ ๋ฑ๋ก๋ ๋์ ์๋น์ค์ ๊ณ ์ ID.
- ์ญํ : "์ด๋ค ๋ ์๋ค์ ๋ถ๋ฅผ ๊ฒ์ธ๊ฐ?"๋ฅผ ๊ฒฐ์ ํ๋ ๊ทธ๋ฃน ์ด๋ฆ.
- ๋์ ํ๋ก์ธ์ค (3๋จ๊ณ)
- ๋ชฉ๋ก Fetch (์ ์ฒด ์กฐํ)
- Feign์ด ์ ๋ ์นด์๊ฒ "my-service ์ฃผ์ ๋ค ์ค!"๋ผ๊ณ ์์ฒญ.
- ์ ๋ ์นด๋ ํด๋น ์ด๋ฆ์ ๊ฐ์ง ๋ชจ๋ ์ธ์คํด์ค(A, B, C)์ ์ฃผ์ ๋ชฉ๋ก์ ๋ฐํ.
- ์บ์ฑ (๋ฉ๋ชจ๋ฆฌ ์ ์ฅ)
- ์ฐ๋ฆฌ ์๋ฒ(ํธ์ถ์)๋ ๋ฐ์ ์ฃผ์๋ก์ ์๊ธฐ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํด ๋๊ณ ๊ด๋ฆฌํจ.
- ๋ก๋ ๋ฐธ๋ฐ์ฑ (์ ํ ํธ์ถ)
- Ribbon์ด ๋ฉ๋ชจ๋ฆฌ ์ ๋ชฉ๋ก ์ค ๋ฑ ํ๋๋ฅผ ์ ํ (๊ธฐ๋ณธ: ๋ผ์ด๋ ๋ก๋น).
- ๊ฒฐ์ ๋ ์๋ฒ ์ฃผ์๋ก๋ง ์ต์ข HTTP ์์ฒญ์ ๋ณด๋.
- ๋ชฉ๋ก Fetch (์ ์ฒด ์กฐํ)
์คํ๋ง์ ์ด ์ธํฐํ์ด์ค๋ฅผ ๋ณด๊ณ ์ด๋ ๊ฒ ํด์ํฉ๋๋ค:
"์ํ! ๊ฐ๋ฐ์๊ฐ 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 ๋์ ์๋ฆฌ
- ์๋น์ค ์ด๋ฆ: @FeignClient(name = "my-service") ์ด๋ ธํ ์ด์ ์ Eureka์ ๋ฑ๋ก๋ ์๋น์ค ์ด๋ฆ์ ์ฐธ์กฐ
- ์๋น์ค ์ธ์คํด์ค ์กฐํ: Eureka ์๋ฒ์์ my-service๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ฑ๋ก๋ ์๋น์ค ์ธ์คํด์ค ๋ชฉ๋ก์ ์กฐํ
- ๋ก๋ ๋ฐธ๋ฐ์ฑ: ์กฐํ๋ ์๋น์ค ์ธ์คํด์ค ๋ชฉ๋ก ์ค ํ๋๋ฅผ ์ ํํ์ฌ ์์ฒญ์ ๋ณด๋ ๋๋ค. ์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก Ribbon์ ์ฌ์ฉํ์ฌ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ํ
- ์์ฒญ ๋ถ๋ฐฐ: ์ฌ๋ฌ ์๋น์ค ์ธ์คํด์ค๊ฐ ์์ ๊ฒฝ์ฐ, Round Robin ๋๋ ๋ค๋ฅธ ์ค์ ๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ์ฌ ์์ฒญ์ ๋ถ๋ฐฐ
์๋๋ฆฌ์ค๋ก ์๊ฐํด ๋ณด๊ธฐ
- ๐ก Order ์๋น์ค๋ Product ์๋น์ค๋ฅผ ํธ์ถํ์ฌ ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์ด ๊ณผ์ ์์ ์ด๋ป๊ฒ ๋์ํ๋์ง ์ค๋ช
ํ๊ฒ ์ต๋๋ค.
- Order ์๋น์ค ์ธ์คํด์ค: 1๊ฐ
- Product ์๋น์ค ์ธ์คํด์ค: 3๊ฐ
- Order ์๋น์ค ์คํ: Order ์๋น์ค๊ฐ ์คํ๋๋ฉด Eureka ์๋ฒ์์ Product ์๋น์ค ์ธ์คํด์ค ๋ชฉ๋ก์ ๊ฐ์ ธ์ต๋๋ค.
- Product ์๋น์ค ํธ์ถ: Order ์๋น์ค์์ Product ์๋น์ค์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด FeignClient๋ฅผ ์ฌ์ฉํ์ฌ ํธ์ถํฉ๋๋ค.
- Ribbon์ ํตํ ๋ก๋ ๋ฐธ๋ฐ์ฑ: FeignClient๋ Ribbon์ ํตํด 3๊ฐ์ Product ์ธ์คํด์ค ์ค ํ๋๋ฅผ ์ ํํ์ฌ ํธ์ถํฉ๋๋ค. ์ด ๊ณผ์ ์์ Round Robin ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ์ฌ ์์ฒญ์ ์์ฐจ์ ์ผ๋ก ๋ถ๋ฐฐํฉ๋๋ค.
- ์๋ต ์ฒ๋ฆฌ: ์ ํ๋ 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๋จ๊ณ์
๋๋ค.
- ํ๊ฒ ์ ์ : Feign์ด @FeignClient(name="product-service")๋ฅผ ๋ณด๊ณ , Ribbon์ ํตํด ํธ์ถํ ์๋ฒ 1๊ฐ๋ฅผ ์ ํํฉ๋๋ค.
- URL ์กฐ๋ฆฝ: ์ ํ๋ ์๋ฒ์ ๊ธฐ๋ณธ ์ฃผ์์ @GetMapping์ ๊ฒฝ๋ก์ @PathVariable์ ๊ฐ์ ํฉ์ณ์ ์์ ํ URL์ ๋ง๋ญ๋๋ค. (์: http://{์ ํ๋์๋ฒ์ฃผ์}/product/112)
- ์์ฒญ ๋ฐ ์๋ต: ์กฐ๋ฆฝ๋ 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) ํ๊ฒฝ์์ ์ฌ๋ฌ ๊ฐ์ ์๋น์ค๊ฐ ์ด๋ป๊ฒ ์๋ก๋ฅผ ๋ฐ๊ฒฌํ๊ณ , ๋ถํ๋ฅผ ๋ถ์ฐํ๋ฉฐ ํต์ ํ๋์ง ์ง์ ๊ตฌํํด ๋ณด์์ต๋๋ค. ์ ์ฒด์ ์ธ ํ๋ฆ๊ณผ ํ์ผ๋ณ ์ค๊ณ๋๋ฅผ ์์ฝํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๐ ์ ์ฒด ์ํคํ ์ฒ ๋์ ํ๋ฆ
- ์๋น์ค ๋ฑ๋ก (Eureka Registry): Product ์๋น์ค์ Order ์๋น์ค๋ ๊ฐ๊ฐ์ ํ๊ฒฝ ์ค์ (application.yml)์ ํตํด ์์ ๋ค์ ์ด๋ฆ๊ณผ ํฌํธ ๋ฒํธ๋ฅผ ์ ๋ ์นด(Eureka) ์๋ฒ์ ๋ฑ๋กํฉ๋๋ค.
- ๋ค์ค ์ธ์คํด์ค ์คํ: ํธ๋ํฝ ๋ถ์ฐ ํ ์คํธ๋ฅผ ์ํด Product ์๋น์ค๋ฅผ ์ธํ ๋ฆฌ์ ์ด์ ์คํ ๊ตฌ์ฑ์ ํ์ฉํ์ฌ ์๋ก ๋ค๋ฅธ 3๊ฐ์ ํฌํธ(19092, 19093, 19094)๋ก ๋์์ ๋์๋๋ค.
- ์ ์ธ์ ์๊ฒฉ ํธ์ถ (Feign Client): Order ์๋น์ค ๋ด๋ถ์ Product ์๋น์ค์ ์ ๋ณด๋ฅผ ํธ์ถํ ์ ์๋ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ญ๋๋ค. ๋ณต์กํ ํต์ ์ฝ๋ ์์ด ์ด๋ ธํ ์ด์ ๋ง์ผ๋ก ์๊ฒฉ ํธ์ถ ์ค๋น๋ฅผ ๋ง์นฉ๋๋ค.
- ๋ก๋ ๋ฐธ๋ฐ์ฑ (Round-Robin): ์ฌ์ฉ์๊ฐ Order ์๋น์ค๋ก ์ฃผ๋ฌธ์ ์์ฒญํ๋ฉด, Order ์๋น์ค๋ ์ ๋ ์นด์์ ๋ฐ์์จ Product ์ธ์คํด์ค ๋ชฉ๋ก ์ค ํ๋๋ฅผ ์ ํํด ์์ฒญ์ ๋ณด๋ ๋๋ค. ์ด๋ ๋ผ์ด๋ ๋ก๋น(Round-Robin) ์๊ณ ๋ฆฌ์ฆ์ด ์๋ํ์ฌ ํธ๋ํฝ์ ๊ณตํํ๊ฒ ๋ถ๋ฐฐํฉ๋๋ค.
- ๋์ ํ์ธ: ์ต์ข ์๋ต ๋ฉ์์ง์ ํฌํจ๋ Product ์๋ฒ์ ํฌํธ ๋ฒํธ๊ฐ ์๋ก๊ณ ์นจํ ๋๋ง๋ค ์์ฐจ์ ์ผ๋ก ๋ณ๊ฒฝ๋๋ ๊ฒ์ ํตํด, ๋ถ์ฐ ํธ์ถ์ด ์ ์์ ์ผ๋ก ์ด๋ฃจ์ด์ง๊ณ ์์์ ์ฆ๋ช ํฉ๋๋ค.
๐ ํ์ผ๋ณ ํต์ฌ ์ญํ ์ค๊ณ๋
- ๐ [Product Service] - ๋ฐ์ดํฐ ์ ๊ณต์
- ์์ฒญ์ ๋ฐ์ผ๋ฉด ์์ ์ ํฌํธ ๋ฒํธ๋ฅผ ํฌํจํ ์ํ ์ ๋ณด๋ฅผ ๋ฐํํ๋ ์ญํ ์
๋๋ค.
- application.yml: ๋ด ์๋น์ค ์ด๋ฆ(product-service)์ ์ง์ ํ๊ณ , ์คํํ ํฌํธ์ ์ ๋ ์นด ์๋ฒ ์ฃผ์๋ฅผ ๋ฑ๋กํ๋ ํธ์ ํ๊ธฐ.
- ProductApplication.java: ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์คํ.
- ProductController.java: ์ค์ ์์ฒญ์ ๋ฐ์ "๋๋ OOO๋ฒ ํฌํธ์์ ์ผํ๋ Product ์๋ฒ์ผ!"๋ผ๊ณ ์๋ต์ ๋ด๋ ค์ฃผ๋ ์ฐฝ๊ตฌ.
- ์์ฒญ์ ๋ฐ์ผ๋ฉด ์์ ์ ํฌํธ ๋ฒํธ๋ฅผ ํฌํจํ ์ํ ์ ๋ณด๋ฅผ ๋ฐํํ๋ ์ญํ ์
๋๋ค.
- ๐ฆ [Order Service] - ๋ฐ์ดํฐ ์๋น์
- ์ฌ์ฉ์์ ์ฃผ๋ฌธ์ ๋ฐ๊ณ , ํ์์ Product ์๋น์ค์ ํต์ ์ ์์ฒญํ๋ ์ญํ ์
๋๋ค.
- application.yml: ๋ด ์๋น์ค ์ด๋ฆ(order-service)๊ณผ ํฌํธ(19091)๋ฅผ ์ค์ ํ๊ณ ์ ๋ ์นด์ ๋ฑ๋ก.
- OrderApplication.java: @EnableFeignClients๋ฅผ ๋ถ์ฌ ๋ค๋ฅธ ์๋น์ค๋ฅผ ํธ์ถํ ์ ์๋ ๋ฅ๋ ฅ์ ํ์ฑํํ๋ ๋ฉ์ธ ์ค์์น.
- ProductClient.java: ์ ๋ ์นด์ product-service๋ก ๋ฑ๋ก๋ ์๋ฒ๋ฅผ ์ฐพ์๊ฐ๊ธฐ ์ํ ์ธํฐํ์ด์ค ๋ช ์ธ์ (๋ฆฌ๋ชจ์ปจ ์ญํ ).
- OrderService.java: ๋น์ฆ๋์ค ๋ก์ง์ ํต์ฌ. "์ฃผ๋ฌธ ๋ฒํธ 1๋ฒ์ด ๋ค์ด์ค๋ฉด, ProductClient๋ฅผ ํตํด 2๋ฒ ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ ์กฐํฉํด๋ผ!"๋ผ๊ณ ์ง์ํ๋ ๋๋.
- OrderController.java: ์ธ๋ถ ์ฌ์ฉ์(ํด๋ผ์ด์ธํธ)๊ฐ ์ ๊ทผํ ์ ์๋ ์ต์ด์ API ์ง์ ์ .
- ์ฌ์ฉ์์ ์ฃผ๋ฌธ์ ๋ฐ๊ณ , ํ์์ Product ์๋น์ค์ ํต์ ์ ์์ฒญํ๋ ์ญํ ์
๋๋ค.
โ ์ต์ข ํ ์คํธ ๋ฐ ๊ฒฐ๊ณผ
- ๋ชจ๋ ์๋ฒ๋ฅผ ๋์ด ํ ๋ธ๋ผ์ฐ์ ๋ Postman์์ http://localhost:19091/order/1์ ์ฌ๋ฌ ๋ฒ ํธ์ถํด ๋ด ๋๋ค. ๊ฒฐ๊ณผ ๋ฌธ์์ด ๋์ ์ฐํ๋ ํฌํธ ๋ฒํธ(19092 → 19093 → 19094)๊ฐ ๊ณ์ ๋ฐ๋๋ค๋ฉด, ์ ๋ ์นด๋ฅผ ํตํ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ์ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ด ์๋ฒฝํ๊ฒ ์ฑ๊ณตํ ๊ฒ์ ๋๋ค! ๐
๐จ๏ธ ์ํท๋ธ๋ ์ด์ปค
์ํท ๋ธ๋ ์ด์ปค ๊ฐ์
์ํท ๋ธ๋ ์ด์ปค๋?
- ์ํท ๋ธ๋ ์ด์ปค๋ ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ์ ํธ์ถ ์คํจ๋ฅผ ๊ฐ์งํ๊ณ ์์คํ ์ ์ ์ฒด์ ์ธ ์์ ์ฑ์ ์ ์งํ๋ ํจํด
- ์ธ๋ถ ์๋น์ค ํธ์ถ ์คํจ ์ ๋น ๋ฅธ ์คํจ๋ฅผ ํตํด ์ฅ์ ๋ฅผ ๊ฒฉ๋ฆฌํ๊ณ , ์์คํ ์ ๋ค๋ฅธ ๋ถ๋ถ์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ํฉ๋๋ค.
- ์ํ ๋ณํ: ํด๋ก์ฆ๋ -> ์คํ -> ํํ-์คํ
Resilience4j ๊ฐ์
Resilience4j๋?
- Resilience4j๋ ์ํท ๋ธ๋ ์ด์ปค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ์๋น์ค ๊ฐ์ ํธ์ถ ์คํจ๋ฅผ ๊ฐ์งํ๊ณ ์์คํ ์ ์์ ์ฑ์ ์ ์งํฉ๋๋ค.
- ๋ค์ํ ์ํท ๋ธ๋ ์ด์ปค ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ, ์ฅ์ ๊ฒฉ๋ฆฌ ๋ฐ ๋น ๋ฅธ ์คํจ๋ฅผ ํตํด ๋ณต์๋ ฅ์ ๋์ ๋๋ค.
Resilience4j์ ์ฃผ์ ํน์ง
- ์ํท ๋ธ๋ ์ด์ปค ์ํ: ํด๋ก์ฆ๋, ์คํ, ํํ-์คํ ์ํ๋ฅผ ํตํด ํธ์ถ ์คํจ๋ฅผ ๊ด๋ฆฌ
- ํด๋ก์ฆ๋(Closed):
- ๊ธฐ๋ณธ ์ํ๋ก, ๋ชจ๋ ์์ฒญ์ ํต๊ณผ์ํต๋๋ค.
- ์ด ์ํ์์ ํธ์ถ์ด ์คํจํ๋ฉด ์คํจ ์นด์ดํฐ๊ฐ ์ฆ๊ฐํฉ๋๋ค.
- ์คํจ์จ์ด ์ค์ ๋ ์๊ณ๊ฐ(์: 50%)์ ์ด๊ณผํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋ฉ๋๋ค.
- ์์: ์ต๊ทผ 5๋ฒ์ ํธ์ถ ์ค 3๋ฒ์ด ์คํจํ์ฌ ์คํจ์จ์ด 60%์ ๋๋ฌํ๋ฉด ์คํ ์ํ๋ก ์ ํ๋ฉ๋๋ค.
- ์คํ(Open):
- ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋๋ฉด ๋ชจ๋ ์์ฒญ์ ์ฆ์ ์คํจ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
- ์ด ์ํ์์ ์์ฒญ์ด ์คํจํ์ง ์๊ณ ๋ฐ๋ก ์๋ฌ ์๋ต์ ๋ฐํํฉ๋๋ค.
- ์ค์ ๋ ๋๊ธฐ ์๊ฐ์ด ์ง๋ ํ, ์ํท ๋ธ๋ ์ด์ปค๋ ํํ-์คํ ์ํ๋ก ์ ํ๋ฉ๋๋ค.
- ์์: ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋๊ณ 20์ด ๋์ ๋ชจ๋ ์์ฒญ์ด ์ฐจ๋จ๋ฉ๋๋ค.
- ํํ-์คํ(Half-Open):
- ์คํ ์ํ์์ ๋๊ธฐ ์๊ฐ์ด ์ง๋๋ฉด ์ํท ๋ธ๋ ์ด์ปค๋ ํํ-์คํ ์ํ๋ก ์ ํ๋ฉ๋๋ค.
- ํํ-์คํ ์ํ์์๋ ์ ํ๋ ์์ ์์ฒญ์ ํ์ฉํ์ฌ ์์คํ ์ด ์ ์ ์ํ๋ก ๋ณต๊ตฌ๋์๋์ง ํ์ธํฉ๋๋ค.
- ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๋ ํด๋ก์ฆ๋ ์ํ๋ก ์ ํ๋ฉ๋๋ค.
- ์์ฒญ์ด ๋ค์ ์คํจํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๋ ๋ค์ ์คํ ์ํ๋ก ์ ํ๋ฉ๋๋ค.
- ์์: ํํ-์คํ ์ํ์์ 3๊ฐ์ ์์ฒญ์ ํ์ฉํ๊ณ , ๋ชจ๋ ์ฑ๊ณตํ๋ฉด ํด๋ก์ฆ๋ ์ํ๋ก ์ ํ๋ฉ๋๋ค. ๋ง์ฝ ํ๋๋ผ๋ ์คํจํ๋ฉด ๋ค์ ์คํ ์ํ๋ก ์ ํ๋ฉ๋๋ค.
- ํด๋ก์ฆ๋(Closed):
- 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๊ฐ๋ฅผ ๋ฐํ'ํ๊ฑฐ๋, ํน์ ๋ฐ์ดํฐ ์์ด '์์ ์๋ฃ ์ ํธ'๋ง ๋ณด๋ผ ๋ ์ฌ์ฉํ๋ ๋น๋๊ธฐ ์ ์ฉ ๋ฆฌํด ๊ฐ์ฒด์ ๋๋ค.
- ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ (Reactive Programming): ํน์ ์์ ์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ (Non-blocking), ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ๊ฑฐ๋ ๋ณํํ ๋๋ง๋ค ์ฆ๊ฐ์ ์ผ๋ก ๋ฐ์ํ์ฌ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋๋ค.
- Mono<Void>๋ ์๋ฌด ๋ฐ์ดํฐ๋ ๋ฐํํ์ง ์์์ ์๋ฏธํฉ๋๋ค.
- Mono๋ ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์์ 0 ๋๋ 1๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
- 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๊ฐ) ๋ก ์งํํด๋ด ๋๋ค.

๐๏ธ ์ ์ฒด ์์คํ ๊ตฌ์กฐ
- Eureka Server (19090): ๋ชจ๋ ์๋น์ค(Order, Product, Gateway)์ ์ฃผ์๋ฅผ ๊ด๋ฆฌํ๋ ์ ํ๋ฒํธ๋ถ ์ญํ ์ ํฉ๋๋ค.
- Order Service (19092): /order ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ค์ ์๋น์ค์ ๋๋ค.
- Product Service (19093, 19094): /product ์์ฒญ์ ์ฒ๋ฆฌํ๋ฉฐ, ํฌํธ๊ฐ ๋ ๊ฐ๋ผ ๊ฒ์ดํธ์จ์ด๊ฐ ๋ก๋๋ฐธ๋ฐ์ฑ(๋ถ์ฐ ์ฒ๋ฆฌ)์ ์ํํฉ๋๋ค.
- 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: ์ด ํํฐ๋ฅผ ์ฒด์ธ์ ๊ฐ์ฅ ๋์ ๋ฐฐ์นํฉ๋๋ค. ๋ชจ๋ ์๋น์ค ๋ก์ง๊ณผ ๋ค๋ฅธ ํํฐ๋ค์ ์ฒ๋ฆฌ๊ฐ ๋๋ ํ ์ต์ข ์ ์ผ๋ก ์๋ต ์ํ๋ฅผ ํ์ธํ๊ธฐ์ ์ ํฉํ ์ค์ ์ ๋๋ค.
- โ
์์ฝ
- ๋์ ์์ : ์๋น์ค์ ์๋ต์ด ๊ฒ์ดํธ์จ์ด์ ๋์ฐฉํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ก๋๊ธฐ ์ง์ ์ ์คํ๋ฉ๋๋ค.
- ๊ตฌํ ํน์ง: then ๋ฉ์๋๋ฅผ ํตํด ๋น๋๊ธฐ ์ฒ๋ฆฌ์ '์๋ฃ ์ดํ' ์์ ์ ์ก๋ ๊ฒ์ด ํต์ฌ์ ๋๋ค.
- ์ค์ต ์๋ฏธ: ์ด ํํฐ๊น์ง ํต๊ณผํด์ผ ์ฌ์ฉ์๋ ์ต์ข ์ ์ผ๋ก 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():
- getRequest(): ๋ฐ๊ตฌ๋์์ ์์ฒญ ์ ๋ณด๋ฅผ ๊บผ๋ ๋๋ค.
- getURI(): ์์ฒญ๋ ์ ์ฒด ์ฃผ์ ์ ๋ณด(์: http://localhost:19091/auth/signIn)๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- 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 ์๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ์ค์ ๊ฐฑ์ ์ ์ฐจ
- Config ์๋ฒ์์ ์ค์ ํ์ผ์ ๋ณ๊ฒฝํฉ๋๋ค.
- ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ /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 ์์ฒญ์ ํฉ๋๋ค. ์๋ต์ผ๋ก ๋ฉ์์ง๊ฐ ์ ๋ฐ์ดํธ ๋จ์ ํ์ธํ ์ ์์ต๋๋ค.

๋ค์ http://localhost:19083/product๋ฅผ ํธ์ถ ํ๋ฉด ๋ฉ์์ง๊ฐ ๋ณ๊ฒฝ๋ ๊ฒ์ ํ์ธ ํ ์ ์์ต๋๋ค.

*์ฐธ๊ณ http://localhost:18080/product-service/local ๋ก ์ ์ํ๋ฉด product-service์ ์ค์ ๊ฐ๋ค์ ๋ณผ ์ ์์ต๋๋ค. (์ด๋ฅผ ํตํด ๋ค๋ฅธ ์ค์ ๊ฐ๋ค๋ ํ์ธํ ์ ์์ต๋๋ค.)

๐ ํต์ฌ ๊ฐ๋ ์์ฝ
- ์ค์ ํ์ผ ๋งค์นญ ๊ท์น (Naming Convention)
- spring.application.name = product-service
- spring.profiles.active = local
- ์ ๋ ๊ฐ์ง ์ค์ ์ด ๊ฒฐํฉ๋์ด, Config ์๋ฒ์ ์๋ product-service-local.yml ํ์ผ์ ์ต์ฐ์ ์ผ๋ก ์ฝ์ด์ต๋๋ค.
- ์ ๋ ์นด(Eureka) ์ฐ๋์ ์ฅ์ (discovery.enabled: true)
- ๊ธฐ์กด์๋ ํด๋ผ์ด์ธํธ YAML์ uri: http://localhost:18080 ์ฒ๋ผ ์ค์ ์๋ฒ ์ฃผ์๋ฅผ ์ง์ ์ ์ด์ผ ํ์ต๋๋ค.
- ์ ๋ ์นด๋ฅผ ์ฐ๋ํ๋ฉด service-id: config-server ์ด๋ฆ๋ง์ผ๋ก ์ฃผ์๋ฅผ ์ฐพ์๋ด๋ฏ๋ก, ์ค์ ์๋ฒ์ IP๋ ํฌํธ๊ฐ ๋ณ๊ฒฝ๋์ด๋ ํด๋ผ์ด์ธํธ ์ฝ๋๋ฅผ ์์ ํ ํ์๊ฐ ์์ต๋๋ค.
- ๋์ ์ค์ ๊ฐฑ์ (@RefreshScope + Actuator)
- ์ฝ๋๋ฅผ ์์ ํ๊ฑฐ๋ ์๋ฒ๋ฅผ ๋ด๋ฆฌ์ง ์์๋, product-service-local.yml์ ๋ฉ์์ง ๋ด์ฉ์ ์์ ํ ๋ค ํด๋ผ์ด์ธํธ ์ธก์ /actuator/refresh POST ์์ฒญ์ ๋ณด๋ด๋ฉด ProductController์ message ๋ณ์ ๊ฐ์ด ์ฆ์ ๋ฐ๋๋๋ค. (์ด๊ฒ์ด MSA์์ Config Server๋ฅผ ์ฐ๋ ๊ฐ์ฅ ํฐ ์ด์ ์ ๋๋ค.)
๐จ๏ธ ๋ถ์ฐ์ถ์
๋ถ์ฐ ์ถ์
๋ถ์ฐ ์ถ์ ์ด๋?
- ๋ถ์ฐ ์ถ์ ์ ๋ถ์ฐ ์์คํ ์์ ์๋น์ค ๊ฐ์ ์์ฒญ ํ๋ฆ์ ์ถ์ ํ๊ณ ๋ชจ๋ํฐ๋งํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
- ๊ฐ ์๋น์ค์ ํธ์ถ ๊ด๊ณ์ ์ฑ๋ฅ์ ์๊ฐํํ์ฌ ๋ฌธ์ ๋ฅผ ์ง๋จํ๊ณ ํด๊ฒฐํ ์ ์๋๋ก ๋์ต๋๋ค.
- ์ฃผ์ ๊ฐ๋
: ํธ๋ ์ด์ค(Trace), ์คํฌ(Span), ์ปจํ
์คํธ(Context)
- ํธ๋ ์ด์ค(Trace) : ํธ๋ ์ด์ค๋ ํ๋์ ์์ฒญ์ด ์์๋ถํฐ ๋๊น์ง ๊ฐ ์๋น์ค๋ฅผ ๊ฑฐ์น๋ ์ ์ฒด ํ๋ฆ์ ๋ํ๋
๋๋ค
- ํ๋์ ํธ๋ ์ด์ค๋ ์ฌ๋ฌ ๊ฐ์ ์คํฌ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค
- ๋ถ์ฐ ์์คํ ์์ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ์ฌ๋ฌ ์๋น์ค๋ก ์ ๋ฌ๋ ๋, ๊ฐ ์๋น์ค ํธ์ถ์ด ํธ๋ ์ด์ค์ ์ผ๋ถ๋ก ๊ธฐ๋ก๋ฉ๋๋ค
- ํธ๋ ์ด์ค ID๋ ๊ฐ ์คํฌ์ ๊ณตํต์ผ๋ก ๋ถ์ฌ๋๋ฉฐ, ์ด๋ฅผ ํตํด ์ ์ฒด ์์ฒญ ํ๋ฆ์ ์ถ์ ํ ์ ์์ต๋๋ค
- ์คํฌ(Span) : ์คํฌ์ ๋ถ์ฐ ์ถ์ ์์ ๊ฐ์ฅ ์์ ๋จ์๋ก, ํน์ ์๋น์ค ๋ด์์์ ๊ฐ๋ณ ์์
๋๋ ์์ฒญ์ ๋ํ๋
๋๋ค.
- ๊ฐ ์คํฌ์ ์์ ์๊ฐ๊ณผ ์ข ๋ฃ ์๊ฐ์ ๊ธฐ๋กํ์ฌ ์์ ์ ์ง์ ์๊ฐ์ ๋ํ๋ ๋๋ค.
- ์คํฌ์ ๊ณ ์ ํ ์คํฌ ID๋ฅผ ๊ฐ์ง๋ฉฐ, ์ด๋ ํธ๋ ์ด์ค ID์ ํจ๊ป ํน์ ์์ ์ ์๋ณํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- ์คํฌ์ ๋ถ๋ชจ-์์ ๊ด๊ณ๋ฅผ ๊ฐ์ง ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ํธ์ถ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํํํฉ๋๋ค.
- ์คํฌ์๋ ๋ฉํ๋ฐ์ดํฐ(ํ๊ทธ, ๋ก๊ทธ, ์ด๋ฒคํธ ๋ฑ)๋ฅผ ์ถ๊ฐํ์ฌ ์์ธํ ์ ๋ณด๋ฅผ ๊ธฐ๋กํ ์ ์์ต๋๋ค.
- ์ปจํ
์คํธ(Context) : ์ปจํ
์คํธ๋ ์์ฒญ์ด ์๋น์ค ๊ฐ์ ์ ๋ฌ๋ ๋ ํจ๊ป ์ ํ๋์ด, ๊ฐ ์๋น์ค๊ฐ ์์ฒญ์ ์ ์ฒด ํ๋ฆ์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ง ์ ์๊ฒ ํฉ๋๋ค.
- ์ปจํ ์คํธ๋ ํธ๋ ์ด์ค ID, ์คํฌ ID, ๋ถ๋ชจ ์คํฌ ID ๋ฑ์ ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ๊ฐ ์๋น์ค๊ฐ ์์ฒญ์ ์ถ์ฒ์ ๊ฒฝ๋ก๋ฅผ ์ถ์ ํ ์ ์๋๋ก ๋์ต๋๋ค.
- ์๋น์ค ํธ์ถ ๊ฐ์ ์ปจํ ์คํธ๋ฅผ ์ ์งํจ์ผ๋ก์จ, ๋ถ์ฐ ์์คํ ์ ์ฒด์์ ์ผ๊ด๋ ์ถ์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ํธ๋ ์ด์ค(Trace) : ํธ๋ ์ด์ค๋ ํ๋์ ์์ฒญ์ด ์์๋ถํฐ ๋๊น์ง ๊ฐ ์๋น์ค๋ฅผ ๊ฑฐ์น๋ ์ ์ฒด ํ๋ฆ์ ๋ํ๋
๋๋ค
์ ๋ถ์ฐ ์ถ์ ์ด ํ์ํ๊ฐ?
- ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์๋ ์ฌ๋ฌ ์๋น์ค๊ฐ ํ๋ ฅํ์ฌ ํ๋์ ์์ฒญ์ ์ฒ๋ฆฌํฉ๋๋ค.
- ์๋น์ค ๊ฐ์ ๋ณต์กํ ํธ์ถ ๊ด๊ณ๋ก ์ธํด ๋ฌธ์ ๋ฐ์ ์ ์์ธ์ ํ์ ํ๊ธฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค.
- ๋ถ์ฐ ์ถ์ ์ ํตํด ๊ฐ ์๋น์ค์ ํธ์ถ ํ๋ฆ์ ๋ช ํํ ํ์ ํ๊ณ , ์ฑ๋ฅ ๋ณ๋ชฉ์ด๋ ์ค๋ฅ๋ฅผ ๋น ๋ฅด๊ฒ ์ง๋จํ ์ ์์ต๋๋ค.
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์ด๋ผ๋ ๋จ์ด๊ฐ ๋์ต๋๋ค. ์ด ๋ ๊ฐ์ง๋ฅผ ๊ตฌ๋ถํ๋ ๊ฒ์ด ๋ถ์ฐ ์ถ์ ์ ์ ๋ถ์
๋๋ค.
- Trace (ํธ๋ ์ด์ค): ์ฌ์ฉ์์ ์ต์ด ์์ฒญ๋ถํฐ ์ต์ข ์๋ต๊น์ง์ ์ ์ฒด ํ๋ฆ(1๊ฐ์ ํฐ ์์ )์ ์๋ฏธํฉ๋๋ค. (๊ณ ์ ํ Trace ID๋ฅผ ๊ฐ์ง๋๋ค.)
- Span (์คํฌ): ์ ์ฒด ํ๋ฆ ์์์ ๊ฐ ์๋น์ค๊ฐ ์ํํ ๊ฐ๋ณ ์์
๋จ์๋ฅผ ์๋ฏธํฉ๋๋ค. (๊ฐ์ ๊ณ ์ ํ Span ID๋ฅผ ๊ฐ์ง๋๋ค.)
- ๊ณต์: 1๊ฐ์ Trace = N๊ฐ์ Span
๐ก ์ Spans ๋ฆฌ์คํธ๊ฐ '3'์ผ๋ก ๋์ฌ๊น์? (์ค์ต ๊ฒฐ๊ณผ ๋ถ์)
- http://localhost:19091/order/1 ์ ํธ์ถํ์ ๋ Zipkin์ Span์ด 3๊ฐ ์ฐํ๋ ๊ธฐ์ ์ ์ธ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Span 1 (Order Service): ์ฌ์ฉ์์ ์์ฒญ์ด Order ์๋ฒ์ ์ต์ด๋ก ๋์ฐฉํ์ฌ ์ฒ๋ฆฌ๋ฅผ ์์ํ ์์ .
- Span 2 (Feign Client): Order ์๋ฒ๊ฐ Product ์๋ฒ๋ก HTTP ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ํด ํต์ ์ ์๋ํ๊ณ ๋๊ธฐํ ์์ .
- Span 3 (Product Service): Product ์๋ฒ๊ฐ ์์ฒญ์ ์ ๋ฌ๋ฐ์ ์ค์ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ณ ์๋ตํ ์์ .
์ด 3๊ฐ์ Span์ด ๋ชจ์ฌ 1๊ฐ์ Trace(์ ์ฒด ํ๋ฆ)๋ฅผ ๊ตฌ์ฑํ๋ฉฐ, Zipkin์ ์ด๋ฅผ ์๊ฐ ์์๋๋ก ์ฐ๊ฒฐํ์ฌ ๊ทธ๋ํ๋ก ๋ณด์ฌ์ค๋๋ค.
๐จ๏ธ ์ด๋ฒคํธ ๋๋ฆฌ๋ธ
์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ
์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ๋?
- ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ๋ ์์คํ ์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ(์ํ ๋ณํ๋ ํ๋)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ ์ํํธ์จ์ด ์ค๊ณ ์คํ์ผ์ ๋๋ค. ์ด๋ฒคํธ๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, ์๋น์ค ๊ฐ์ ๋์จํ ๊ฒฐํฉ์ ํตํด ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ ์ ์๊ฒ ํฉ๋๋ค.
์ฃผ์ ๊ฐ๋
- ์ด๋ฒคํธ: ์์คํ ๋ด์์ ๋ฐ์ํ๋ ์ํ ๋ณํ๋ ํ๋์ ๋ํ๋ด๋ ๋ฉ์์ง์ ๋๋ค.
- ์ด๋ฒคํธ ์์ค: ์ด๋ฒคํธ๋ฅผ ์์ฑํ์ฌ ์ด๋ฒคํธ ๋ฒ์ค์ ์ ๋ฌํ๋ ์ญํ ์ ํฉ๋๋ค.
- ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ด๋ฒคํธ๋ฅผ ์์ ํ์ฌ ์ฒ๋ฆฌํ๋ ์ญํ ์ ํฉ๋๋ค.
- ์ด๋ฒคํธ ๋ฒ์ค: ์ด๋ฒคํธ ์์ค์ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๊ฐ์ ๋ฉ์์ง ์ ๋ฌ์ ์ค๊ฐํฉ๋๋ค.
์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ์ ์ฅ์
- ๋์จํ ๊ฒฐํฉ
- ์๋น์ค ๊ฐ์ ๊ฐํ ์ข ์์ฑ์ ์ ๊ฑฐํ์ฌ ๋ ๋ฆฝ์ ์ธ ๊ฐ๋ฐ๊ณผ ๋ฐฐํฌ๊ฐ ๊ฐ๋ฅ
- ์ด๋ฒคํธ ๊ธฐ๋ฐ ํต์ ์ ํตํด ์๋น์ค ๊ฐ์ ๊ฒฐํฉ๋ ๋ฎ์ถค
- ํ์ฅ์ฑ
- ์ํ ํ์ฅ์ด ์ฉ์ดํ์ฌ ๋๊ท๋ชจ ์์คํ ์์ ์ ์ฉ
- ์ด๋ฒคํธ ํ๋ก๋์์ ์ปจ์๋จธ๋ฅผ ๋
๋ฆฝ์ ์ผ๋ก ํ์ฅ ๊ฐ๋ฅ
- ๐ข ์ด๋ฒคํธ ํ๋ก๋์ (Producer): ์์คํ ์์ ์ํ ๋ณํ๊ฐ ๋ฐ์ํ์ ๋, ์ด๋ฅผ ์ด๋ฒคํธ ๋ฉ์์ง๋ก ๋ง๋ค์ด ๋ธ๋ก์ปค์ ๋ฐํ(Publish)ํ๋ ์๋น์ค์ ๋๋ค.
- ๐ฎ ๋ฉ์์ง ๋ธ๋ก์ปค (Broker): ํ๋ก๋์๊ฐ ๋ณด๋ธ ์ด๋ฒคํธ๋ฅผ ์์ ํ๊ฒ ๋ณด๊ดํ๊ณ , ํ์ํ ์ปจ์๋จธ๋ค์๊ฒ ์ ๋ฌํด ์ฃผ๋ ๋น๋๊ธฐ ์ค๊ณ์(์ฐ์ฒด๊ตญ) ์ญํ ์ ๋๋ค." (์: Kafka, RabbitMQ)
- ๐ง ์ด๋ฒคํธ ์ปจ์๋จธ (Consumer): ๋ธ๋ก์ปค๋ฅผ ๊ตฌ๋ (Subscribe)ํ๊ณ ์๋ค๊ฐ, ์ด๋ฒคํธ๊ฐ ๋์ฐฉํ๋ฉด ์ด๋ฅผ ๊ฐ์ ธ์ ์์ ์ ๋น์ฆ๋์ค ๋ก์ง(์๋ฆผ ๋ฐ์ก, DB ์ ๋ฐ์ดํธ ๋ฑ)์ ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ์๋น์ค์ ๋๋ค.
- ๋น๋๊ธฐ ์ฒ๋ฆฌ
- ์ด๋ฒคํธ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ ์์คํ ์ ์๋ต์ฑ ํฅ์
- ์์ฒญ๊ณผ ์๋ต์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ ์ฑ๋ฅ ์ต์ ํ
"๊ฒฐ๊ณผ์ ์ผ๋ก ๋ธ๋ก์ปค๋ผ๋ ์ค๊ฐ ๋ค๋ฆฌ๊ฐ ์๊ธฐ ๋๋ฌธ์, ํ๋ก๋์์ ์ปจ์๋จธ๋ ์๋ก๊ฐ ๋๊ตฐ์ง ๋ชฐ๋ผ๋(๋์จํ ๊ฒฐํฉ) ํต์ ํ ์ ์์ผ๋ฉฐ, ๊ฐ์ ํธ๋ํฝ์ ๋ง์ถฐ ๋ ๋ฆฝ์ ์ผ๋ก ์ค์ผ์ผ ์์(ํ์ฅ)ํ ์ ์์ต๋๋ค."
์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ์ ๋จ์
- ๋ณต์ก์ฑ ์ฆ๊ฐ
- ์ด๋ฒคํธ ๊ธฐ๋ฐ ํต์ ์ผ๋ก ์ธํด ์์คํ ์ ๋ณต์ก์ฑ ์ฆ๊ฐ ๊ฐ๋ฅ
- ์ด๋ฒคํธ ํ๋ฆ๊ณผ ์ํ ๊ด๋ฆฌ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณ ํ์
- ์ฅ์ ์ ํ
- ์ด๋ฒคํธ ์คํจ ์ ๋ค๋ฅธ ์๋น์ค๋ก ์ฅ์ ๊ฐ ์ ํ๋ ์ ์์
- ์ด๋ฒคํธ ์ฌ์ฒ๋ฆฌ ๋ฐ ์ฅ์ ๋ณต๊ตฌ ๋ฉ์ปค๋์ฆ ๊ตฌํ ํ์
์์: ์จ๋ผ์ธ ์ผํ๋ชฐ
- ์ด๋ฒคํธ ์์ค: ์ฌ์ฉ์๊ฐ ์จ๋ผ์ธ ์ผํ๋ชฐ์์ ์ฃผ๋ฌธ์ ํฉ๋๋ค.
- ์ฃผ๋ฌธ ์๋น์ค๊ฐ '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํต๋๋ค.
- ์ด๋ฒคํธ ๋ฒ์ค: Kafka๋ RabbitMQ์ ๊ฐ์ ๋ฉ์์ง ๋ธ๋ก์ปค๊ฐ '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํฉ๋๋ค.
- ์ด๋ฒคํธ ํธ๋ค๋ฌ:
- ์ฌ๊ณ ์๋น์ค: '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์์ ํ์ฌ ์ฌ๊ณ ๋ฅผ ํ์ธํ๊ณ ์ ๋ฐ์ดํธํฉ๋๋ค.
- ๋ฐฐ์ก ์๋น์ค: '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์์ ํ์ฌ ๋ฐฐ์ก ์ค๋น๋ฅผ ์์ํฉ๋๋ค.
- ๊ฒฐ์ ์๋น์ค: '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์์ ํ์ฌ ๊ฒฐ์ ์ฒ๋ฆฌ๋ฅผ ํฉ๋๋ค.
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 ๋น๊ต
- ๊ณตํต์
- ํ์ฅ์ฑ: ๋ ๋ค ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์ ํ์ฅ์ฑ์ ์ง์ํฉ๋๋ค.
- ๊ด๋ฆฌ์ฑ: ์๋น์ค์ ๋ฐฐํฌ, ๊ด๋ฆฌ, ํ์ฅ ๋ฑ์ ์ฝ๊ฒ ํ ์ ์๋๋ก ๋์์ค๋๋ค.
- ๊ณ ๊ฐ์ฉ์ฑ: ์๋น์ค์ ๊ฐ์ฉ์ฑ์ ๋์ด๊ณ , ์ฅ์ ๋ฐ์ ์ ์๋ ๋ณต๊ตฌ๋ฅผ ์ง์ํฉ๋๋ค.
- ์ฐจ์ด์
- ์ด์ :
- Spring Cloud: ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ์ ํต์ , ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ, ๊ตฌ์ฑ ๊ด๋ฆฌ ๋ฑ ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ์ ๋ฌธ์ ํด๊ฒฐ์ ์ด์
- ์ฟ ๋ฒ๋คํฐ์ค: ์ปจํ ์ด๋ ๊ด๋ฆฌ, ๋ฐฐํฌ, ์ค์ผ์ผ๋ง ๋ฑ ์ธํ๋ผ ๋ ๋ฒจ์ ๋ฌธ์ ํด๊ฒฐ์ ์ด์
- ๊ตฌ์ฑ ์์:
- Spring Cloud: Eureka, Ribbon, Zuul, Config Server, Hystrix ๋ฑ ๋ค์ํ ๋ง์ดํฌ๋ก์๋น์ค ํจํด์ ์ง์ํ๋ ๊ตฌ์ฑ ์์
- MSA, ์ ํ๋ฆฌ์ผ์ด์ ๋จ์ ์ด์
- ์ฟ ๋ฒ๋คํฐ์ค: Pod, Deployment, Service, Ingress, ConfigMap, Secret ๋ฑ ์ปจํ
์ด๋ ์ค์ผ์คํธ๋ ์ด์
์ ํ์ํ ๊ตฌ์ฑ ์์
- ์ธํ๋ผ ์ด์
- Spring Cloud: Eureka, Ribbon, Zuul, Config Server, Hystrix ๋ฑ ๋ค์ํ ๋ง์ดํฌ๋ก์๋น์ค ํจํด์ ์ง์ํ๋ ๊ตฌ์ฑ ์์
- ๋ฐฐํฌ ๋ฐฉ์:
- 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 ์ ๋ฌธ์ฑ์ด ์๋ ํ์์๋ ์ด์์์ ์ด๋ ค์์ ๊ฒช์ ์ ์์
'Back-End > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ํ๋ก์ ํธ ๊ด๋ฆฌ ์ฌํ: ์ฑํฐ 2 (CI/CD) (0) | 2026.05.14 |
|---|---|
| ํ๋ก์ ํธ ๊ด๋ฆฌ ์ฌํ: ์ฑํฐ 1 (Docker) (0) | 2026.05.04 |
| Spring ์๋ จ: ์ฑํฐ 2 (0) | 2026.04.08 |
| Spring ์๋ จ: ์ฑํฐ 1 (0) | 2026.04.07 |
| Spring ์ ๋ฌธ (0) | 2026.04.06 |