Netty 背景、现状与趋势

Stay focused and work hard!

揭开 Netty 面纱

Netty 是什么?

Netty 官网

Netty 的官网首页有这样一句话:

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

翻译:Netty 是一个异步事件驱动的网络应用程序框架用于快速开发可维护的高性能协议服务器和客户端

总结:

  1. 本质:网络应用程序框架。
  2. 实现:异步、事件驱动。
  3. 特性:高性能、可维护、快速开发。
  4. 用途:开发服务器和客户端。

Netty 结构图(摘自官网)

img

整体分为三部分:

  1. Core:Netty 的核心。
  2. Transport:支持的传输协议。
  3. Protocol Support:支持的应用层协议。

Netty 使用示例

摘自 Netty 源码!

引入 Maven 依赖:

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

Netty 实现客户端和服务端

服务端代码

/**
 * Echoes back any received data from a client.
 */
public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
              // 两种设置 keepalive 风格
             .childOption(ChannelOption.SO_KEEPALIVE, true)
             .childOption(NioChannelOption.SO_KEEPALIVE, true)

              // 切换到 unpooled 的方式之一
             .childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

服务端处理逻辑代码

/**
 * Handler implementation for the echo server.
 */
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

客户端代码

/**
 * Sends one message when a connection is open and echoes back any received
 * data to the server.  Simply put, the echo client initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public final class EchoClient {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.git
        final SslContext sslCtx;
        if (SSL) {
            sslCtx = SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }
}

客户端处理逻辑代码

/**
 * Handler implementation for the echo client.  It initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public class EchoClientHandler extends ChannelInboundHandlerAdapter {

    private final ByteBuf firstMessage;

    /**
     * Creates a client-side handler.
     */
    public EchoClientHandler() {
        firstMessage = Unpooled.buffer(EchoClient.SIZE);
        for (int i = 0; i < firstMessage.capacity(); i ++) {
            firstMessage.writeByte((byte) i);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
       ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

Netty 实现 http 服务端

服务端代码

/**
 * An HTTP server that sends back the content of the received HTTP request
 * in a pretty plaintext form.
 */
public final class HttpHelloWorldServer {


    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer() {
                 @Override
                 protected void initChannel(Channel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new HttpServerCodec());
                     p.addLast(new HttpServerExpectContinueHandler());
                     p.addLast(new HttpHelloWorldServerHandler());
                 }
             });

            Channel ch = b.bind(8080).sync().channel();

            System.err.println("Open your web browser and navigate to " +
                    "http://127.0.0.1:8080");

            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

服务端处理逻辑代码

public class HttpHelloWorldServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    private static final byte[] CONTENT = "helloworld".getBytes();

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
        if (msg instanceof HttpRequest) {
            HttpRequest req = (HttpRequest) msg;

            FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK,
                                                                    Unpooled.wrappedBuffer(CONTENT));
            response.headers()
                    .set(CONTENT_TYPE, TEXT_PLAIN)
                    .setInt(CONTENT_LENGTH, response.content().readableBytes());

            ChannelFuture f = ctx.write(response);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

为什么不直接用 JDK NIO?

当我们选择一个方案而不选择另外一个方案的时候,往往有两点原因:

  • 做得更多(功能)
  • 做得更好(性能)

Netty 做得更多

支持常见应用层协议

支持比如 HTTP、WebSocket、gzip 等协议,如果不支持的话,那么传输的编码和解码功能就得我们自己实现。

解决传输问题:粘包、半包现象

当数据在网络上传输的时候,难免会出现一些问题,比如粘包、半包问题,这些问题 Netty 都帮我们解决好了。

支持流量整型

在传输的过程中,如果我们需要做更多定制的功能,比如流量控制、黑白名单等功能,Netty 也帮我们做了。

完善的断连、Idle 等异常处理

Netty 处理了各种各样乱七八糟的情况。

Netty 做得更好

Netty 做得更好之一:规避 JDK NIO Bug

(1)例子 1

image-20220707130255720.png

官方的处理方式是暂时不修复。

Netty 是解决了这个问题。

image-20220707130348345.png

(2)例子 2

image-20220707130535552.png

官方在 JDK 12 才修复了这个 Bug

Netty 直接避开这个问题:

image-20220707130616661.png

Netty 做得更好之二:API 更友好更强大

  1. JDKNIO 一些 API 不够友好,功能薄弱,比如 NIOByteBuffer 的设计简直是反人类,而 Netty 对其进行了增强,ByteBuf,更加友好,并且功能更加强大(比如动态扩容);
  2. 除了 NIO 之外,也提供了一些其他增强:JDKThreadLocal -> Netty FastThreadLocal,在高并发场景下,性能会更好。

Netty 做得更好之三:隔离变化、屏蔽细节

  1. 屏蔽 JDK NIO 的实现变化:nio -> nio2(aio) -> ...,Netty 将其抽象出来,这样版本升级的时候,不用修改太多代码。
  2. 屏蔽 JDK NIO 的实现细节:Netty 把一些实现细节都做帮我们做好了,所以使用起来非常简单。

直接用 JDK NIO 实现的可能性

  1. 大概写多少行代码?

image-20220707131541946.png

  1. 可能面对的问题

image-20220707131623787.png

  1. 踏平多少 JDK NIO Bug

image-20220707131654101.png

  1. 未来能维护多久?Netty 已经维护 15 年(from 2004 ~ 2019)

为什么独选 Netty

为什么不选择 Apache Mina

image-20220707131947935.png

为什么不选 SunGrizzly

image-20220707132030884.png

为什么不选 Apple SwiftNIO、ACE 等?

这些都不是 Java 语言方面的,不考虑。

为什么不选 Cindy 等?

生命周期不长。

为什么不选 Tomcat、Jetty

通信层功能并没有独立出来。

疑问:为什么 Tomcat 不选择 Netty

历史原因,Tomcat 是 1990 年左右出现的,而 Netty 是 2004 年才出现的,有很多类似的例子都是因为历史原因。

Netty 的前尘往事

题外 1:废弃 Netty 5.0 原因

image-20220707132601707.png

题外 2:与 Apache Mina 的关系

image-20220707132634839.png

Netty 的现状与趋势

社区现状

GitHub Netty 地址

image-20220707132941055.png

一些典型项目

  • 数据库:Cassandra
  • 大数据处理:Spark、Hadoop
  • 消息队列:Rocket MQ
  • 检索:ElasticSearch
  • 框架:gRPC、Apche Dubbo、Spring 5
  • 分布式协调器:Zookeeper
  • 工具类:async-http-client

Netty 的趋势

  • 更多流行协议的支持
  • 紧跟 JDK 新功能的步伐
  • 更多易用、人性化的功能
    • IP 地址黑白名单、流量整形等
  • 越来越多的应用使用 Netty

猜你喜欢

转载自juejin.im/post/7117509584134078471