Otimização do TCP no Núcleo do Sistema Operacional para Alta Concorrência: Demonstração do Handshake de Três Vias

Parâmetros-chave para o handshake TCP de três vias

Preparação do Programa

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.exemplo</groupId>
    <artifactId>TesteAltaConcorrencia</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.112.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.2</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

src/main/resources/logback.xml

<configuration debug="false">
  <appender name="SAIDA_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="${logLevel:-info}">
    <appender-ref ref="SAIDA_CONSOLE" />
  </root>
</configuration>

Lado do Servidor

org.exemplo.servidor.ServidorPrincipal

package org.exemplo.servidor;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AttributeKey;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.System.out;

public class ServidorPrincipal {

    private static final AtomicInteger conexoesAtivas = new AtomicInteger(0);
    private static final ExecutorService poolTrabalho = Executors.newFixedThreadPool(4);

    public static void main(String[] args) {
        final int porta = 7070;
        EventLoopGroup grupoChefe = new NioEventLoopGroup(1);
        EventLoopGroup grupoTrabalhador = new NioEventLoopGroup(4);

        try {
            ServerBootstrap bootstrap = new ServerBootstrap()
                    .group(grupoChefe, grupoTrabalhador)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childHandler(new InicializadorCanalFilho());

            ChannelFuture futuro = bootstrap.bind(porta).sync();
            out.println("Servidor iniciado e escutando na porta: " + porta);
            futuro.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            out.println("Servidor interrompido: " + e.getMessage());
            Thread.currentThread().interrupt();
        } finally {
            grupoChefe.shutdownGracefully();
            grupoTrabalhador.shutdownGracefully();
            poolTrabalho.shutdown();
        }
    }

    private static class InicializadorCanalFilho extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel ch) {
            ch.pipeline().addLast(new ManipuladorEventosConexao());
        }
    }

    @ChannelHandler.Sharable
    private static class ManipuladorEventosConexao extends ChannelInboundHandlerAdapter {
        private final AttributeKey<Integer> idConexao = AttributeKey.valueOf("idConexao");

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            int id = conexoesAtivas.incrementAndGet();
            ctx.channel().attr(idConexao).set(id);
            out.println("Canal " + ctx.channel().remoteAddress() + " ativado. Conexão #" + id + " estabelecida.");
            super.channelActive(ctx);
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            Integer id = ctx.channel().attr(idConexao).get();
            out.println("Canal " + ctx.channel().remoteAddress() + " inativado. Conexão #" + id + " encerrada.");
            super.channelInactive(ctx);
        }
    }
}

Comando de Inicialização do Servidor

java -cp TesteAltaConcorrencia-1.0-SNAPSHOT.jar:./lib/* org.exemplo.servidor.ServidorPrincipal -Djava.net.preferIPv4Stack=true -Xmx2g -Xms2g

Lado do Cliante

org.exemplo.cliente.ClientePrincipal

package org.exemplo.cliente;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.System.out;

public class ClientePrincipal {

    public static void main(String[] args) throws InterruptedException {
        if (args.length < 1) {
            out.println("Uso: java ClientePrincipal <num_conexoes> [host] [porta]");
            return;
        }

        final int totalConexoes = Integer.parseInt(args[0]);
        final String host = args.length > 1 ? args[1] : "localhost";
        final int porta = args.length > 2 ? Integer.parseInt(args[2]) : 7070;

        AtomicInteger conexoesEstabelecidas = new AtomicInteger(0);
        CountDownLatch latchConexoes = new CountDownLatch(totalConexoes);
        AttributeKey<Integer> attrId = AttributeKey.valueOf("clientId");

        NioEventLoopGroup grupoEvento = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap()
                .group(grupoEvento)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                int id = conexoesEstabelecidas.incrementAndGet();
                                ctx.channel().attr(attrId).set(id);
                                out.println("Conexão #" + id + " com " + ctx.channel().remoteAddress() + " estabelecida.");
                                latchConexoes.countDown();
                                super.channelActive(ctx);
                            }

                            @Override
                            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                out.println("Conexão #" + ctx.channel().attr(attrId).get() + " encerrada.");
                                super.channelInactive(ctx);
                            }
                        });
                    }
                });

        out.println("Iniciando " + totalConexoes + " conexões com " + host + ":" + porta);
        long tempoInicio = System.currentTimeMillis();

        ChannelFuture[] futurosConexao = new ChannelFuture[totalConexoes];
        for (int i = 0; i < totalConexoes; i++) {
            futurosConexao[i] = bootstrap.connect(host, porta);
        }

        boolean todasConectadas = latchConexoes.await(30, TimeUnit.SECONDS);
        long tempoFim = System.currentTimeMillis();

        if (todasConectadas) {
            out.println("Todas as " + totalConexoes + " conexões estabelecidas em " + (tempoFim - tempoInicio) + "ms");
        } else {
            out.println("Apenas " + (totalConexoes - latchConexoes.getCount()) + " de " + totalConexoes + " conexões foram estabelecidas no tempo limite.");
        }

        // Manter conexões abertas por um momento para demonstração
        Thread.sleep(5000);

        for (ChannelFuture futuro : futurosConexao) {
            if (futuro.channel().isActive()) {
                futuro.channel().close();
            }
        }
        grupoEvento.shutdownGracefully().sync(5, TimeUnit.SECONDS);
    }
}

Parâmetros do Sistema Operacional

  • fs.file-max: Define o número máximo de descritores de arquivo no sistema. Deve ser aumentado para suportar um grande número de conexões.
  • net.core.somaxconn: Define o tamenho máximo da fila de pendência (backlog) do socket no kernel. Um valor baixo pode limitar a taxa de novas conexões em ambientes de alta concorrência.

Tags: TCP Netty java alta-concorrência handshake-tres-vias

Publicado em 7-14 18:39