System Design · caderno de estudo

Rabiscos de System Design

Diagramas animados pra fixar o que acontece por baixo do código. Um tema por prancha, uma ideia por cena.

Série Java Networking · 04

Selector: milhares de conexões numa thread só

new Thread(() -> handle(server.accept())).start(); // uma thread por cliente
clientesthreadsthread-1 processando thread-2 read() · esperando thread-3 read() · esperando thread-4 read() · esperando thread-5 read() · esperando thread-6 read() · esperando thread-7 read() · esperando thread-8 read() · esperando + 992 threads iguais a essasthreads1.000stack reservada≈ 1 GB-Xss padrão: 1 MB por threadcada uma trabalha ~7% do tempoo resto é espera e troca de contexto
01 / 06

Bloqueante: cada conexão prende uma thread

No servidor da prancha 02, cada cliente ganha uma thread que passa quase o tempo todo parada no read(). Com 10.000 clientes são 10.000 threads, cada uma com a sua stack, e a CPU gasta tempo trocando de uma pra outra. Mude o número de conexões e veja a conta.

Conexões simultâneas

Use ← e → pra trocar de cena, espaço pra pausar.

Pra fixar

  • Bloqueante: uma thread por conexão, quase sempre parada no read(). Com milhares de clientes, a memória das stacks e a troca de contexto pesam.
  • configureBlocking(false) faz accept(), read() e write() voltarem na hora com o que tiver; read() pode devolver 0.
  • O canal se registra no Selector com os eventos de interesse (OP_ACCEPT, OP_CONNECT, OP_READ, OP_WRITE). Cada registro é uma SelectionKey.
  • select() bloqueia até algum canal ficar pronto e devolve quantos estão; selectedKeys() traz só esses.
  • Tire a chave do selectedKeys() depois de atender (it.remove()), senão ela volta no próximo select().
  • É a base do Netty, do conector NIO do Tomcat e do cliente do Kafka: poucas threads, cada uma com o seu Selector.

O loop de eventos

Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
  selector.select();                          // sleeps until some socket is ready
  Iterator<SelectionKey> it = selector.selectedKeys().iterator();
  while (it.hasNext()) {
    SelectionKey key = it.next();
    it.remove();                              // or it comes back on the next select()
    if (key.isAcceptable()) {
      SocketChannel client = server.accept();
      client.configureBlocking(false);
      client.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
    } else if (key.isReadable()) {
      SocketChannel client = (SocketChannel) key.channel();
      if (client.read((ByteBuffer) key.attachment()) == -1) client.close();
    }
  }
}

Próxima prancha: UDP (em breve).