最近Nginx发布了1.9.1版,其中一个新的特性就是支持socket的SO_REUSEPORT选项。这个socket的SO_REUSEPORT选项已经有许多现实世界的应用。对NGINX而言,它经过将链接均衡的分给多个进程以提高性能。SO_REUSEPORT已经在一些操做系统上实现了支持。这个选项容许多个socket监听同一个IP地址+端口。内核负载均衡这些进来的sockets链接,将这些socket有效的分片。 尽管Java很早就有一个特性请求:JDK-6432031,可是时至今日,Oracle JDK依然不支持这个选项,所以咱们只能经过hack的方式在Java中使用此特性。html
Google已经在内部服务器中开启了SO_REUSEPORT
这个特性: Scaling Techniques for Servers with High Connection Rateslinux
####SO_REUSEPORTgit
就像聂永的博客中所说:github
运行在Linux系统上网络应用程序,为了利用多核的优点,通常使用如下比较典型的多进程/多线程服务器模型:web
单线程listen/accept,多个工做线程接收任务分发,虽CPU的工做负载再也不是问题,但会存在:bootstrap
全部工做线程都accept()在同一个服务器套接字上呢,同样存在问题:windows
上面模型虽然能够作到线程和CPU核绑定,但都会存在:缓存
SO_REUSEPORT
在*BSD
平台早已经实现,而Linux平台则由谷歌工程师实现并于2013年正式归入Linux的trunk (kernel 3.9)。服务器
####当前的操做系统支持状况:网络
SO_REUSEADDR
选项,没有SO_REUSEPORT
。windows上设置了SO_REUSEADDR
的socket其行为与BSD上设定了SO_REUSEPORT
和SO_REUSEADDR
的行为大体同样;####和SO_REUSEADDR
的区别 #####SO_REUSEADDR
提供以下四个功能:
当使用通配符的时候更为复杂,这里有一张表格列出了各类状况(服务器的IP地址是192.168.0.1):
SO_REUSEADDR | socketA | socketB | Result |
---|---|---|---|
ON/OFF | 192.168.0.1:21 | 192.168.0.1:21 | Error (EADDRINUSE) |
ON/OFF | 192.168.0.1:21 | 10.0.0.1:21 | OK |
ON/OFF | 10.0.0.1:21 | 192.168.0.1:21 | OK |
OFF | 0.0.0.0:21 | 192.168.1.0:21 | Error (EADDRINUSE) |
OFF | 192.168.1.0:21 | 0.0.0.0:21 | Error (EADDRINUSE) |
ON | 0.0.0.0:21 | 192.168.1.0:21 | OK |
ON | 192.168.1.0:21 | 0.0.0.0:21 | OK |
ON/OFF | 0.0.0.0:21 | 0.0.0.0:21 | Error (EADDRINUSE) |
####SO_REUSEPORT
选项有以下语义:
####Netty的实现
Netty不是惟一经过hack方式实现SO_REUSEPORT
特性的Java网络框架。好比下面的方式,使用sun.nio.ch.Net
进行设置:
import sun.nio.ch.Net; ...... public void setReusePort(ServerSocketChannel serverChannel) { try { Field fieldFd = serverChannel.getClass().getDeclaredField("fd"); //NoSuchFieldException fieldFd.setAccessible(true); FileDescriptor fd = (FileDescriptor)fieldFd.get(serverChannel); //IllegalAccessException Method methodSetIntOption0 = Net.class.getDeclaredMethod("setIntOption0", FileDescriptor.class, Boolean.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE); methodSetIntOption0.setAccessible(true); methodSetIntOption0.invoke(null, fd, false, '\uffff', SO_REUSEPORT, 1); } catch (Exception e) { System.out.println(e.toString()); } } ``` 可是本文将关注Netty,由于Netty提供了一个经过JNI封装的库,能够更方便的进行`SO_REUSEPORT`设置。 自4.0.16版本,Netty为Linux提供了 native socket transport,经过JNI的方式实现,它能够提供更高的性能和极少的垃圾回收。 * 它兼容NIO的方式,只需改为: ```Java NioEventLoopGroup → EpollEventLoopGroup NioEventLoop → EpollEventLoop NioServerSocketChannel → EpollServerSocketChannel NioSocketChannel → EpollSocketChannel ``` * Maven pom.xml加入: ```Xml <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.2.3.Final</version> </extension> </extensions> ... </build> <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-transport-native-epoll</artifactId> <version>${project.version}</version> <classifier>${os.detected.classifier}</classifier> </dependency> ... </dependencies> ``` * SBT配置文件的话则加入: ```Sbt "io.netty" % "netty-transport-native-epoll" % "${project.version}" classifier "linux-x86_64" ``` * 代码中设置`SO_REUSEPORT`: ```Java bootstrap.option(EpollChannelOption.SO_REUSEPORT, true) ``` * 很简单,完整的代码能够参照:[WebServer.scala](https://github.com/smallnest/C1000K-Servers/blob/master/netty/src/main/scala/com/colobu/webtest/netty/WebServer.scala) ####参考文档 * Socket Sharding in NGINX Release 1.9.1 https://github.com/tokuhirom/jetty-so_reuseport-sample * SO_REUSEPORT学习笔记 http://www.cnblogs.com/mydomain/archive/2011/08/23/2150567.html http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t http://blog.chinaunix.net/uid-26133817-id-4814341.html https://lwn.net/Articles/542629/