diff --git a/docs/netty.md b/docs/netty.md index 6604c7f..7e7e065 100644 --- a/docs/netty.md +++ b/docs/netty.md @@ -1,218 +1,249 @@ -### 秒杀nginx优化 +### Netty 教程 有问题或者宝贵意见联系我的QQ,非常希望你的加入! -##要求: ->目标 - 1.并发优化 - 2.KeepALive长连接优化 - 3.压缩优化 - 4.配置缓存 +####了解NIO和IO + +| NIO | IO | +| :---:| :---:| +| 面向缓存区 | 面向流 | +| 非阻塞IO | 阻塞IO | +| 选择器 |无 | + +#####面向缓存区和面向流 + Java NIO和IO之间的第一差异在于IO是面向流的,其中NIO是面向缓冲的。 -#### [/docs/tools/nginx nginx优化相关包](/docs/tools) + 面向流的Java IO意味着您一次从流中读取一个或多个字节。你读取的字节取决于你所做的。 + 他们没有任何缓存空间。此外,你不能向前或向后移动流中的数据。如果您需要在从流中读取的数据中前后移动,则需要首先将其缓存在缓冲区中。 + + Java NIO的缓存导向方法略有不同。数据先被读入缓存区然后再被处理。您可以根据需要在缓冲区中向前后移动。 + 这样可以在处理过程中给予您更多的灵活性。但是,您还需要检查缓冲区是否包含所有需要的数据,以便完全处理它。 + 而且,您需要确保在缓冲区中读取更多数据时,不要覆盖尚未处理的在缓冲区中的数据。 + +#####非阻塞IO和阻塞IO + + Java IO的各种流是阻塞的。 + 这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 + + Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。 + 一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。 + +######选择器 + Java NIO的选择器允许单个线程监视多个通道的输入。您可以使用选择器注册多个通道,然后使用单个线程“选择”具有可用于处理的输入的通道,或选择准备好进行写入的通道。这种选择器机制使单线程更容易管理多个通道。 + +####IO编程 +我们简化下场景:客户端每隔两秒发送一个带有时间戳的"hello world"给服务端,服务端收到之后打印。 +为了方便演示,下面例子中,服务端和客户端各一个类,把这两个类拷贝到你的IDE中,先后运行 IOServer.java 和IOClient.java可看到效果。 +下面是传统的IO编程中服务端实现 + +`````java +public class IOServer { + public static void main(String[] args) throws Exception { + + ServerSocket serverSocket = new ServerSocket(8000); + + // (1) 接收新连接线程 + new Thread(() -> { + while (true) { + try { + // (1) 阻塞方法获取新的连接 + Socket socket = serverSocket.accept(); + + // (2) 每一个新的连接都创建一个线程,负责读取数据 + new Thread(() -> { + try { + byte[] data = new byte[1024]; + InputStream inputStream = socket.getInputStream(); + while (true) { + int len; + // (3) 按字节流方式读取数据 + while ((len = inputStream.read(data)) != -1) { + System.out.println(new String(data, 0, len)); + } + } + } catch (IOException e) { + } + }).start(); + + } catch (IOException e) { + } + + } + }).start(); + } +} +//server端首先创建了一个serverSocket来监听8000端口。 +//然后创建一个线程,线程里面不断调用阻塞方法 serversocket.accept();获取新的连接,见(1),当获取到新的连接之后,给每条连接创建一个新的线程,这个线程负责从该连接中读取数据,见(2),然后读取数据是以字节流的方式,见(3)。 +````` + +```java +public class IOClient { + + public static void main(String[] args) { + new Thread(() -> { + try { + Socket socket = new Socket("127.0.0.1", 8000); + while (true) { + try { + socket.getOutputStream().write((new Date() + ": hello world").getBytes()); + socket.getOutputStream().flush(); + Thread.sleep(2000); + } catch (Exception e) { + } + } + } catch (IOException e) { + } + }).start(); + } +} +//客户端的代码相对简单,连接上服务端8000端口之后,每隔2秒,我们向服务端写一个带有时间戳的 "hello world" +``` +上面的demo,从服务端代码中我们可以看到,在传统的IO模型中,每个连接创建成功之后都需要一个线程来维护,每个线程包含一个while死循环,那么1w个连接对应1w个线程,继而1w个while死循环,这就带来如下几个问题: + +线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起 +线程切换效率低下:单机cpu核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降。 +除了以上两个问题,IO编程中,我们看到数据读写是以字节流为单位,效率不高。 + +为了解决这三个问题,JDK在1.4之后提出了NIO +####NIO编程 + 关于NIO相关的文章网上也有很多,这里不打算详细深入分析,下面简单描述一下NIO是如何解决以上三个问题的。 + +#####线程资源受限 + 在NIO模型中,他把这么多while死循环变成一个死循环,这个死循环由一个线程控制,那么他又是如何做到一个线程,一个while死循环就能监测1w个连接是否有数据可读的呢? + 这就是NIO模型中selector的作用,一条连接来了之后,现在不创建一个while死循环去监听是否有数据可读了,而是直接把这条连接注册到selector上,然后,通过检查这个selector,就可以批量监测出有数据可读的连接,进而读取数据, +#####线程切换效率低下 + 由于NIO模型中线程数量大大降低,线程切换效率因此也大幅度提高 +#####IO读写以字节为单位 + NIO解决这个问题的方式是数据读写不再以字节为单位,而是以字节块为单位。IO模型中,每次都是从操作系统底层一个字节一个字节地读取数据,而NIO维护一个缓冲区,每次可以从这个缓冲区里面读取一块的数据, + 这就好比一盘美味的豆子放在你面前,你用筷子一个个夹(每次一个),肯定不如要勺子挖着吃(每次一批)效率来得高。 + +介绍完IO/NIO我们来真正介绍Netty吧 + +####了解什么是Netty + + * Netty的官方解释 + + Netty是由JBOSS提供的一个java开源框架。 + Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。 + + * Netty的简单概括 + + Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架。 + +####Netty的特性呢 +>1.统一的API,支持多种传输类型,阻塞的和非阻塞的简单而强大的线程模型真正的无连接数据报套接字支持链接逻辑组件以支持复用。 + +>2.拥有比Java的核心API更高的吞吐量以及更低的延迟得益于池化和复用,拥有更低的资源消耗最少的内存复制。 + +>3.不会因为慢速、快速或者超载的连接而导致OutOfMemoryError消除在高速网络中NIO应用程序常见的不公平读/写比率。 + +>4.完整的SSL/TLS以及StartTLS支持可用于受限环境下,如Applet和OSGI。 + +>等等。 + +好 让我们动手写一个Netty客户端和服务端吧 +####Netty客户端 +```java +package com.example.netty.http; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.*; +import io.netty.util.AsciiString; + + +public class HttpHandler extends SimpleChannelInboundHandler { + + private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { + System.out.println("class:" + msg.getClass().getName()); + DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + Unpooled.wrappedBuffer("test".getBytes())); + + HttpHeaders heads = response.headers(); + heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8"); + heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); // 3 + heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + + ctx.write(response); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + System.out.println("channelReadComplete"); + super.channelReadComplete(ctx); + ctx.flush(); // 4 + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + System.out.println("exceptionCaught"); + if (null != cause) cause.printStackTrace(); + if (null != ctx) ctx.close(); + } +} - 安装:cd - yum install -y gcc gcc-c++ - ./configure --prefix=/usr/local/nginx --with-pcre=/home/qiurunze/下载/pcre-8.38 --with-http_stub_status_module --with-http_gzip_static_module --add-module=/home/qiurunze/下载/ngx_cache_purge-2.3 - make - make install - - ps -ef | grep nginx - - ./sbin/nginx -s reload - - http://nginx.org/en/docs/ - - 1.工作线程数和并发连接数 - worker_processes 4; #cpu,如果nginx单独在一台机器上 - worker_processes auto; - events { - worker_connections 4096;#每一个进程打开的最大连接数,超出了log中会有记录 - multi_accept on; #可以一次建立多个连接 - use epoll; +``` +####Netty服务端 + +```java + +package com.example.netty.http; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.sctp.nio.NioSctpServerChannel; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; + + +public class HttpServer { + private final int port ; + + public HttpServer(int port) { + this.port = port; } - worker_rlimit_nofile 10240;每个进程打开的最大的文件数,受限于操作系统: - vi /etc/security/limits.conf - * hard nofile 102400 - * soft nofile 102400 - * soft core unlimited - * soft stack 10240 - - 2.操作系统优化 - 配置文件/etc/sysctl.conf - sysctl -w net.ipv4.tcp_syncookies=1#防止一个套接字在有过多试图连接到达时引起过载 - sysctl-w net.core.somaxconn=1024#默认128,连接队列 - sysctl-w net.ipv4.tcp_fin_timeout=10 # timewait的超时时间 - sysctl -w net.ipv4.tcp_tw_reuse=1 #os直接使用timewait的连接 - sysctl -w net.ipv4.tcp_tw_recycle = 0 #回收禁用 - - 3.Keepalive长连接 - Nginx与upstream server: - upstream server_pool{ - server localhost:8080 weight=1 max_fails=2 fail_timeout=30s; - keepalive 300; #300个长连接 + + public void start(){ + ServerBootstrap bootStrap = new ServerBootstrap(); + NioEventLoopGroup group = new NioEventLoopGroup(); + bootStrap.group(group).channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + System.out.println("channel :" +ch); + ch.pipeline().addLast("decoder", new HttpRequestDecoder()) + .addLast("encoder", new HttpResponseEncoder()) // 2 + .addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3 + .addLast("handler", new HttpHandler()); + } + }); } - 同时要在location中设置: - location / { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - 客户端与nginx(默认是打开的): - - - 4.keepalive_timeout 60s; #长连接的超时时间 - keepalive_requests 100; #100个请求之后就关闭连接,可以调大 - keepalive_disable msie6; #ie6禁用启用压缩 - gzip on; - gzip_disable "MSIE [1-6]\.(?!.*SV1)"; - gzip_proxied any; - gzip_types text/html text/plain application/x-javascript application/javascript text/css application/xml - gzip_vary on; #Vary: Accept-Encoding - gzip_static on; #如果有压缩好的 直接使用 - - - - 5.状态监控 - location = /nginx_status { - stub_status on; - access_log off; - allow ; - deny all; - } - 输出结果: - Active connections: 1 - server accepts handled requests - 17122 17122 34873 - Reading: 0 Writing: 1 Waiting: 0 - Active connections:当前实时的并发连接数 - accepts:收到的总连接数, - handled:处理的总连接数 - requests:处理的总请求数 - Reading:当前有都少个读,读取客户端的请求 - Writing:当前有多少个写,向客户端输出 - Waiting:当前有多少个长连接(reading + writing) - reading – nginx reads request header - writing – nginx reads request body, processes request, or writes response to a client - waiting – keep-alive connections, actually it is active - (reading + writing) - - 6.实时请求信息统计ngxtop - https://github.com/lebinh/ngxtop - (1)安装python-pip - yum install epel-release - yum install python-pip - (2)安装ngxtop - pip install ngxtop - (3)使用 - 指定配置文件: ngxtop -c ./conf/nginx.conf - 查询状态是200: ngxtop -c ./conf/nginx.conf --filter 'status == 200' - 查询那个ip访问最多: ngxtop -c ./conf/nginx.conf --group-by remote_addr - - ----------------------------------------------------------------------------- - nginx.conf配置文件 - user www; - worker_processes 4;#取决于cpu - - error_log logs/error.log; - - pid logs/nginx.pid; - - worker_rlimit_nofile 10240; #每个进程打开的最大的文件数,受限于操作系统/etc/security/limits.conf - - events { - worker_connections 10240;#每一个进程打开的最大连接数,超出了log中会有记录 - multi_accept on; #可以一次建立多个连接 - use epoll; - } - - http { - include mime.types; - default_type application/octet-stream; - server_tokens off; #隐藏版本号 - client_max_body_size 10m; #文件上传需要调大 - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log logs/access.log main; - #默认写日志:打开文件写入关闭,max:缓存的文件描述符数量,inactive缓存时间,valid:检查时间间隔,min_uses:在inactive时间段内使用了多少次加入缓存 - open_log_file_cache max=200 inactive=20s valid=1m min_uses=2; - - sendfile on; - tcp_nopush on; - #与浏览器的长连接 - keepalive_timeout 65;#长连接超时时间 - keepalive_requests 500;#500个请求以后,关闭长连接 - keepalive_disable msie6; - # 启用压缩 - gzip on; - gzip_disable "MSIE [1-6]\.(?!.*SV1)"; - gzip_proxied any; - gzip_types text/plain application/x-javascript application/javascript text/css application/xml; - gzip_vary on; #Vary: Accept-Encoding - gzip_static on; #如果有压缩好的 直接使用 - #超时时间 - proxy_connect_timeout 5; #连接proxy超时 - proxy_send_timeout 5; # proxy连接nginx超时 - proxy_read_timeout 60;# proxy响应超时 - # 开启缓存,2级目录 - proxy_cache_path /usr/local/nginx/proxy_cache levels=1:2 keys_zone=cache_one:200m inactive=1d max_size=20g; - proxy_ignore_headers X-Accel-Expires Expires Cache-Control; - proxy_hide_header Cache-Control; - proxy_hide_header Pragma; - - #反向代理服务器集群 - upstream server_pool{ - server localhost:8080 weight=1 max_fails=2 fail_timeout=30s; - server localhost:8081 weight=1 max_fails=2 fail_timeout=30s; - keepalive 200; # 最大的空闲的长连接数 + + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println( + "Usage: " + HttpServer.class.getSimpleName() + + " "); + return; } - - server { - listen 80; - server_name localhost 192.168.220.133; - - location / { - #长连接 - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - #Tomcat获取真实用户ip - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://server_pool; - } - # 状态监控 - location /nginx_status { - stub_status on; - access_log off; - allow 127.0.0.1; - allow 192.168.220.133; - deny all; - } - #用于清除缓存 - location ~ /purge(/.*) - { - allow 127.0.0.1; - allow 192.168.220.133; - deny all; - proxy_cache_purge cache_one $host$1$is_args$args; - } - # 静态文件加缓存 - location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|js|css|html|htm)?$ - { - expires 1d; - proxy_cache cache_one; - proxy_cache_valid 200 304 1d; - proxy_cache_valid any 1m; - proxy_cache_key $host$uri$is_args$args; - proxy_pass http://server_pool; - } - } - } \ No newline at end of file + int port = Integer.parseInt(args[0]); + new HttpServer(port).start(); + } + +} + +``` +####参考文档 +[Netty是什么?](https://www.jianshu.com/p/a4e03835921a) \ No newline at end of file