π― μμνλ©°
βSpring WebFlux μ°λ©΄ λλλ° μ κ΅³μ΄ μμ NIOλ‘β¦?β
λκ΅°κ°λ μ΄λ κ² μκ°ν μ μμ΅λλ€. νμ§λ§ λλ‘λ νλ μμν¬ λ°λ°λ₯μ΄ μ΄λ»κ² λμκ°λμ§ μ§μ ꡬνν΄λ³΄λ κ²μ΄ μ΅κ³ μ νμ΅μ΄μ£ . λ§μΉ μλμ°¨ μ΄μ λ§ νλ€κ° μμ§μ μ§μ λ―μ΄λ³΄λ κ²μ²λΌμ.
μ΄ κΈμ IRC μλ²λ₯Ό μμ Java NIOλ‘ κ΅¬ννλ©΄μ λ§μ£Όν νμ€μ μΈ λ¬Έμ λ€κ³Ό ν΄κ²° λ°©λ²μ λ΄μμ΅λλ€.
π¦ 1. μμ‘΄μ±: λλκ²λ μ무κ²λ νμ μμ΅λλ€
// build.gradleμ μΆκ°ν κ²μ΄ μμ΅λλ€!
λ§μ΅λλ€. λ¨ ν μ€μ μμ‘΄μ±λ μΆκ°νμ§ μμ΅λλ€.
Java NIO(java.nio.*)λ JDKμ κΈ°λ³Έ ν¬ν¨λμ΄ μμ΅λλ€. JDK 1.4λΆν° μ 곡λ μ€λλ(?) μΉκ΅¬μ£ . μΈλΆ λΌμ΄λΈλ¬λ¦¬ μμ΄ μμ Java μ½λλ§μΌλ‘ κ³ μ±λ₯ λ€νΈμν¬ μλ²λ₯Ό λ§λ€ μ μλ€λ κ², λλμ§ μλμ?
ποΈ 2. μν€ν μ²: Spring Bootμ NIOμ μνν λκ±°
μ¬κΈ°μλΆν° μ§μ§ κ³ λ―Όμ΄ μμλ©λλ€.
β οΈ ν΅μ¬ λ¬Έμ : λ κ°μ λΌμ΄νμ¬μ΄ν΄
Spring Boot μ ν리μΌμ΄μ μ μμνκ³ , λΉμ μ΄κΈ°ννκ³ , μΉ μλ²λ₯Ό λμ°κ³ β¦ κ·Έλ¦¬κ³ λμ λ©μΈ μ€λ λλ ν μΌμ λλ λλ€.
νμ§λ§ NIO μλ²λ while(true) 무ν 루νλ‘ κ³μ μ΄λ²€νΈλ₯Ό κ°μν΄μΌ ν©λλ€.
λ§μ½ λ©μΈ μ€λ λμμ NIO 루νλ₯Ό λ리면?
β Spring Bootκ° λ¨Ήν΅μ΄ λ©λλ€. μ ν리μΌμ΄μ
μ΄ μμμ‘°μ°¨ μ λμ£ .
λ§μ½ λ³λ μ€λ λ μμ΄ μ€ννλ©΄?
β μλ²λ 컀λ₯μ
μ λ°μ μ μμ΅λλ€.
β ν΄κ²° λ°©λ²: μ°μν λΆλ¦¬
@Component
public class IRCServer {
private Selector selector;
private ServerSocketChannel serverSocket;
// Springμ΄ κ΄λ¦¬νλ μ»΄ν¬λνΈλ‘ λ±λ‘
}
@Component
public class ServerInitializer implements ApplicationRunner {
@Autowired
private IRCServer ircServer;
@Override
public void run(ApplicationArguments args) throws Exception {
// Spring Bootκ° μμ ν λ¨κ³ λμ IRC μλ² μμ
ircServer.start();
}
}
ν΅μ¬ ν¬μΈνΈ:
- IRCServerλ Spring Componentλ‘ λ§λ€μ΄ μμ‘΄μ± μ£Όμ ννμ λ°μ΅λλ€
- ApplicationRunnerλ‘ μμ μμ μ μ μ΄ν©λλ€
- NIO 루νλ λ°λμ λ³λ μ€λ λμμ μ€νν©λλ€
βοΈ 3. Event Loop: Nettyκ° λ΄λΆμμ νλ κ·Έ μΌ
μ§μ ꡬννλ©΄ μ΄λ° λͺ¨μ΅μ΄ λ©λλ€:
public class NioIrcServer implements Runnable {
private Selector selector;
private ServerSocketChannel serverSocket;
public void start() throws Exception {
// 1. Selector μμ± - μ΄λ²€νΈλ₯Ό κ°μν κ΄μ ν
selector = Selector.open();
// 2. μλ² μμΌ μμ± λ° λ°μΈλ©
serverSocket = ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress(6667)); // IRC νμ€ ν¬νΈ
// 3. π Non-blocking λͺ¨λ μ€μ (μ΄κ² NIOμ ν΅μ¬!)
serverSocket.configureBlocking(false);
// 4. Accept μ΄λ²€νΈλ₯Ό Selectorμ λ±λ‘
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
// 5. λ³λ μ€λ λμμ μ΄λ²€νΈ 루ν μμ
new Thread(this, "IRC-NIO-Thread").start();
}
@Override
public void run() {
while (true) { // 무ν 루ν - μλ²κ° μ΄μμλ ν κ³μ
try {
// β° μ΄λ²€νΈκ° λ°μν λκΉμ§ λκΈ°
// (blockingμ΄μ§λ§, μ¬λ¬ μ±λμ λμμ κ°μνλ ν¨μ¨μ μΈ λκΈ°)
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) {
// π μλ‘μ΄ ν΄λΌμ΄μΈνΈ μ μ!
handleAccept(key);
} else if (key.isReadable()) {
// π¨ ν΄λΌμ΄μΈνΈκ° λ°μ΄ν°λ₯Ό 보λμ΄μ
handleRead(key);
}
iter.remove(); // μ²λ¦¬ν ν€λ λ°λμ μ κ±°!
}
} catch (Exception e) {
// μλ¬ μ²λ¦¬...
}
}
}
}
μ΄κ² λ°λ‘ Netty, Reactor, Vert.x κ°μ λΌμ΄λΈλ¬λ¦¬λ€μ΄ λ΄λΆμμ νλ μΌμ
λλ€.
μ°λ¦¬κ° μ§μ ꡬννλ©΄μ κ·Έλ€μ κ³ λ§μμ λΌμ λ¦¬κ² λλΌκ² λ©λλ€β¦ π
π₯ 4. νμ€μ λ²½: μ§μ ꡬννλ©΄ λ§μ£Όνλ λ¬Έμ λ€
βμ΄λ‘ μμΌλ‘λ κ°λ¨ν΄ 보μ΄λλ°?βλΌκ³ μκ°νλ€λ©΄, μ΄μ λΆν°κ° μ§μ§μ λλ€.
1οΈβ£ TCP ν¨ν· ννΈν: μ λͺ λμ Half-Packet λ¬Έμ
μν© μ¬ν:
ν΄λΌμ΄μΈνΈ: "PRIVMSG #channel :Hello World\r\n" μ μ‘
μλ² μμ 1μ°¨: "PRIVMSG #cha"
μλ² μμ 2μ°¨: "nnel :Hello World\r\n"
νΉμ λ°λλ‘:
ν΄λΌμ΄μΈνΈ: "NICK user1\r\n" + "USER user1 0 * :Real Name\r\n" μ μ‘
μλ² μμ 1μ°¨: "NICK user1\r\nUSER user1 0 * :Real Name\r\n"
μ΄κ² λ¬΄μ¨ μΌμΈκ°μ?
TCPλ μ€νΈλ¦Ό κΈ°λ° νλ‘ν μ½μ λλ€. λ©μμ§ κ²½κ³λ₯Ό 보μ₯νμ§ μμ΅λλ€.
- Fragmentation: ν λ©μμ§κ° μ¬λ¬ ν¨ν·μΌλ‘ μͺΌκ°μ Έμ λμ°©
- Sticky Packets: μ¬λ¬ λ©μμ§κ° ν ν¨ν·μ λΆμ΄μ λμ°©
ν΄κ²° λ°©λ²: μ§μ λ§λλ Frame Decoder
public class MessageFrameDecoder {
private ByteBuffer buffer = ByteBuffer.allocate(1024);
public List<String> decode(ByteBuffer incoming) {
List<String> messages = new ArrayList<>();
// κΈ°μ‘΄ λ²νΌμ μ λ°μ΄ν° μΆκ°
buffer.put(incoming);
buffer.flip();
// \r\nμ μ°Ύμ λ©μμ§ μΆμΆ
while (buffer.hasRemaining()) {
byte b = buffer.get();
if (b == '\n') {
// μμ ν λ©μμ§ λ°κ²¬!
messages.add(extractMessage());
}
}
buffer.compact(); // λ¨μ λ°μ΄ν°λ λ€μ λ²μ μν΄ λ³΄κ΄
return messages;
}
}
μ΄ λΆλΆμ΄ κ°μ₯ λ²κ·Έκ° λ§μ΄ λ°μνλ μ§μ μ
λλ€. Nettyμ DelimiterBasedFrameDecoderκ° μΌλ§λ κ³ λ§μ΄μ§ μκ² λ©λλ€.
2οΈβ£ ByteBuffer: κΉλ€λ‘μ΄ μΉκ΅¬
ByteBufferλ λ¨μν΄ λ³΄μ΄μ§λ§ μ€μνκΈ° μ½μ΅λλ€:
// β νν μ€μ
buffer.put(data);
// flip() μμ΄ λ°λ‘ μ½μΌλ©΄? β μ°λ κΈ° κ°!
buffer.get();
// β
μ¬λ°λ₯Έ μ¬μ©
buffer.put(data); // Write λͺ¨λ
buffer.flip(); // Read λͺ¨λλ‘ μ ν
buffer.get(); // μ΄μ μ½μ μ μμ
buffer.compact(); // λ¨μ λ°μ΄ν° μ 리
position, limit, capacityμ μΌκ°κ΄κ³λ₯Ό μλ²½ν μ΄ν΄ν΄μΌ ν©λλ€.
3οΈβ£ 리μμ€ λμ: μ‘°μ©ν λ€κ°μ€λ μ¬μ
try {
readData(key);
} catch (IOException e) {
// β μ΄λ κ²λ§ νλ©΄?
e.printStackTrace();
// μμΌμ μ΄λ¦° μ±λ‘, SelectionKeyλ λ±λ‘λ μ±λ‘...
// μκ°μ΄ μ§λλ©΄ "Too many open files" μλ¬ λ°μ!
}
μ¬λ°λ₯Έ μ²λ¦¬:
try {
readData(key);
} catch (IOException e) {
// β
리μμ€ μ 리
key.cancel(); // Selectorμμ μ κ±°
key.channel().close(); // μμΌ λ«κΈ°
clientMap.remove(key.channel()); // μ ν리μΌμ΄μ
μν μ 리
log.info("Client disconnected: {}", e.getMessage());
}
π λ§μΉλ©°: κ·Έλλ ν λ²μ―€μ ν΄λ³Ό λ§ν μ¬μ
μμ NIO ꡬνμ λΆλͺ κ³ λ μμ μ λλ€. Nettyλ WebFluxλ₯Ό μ°λ©΄ μ΄ λͺ¨λ κ±Έ νλ μμν¬κ° ν΄κ²°ν΄μ£ΌλκΉμ.
νμ§λ§ μ΄ κ²½νμ ν΅ν΄ μ»λ κ²:
- β Non-blocking I/Oμ μ§μ§ μλ μ리 μ΄ν΄
- β νλ μμν¬κ° ν΄κ²°νλ λ¬Έμ λ€μ νΌλΆλ‘ 체κ°
- β μ±λ₯ νλ μ μ΄λλ₯Ό λ΄μΌ νλμ§ κ°κ° μ΅λ
- β λ©΄μ μμ βReactor ν¨ν΄βμ μμ μκ² μ€λͺ κ°λ₯!
νλ μμν¬λ λ§λ²μ΄ μλλΌ λκ΅°κ°μ λ Έλ ₯μ λλ€. κ·Έ λ Έλ ₯μ μ΄ν΄νλ κ°λ°μκ° λλ κ², κ·Έκ²μ΄ μ΄ μ¬μ μ μ§μ§ κ°μΉμ λλ€.