Backend/JAVA

Streaming์ด๋ž€?

dddzr 2025. 12. 18. 07:13

๐Ÿ“Œ 1. Streaming์ด๋ž€?

Streaming์€ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋ชจ๋‘ ๋ณด๋‚ด๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์กฐ๊ฐ(chunk) ๋‹จ์œ„๋กœ ๋‚˜๋ˆ ์„œ ์ˆœ์ฐจ์ ์œผ๋กœ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

๐Ÿ“– ์˜ˆ๋ฅผ ๋“ค์–ด:

  • ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ์ „์†ก ์‹œ ์ „์ฒด๋ฅผ ๋‹ค ๋“ค๊ณ  ์žˆ์ง€ ์•Š๊ณ  ์กฐ๊ธˆ์”ฉ ์ฝ๊ณ  ๋ณด๋‚ด๋Š” ๋ฐฉ์‹
  • ์ฑ„ํŒ…, ๋กœ๊ทธ, ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ๋“ฑ ๊ณ„์† ์ด์–ด์ง€๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ
  • ๋‚˜๋Š” ai API ์‘๋‹ต์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋นจ๋ฆฌ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ๋‹ค!!

๐Ÿ“Œ 2. Reactive Stream ๊ธฐ๋ณธ ๊ฐœ๋…

Reactive Stream์€ ๋น„๋™๊ธฐ + ๋…ผ๋ธ”๋กœํ‚น ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ํ๋ฆ„(์ŠคํŠธ๋ฆผ)์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ์ˆ ์ด๋‹ค.

Java์—์„œ๋Š” ์ด๋ฅผ Flux, Mono ๊ฐ™์€ ํด๋ž˜์Šค๋กœ ํ‘œํ˜„ํ•˜๋ฉฐ, Spring WebFlux๋Š” ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘๋™ํ•˜๋Š” ๋น„๋™๊ธฐ ์›น ํ”„๋ ˆ์ž„์›Œํฌ๋‹ค.


๐Ÿ“Œ 3. Mono vs Flux

Reactive Streams์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์‘๋‹ต ํ˜•ํƒœ(๋ฆฌํ„ด ํƒ€์ž…)์ด๋‹ค.

๊ตฌ๋ถ„ Mono Flux
์˜๋ฏธ 0๊ฐœ ๋˜๋Š” 1๊ฐœ์˜ ๋ฐ์ดํ„ฐ 0๊ฐœ ์ด์ƒ (๋ฌด์ œํ•œ ๊ฐ€๋Šฅ)
๋น„์œ  ๋‹จ์ผ ์‘๋‹ต (๋‹จ๊ฑด) ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต (๋‹ค๊ฑด / ์‹ค์‹œ๊ฐ„)
์‚ฌ์šฉ ์˜ˆ ๋กœ๊ทธ์ธ ๊ฒฐ๊ณผ, ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ, ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์†ก
์‘๋‹ต ๋ฐฉ์‹ ๋‹จ์ผ JSON text/event-stream, chunked ๋“ฑ ๊ฐ€๋Šฅ
๋Œ€ํ‘œ ๋ฉ”์„œ๋“œ bodyToMono(...) bodyToFlux(...)

๐Ÿ“Œ 4. ์ฃผ์š” ์—ฐ์‚ฐ์ž/ํ•จ์ˆ˜ ์„ค๋ช…

ํ•จ์ˆ˜ ์—ญํ•  ์ฃผ์˜ ์‚ฌํ•ญ
subscribe() ์‹ค์ œ ์ŠคํŠธ๋ฆผ ์‹คํ–‰ (์—†์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ์ผ์–ด๋‚จ) ๋น„๋™๊ธฐ ์†Œ๋น„
block() / blockLast() Mono/Flux ์‹คํ–‰ ๋ฐ ๊ฒฐ๊ณผ ๋Œ€๊ธฐ ๋™๊ธฐ ๋ฐฉ์‹
doOnNext() ๋ฐ์ดํ„ฐ emit๋  ๋•Œ๋งˆ๋‹ค ๋ถ€๊ฐ€ ์ž‘์—… ์ค‘๊ฐ„ ๋กœ๊ทธ ์ฐ๊ธฐ ๋“ฑ
doOnSubscribe() ๊ตฌ๋… ์‹œ์ž‘ ์‹œ ์‹คํ–‰ ํ๋ฆ„ ํŠธ๋ฆฌ๊ฑฐ
doOnComplete() ์ŠคํŠธ๋ฆผ ์ •์ƒ ์ข…๋ฃŒ ์‹œ ์‹คํ–‰ onError ์‹œ์—๋Š” ํ˜ธ์ถœ ์•ˆ ๋จ
doFinally() ์ŠคํŠธ๋ฆผ ์ข…๋ฃŒ ์‹œ ํ•ญ์ƒ ์‹คํ–‰ (์„ฑ๊ณต/์‹คํŒจ ๋ชจ๋‘) ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ ๋“ฑ์— ์œ ์šฉ
bodyToMono(...) ์‘๋‹ต์„ Mono(๋‹จ์ผ ๋ฐ์ดํ„ฐ)๋กœ ๋ณ€ํ™˜ POST ์š”์ฒญ ๊ฒฐ๊ณผ ๋“ฑ์— ์‚ฌ์šฉ
bodyToFlux(...) ์‘๋‹ต์„ Flux(๋‹ค๊ฑด ์ŠคํŠธ๋ฆผ)์œผ๋กœ ๋ณ€ํ™˜ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต ๋ฐ›์„ ๋•Œ ์‚ฌ์šฉ
DataBufferUtils.read(...) ํŒŒ์ผ์„ DataBuffer ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ฝ์Œ ์„œ๋ฒ„ ํŒŒ์ผ ์‘๋‹ต์— ์œ ์šฉ
DataBufferUtils.write(...) DataBuffer ์ŠคํŠธ๋ฆผ์„ ํŒŒ์ผ๋กœ ์ €์žฅ ํด๋ผ์ด์–ธํŠธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์‹œ ํ™œ์šฉ

 

โœ… bodyToMono(T), .bodyToFlux(T) ์ธ์ž ํƒ€์ž…

ํƒ€์ž… ์˜ˆ์‹œ ์„ค๋ช…
DataBuffer.class ๋ฐ”์ดํŠธ ์กฐ๊ฐ์„ ์ง์ ‘ ๋ฐ›์Œ (๋กœ์šฐ ์ŠคํŠธ๋ฆผ)
->์ง์ ‘ CharsetDecoder๋กœ ๋””์ฝ”๋”ฉํ•ด์•ผ ํ•จ
String.class ๋ฐ”์ดํŠธ๋ฅผ ๋ฌธ์ž๋กœ ๋””์ฝ”๋”ฉํ•œ ๋ฌธ์ž์—ด ๋ฐ›์Œ
์ปค์Šคํ…€ DTO ํด๋ž˜์Šค (์˜ˆ: MyDto.class) JSON, XML ๋“ฑ์„ ์ž๋™์œผ๋กœ ์—ญ์ง๋ ฌํ™”(deserialize) ํ•˜์—ฌ ๊ฐ์ฒด๋กœ ๋ฐ›์Œ

.bodyToFlux(String.class)๋Š” ๋ณดํ†ต ์ž‘์€ ํ…์ŠคํŠธ ์กฐ๊ฐ์ด๋‚˜ JSON ๋ผ์ธ ๋‹จ์œ„ ๋“ฑ์—์„œ ์“ฐ๊ณ ,
๋Œ€์šฉ๋Ÿ‰ ๋ฐ”์ด๋„ˆ๋ฆฌ๋‚˜ ์ปค์Šคํ…€ ์ธ์ฝ”๋”ฉ ์ฒ˜๋ฆฌํ•  ๋• .bodyToFlux(DataBuffer.class)๋กœ ๋ฐ›๋Š” ๊ฒŒ ์•ˆ์ „!


๐Ÿ“Œ 5. ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ ์ผ€์ด์Šค

์ผ€์ด์Šค ์‚ฌ์šฉ ๋ฐฉ์‹ ์„ค๋ช…
์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ, ์•Œ๋ฆผ Flux + SSE(text/event-stream) 1์ดˆ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ ์ „์†ก
๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ Flux<DataBuffer> → Mono<Void> ์กฐ๊ฐ ๋‹จ์œ„ ์ „์†ก
ํŒŒ์ผ ์—…๋กœ๋“œ Flux<DataBuffer> ์ŠคํŠธ๋ฆฌ๋ฐ์œผ๋กœ ์„œ๋ฒ„์— ์ „๋‹ฌ
๋‹จ์ผ ์š”์ฒญ ๊ฒฐ๊ณผ Mono ๋น„๋™๊ธฐ ๋‹จ๊ฑด ์‘๋‹ต (์˜ˆ: ๋กœ๊ทธ์ธ)

 

โœ… Flux ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream() {
    return Flux.interval(Duration.ofSeconds(1))
               .map(i -> "๋ฐ์ดํ„ฐ " + i)
               .take(5);
}

 

โœ… WebClient๋กœ Flux ๋ฐ›๊ธฐ

WebClient.create("http://localhost:8080")
    .get().uri("/stream")
    .accept(MediaType.TEXT_EVENT_STREAM)
    .retrieve()
    .bodyToFlux(String.class)
    .subscribe(System.out::println);

 

โœ… ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ (DataBufferUtils ํ™œ์šฉ)

Flux<DataBuffer> fileStream = WebClient.create()
    .get().uri("/files/sample.pdf")
    .retrieve()
    .bodyToFlux(DataBuffer.class);

DataBufferUtils.write(fileStream, path, StandardOpenOption.CREATE)
    .block(); // ์™„๋ฃŒ ๋Œ€๊ธฐ

 

โœ… ํŒŒ์ผ ์‘๋‹ต ์„œ๋ฒ„ ์ฝ”๋“œ

@GetMapping("/files/sample.pdf")
public Mono<Void> downloadFile(ServerHttpResponse response) {
    Flux<DataBuffer> fileStream = DataBufferUtils.read(filePath, response.bufferFactory(), 4096);
    response.getHeaders().setContentType(MediaType.APPLICATION_PDF);
    return response.writeWith(fileStream);
}

 

โœ…ํด๋ผ์ด์–ธํŠธ - WebClient๋กœ ํŒŒ์ผ ์—…๋กœ๋“œํ•˜๊ธฐ

import org.springframework.core.io.FileSystemResource;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

public class FileUploadClient {
    public static void main(String[] args) {
        WebClient webClient = WebClient.create("http://localhost:8080");

        Mono<String> result = webClient.post()
                .uri("/upload")
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .bodyValue(
                        MultipartBodyBuilderHelper.build("file", "C:/temp/sample.pdf")
                )
                .retrieve()
                .bodyToMono(String.class);

        result.subscribe(System.out::println);
    }
}

 

๐Ÿ‘‰ MultipartBodyBuilderHelper๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ๋”ฐ๋กœ ์ •์˜

import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.core.io.FileSystemResource;

public class MultipartBodyBuilderHelper {
    public static org.springframework.util.MultiValueMap<String, Object> build(String field, String filePath) {
        MultipartBodyBuilder builder = new MultipartBodyBuilder();
        builder.part(field, new FileSystemResource(filePath));
        return builder.build();
    }
}

 

 

โœ…์„œ๋ฒ„ - WebFlux ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํŒŒ์ผ ์ˆ˜์‹  ๋ฐ ์ €์žฅ

import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
public class FileUploadController {

    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<String> handleUpload(@RequestPart("file") FilePart filePart) {
        Path path = Paths.get("uploaded_" + filePart.filename());

        // DataBuffer๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ ์ €์žฅ
        return DataBufferUtils.write(filePart.content(), path)
                .then(Mono.just("ํŒŒ์ผ ์—…๋กœ๋“œ ์„ฑ๊ณต: " + filePart.filename()));
    }
}

 


๐Ÿ“Œ 6. ๋””์ฝ”๋”ฉ ์ฒ˜๋ฆฌ

โœ… 6-1. ๋””์ฝ”๋”ฉ ์ฒ˜๋ฆฌ ํ๋ฆ„

  • DataBuffer → byte[] → ByteBuffer ๋ˆ„์  → CharsetDecoder.decode() → CharBuffer → String

CharsetDecoder๋Š” ByteBuffer๋งŒ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›์œผ๋ฏ€๋กœ, DataBuffer๋ฅผ ์ง์ ‘ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋‹ค.

 

โœ… 6-2. WebFlux DataBuffer ๋ฐ์ดํ„ฐ์ฒ˜๋ฆฌ(๋””์ฝ”๋”ฉ) ์˜ˆ์ œ

  • ์„œ๋ฒ„๋Š” UTF-8 ๋“ฑ ๋ฌธ์ž ์ธ์ฝ”๋”ฉ๋œ ๋ฐ”์ดํŠธ๋ฅผ ๋ณด๋‚ด๊ณ , ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„๋Š” ์ด ๋ฐ”์ดํŠธ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋””์ฝ”๋”ฉํ•ด์•ผ ์ตœ์ข… ๋ฐ์ดํ„ฐ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • bodyToFlux(DataBuffer.class)๋กœ ๋ฐ›์œผ๋ฉด, doOnNext(buffer -> {//์—ฌ๊ธฐ์„œ ์ฒ˜๋ฆฌ!!})
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;

import reactor.core.publisher.Flux;

public class DataBufferDecodingExample {

    // UTF-8 ๋ฌธ์ž ๋””์ฝ”๋” ์ƒ์„ฑ
    private final CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();

    // ๋ˆ„์  ๋ฐ”์ดํŠธ ๋ฒ„ํผ (์ž…๋ ฅ ๋ฐ์ดํ„ฐ ์ €์žฅ)
    private final ByteBuffer cumulativeBuffer = ByteBuffer.allocate(8192);

    // ๋ฌธ์ž ๋ฒ„ํผ (๋””์ฝ”๋”ฉ ๊ฒฐ๊ณผ ์ž„์‹œ ์ €์žฅ)
    private final CharBuffer charBuffer = CharBuffer.allocate(8192);

    // ๋ฌธ์ž์—ด ๋ˆ„์  ์ €์žฅ์šฉ
    private final StringBuilder decodedStr = new StringBuilder();

    public void decodeDataBufferFlux(Flux<DataBuffer> dataBufferFlux) {
        dataBufferFlux
            .doOnNext(buffer -> {
                // 1๏ธโƒฃ DataBuffer๋ฅผ byte[]๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณต์‚ฌ
                byte[] newBytes = new byte[buffer.readableByteCount()];
                buffer.read(newBytes);
                DataBufferUtils.release(buffer); // ์‚ฌ์šฉ ํ›„ ๋ฐ˜๋“œ์‹œ ํ•ด์ œ!

                // 2๏ธโƒฃ ๋ˆ„์  ๋ฐ”์ดํŠธ ๋ฒ„ํผ์— ์ƒˆ ๋ฐ”์ดํŠธ ์ถ”๊ฐ€
                cumulativeBuffer.put(newBytes);

                // 3๏ธโƒฃ ์ฝ๊ธฐ ๋ชจ๋“œ๋กœ ์ „ํ™˜ํ•˜์—ฌ ๋””์ฝ”๋”ฉ ์ค€๋น„
                cumulativeBuffer.flip();

                // 4๏ธโƒฃ ์—ฌ๋Ÿฌ ์ƒํƒœ์— ๋Œ€์‘ํ•ด ๋””์ฝ”๋”ฉ ๋ฐ˜๋ณต ์ˆ˜ํ–‰
                while (true) {
                    CoderResult result = decoder.decode(cumulativeBuffer, charBuffer, false);
                    charBuffer.flip(); // ์“ฐ๊ธฐ๋ชจ๋“œ → ์ฝ๊ธฐ๋ชจ๋“œ ์ „ํ™˜

                    // 5๏ธโƒฃ ๋””์ฝ”๋”ฉ๋œ ๋ฌธ์ž๋“ค ๋ˆ„์  ์ €์žฅ
                    decodedStr.append(charBuffer);

                    charBuffer.clear(); // ๋ฌธ์ž ๋ฒ„ํผ ์ดˆ๊ธฐํ™”

                    if (result.isUnderflow()) {
                        // ์ž…๋ ฅ ๋ฐ”์ดํŠธ ๋ถ€์กฑ → ๋‹ค์Œ ์กฐ๊ฐ ๊ธฐ๋‹ค๋ฆผ
                        break;
                    } else if (result.isOverflow()) {
                        // ๋ฌธ์ž ๋ฒ„ํผ ๊ฝ‰ ์ฐธ → ๊ณ„์† ๋””์ฝ”๋”ฉ
                        continue;
                    } else if (result.isError()) {
                        System.err.println("๋””์ฝ”๋”ฉ ์—๋Ÿฌ ๋ฐœ์ƒ");
                        break;
                    }
                }

                // 6๏ธโƒฃ ์ฝ์ง€ ์•Š์€ ๋ฐ”์ดํŠธ๋ฅผ ์•ž์œผ๋กœ ์ด๋™ (๋‹ค์Œ ์กฐ๊ฐ๊ณผ ํ•ฉ์น˜๊ธฐ ์œ„ํ•ด)
                cumulativeBuffer.compact();

                // 7๏ธโƒฃ(ํ•„์š” ์‹œ ์—ฌ๊ธฐ์„œ decodedStr.toString()์œผ๋กœ ๋ฌธ์ž์—ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
                try {
                    PrintWriter writer = response.getWriter();
                    writer.write("data: " + decodedStr.toString() + "\n\n"); // SSE ๋ฐ์ดํ„ฐ ํฌ๋งท
                    writer.flush(); // ์ฆ‰์‹œ ์ „์†ก
                    logger.debug("ํด๋ผ์ด์–ธํŠธ ์ „์†ก: {} ({} bytes)", decodedStr.toString(), decodedStr.length());
                } catch (IOException e) {
                    logger.error("ํด๋ผ์ด์–ธํŠธ ์ „์†ก ์˜ค๋ฅ˜", e);
                    asyncContext.complete(); // ์—๋Ÿฌ ์‹œ ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ์ข…๋ฃŒ
                }

            })
            .subscribe();
    }
}
  • ์œ„ ์ฝ”๋“œ๋Š” WebFlux ์„œ๋ฒ„ ํ˜น์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์กฐ๊ฐ์กฐ๊ฐ ์˜ค๋Š” ๋ฐ”์ดํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ UTF-8 ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํŒจํ„ด์ด๋‹ค.
  • ์‹ค์‹œ๊ฐ„ AI ํ…์ŠคํŠธ ์‘๋‹ต ์ฒ˜๋ฆฌ, ๋Œ€์šฉ๋Ÿ‰ JSON ์ŠคํŠธ๋ฆผ ํŒŒ์‹ฑ, SSE(Server-Sent Events) ์ฒ˜๋ฆฌ ๋“ฑ์— ํ™œ์šฉ๋œ๋‹ค.

1๏ธโƒฃ์™œ DataBuffer๋ฅผ ๋ฐ”๋กœ ๋””์ฝ”๋”ฉํ•˜์ง€ ์•Š๊ณ  ๋ณต์‚ฌํ•˜๋Š”๊ฐ€?

 DataBuffer๋Š” Netty ๊ธฐ๋ฐ˜์˜ ์ž„์‹œ ๋ฉ”๋ชจ๋ฆฌ ๋ฒ„ํผ์ด๋ฏ€๋กœ

  • ์‚ฌ์šฉ ํ›„ ๋ฐ˜๋“œ์‹œ DataBufferUtils.release(buffer)๋กœ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•ด์ œํ•ด์•ผ ํ•œ๋‹ค.
  • ๋ฒ„ํผ๋ฅผ ๋ฐ”๋กœ ์žฌํ™œ์šฉํ•˜์ง€ ์•Š๊ณ , byte[]์— ๋ณต์‚ฌํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.
  • DataBuffer๋Š” ์žฌ์‚ฌ์šฉ๋˜๋Š” ํ’€(pool) ๋ฉ”๋ชจ๋ฆฌ๋ผ, ํ•ด์ œ(release()) ํ›„ ์ง์ ‘ ์ฐธ์กฐํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.


2๏ธโƒฃ ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ๋Š” ์กฐ๊ฐ์œผ๋กœ ๋“ค์–ด์™€์„œ, ๋‹จ์ผ DataBuffer๋งŒ ๋ณด๊ณ  ๋””์ฝ”๋”ฉํ•˜๋ฉด ์ค‘๊ฐ„ ์กฐ๊ฐ์ด ๊นจ์ ธ์„œ ๋ฌธ์ž์—ด ์†์ƒ(๏ฟฝ)์ด ์ƒ๊ธด๋‹ค.

  • "hello "   → DataBuffer 1  
  • "wor"      → DataBuffer 2  (๋‹จ๋… ๋””์ฝ”๋”ฉํ•˜๋ฉด ๊นจ์ง)
  • "ld\n"     → DataBuffer 3
  • ๋”ฐ๋ผ์„œ ์•ˆ์ „ํ•˜๊ฒŒ byte[]์— ๋ณต์‚ฌ ํ›„, ByteBuffer ๋ˆ„์  ๋ฒ„ํผ์— ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฌธ์ž ๋””์ฝ”๋”ฉ์„ ํ•ด์•ผ ํ•œ๋‹ค.

3๏ธโƒฃdecoder.decode()๋Š” ์ถœ๋ ฅ์šฉ ๋ฌธ์ž ๋ฒ„ํผ๊ฐ€ ๊ฝ‰ ์ฐจ๋ฉด Overflow ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜

  • ์ด๋Ÿด ๋•Œ ๊ณ„์† ํ˜ธ์ถœํ•ด์„œ ๋‚จ์€ ๋ฌธ์ž๋ฅผ ๋” ๋””์ฝ”๋”ฉํ•ด์•ผ ํ•จ

๊ทธ๋ž˜์„œ while๋ฌธ์œผ๋กœ ๋ฐ˜๋ณตํ•˜๋ฉฐ ์ถœ๋ ฅ ๋ฒ„ํผ๊ฐ€ ๋น„์›Œ์งˆ ๋•Œ๊นŒ์ง€ ๋””์ฝ”๋”ฉํ•ด์•ผ
ํ˜„์žฌ ๋ฒ„ํผ ๋‚ด ๋ชจ๋“  ๋ฐ”์ดํŠธ๋ฅผ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ


๐Ÿ“Œ 7. Streaming ์š”์  ์š”์•ฝ ๐ŸŽฏ

  • Flux๋Š” ์ŠคํŠธ๋ฆผ, Mono๋Š” ๋‹จ๊ฑด ์‘๋‹ต
  • subscribe()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ๋ฐ์ดํ„ฐ๊ฐ€ ํ๋ฅด๊ธฐ ์‹œ์ž‘ํ•จ
  • DataBuffer / DataBufferUtils๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ ๋‹ค๋ฃฐ ๋•Œ ํ•ต์‹ฌ
  • WebFlux๋Š” ๋น„๋™๊ธฐ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต์„ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ
  • ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต์€ text/event-stream ๋˜๋Š” chunked๋กœ ์‚ฌ์šฉ๋จ