WebClient ColdStart ๋ฌธ์ œ
github.comยท1dยท
Discuss: DEV
๐Ÿ IndieWeb
Preview
Report Post

์ปฌ๋ฆฌ, CJ, ์ฟ ํŒก ๋“ฑ ๋ฌผ๋ฅ˜ ์ธํ”„๋ผ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ๋Š” ํšŒ์‚ฌ๋“ค์€ 3PL์ด๋ผ๋Š” ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. 3PL์ด๋ž€ ๋ฌผ๋ฅ˜ ์ธํ”„๋ผ๋ฅผ ๊ฐ–์ถ˜ ํšŒ์‚ฌ๊ฐ€ ๊ทธ๋ ‡์ง€ ๋ชปํ•œ ํŒ๋งค์ฒ˜๋กœ๋ถ€ํ„ฐ ๋ฐฐ์†ก ์—…๋ฌด๋ฅผ ์œ„ํƒ๋ฐ›์•„ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.

ํŒ๋งค์ฒ˜๋Š” ๋ฐฐ์†ก์ด ํ•„์š”ํ•œ ์ฃผ๋ฌธ ๋ชฉ๋ก์„ 3PL ์‹œ์Šคํ…œ์— ๋“ฑ๋กํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด ๊ณผ์ •์—์„œ ์ž…๋ ฅ๋œ ์ฃผ๋ฌธ์ด ์œ ํšจํ•œ ์ฃผ๋ฌธ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ ์—ฌ๋Ÿฌ ์‹œ์Šคํ…œ๊ณผ ์†Œํ†ตํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ์œ„ํ•œ ๋„๊ตฌ๋กœ๋Š” WebClient๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ์š”. ์•„๋ž˜ ์ง€ํ‘œ์—์„œ ๋ณด๋“ฏ์ด, ์™ธ๋ถ€ API ํ˜ธ์ถœ๊นŒ์ง€์˜ ์ง€์—ฐ ์‹œ๊ฐ„์ด ์›์ธ์„ ์•Œ ์ˆ˜ ์—†์ด ๊ธธ์–ด์ง€๋Š” ํ˜„์ƒ์ด '๊ฐ„ํ—์ '์œผ๋กœ ๋ฐœ๊ฒฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

AP...</div><div class=

์ปฌ๋ฆฌ, CJ, ์ฟ ํŒก ๋“ฑ ๋ฌผ๋ฅ˜ ์ธํ”„๋ผ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ๋Š” ํšŒ์‚ฌ๋“ค์€ 3PL์ด๋ผ๋Š” ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. 3PL์ด๋ž€ ๋ฌผ๋ฅ˜ ์ธํ”„๋ผ๋ฅผ ๊ฐ–์ถ˜ ํšŒ์‚ฌ๊ฐ€ ๊ทธ๋ ‡์ง€ ๋ชปํ•œ ํŒ๋งค์ฒ˜๋กœ๋ถ€ํ„ฐ ๋ฐฐ์†ก ์—…๋ฌด๋ฅผ ์œ„ํƒ๋ฐ›์•„ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.

ํŒ๋งค์ฒ˜๋Š” ๋ฐฐ์†ก์ด ํ•„์š”ํ•œ ์ฃผ๋ฌธ ๋ชฉ๋ก์„ 3PL ์‹œ์Šคํ…œ์— ๋“ฑ๋กํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด ๊ณผ์ •์—์„œ ์ž…๋ ฅ๋œ ์ฃผ๋ฌธ์ด ์œ ํšจํ•œ ์ฃผ๋ฌธ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ ์—ฌ๋Ÿฌ ์‹œ์Šคํ…œ๊ณผ ์†Œํ†ตํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ์œ„ํ•œ ๋„๊ตฌ๋กœ๋Š” WebClient๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ์š”. ์•„๋ž˜ ์ง€ํ‘œ์—์„œ ๋ณด๋“ฏ์ด, ์™ธ๋ถ€ API ํ˜ธ์ถœ๊นŒ์ง€์˜ ์ง€์—ฐ ์‹œ๊ฐ„์ด ์›์ธ์„ ์•Œ ์ˆ˜ ์—†์ด ๊ธธ์–ด์ง€๋Š” ํ˜„์ƒ์ด '๊ฐ„ํ—์ '์œผ๋กœ ๋ฐœ๊ฒฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

APM

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” ํ•ด๋‹น ํ˜„์ƒ์˜ ์›์ธ์„ ํŒŒ์•…ํ•˜๋ฉฐ ์•Œ๊ฒŒ ๋œ WebClient์˜ ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ์™€ Reactor Netty์˜ ์•„ํ‚คํ…์ฒ˜, ๊ทธ๋ฆฌ๊ณ  ํ•ด๊ฒฐ์ฑ…์„ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

์›์ธ ํŒŒ์•…์„ ์œ„ํ•œ ๊ฐ€์ •

์ง€์—ฐ์ด ๋ฐœ์ƒํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์š”์ฒญ์ด ์–ด๋”˜๊ฐ€์—์„œ ์ฆ‰์‹œ ์ฒ˜๋ฆฌ๋˜์ง€ ๋ชปํ•˜๊ณ  ๋Œ€๊ธฐํ•˜๊ณ  ์žˆ์—ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

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

WebClient ์‹คํ–‰ ๋ฉ”์ปค๋‹ˆ์ฆ˜

๋จผ์ € ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ์ฝ”๋“œ์˜ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

OrderRegistrationService.java

private final static int CONCURRENCY_CALL = 10;

List<RefineResult> results = Flux.fromIterable(registerDtos) .flatMap(dto -> Mono.defer(() -> omsService.refineAddress(dto.getPrimaryAddress(), dto.getSecondaryAddress()) .defaultIfEmpty(ApiResponse.failure(โ€œNO_RESPONSEโ€, false)) ).map(resp -> new RefineResult(dto, resp)), CONCURRENCY_CALL) .collectList() .block();

OmsService.java

@Override
public Mono<ApiResponse<RefineAddressDto>> refineAddress(String primaryAddress, String secondaryAddress) {
    RefineAddressInput request = RefineAddressInput.builder()
        .primaryAddress(primaryAddress)
        .secondaryAddress(secondaryAddress)
        .build();
<span class="k">return</span> <span class="n">omsClient</span><span class="o">.</span><span class="na">post</span><span class="o">()</span>
    <span class="o">.</span><span class="na">uri</span><span class="o">(</span><span class="s">"/refine-address"</span><span class="o">)</span>
    <span class="o">.</span><span class="na">bodyValue</span><span class="o">(</span><span class="n">request</span><span class="o">)</span>
    <span class="o">.</span><span class="na">retrieve</span><span class="o">()</span>
    <span class="o">.</span><span class="na">bodyToMono</span><span class="o">(</span><span class="nc">RefineAddressOutput</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
    <span class="o">.</span><span class="na">timeout</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofSeconds</span><span class="o">(</span><span class="mi">5</span><span class="o">))</span>
    <span class="o">.</span><span class="na">retryWhen</span><span class="o">(</span><span class="nc">RetryPolicy</span><span class="o">.</span><span class="na">fixedDelay</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">ofMillis</span><span class="o">(</span><span class="mi">100</span><span class="o">),</span> <span class="s">"[OMS ์ฃผ์†Œ์ •์ œ] ์žฌ์‹œ๋„ ์š”์ฒญ: "</span> <span class="o">+</span> <span class="n">request</span><span class="o">))</span>
    <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">output</span> <span class="o">-&gt;</span> <span class="n">output</span><span class="o">.</span><span class="na">isSuccess</span><span class="o">()</span> <span class="o">?</span> <span class="nc">ApiResponse</span><span class="o">.</span><span class="na">success</span><span class="o">(</span><span class="nc">RefineAddressDto</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">output</span><span class="o">))</span>
        <span class="o">:</span> <span class="nc">ApiResponse</span><span class="o">.&lt;</span><span class="nc">RefineAddressDto</span><span class="o">&gt;</span><span class="n">failure</span><span class="o">(</span><span class="s">"์‘๋‹ต ๊ฒฐ๊ณผ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Œ"</span><span class="o">,</span> <span class="kc">false</span><span class="o">))</span>
    <span class="o">.</span><span class="na">onErrorResume</span><span class="o">(</span><span class="n">ex</span> <span class="o">-&gt;</span> <span class="nc">ExternalErrorHandler</span><span class="o">.</span><span class="na">handleError</span><span class="o">(</span><span class="n">ex</span><span class="o">,</span> <span class="n">extractOmsErrorMessage</span><span class="o">(</span><span class="n">ex</span><span class="o">),</span> <span class="s">"OMS ์ฃผ์†Œ์ •์ œ"</span><span class="o">));</span>

}

Cold Sequence์™€ ๊ตฌ๋… ์‹œ์ 

์œ„ ์ฝ”๋“œ์—์„œ ์‹ค์ œ HTTP ์š”์ฒญ์ด ์–ธ์ œ ๋ฐœ์ƒํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๋ ค๋ฉด WebClient์˜ Cold Sequence ํŠน์„ฑ์„ ๋จผ์ € ์ดํ•ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

WebClient์˜ ๋ฆฌ์•กํ‹ฐ๋ธŒ ์ฒด์ธ์€ Cold Sequence๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๊ตฌ๋…์ด ๋ฐœ์ƒํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” ํŒŒ์ดํ”„๋ผ์ธ๋งŒ ์ •์˜๋  ๋ฟ, ์‹ค์ œ ์‹คํ–‰์€ ์ผ์–ด๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. HTTP ์š”์ฒญ ๋ฐœ์†ก ์‹œ์ ์€ subscribe()๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์ˆœ๊ฐ„์ด๋ฉฐ, ์ฝ”๋“œ์ƒ์˜ block()์ด ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

List<RefineResult> results = Flux.fromIterable(registerDtos)
    .flatMap(dto -> Mono.defer(() -> ...))
    .collectList()
    .block();  // โ† ๊ตฌ๋… ์‹œ์ž‘์ 

block()์˜ ๊ตฌ๋… ์‹ ํ˜ธ๋Š” ์—ญ๋ฐฉํ–ฅ(upstream)์œผ๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค

block() โ†’ collectList() โ†’ flatMap() โ†’ Mono.defer() โ†’ WebClient ์ฒด์ธ

flatMap(Function, int concurrency)์€ ์ธ์ž๋กœ ์ „๋‹ฌ๋œ concurrency ์ˆ˜ ๋งŒํผ์˜ Mono๋ฅผ ๋™์‹œ์— ๊ตฌ๋…ํ•ฉ๋‹ˆ๋‹ค. Mono.defer()๋Š” ๊ฐ ๊ตฌ๋… ์‹œ์ ๋งˆ๋‹ค ๋‚ด๋ถ€ ๋žŒ๋‹ค๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ƒˆ๋กœ์šด Mono๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ, ๊ฐ DTO๋งˆ๋‹ค ๋…๋ฆฝ์ ์ธ HTTP ์š”์ฒญ ํŒŒ์ดํ”„๋ผ์ธ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

// ๊ตฌ๋…๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด WebClient ์ฒด์ธ ์ƒ์„ฑ
Mono.defer(() -> omsService.refineAddress(...))

TaskQueue๋กœ์˜ ์ „๋‹ฌ

omsService.refineAddress(...)๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” Mono๊ฐ€ ๊ตฌ๋…๋˜๋ฉด ์š”์ฒญ ์„ค์ •์„ ๋นŒ๋“œํ•˜๊ณ , .retrieve() ์ดํ›„ ์ฒด์ธ์ด ๊ตฌ๋…๋˜๋ฉด์„œ ์“ฐ๊ธฐ ์š”์ฒญ์ด TaskQueue์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

omsClient.post()
    .uri("/refine-address")
    .bodyValue(request)
    .retrieve()
    .bodyToMono(RefineAddressOutput.class)

POST ์š”์ฒญ์ด NioEventLoop์˜ TaskQueue์— ์ €์žฅ๋˜๋ฉด, WebClient๋ฅผ ํ˜ธ์ถœํ•œ ์Šค๋ ˆ๋“œ์˜ ์—ญํ• ์€ ์—ฌ๊ธฐ์„œ ๋๋‚ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์ž‘์—…์€ EventLoop ์Šค๋ ˆ๋“œ๊ฐ€ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

Netty EventLoop ์Šค๋ ˆ๋“œ์˜ ๋™์ž‘ ์›๋ฆฌ

WebClient์˜ HTTP ์š”์ฒญ์ด TaskQueue์— ์ €์žฅ๋˜๋Š” ์ด์œ ๋Š” Netty์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„ ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋ชจ๋ธ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๋ชจ๋ธ์„ ์ดํ•ดํ•˜๋ ค๋ฉด ๋จผ์ € ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์˜ ๊ธฐ๋ณธ ๊ฐœ๋…์„ ์งš๊ณ  ๋„˜์–ด๊ฐ€์•ผ ํ•ฉ๋‹ˆ๋‹ค.

User Space์™€ Kernel Space

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

Web protocol

์†Œ์ผ“ ๋ฒ„ํผ์— ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฝ๊ณ  ์“ฐ๋А๋ƒ์— ๋”ฐ๋ผ Blocking I/O์™€ Non-blocking I/O๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค. ๋‘˜์˜ ์ฐจ์ด๋Š” ์Šค๋ ˆ๋“œ๊ฐ€ ์‹œ์Šคํ…œ ์ฝœ ํ›„ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š”์ง€ ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค.

  • Blocking I/O: ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ์Šค๋ ˆ๋“œ๊ฐ€ ๋Œ€๊ธฐ
  • Non-blocking I/O: ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ฆ‰์‹œ ๋ฐ˜ํ™˜, ์Šค๋ ˆ๋“œ๋Š” ๋‹ค๋ฅธ ์ž‘์—… ์ˆ˜ํ–‰ ๊ฐ€๋Šฅ

ํšจ์œจ์ ์ธ Non-blocking I/O๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ํŠน์ • ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•ด ๋†“๊ณ  ํ•ด๋‹น ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ๋กœ ์—ฌ๋Ÿฌ ์ฑ„๋„์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Multiplexing I/O์™€ Selector

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์†Œ์ผ“ ํ†ต์‹ ์—์„œ๋Š” ํ•˜๋‚˜์˜ Selector๊ฐ€ ์—ฌ๋Ÿฌ ์†Œ์ผ“ ์ฑ„๋„์˜ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๋ฉฐ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ Multiplexing I/O๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

Multiplexing I/O

Linux์—์„œ ์ด Multiplexing I/O๋Š” epoll ์‹œ์Šคํ…œ ์ฝœ๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. Java NIO์˜ Selector๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ด epoll์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Selector.select()์˜ ์‹ค์ œ ๋™์ž‘

OS ์ปค๋„์ด ๋Šฅ๋™์ ์œผ๋กœ I/O ์ด๋ฒคํŠธ๋ฅผ Selector์— ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Selector.select()๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์œ ์ € ๋ชจ๋“œ์—์„œ ์ปค๋„ ๋ชจ๋“œ๋กœ ์ „ํ™˜๋˜๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ epoll_wait() ์‹œ์Šคํ…œ ์ฝœ์ด ํ˜ธ์ถœ๋˜๋ฉด์„œ ํ˜ธ์ถœ ์Šค๋ ˆ๋“œ๋Š” ์ปค๋„์—์„œ ๋ธ”๋กœํ‚น ์ƒํƒœ๋กœ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.

How Selector work

epoll_wait์„ ํ˜ธ์ถœํ•˜๋ฉด, OS ์ปค๋„์€ ์ด์ „์— epoll_ctl๋กœ ๋“ฑ๋ก๋œ ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ(์†Œ์ผ“)๋“ค์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋‹ค๊ฐ€, ๋„คํŠธ์›Œํฌ ์นด๋“œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ•˜๊ฑฐ๋‚˜ ์†Œ์ผ“ ๋ฒ„ํผ์— ์“ฐ๊ธฐ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง€๋Š” ๋“ฑ์˜ I/O ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ด๋ฅผ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค. I/O๊ฐ€ ๋ฐœ์ƒํ•œ ์†Œ์ผ“์€ ์ปค๋„ ๋‚ด Ready Queue์— ์ถ”๊ฐ€๋˜๊ณ , epoll_wait()์ด ๋ฐ˜ํ™˜๋˜์–ด ๋Œ€๊ธฐ ์ค‘์ด๋˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๊นจ์–ด๋‚ฉ๋‹ˆ๋‹ค.

์ฆ‰, User Space๊ฐ€ ์ปค๋„์— ์š”์ฒญํ•˜๊ณ  ์‹œ์Šคํ…œ ์ฝœ๋กœ ์‘๋‹ต๋ฐ›๋Š” pull ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

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

NioEventLoop์˜ ๊ตฌ์กฐ

์ด์ œ Netty์˜ EventLoop๊ฐ€ Selector๋ฅผ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

EventLoop์˜ ๊ตฌํ˜„์ฒด์ธ NioEventLoop๋Š” 1 Thread + 1 Selector + 1 TaskQueue๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

NioEventLoop Structure

EventLoop ์Šค๋ ˆ๋“œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ CPU ์ฝ”์–ด ์ˆ˜๋งŒํผ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. Math.max(Runtime.getRuntime().availableProcessors(), 4)

๊ฐ EventLoop ์Šค๋ ˆ๋“œ๋Š” ์ „์šฉ NioEventLoop ์ธ์Šคํ„ด์Šค๋ฅผ ์‹คํ–‰ํ•˜๋ฉฐ, ๋‹จ์ผ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฌดํ•œ ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ๋‘ ๊ฐ€์ง€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  1. I/O ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ (๋„คํŠธ์›Œํฌ ์ฝ๊ธฐ/์“ฐ๊ธฐ)
  2. TaskQueue์˜ ์ž‘์—… ์ฒ˜๋ฆฌ (์‚ฌ์šฉ์ž๊ฐ€ ๋“ฑ๋กํ•œ Runnable)
// ๊ฐœ๋…์ ์ธ ์ฝ”๋“œ
while (true) {
    // 1. ๋„คํŠธ์›Œํฌ์—์„œ ๋ญ”๊ฐ€ ์ผ์–ด๋‚ฌ๋Š”์ง€ ํ™•์ธ
    ๋„คํŠธ์›Œํฌ_์ด๋ฒคํŠธ_ํ™•์ธ();
    // 2. ์ผ์–ด๋‚œ ์ผ๋“ค ์ฒ˜๋ฆฌ
    ์ด๋ฒคํŠธ๋“ค_์ฒ˜๋ฆฌ();
    // 3. ๋ˆ„๊ฐ€ ์‹œ์ผœ๋†“์€ ์ž‘์—…๋“ค ์ฒ˜๋ฆฌ
    ์ž‘์—…ํ์—์„œ_์ž‘์—…๊บผ๋‚ด์„œ_์‹คํ–‰();
}

์‹ค์ œ Netty ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด (Netty 4.2 ๊ธฐ์ค€)

// SingleThreadIoEventLoop.java:153-164
protected void run() {
    do {
        runIo();                    // โ† 1+2: I/O ํ™•์ธ ๋ฐ ์ฒ˜๋ฆฌ
        runAllTasks(maxTasksPerRun); // โ† 3: ์ž‘์—…ํ ์ฒ˜๋ฆฌ
    } while (!confirmShutdown());
}

runIo()๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ NioIoHandler.run()์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

// NioIoHandler.java:420-485
public int run(IoExecutionContext runner) {
    // 1๋‹จ๊ณ„: select - I/O ์ด๋ฒคํŠธ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
    select(runner, wakenUp.getAndSet(false));
<span class="c1">// 2๋‹จ๊ณ„: ์žˆ์œผ๋ฉด ์ฒ˜๋ฆฌ</span>
<span class="k">return</span> <span class="nf">processSelectedKeys</span><span class="o">();</span>

}

์ด์ œ ๊ฐ ๋‹จ๊ณ„๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. select() - I/O ์ด๋ฒคํŠธ ๊ฐ์ง€

EventLoop๋Š” I/O ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์™€ TaskQueue์— ์Œ“์ธ ์ž‘์—… ์ฒ˜๋ฆฌ, ๋‘ ๊ฐ€์ง€ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ Selector.select()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•  I/O ์ด๋ฒคํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

select() ๋ฉ”์„œ๋“œ๋Š” TaskQueue์— ์ž‘์—…์ด ์กด์žฌํ•˜๋Š”์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ ์ ˆํ•œ select ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

// NioIoHandler.java
private void select(IoExecutionContext runner, boolean oldWakenUp) {
    Selector selector = this.selector;
<span class="k">for</span> <span class="o">(;;)</span> <span class="o">{</span>
    <span class="c1">// ํƒœ์Šคํฌ๊ฐ€ ์žˆ์œผ๋ฉด ์ฆ‰์‹œ ํ™•์ธํ•˜๊ณ  ๋„˜์–ด๊ฐ</span>
    <span class="k">if</span> <span class="o">(!</span><span class="n">runner</span><span class="o">.</span><span class="na">canBlock</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">wakenUp</span><span class="o">.</span><span class="na">compareAndSet</span><span class="o">(</span><span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">))</span> <span class="o">{</span>
        <span class="n">selector</span><span class="o">.</span><span class="na">selectNow</span><span class="o">();</span>   <span class="c1">// ์ž‘์—… ์žˆ์œผ๋ฉด ๋ฐ”๋กœ ํ™•์ธ</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// ํƒœ์Šคํฌ๊ฐ€ ์—†์œผ๋ฉด ์ด๋ฒคํŠธ ์˜ฌ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ</span>
    <span class="kt">int</span> <span class="n">selectedKeys</span> <span class="o">=</span> <span class="n">selector</span><span class="o">.</span><span class="na">select</span><span class="o">(</span><span class="n">timeoutMillis</span><span class="o">);</span>
<span class="o">}</span>

}

@Override
public boolean canBlock() {
   assert inEventLoop();
   return !hasTasks() && !hasScheduledTasks();
}

TaskQueue๊ฐ€ ๋น„์—ˆ์„ ๋•Œ

TaskQueue๊ฐ€ ๋น„์–ด์žˆ์œผ๋ฉด Netty๋Š” select(timeout)์„ ํ˜ธ์ถœํ•˜์—ฌ ์ปค๋„๋กœ ๋ถ€ํ„ฐ I/O ์ด๋ฒคํŠธ ์‹ ํ˜ธ๋ฅผ ๋ฐ›๊ฑฐ๋‚˜ ํƒ€์ž„์•„์›ƒ์ด ๋  ๋•Œ๊นŒ์ง€ ๋ธ”๋กœํ‚น ์ƒํƒœ๋กœ ๋Œ€๊ธฐํ•˜์—ฌ CPU ์‚ฌ์šฉ์„ ์ค„์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ ๋Œ€๊ธฐ ์ค‘ TaskQueue์— ์ƒˆ task๊ฐ€ ๋“ค์–ด์˜ค๋ฉด, wakeup ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด select()์˜ ๋ธ”๋กœํ‚น์„ ๊นจ์›Œ์„œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜์‹œํ‚ค๊ณ , ๋ฃจํ”„๋ฅผ ๋Œ๋ฉฐ TaskQueue๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

TaskQueue๊ฐ€ ์žˆ์„ ๋•Œ

TaskQueue์— ์ž‘์—…์ด ์žˆ์œผ๋ฉด selectNow()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ I/O ์ด๋ฒคํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•˜๊ณ , ๊ณง๋ฐ”๋กœ ํ…Œ์Šคํฌ ์‹คํ–‰์œผ๋กœ ๋„˜์–ด๊ฐ€ ์ž‘์—… ์ง€์—ฐ์„ ์ค„์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ TaskQueue์— ์ž‘์—…์ด ์žˆ๋Š” ์ƒํ™ฉ์—์„œ select(timeout)์„ ํ˜ธ์ถœํ•ด ๋ธ”๋กœํ‚น๋˜๋ฉด, EventLoop ์Šค๋ ˆ๋“œ๊ฐ€ ์ž ๋“ค์–ด ํ…Œ์Šคํฌ ์ฒ˜๋ฆฌ๊ฐ€ ์ง€์—ฐ๋˜๊ณ  ์‘๋‹ต์„ฑ์ด ๋–จ์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ selectNow()๋งŒ ๊ณ„์† ์ˆ˜ํ–‰ํ•˜๋ฉด ์ค€๋น„๋œ I/O ์ด๋ฒคํŠธ๊ฐ€ ์—†์–ด๋„ ๊ณ„์† ํ™•์ธํ•˜๋ฏ€๋กœ ๋ถˆํ•„์š”ํ•œ ๋ฐ˜๋ณต์œผ๋กœ busy-wait(CPU ๋‚ญ๋น„)์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฆ‰, Netty์˜ select()๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋ฐฉ์‹์„ ์„ ํƒํ•˜์—ฌ CPU๋ฅผ ๋‚ญ๋น„ํ•˜์ง€ ์•Š๊ณ  ํšจ์œจ์ ์œผ๋กœ I/O ์ด๋ฒคํŠธ๋ฅผ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.

select() ํ˜ธ์ถœ ์ดํ›„์˜ ๋‚ด๋ถ€ ๋™์ž‘

์•ž์„œ Netty๊ฐ€ ์ƒํ™ฉ์— ๋”ฐ๋ผ select(timeout) ๋˜๋Š” selectNow()๋ฅผ ์„ ํƒ์ ์œผ๋กœ ํ˜ธ์ถœํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ด ํ˜ธ์ถœ์ด ์‹ค์ œ๋กœ ์–ด๋–ค ๊ณผ์ •์„ ๊ฑฐ์ณ ์ปค๋„๊นŒ์ง€ ๋„๋‹ฌํ•˜๊ณ , ๋‹ค์‹œ ๋Œ์•„์˜ค๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Selector.select()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด JDK ๋‚ด๋ถ€์˜ SelectorImpl ํด๋ž˜์Šค๊ฐ€ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

// SelectorImpl.java
@Override
    public final int select(long timeout) throws IOException {
        return lockAndDoSelect(null, (timeout == 0) ? -1 : timeout);
    }

lockAndDoSelect()๋Š” ๋™๊ธฐํ™”๋ฅผ ์ˆ˜ํ–‰ํ•œ ๋’ค Multiplexing I/O๋ฅผ ๋‹ด๋‹นํ•˜๋Š” doSelect()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

// SelectorImpl.java
private int lockAndDoSelect(Consumer<SelectionKey> action, long timeout)
      throws IOException
{
    synchronized (this) {
        ensureOpen();
        if (inSelect)
            throw new IllegalStateException("select in progress");
        inSelect = true;
        try {
            synchronized (publicSelectedKeys) {
                return doSelect(action, timeout);
            }
        } finally {
            inSelect = false;
        }
    }
}

์—ฌ๊ธฐ์„œ doSelect()๋Š” ์ถ”์ƒ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. ์šด์˜์ฒด์ œ๋งˆ๋‹ค ํšจ์œจ์ ์ธ Multiplexing I/O ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, JDK๋Š” ํ”Œ๋žซํผ๋ณ„๋กœ ๋‹ค๋ฅธ ๊ตฌํ˜„์ฒด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

OS ๊ตฌํ˜„ ํด๋ž˜์Šค ์‹œ์Šคํ…œ ์ฝœ
Linux EPollSelectorImpl epoll_wait()
macOS KQueueSelectorImpl kevent()
Windows WindowsSelectorImpl IOCP

์ด ๊ธ€์—์„œ๋Š” ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” Linux์˜ epoll ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์„ ์ค‘์‹ฌ์œผ๋กœ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. (JDK 21 ๊ธฐ์ค€)

EPollSelectorImpl ์ธ์Šคํ„ด์Šค๋Š” ์–ธ์ œ ์ƒ์„ฑ๋˜๋Š”๊ฐ€?

EPollSelectorImpl ์ธ์Šคํ„ด์Šค๋Š” Selector.open() ํ˜ธ์ถœ ์‹œ์ ์— ์ดˆ๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค.

  1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ new NioEventLoopGroup(n) ํ˜ธ์ถœ
  2. ๋‚ด๋ถ€์ ์œผ๋กœ n๊ฐœ์˜ NioIoHandler ์ƒ์„ฑ
  3. ๊ฐ NioIoHandler ์ƒ์„ฑ์ž์—์„œ provider.openSelector() ํ˜ธ์ถœ
  4. Linux ํ™˜๊ฒฝ์—์„œ๋Š” EPollSelectorImpl ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

EPollSelectorImpl ์ƒ์„ฑ ์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ดˆ๊ธฐํ™”๊ฐ€ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

EPollSelectorImpl(SelectorProvider sp) throws IOException {
   super(sp);

// 1. epoll ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ (epoll_create ์‹œ์Šคํ…œ ์ฝœ) this.epfd = EPoll.create();

// 2. epoll_wait ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋„ค์ดํ‹ฐ๋ธŒ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น this.pollArrayAddress = EPoll.allocatePollArray(NUM_EPOLLEVENTS);

// 3. wakeup์šฉ EventFD ์ƒ์„ฑ this.eventfd = new EventFD(); IOUtil.configureBlocking(IOUtil.newFD(eventfd.efd()), false);

// 4. EventFD๋ฅผ epoll์— EPOLLIN์œผ๋กœ ๋“ฑ๋ก EPoll.ctl(epfd, EPOLL_CTL_ADD, eventfd.efd(), EPOLLIN); }

์ฆ‰, ํ•˜๋‚˜์˜ EventLoop๋งˆ๋‹ค ํ•˜๋‚˜์˜ epoll ์ธ์Šคํ„ด์Šค๊ฐ€ ๋งคํ•‘๋ฉ๋‹ˆ๋‹ค.

epoll์˜ ์„ธ ๊ฐ€์ง€ ์‹œ์Šคํ…œ ์ฝœ

epoll์€ ์„ธ ๊ฐ€์ง€ ์‹œ์Šคํ…œ ์ฝœ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค

  • epoll_create: epoll ์ธ์Šคํ„ด์Šค(์ฑ„๋„ ๊ฐ์‹œ ์ €์žฅ์†Œ) ์ƒ์„ฑ
  • epoll_ctl: ๊ฐ์‹œํ•  FD ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ
  • epoll_wait: ์ด๋ฒคํŠธ(read/write)๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๊ณ , ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ FD ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜

JDK์˜ EPoll.wait()๋Š” JNI๋ฅผ ํ†ตํ•ด ์ปค๋„์˜ epoll_wait() ์‹œ์Šคํ…œ ์ฝœ์„ ์ง์ ‘ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

epoll_wait()๋Š” ๋ฏธ๋ฆฌ ํ• ๋‹น๋œ ๋„ค์ดํ‹ฐ๋ธŒ ๋ฉ”๋ชจ๋ฆฌ์˜ epoll_event ๊ตฌ์กฐ์ฒด ๋ฐฐ์—ด์— ์ค€๋น„๋œ ์ด๋ฒคํŠธ ์ •๋ณด๋ฅผ ์ฑ„์šฐ๊ณ , ์ค€๋น„๋œ ์ด๋ฒคํŠธ ๊ฐœ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฐ์—ด์—๋Š” ๊ฐ FD์™€ ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ ํƒ€์ž…(EPOLLIN/EPOLLOUT/EPOLLERR ๋“ฑ)์ด ๋‹ด๊ฒจ ์žˆ์Šต๋‹ˆ๋‹ค.

EPollSelectorImpl.doSelect()์—์„œ ์ด ๋ฉ”์„œ๋“œ๋“ค์ด ์‹ค์ œ๋กœ ํ˜ธ์ถœ๋˜๋Š” ํ๋ฆ„์„ ๋ณด๋ฉด:

// EpollSelectorImpl.java
@Override
protected int doSelect(Consumer<SelectionKey> action, long timeout) throws IOException {
    int to = (int) Math.min(timeout, Integer.MAX_VALUE);
<span class="kt">int</span> <span class="n">numEntries</span><span class="o">;</span>
<span class="n">processUpdateQueue</span><span class="o">();</span>      <span class="c1">// epoll_ctl๋กœ ๊ด€์‹ฌ ์ด๋ฒคํŠธ ๋ณ€๊ฒฝ ๋ฐ˜์˜</span>
<span class="n">processDeregisterQueue</span><span class="o">();</span>

<span class="k">try</span> <span class="o">{</span>
    <span class="n">begin</span><span class="o">(</span><span class="n">blocking</span><span class="o">);</span>
    <span class="c1">// epoll_wait ์‹œ์Šคํ…œ ์ฝœ ํ˜ธ์ถœ</span>
    <span class="n">numEntries</span> <span class="o">=</span> <span class="nc">EPoll</span><span class="o">.</span><span class="na">wait</span><span class="o">(</span><span class="n">epfd</span><span class="o">,</span> <span class="n">pollArrayAddress</span><span class="o">,</span> <span class="no">NUM_EPOLLEVENTS</span><span class="o">,</span> <span class="n">to</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
    <span class="n">end</span><span class="o">(</span><span class="n">blocking</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// ๋ฐ˜ํ™˜๋œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ</span>
<span class="k">return</span> <span class="nf">processEvents</span><span class="o">(</span><span class="n">numEntries</span><span class="o">,</span> <span class="n">action</span><span class="o">);</span>

}

  1. processSelectedKeys() - I/O ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

EPoll.wait()๊ฐ€ ์ด๋ฒคํŠธ ๊ฐœ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด, EPollSelectorImpl.processEvents()๊ฐ€ ํ•ด๋‹น ๊ฐœ์ˆ˜๋งŒํผ ์ด๋ฒคํŠธ ๋ฐฐ์—ด์„ ์ˆœํšŒํ•˜๋ฉฐ ๊ฐ FD์— ์—ฐ๊ฒฐ๋œ SelectionKey๋ฅผ ์ฐพ์•„ selectedKeys์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ Netty์˜ NioIoHandler.processSelectedKeys()๊ฐ€ seletedKeys๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ๊ฐ ์ฑ„๋„์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

 // NioIoHandler.java
  private int processSelectedKeysOptimized() {
      int handled = 0;
      for (int i = 0; i < selectedKeys.size; ++i) {
          SelectionKey k = selectedKeys.keys[i];
          selectedKeys.keys[i] = null;  // GC๋ฅผ ์œ„ํ•ด null ์ฒ˜๋ฆฌ
      <span class="n">processSelectedKey</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>  <span class="c1">// ๊ฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ</span>
      <span class="o">++</span><span class="n">handled</span><span class="o">;</span>
  <span class="o">}</span>
  <span class="k">return</span> <span class="n">handled</span><span class="o">;</span>

}

  private void processSelectedKey(SelectionKey k) {
      final DefaultNioRegistration registration = (DefaultNioRegistration) k.attachment();
  <span class="c1">// ์ค€๋น„๋œ ์ด๋ฒคํŠธ๋ฅผ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ</span>
  <span class="c1">// OP_READ  โ†’ ๋ฐ์ดํ„ฐ ์ˆ˜์‹ </span>
  <span class="c1">// OP_WRITE โ†’ ๋ฐ์ดํ„ฐ ์†ก์‹ </span>
  <span class="c1">// OP_CONNECT โ†’ ์—ฐ๊ฒฐ ์™„๋ฃŒ</span>
  <span class="c1">// OP_ACCEPT โ†’ ์ƒˆ ์—ฐ๊ฒฐ ์š”์ฒญ</span>
  <span class="n">registration</span><span class="o">.</span><span class="na">handle</span><span class="o">(</span><span class="n">k</span><span class="o">.</span><span class="na">readyOps</span><span class="o">());</span>

}

registration.handle()์€ ๋‚ด๋ถ€์ ์œผ๋กœ AbstractNioChannel.AbstractNioUnsafe.handle()์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์ด๋ฒคํŠธ ํƒ€์ž…์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

// AbstractNioChannel.java:420-450
@Override
public void handle(IoRegistration registration, IoEvent event) {
    NioIoOps nioReadyOps = ((NioIoEvent) event).ops();
<span class="c1">// 1. OP_CONNECT: ์—ฐ๊ฒฐ ์™„๋ฃŒ ์ฒ˜๋ฆฌ (๊ฐ€์žฅ ๋จผ์ € ์ฒ˜๋ฆฌ)</span>
<span class="k">if</span> <span class="o">(</span><span class="n">nioReadyOps</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="nc">NioIoOps</span><span class="o">.</span><span class="na">CONNECT</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">removeAndSubmit</span><span class="o">(</span><span class="nc">NioIoOps</span><span class="o">.</span><span class="na">CONNECT</span><span class="o">);</span>
    <span class="n">unsafe</span><span class="o">().</span><span class="na">finishConnect</span><span class="o">();</span>
<span class="o">}</span>

<span class="c1">// 2. OP_WRITE: ์“ฐ๊ธฐ ๊ฐ€๋Šฅ ์ƒํƒœ - ๋Œ€๊ธฐ ์ค‘์ธ ๋ฒ„ํผ ์ „์†ก</span>
<span class="k">if</span> <span class="o">(</span><span class="n">nioReadyOps</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="nc">NioIoOps</span><span class="o">.</span><span class="na">WRITE</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">forceFlush</span><span class="o">();</span>
<span class="o">}</span>

<span class="c1">// 3. OP_READ / OP_ACCEPT: ๋ฐ์ดํ„ฐ ์ˆ˜์‹  ๋˜๋Š” ์ƒˆ ์—ฐ๊ฒฐ ์ˆ˜๋ฝ</span>
<span class="k">if</span> <span class="o">(</span><span class="n">nioReadyOps</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="nc">NioIoOps</span><span class="o">.</span><span class="na">READ_AND_ACCEPT</span><span class="o">)</span> <span class="o">||</span> <span class="n">nioReadyOps</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="nc">NioIoOps</span><span class="o">.</span><span class="na">NONE</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">read</span><span class="o">();</span>
<span class="o">}</span>

}

  1. runAllTasks() - Non-I/O Task ์ฒ˜๋ฆฌ

I/O ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚˜๋ฉด runAllTasks()๊ฐ€ ํ˜ธ์ถœ๋˜์–ด TaskQueue์— ์Œ“์ธ ์ž‘์—…๋“ค์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. WebClient์˜ HTTP ์š”์ฒญ๋„ ๋ฐ”๋กœ ์ด ๋‹จ๊ณ„์—์„œ ์‹ค์ œ๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค.

// SingleThreadEventExecutor.java
protected boolean runAllTasks(long timeoutNanos) {
     // ์Šค์ผ€์ค„ ํ์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ํƒœ์Šคํฌ๋ฅผ TaskQueue๋กœ ์ด๋™
     fetchFromScheduledTaskQueue();
     Runnable task = pollTask();
 <span class="kd">final</span> <span class="kt">long</span> <span class="n">deadline</span> <span class="o">=</span> <span class="n">timeoutNanos</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">getCurrentTimeNanos</span><span class="o">()</span> <span class="o">+</span> <span class="n">timeoutNanos</span> <span class="o">:</span> <span class="mi">0</span><span class="o">;</span>
 <span class="kt">long</span> <span class="n">runTasks</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

 <span class="k">for</span> <span class="o">(;;)</span> <span class="o">{</span>
     <span class="n">safeExecute</span><span class="o">(</span><span class="n">task</span><span class="o">);</span> <span class="c1">// ํ…Œ์Šคํฌ ์‹คํ–‰</span>

     <span class="n">runTasks</span> <span class="o">++;</span>

        <span class="c1">// Check timeout every 64 tasks because nanoTime() is relatively expensive.</span>
        <span class="c1">// XXX: Hard-coded value - will make it configurable if it is really a problem.</span>
        <span class="k">if</span> <span class="o">((</span><span class="n">runTasks</span> <span class="o">&amp;</span> <span class="mh">0x3F</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">lastExecutionTime</span> <span class="o">=</span> <span class="n">getCurrentTimeNanos</span><span class="o">();</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">lastExecutionTime</span> <span class="o">&gt;=</span> <span class="n">deadline</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span>

        <span class="n">task</span> <span class="o">=</span> <span class="n">pollTask</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">task</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">lastExecutionTime</span> <span class="o">=</span> <span class="n">getCurrentTimeNanos</span><span class="o">();</span>
            <span class="k">break</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="n">afterRunningAllTasks</span><span class="o">();</span>
    <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>

์ „์ฒด ํ๋ฆ„ ์š”์•ฝ

์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธ ๋‚ด์šฉ์„ ํ•˜๋‚˜์˜ ๋‹ค์ด์–ด๊ทธ๋žจ์œผ๋กœ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

overall

๋ณ‘๋ชฉ์ด ๋ฐœ์ƒํ•œ ์ธ์Šคํ„ด์Šค์˜ vCPU ์ˆ˜๋Š” 2๊ฐœ์˜€์Šต๋‹ˆ๋‹ค. Netty์˜ EventLoop ์Šค๋ ˆ๋“œ ์ˆ˜๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Math.max(availableProcessors(), 4)๋กœ ๊ฒฐ์ •๋˜๋ฏ€๋กœ, ์ด ํ™˜๊ฒฝ์—์„œ๋Š” EventLoop ์Šค๋ ˆ๋“œ๊ฐ€ ์ด 4๊ฐœ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธ ๋ฐ”์™€ ๊ฐ™์ด Netty์˜ EventLoop๋Š” Multiplexing I/O ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ ์€ ์ˆ˜์˜ ์Šค๋ ˆ๋“œ๋กœ๋„ ๋งŽ์€ ๋™์‹œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. epoll_wait()์€ ์ˆ˜์ฒœ ๊ฐœ์˜ ์ฑ„๋„์ด ๋“ฑ๋ก๋˜์–ด ์žˆ์–ด๋„ ์‹ค์ œ๋กœ I/O ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ์ฑ„๋„๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ๋™์‹œ ์š”์ฒญ ์ˆ˜๊ฐ€ EventLoop ์Šค๋ ˆ๋“œ ์ˆ˜๋ณด๋‹ค ๋งŽ๋‹ค๊ณ  ํ•ด์„œ ๋ณ‘๋ชฉ์ด ๋ฐœ์ƒํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ "๋™์‹œ ์š”์ฒญ 10๊ฐœ > EventLoop ์Šค๋ ˆ๋“œ 4๊ฐœ"๋Š” ๋ณ‘๋ชฉ์˜ ์›์ธ์ด ์•„๋‹™๋‹ˆ๋‹ค.

๋˜ ๋‹ค๋ฅธ ๊ฐ€์„ค: Parallel Scheduler ์Šค๋ ˆ๋“œ ๊ฒฝํ•ฉ

๊ทธ๋ ‡๋‹ค๋ฉด ๋ฌด์—‡์ด ๋ฌธ์ œ์˜€์„๊นŒ์š”? ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

return omsClient.post()
   .uri("/refine-address")
   .bodyValue(request)
   .retrieve()
   .bodyToMono(RefineAddressOutput.class)
   .timeout(Duration.ofSeconds(5)) // โ† ์—ฌ๊ธฐ
   .retryWhen(

Similar Posts

Loading similar posts...