最近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/