๐ง ๋ค์ด๊ฐ๊ธฐ์
๋ชจ๋๋ฆฌํฑ ์ํคํ ์ฒ์์ ๋ฒ์ด๋ ์๋น์ค ๋จ์๋ก ์ชผ๊ฐ์ง ๋ง์ดํฌ๋ก ์๋น์ค ์ํคํ ์ฒ์ ๋ค์ด์๊ฒ ๋๋ฉด ๋ชจ๋๋ฆฌํฑ์ ๋จ์ ์ ๋ณด์ํ ์ ์๋ค. ๋ชจ๋๋ฆฌํฑ์ ๋ชจ๋ ๊ธฐ๋ฅ์ด ํ๋์ application์ ์ง์ค ๋์ด ์๊ธฐ ๋๋ฌธ์ ํ๋์ ๊ธฐ๋ฅ์์ ๋ฐ์ํ ์ฅ์ ๊ฐ ์๋น์ค ์ ์ฒด๋ก ์ด์ด์ง ์ฌ์ง๊ฐ ์๋ค. ๋ฐ๋ฉด MSA ํ๊ฒฝ์ ์๋น์ค๋ค ๊ฐ์ ๋ ๋ฆฝ์ฑ์ด ์ ์ง๋๊ธฐ์ ์ฅ์ ๋ฅผ ๊ฒฉ๋ฆฌ ์ํฌ ์ ์๋ค.
ํ์ง๋ง ๊ณผ์ฐ ์ ๋ง ์ฅ์ ๋ฅผ ๊ฒฉ๋ฆฌ์ํฌ ์ ์์๊น์ ๋ํ ๊ณ ๋ฏผ์ ํด๋ด์ผํ๋ค.
์์ ๊ฐ์ด ํด๋ผ์ด์ธํธ๊ฐ ์๋น์ค A๋ฅผ ํตํด์ ์์ฒญ๊ณผ ์๋ต์ ๋ฐ๊ณ ์๋น์ค A๋ client์ ์์ฒญ์ ์๋น์ค B๋ก ์ ๋ฌํด B๋ก๋ถํฐ ์จ ์๋ต์ client์๊ฒ ์ ํด์ค๋ค๊ณ ๊ฐ์ ํ์.
๊ทธ๋ ๋ค๋ฉด ์์์ ๋ง์ฝ ์๋น์ค B์ ์ฅ์ ๊ฐ ๋ฐ์ํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
์๋น์ค B์ ์ฅ์ ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์๊ธฐ๋ ๊ฒฝ์ฐ์ ์๋ ๋ ๊ฐ์ง์ด๋ค.
- ์๋น์ค B์์ A๋ก error ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๋ค.
- ์๋น์ค A๊ฐ ํ์์์๋ ๋ ๊น์ง ์๋น์ค B์ ์๋ต์ ๊ธฐ๋ค๋ฆฐ๋ค.
์ ์์ ๊ฒฝ์ฐ์๋ ์๋น์ค B๊ฐ ์ฃฝ์ด์๋ ๋์์ ๊ณ์ํด์ error ๋ฉ์์ง๋ฅผ ๋ฐ์ ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋, ํ์์ ๊ฒฝ์ฐ์๋ ์๋น์ค B๊ฐ ์ฃฝ์ด์๋๋์ ์๋น์ค A๋ ๊ณ์ํด์ ์ฐ๋ ๋๋ฅผ ์ฌ์ฉํด๊ฐ๋ฉฐ ์๋น์ค B์ ๋ํ ์๋ต์ ๋ฐ๊ธฐ ์ํด ์์ฒญ์ ๊ณ์ ๋ ๋ฆฌ๊ณ ๊ธฐ๋ค๋ฆฌ๊ณ ๋ฅผ ๋ฐ๋ณตํ ๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ ๋ฐ๋ณต์ ๊ฒฐ๊ตญ ์ฐ๋ ๋์ ๊ณ ๊ฐ๋ก ์ด์ด์ ธ ์๋น์ค A์๋ ์ฅ์ ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
์ด๊ฒ์ด ๋ฐ๋ก MSA ํ๊ฒฝ์์ ์ผ์ด๋ ์ ์๋ ์ฅ์ ์ ํ ํ์์ด๋ค.
์๋ง์กด ๋ถ์ฌ์ฅ์ธ ๋ฒ๋ ๋ณด๊ฒ์ค๊ฐ ๋งํ๋ฏ์ด, "์คํจํ์ง ์๋ ์์คํ ์ ๋ง๋๋ ๊ฒ์ ๋ถ๊ฐ๋ฅํ๋ค"
๊ทธ๋ฌ๋ฏ๋ก ์ฅ์ ์ ํ๋ฅผ ๋ง๊ธฐ ์ํด์๋ ๋น ๋ฅด๊ฒ ์คํจํ๊ณ ์๋ฌ๋ฉ์์ง๋ฅผ ๋์ ธ์ฃผ๋ ๊ฒ์ด ์ต์ ์ด๋ค. (Fail-Fast)
๋ฐ๋ผ์ ์ด๋ป๊ฒ ํ๋ฉด ๋น ๋ฅด๊ฒ ์คํจํ ์ ์๋์ง, ์ฅ์ ์ ํ๋ฅผ ๋ง๊ธฐ ์ํ ์ ๋ต์๋ ๋ฌด์์ด ์๋์ง ์ดํด๋ณผ ์์ ์ด๋ค.
๐ธ๋ถ์ฐ ํ๊ฒฝ ๋ชจ๋ํฐ๋ง
๋ถ์ฐ ํ๊ฒฝ์์๋ ์ฌ๋ฌ ์๋น์ค๊ฐ ํ๋ ฅํ์ฌ ํ๋์ ์์ฒญ์ ์ฒ๋ฆฌํ๋ค. ๋ฐ๋ผ์ ํน์ ์์ฒญ์ด ์ด๋ค ๊ฒฝ๋ก๋ฅผ ๊ฑฐ์ณค๊ณ , ์ด๋ ๋ถ๋ถ์์ ์ง์ฐ์ด ๋ฐ์ํ์ผ๋ฉฐ, ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์ด๋ ์๋น์ค์์ ๋ฌธ์ ๊ฐ ์๊ฒผ๋์ง๋ฅผ ์์๋ด๋ ๊ฒ์ด ์ค์ํ๋ค. ์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ ๊ธฐ์ ์ด ๋ถ์ฐ ํธ๋ ์ด์ฑ(distributed tracing) ์ด๋ค.
๋ถ์ฐ ํธ๋ ์ด์ฑ์ ํตํด ๊ฐ ์์ฒญ์ ํ๋ฆ์ ์๊ฐ์ ์ผ๋ก ํ์ธํ๊ณ , ๋ณ๋ชฉ ๊ตฌ๊ฐ์ ๋ถ์ํ๋ฉฐ, ์ฅ์ ๋ฐ์ ์ ๋น ๋ฅด๊ฒ ๋์ํ ์ ์๋ค. ๋ํ์ ์ธ ๋ถ์ฐ ํธ๋ ์ด์ฑ ๋๊ตฌ๋ก๋ Jaeger, Zipkin, OpenTelemetry ๋ฑ์ด ์์ผ๋ฉฐ, ์ด๋ฌํ ๋๊ตฌ๋ฅผ ํ์ฉํ๋ฉด ๋ง์ดํฌ๋ก์๋น์ค ํ๊ฒฝ์์๋ ์ฒด๊ณ์ ์ธ ๋ชจ๋ํฐ๋ง๊ณผ ๋๋ฒ๊น ์ด ๊ฐ๋ฅํ๋ค.
์ด ์ค ํธ์ํฐ์์ ์ฌ์ฉํ๋ ๋ถ์ฐ ํ๊ฒฝ ์์คํ ์ธ Zipkin์ ๋ํด ์ดํด๋ณด๊ณ ์ ํ๋ค.
๐นZipkin
Zipkin์ ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ์ ์์ฒญ ํ๋ฆ์ ์ถ์ ํ๋ ๋ถ์ฐ ํธ๋ ์ด์ฑ ์์คํ ์ด๋ค.
์๋๋ Zipkin์ ํ์ฉํ ๋ง์ดํฌ๋ก์๋น์ค ๋ชจ๋ํฐ๋ง ๊ตฌ์กฐ์ด๋ค
- Microservice A, B, C: ๊ฐ๊ฐ ๋ ๋ฆฝ์ ์ธ Spring Boot ๊ธฐ๋ฐ์ ๋ง์ดํฌ๋ก์๋น์ค
- Collector: ๊ฐ ๋ง์ดํฌ๋ก์๋น์ค์์ ๋ฐ์ํ ํธ๋ ์ด์ฑ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋ ์ญํ
- DB: ์์ง๋ ํธ๋ ์ด์ฑ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ์ฌ ๋ถ์ ๊ฐ๋ฅํ๋๋ก ํจ
- Query Service: ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ์ฌ ์ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํ๋ ์๋น์ค
- WebUI: ํธ๋ ์ด์ฑ ๋ฐ์ดํฐ๋ฅผ ์๊ฐ์ ์ผ๋ก ์ ๊ณตํ๋ ์ธํฐํ์ด์ค
๊ฐ ๋ง์ดํฌ๋ก์๋น์ค์์ ๋ฐ์ํ๋ ์์ฒญ ๋ฐ ์๋ต ์ ๋ณด๋ Trace ID์ Span ID๋ฅผ ํฌํจํ์ฌ Zipkin์ Collector๋ก ์ ์ก๋๋ค. Collector๋ ์ด ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ์(DB)์ ์ ์ฅํ๊ณ , ์ฌ์ฉ์๋ Query Service์ WebUI๋ฅผ ํตํด ํธ๋ ์ด์ฑ ์ ๋ณด๋ฅผ ์กฐํํ ์ ์๋ค. ์ด๋ฅผ ํตํด ์์ฒญ์ด ์ด๋ค ๋ง์ดํฌ๋ก์๋น์ค๋ฅผ ๊ฑฐ์ณค๋์ง, ์ด๋ ๋จ๊ณ์์ ์ง์ฐ์ด ๋ฐ์ํ๋์ง๋ฅผ ์๊ฐ์ ์ผ๋ก ๋ถ์ํ ์ ์๋ค.
๐นTracing
์ด ๊ทธ๋ฆผ์์๋ ํ๋์ ์์ฒญ์ด ์ฌ๋ฌ ๊ฐ์ ์๋น์ค(Service 1, 2, 3, 4)๋ฅผ ๊ฑฐ์ณ ๊ฐ๋ ๊ณผ์ ์์ Trace ID์ Span ID๊ฐ ์ด๋ป๊ฒ ๊ด๋ฆฌ๋๋์ง๋ฅผ ๋ณด์ฌ์ค๋ค.
- Trace ID : ์ด๋ฒคํธ chain์ id
- Span ID : ํ์ฌ ์ด๋ฒคํธ์ id
- Parent Span ID : ๋๋ฅผ ํธ์ถํ Span ID
๐กTrace ID์ Span ID๋ฅผ ํตํด์ call chain์ ์์๋ฅผ ์ ์ ์๋ค.
- Service 1์์ ํด๋ผ์ด์ธํธ ์์ฒญ์ ๋ฐ์ผ๋ฉด ์ฒ์์๋ Trace ID์ Span ID๊ฐ ์๋ ์ํ์ด๋ค.
- Service 1์ด ์์ฒญ์ ์ฒ๋ฆฌํ ํ, Trace ID = X์ Span ID = A๋ฅผ ์์ฑํ์ฌ ํ์ ์๋น์ค๋ก ์ ๋ฌํ๋ค.
- Service 2๋ Service 1์์ ์ ๋ฌ๋ Trace ID๋ฅผ ์ ์งํ๋ฉด์ ์๋ก์ด Span ID = B, C๋ฅผ ์์ฑํ์ฌ ํธ๋์ญ์ ์ ์ธ๋ถํํ๋ค.
- Service 3๊ณผ Service 4๋ Trace ID๋ฅผ ์ ์งํ ์ฑ ์๋ก์ด Span ID๋ฅผ ์ถ๊ฐํ๋ฉด์ ์์ฒญ์ ์ฒ๋ฆฌํ๋ค.
์ด๋ฌํ ๋ฐฉ์์ผ๋ก Zipkin์ ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ ํธ์ถ ๊ด๊ณ๋ฅผ ์๊ฐ์ ์ผ๋ก ๋ถ์ํ ์ ์๋๋ก ์ง์ํ๋ฉฐ ์ง์ฐ์ด ๋ฐ์ํ ๊ตฌ๊ฐ์ ์๋ณํ๊ณ ๋ฌธ์ ํด๊ฒฐ์ ์ฉ์ดํ๊ฒ ํ๋ค.
๐ธ์ํท ๋ธ๋ ์ด์ปค ํจํด (Circuit Breaker Pattern)
์ํท ๋ธ๋ ์ด์ปค ํจํด:
์ํท ๋ธ๋ ์ด์ปค ํจํด์ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ์ฅ์ ํ์ฐ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ ๋ณดํธ ๋ฉ์ปค๋์ฆ์ด๋ค.
์๋น์ค ๊ฐ ํธ์ถ์ด ์คํจํ๋ฉด ์ถ๊ฐ์ ์ธ ์์ฒญ์ ์ฐจ๋จํ์ฌ ์์คํ ์ด ๊ณผ๋ถํ ์ํ์ ๋น ์ง๋ ๊ฒ์ ๋ฐฉ์งํ๋ค.
์ํท ๋ธ๋ ์ด์ปค ํจํด์ด ๋ฐ๋ก ์ฅ์ ์ ํ๋ฅผ ๋ง๊ธฐ ์ํ ์ ๋ต ์ค ํ๋์ด๋ค.
์ํท ๋ธ๋ ์ด์ปค์ ๋์ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ๋ค.
- Closed (์ ์ ์ํ): ์๋น์ค ์์ฒญ์ด ์ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, ์คํจ์จ์ด ๋ฎ์ ๊ฒฝ์ฐ ์ํท ๋ธ๋ ์ด์ปค๋ ๋ซํ ์ํ๋ฅผ ์ ์งํ๋ค.
- Open (์ฐจ๋จ ์ํ): ์๋น์ค ์คํจ์จ์ด ์ค์ ๋ ์๊ณ๊ฐ์ ์ด๊ณผํ๋ฉด ์ํท์ด ์ด๋ฆฌ๊ณ , ์ถ๊ฐ์ ์ธ ์์ฒญ์ ์ฐจ๋จํ์ฌ ์์คํ ์ ๋ณดํธํ๋ค. => ์ฆ์ ์คํจ ์ฒ๋ฆฌ
- Half-Open (ํ ์คํธ ์ํ): open ์ํ ์ดํ ์ผ์ ์๊ฐ์ด ์ง๋ ํ, ์ํท์ด ๋ฐ์ฏค ์ด๋ ค ์ผ๋ถ ์์ฒญ์ ํ์ฉํ๋ค. ๋ง์ฝ ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ๋ค์ Closed ์ํ๋ก ์ ํํ๊ณ , ์คํจํ๋ฉด Open ์ํ๋ฅผ ์ ์งํ๋ค.
์์ ๊ทธ๋ฆผ์ฒ๋ผ ์ฅ์ ๊ฐ ๋ฐ์ํ ์๋น์ค์ ๋ํด์๋ ํ๋ก๋ฅผ ์ฐจ๋จํจ์ผ๋ก์จ ์ฅ์ ๊ฐ ๋ฐ์ํ ์๋น์ค์ ๋ํ ๋ถํ์ํ ํธ์ถ์ ๋ฐฉ์งํ์ฌ ์ฑ๋ฅ์ ์ ์งํ ์ ์๊ณ , ๊ณผ๋ถํ๋ก ์ธํด ์์คํ ์ ์ฒด๊ฐ ๋ค์ด๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ค.
๊ทธ๋ ๋ค๋ฉด ์ํท ๋ธ๋ ์ด์ปค๋ ์ด๋ ์ธ๋ถ ์ฅ์ ๋ฅผ ์ด๋ป๊ฒ ํ๋จํ ๊น?
์ฅ์ ํ๋จ ๊ธฐ์ค์ ๋ค์๊ณผ ๊ฐ๋ค.
- slow call : ๊ธฐ์ค ์๊ฐ๋ณด๋ค ์ค๋ ๊ฑธ๋ ธ์ ๋
- failure call : ์คํจ or ์ค๋ฅ๋ฅผ ์๋ต ๋ฐ์์ ๋
์ด๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ ๊ท์น์ ๋ง๋ค ์ ์๋ค.
closed ์กฐ๊ฑด
- ํน์ ์์ฒญ์ด n๋ฒ ์ฐ์ ์คํจํ์ ๊ฒฝ์ฐ
- ํน์ ์์ฒญ์ด n๋ฒ ์ฐ์ k์ด ์ด์ ์๊ฐ์ด๊ณผ ํ ๊ฒฝ์ฐ
half-open => open ์กฐ๊ฑด
- ์ฐจ๋จ ํ๋ ์์ฒญ์ด n๋ฒ ์ฐ์ ์ฑ๊ณตํ์ ๊ฒฝ์ฐ
๐น ์ํท ๋ธ๋ ์ด์ปค ํจํด ์ค์ต
GitHub - JHZLO/circuit-breaker-pattern: MSA ํ๊ฒฝ์์ ์ฅ์ ์ ํ๋ฅผ ๋ง๊ธฐ ์ํ ์ ๋ต (Circuit Breaker)
MSA ํ๊ฒฝ์์ ์ฅ์ ์ ํ๋ฅผ ๋ง๊ธฐ ์ํ ์ ๋ต (Circuit Breaker). Contribute to JHZLO/circuit-breaker-pattern development by creating an account on GitHub.
github.com
(์์ธํ ์ค์ต์ ๊นํ๋ธ์ ์ ๋ฆฌํด๋์๋ค)
์ํท ๋ธ๋ ์ด์ปค์ ๊ตฌํ ๋๊ตฌ๋ก๋ ๋ํ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ๋ค.
- Resilience4j: Spring Boot์์ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์ํท ๋ธ๋ ์ด์ปค ๋ผ์ด๋ธ๋ฌ๋ฆฌ.
- Hystrix: Netflix์์ ๊ฐ๋ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ํ์ฌ๋ ์ ์ง๋ณด์๊ฐ ์ค๋จ๋์์ง๋ง ์ฌ์ ํ ์ผ๋ถ ํ๋ก์ ํธ์์ ์ฌ์ฉ๋จ.
์ด ์ค Resilience4j๋ฅผ ํตํ ์ํท ๋ธ๋ ์ด์ปค ์์ ๋ฅผ ๋ค๋ค๋ณด๊ณ ์ ํ๋ค.
@Test
fun failureRateThreshold() {
val circuitBreaker = of(
"circuit-breaker", CircuitBreakerConfig.custom()
.minimumNumberOfCalls(10)
.failureRateThreshold(10f)
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
.build()
)
var successCount = 0
var failureCount = 0
val logicCnt = AtomicInteger()
for (i in 1..100) {
try {
val circuitLogic = decorateConsumer(circuitBreaker, Consumer<AtomicInteger> { someLogic(it) })
circuitLogic.accept(logicCnt)
successCount++
} catch (e: Exception) {
failureCount++
}
}
println("logic trigger ํ์ ${logicCnt.get()}")
println("์ฑ๊ณต-$successCount ์คํจ-$failureCount")
}
/**
* 30% ํ๋ฅ ๋ก error ๋ฐ์ ์ํด.
* */
fun someLogic(cnt: AtomicInteger) {
cnt.getAndIncrement()
if (ThreadLocalRandom.current().nextLong(100) < 30) {
throw RuntimeException("error")
}
}
- minimumNumberOfCalls(10): ์ต์ 10๋ฒ์ ์์ฒญ์ด ์์ด์ผ ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์
- failureRateThreshold(10f): ์คํจ์จ์ด 10% ์ด์์ด๋ฉด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์ด๋ฆผ (์ดํ ์์ฒญ์ ์ฐจ๋จ)
- slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED): ์๊ฐ ๊ธฐ๋ฐ์ผ๋ก ์คํจ์จ์ ๊ณ์ฐ
1๏ธโฃ ํ ์คํธ ๋ฐ๋ณต ์คํ
var successCount = 0
var failureCount = 0
val logicCnt = AtomicInteger()
for (i in 1..100) {
try {
val circuitLogic = decorateConsumer(circuitBreaker, Consumer<AtomicInteger> { someLogic(it) })
circuitLogic.accept(logicCnt)
successCount++
} catch (e: Exception) {
failureCount++
}
}
- 1๋ถํฐ 100๊น์ง ๋ฃจํ๋ฅผ ๋๋ฉด์ someLogic()์ ์คํ
- ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์ ์ฉ๋ ๋ก์ง์ ์คํ(decorateConsumer)
- ์ฑ๊ณตํ๋ฉด successCount++, ์คํจํ๋ฉด failureCount++
2๏ธโฃ ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์ ์ฉ๋ ๋ก์ง ์คํ
fun someLogic(cnt: AtomicInteger) {
cnt.getAndIncrement()
if (ThreadLocalRandom.current().nextLong(100) < 30) {
throw RuntimeException("error")
}
}
- someLogic()์ด ํธ์ถ๋ ๋๋ง๋ค cnt ๊ฐ์ ์ฆ๊ฐ
- 30% ํ๋ฅ ๋ก ์์ธ ๋ฐ์ (ThreadLocalRandom.current().nextLong(100) < 30)
3๏ธโฃ ์คํ ๊ฒฐ๊ณผ
logic trigger ํ์ 10
์ฑ๊ณต-8 ์คํจ-92
- someLogic()์ด ์คํ๋ ์ด ํ์(logicCnt) = 10
- ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์ฐจ๋จํ ํ ์ฑ๊ณต/์คํจ ํ์๋ฅผ ์ถ๋ ฅ
minimumNumberOfCalls 10์ ๋๋ฌ ํ์๋ง์ 10%๊น์ง์ ์ค๋ฅ๋ง ํ์ฉํ๊ธฐ ๋๋ฌธ์ ์ํท์ด ์ด๋ฆฌ๊ณ ๋๋จธ์ง๋ ์ ๋ถ ์คํจ๋ก ์ฒ๋ฆฌํ๋ค.
๐ธRate Limiting
Rate Limiting:
๋จ์ ์๊ฐ๋น ์ฒ๋ฆฌ๋์ ์ ํํ์ฌ ์์คํ ์ ์์ ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ด๋ค.
์ด๋ฅผ ํตํด ์๋น์ค๊ฐ ๊ณผ๋ถํ๋ก ์ธํด ์๋ตํ์ง ๋ชปํ๋ ์ํฉ์ ๋ฐฉ์งํ ์ ์๋ค
์๋น์ค A์ ์์ ์ ์ธ ์๋น์ค๋ฅผ ์ ๊ณตํ๊ธฐ ์ํ ํธ๋ํฝ์ ์ ํ์ด 1000์ด๋ผ๊ณ ๊ฐ์ ํด๋ณด์.
์ด๋, ๋ง์ฝ 1000๋ณด๋ค ์ด์์ธ ํธ๋ํฝ์ด ๋ค์ด์ค๊ฒ ๋๋ฉด ์๋น์ค๊ฐ ๋ถ์์ ํ๊ณ ๋ ๋ชฐ๋ฆด ๊ฒฝ์ฐ์๋ ์๋น์ค์ ์ฅ์ ๋ก ์ด์ด์ง ์ ์๋ค.
์์ ์ ์ธ ์๋น์ค๋ฅผ ์ ๊ณตํ ์ ์๋ ํธ๋ํฝ์ ์ ํ์ ๊ฑธ์ด ์ด๋ณด๋ค ๋์ ํธ๋ํฝ์ด ๋ค์ด์ค๋ ๊ฒฝ์ฐ์๋ ์๋น์ค๊ฐ ์ฅ์ ๋ก ์ด์ด์ง์ง ์๋๋ก ํ๋ ์ ๋ต์ด ๋ฐ๋ก Rate Limiting ๋ฐฉ์์ด๋ค.
์ผ๋ฐ์ ์ผ๋ก ์ธ๋ถ API(Slack, OpenAI API ๋ฑ)๋ฅผ ํธ์ถํ ๋ ๊ณผ๋ํ ์์ฒญ์ผ๋ก ์ฐจ๋จ๋์ง ์๋๋ก ์์ฒญ ํ์๋ฅผ ์ ํํ ๋ ์ฌ์ฉ๋๋ค.(Third-Party API ํธ์ถ)
์ด๋ฌํ ํธ๋ํฝ ์ ํ ๋ฐฉ์์๋ ์ผ๋ฐ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด ๋ ๊ฐ์ง๋ก ๋๋ ์ ์๋ค.
- Global Limit: ๋ชจ๋ ์์ฒญ์ ์ค์ ์ ์ฅ์(DB, Redis ๋ฑ)์์ ์นด์ดํธํ์ฌ ์ ํ
- Local Limit: ๊ฐ Pod(์๋น์ค ์ธ์คํด์ค)์์ ๋ ๋ฆฝ์ ์ผ๋ก ์์ฒญ ํ์๋ฅผ ์นด์ดํธํ์ฌ ์ ํ
๐น๋ํ์ ์ธ ์๊ณ ๋ฆฌ์ฆ
1๏ธโฃ Token Bucket
์ผ์ ๋์ ํ ํฐ์ ๋ฒํท์ ์ ์ฅํ๊ณ , ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค ํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฒญ์ ํ์ฉํ๋ค
ํ ํฐ์ด ์์ผ๋ฉด ์์ฒญ์ ์ฐจ๋จํ๊ณ , ์ฃผ๊ธฐ์ ์ผ๋ก ํ ํฐ์ ๋ค์ ์ฑ์์ฃผ๋ ๋ฐฉ์์ด๋ค.
2๏ธโฃ Leakage Bucket
๋ฌผ ์๋์ด์ ๊ตฌ๋ฉ์ ๋ซ์ด ์ผ์ ํ ์๋๋ก ๋ฌผ์ด ํ๋ฅด๋ ๊ฒ๊ณผ ๊ฐ์ ๊ฐ๋ ์ด๋ค.
๋จ์ ์๊ฐ๋น ๊ฐ์์ค๋ฌ์ด ์์ฒญ ์ฆ๊ฐ๋ฅผ ๋ฐฉ์งํ๊ณ , ์์ฒญ์ ์ผ์ ํ ์๋๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ์กฐ์ ํ๋ ์๊ณ ๋ฆฌ์ฆ์ด๋ค.
๐นRate Limit ์ค์ต
@Test
fun failureRateThreshold() {
val rateLimiter = RateLimiter.of(
"ratelimiter-breaker", RateLimiterConfig.custom()
.limitForPeriod(1) // ๋จ์ ์๊ฐ๋น ์ฒ๋ฆฌ๋
.timeoutDuration(Duration.ofMillis(10)) // throttle ๊ฑธ๋ ธ์๋ ๋๊ธฐ์๊ฐ
.limitRefreshPeriod(Duration.ofSeconds(10)) //token ์ฑ์ฐ๋ ์ฃผ๊ธฐ
.build()
)
var successCount = 0
var failureCount = 0
val logicCnt = AtomicInteger()
for (i in 1..100) {
try {
val limiterLogic = decorateConsumer(rateLimiter, Consumer<AtomicInteger> { someLogic(it) })
limiterLogic.accept(logicCnt)
successCount++
} catch (e: Exception) {
failureCount++
}
}
println("logic trigger ํ์ ${logicCnt.get()}")
println("์ฑ๊ณต-$successCount ์คํจ-$failureCount")
}
fun someLogic(cnt: AtomicInteger) {
cnt.getAndIncrement()
}
- limitForPeriod(1) : ๋จ์ ์๊ฐ(10์ด) ๋์ ์ต๋ 1๊ฐ์ ์์ฒญ๋ง ํ์ฉํ๋ค
- timeoutDuration(Duration.ofMillis(10)) : ๋ง์ฝ Rate Limiter๊ฐ ๋์ํ์ฌ ์์ฒญ์ด ์ฐจ๋จ๋์์ ๋, ์ต๋ 10ms ๋์ ๋๊ธฐ ํ ์คํจ ์ฒ๋ฆฌํ๋ค
- limitRefreshPeriod(Duration.ofSeconds(10)) : 10์ด๋ง๋ค ํ ํฐ์ด ๊ฐฑ์ ๋์ด ์๋ก์ด ์์ฒญ์ ๋ฐ์ ์ ์๋๋ก ํ๋ค
1๏ธโฃ ํ ์คํธ ๋ฐ๋ณต ์คํ
var successCount = 0
var failureCount = 0
val logicCnt = AtomicInteger()
for (i in 1..100) {
try {
val limiterLogic = decorateConsumer(rateLimiter, Consumer<AtomicInteger> { someLogic(it) })
limiterLogic.accept(logicCnt)
successCount++
} catch (e: Exception) {
failureCount++
}
}
- 1๋ถํฐ 100๊น์ง ๋ฃจํ๋ฅผ ๋๋ฉด์ someLogic()์ ์คํ
- limiter logic์ด ์ ์ฉ๋ ๋ก์ง์ ์คํ(decorateConsumer)
- ์ฑ๊ณตํ ์์ฒญ์ด๋ฉด successCount++, ์คํจํ๋ฉด failureCount++.
limitForPeriod(1)๋ก ์ธํด 10์ด์ ํ ๋ฒ๋ง ์์ฒญ์ด ์ฑ๊ณตํ๋ค.
2๏ธโฃRateLimit ๋ก์ง
fun someLogic(cnt: AtomicInteger) {
cnt.getAndIncrement()
}
์ค์ ๋น์ฆ๋์ค ๋ก์ง์ ์คํํ๋ ๋์ ๋จ์ํ ์คํ ํ์ ์นด์ดํธํ๋ค.
3๏ธโฃ ์คํ ๊ฒฐ๊ณผ
logic trigger ํ์ 1
์ฑ๊ณต-1 ์คํจ-99
10์ด์ 1๊ฐ๋ง ํ์ฉํ์๊ธฐ ๋๋ฌธ์ ํ ๋ฒ๋ง ์ฑ๊ณตํ๋ค.
๐ ์ถ์ฒ
<Kurt Farm ์คํฐ๋>