前沿拓展:
MSXML 4.0 Servi
MSXML全名是:「Microsoft XML Cor可普味電憲粒e Services」,主要是用來(lái)執(zhí)行或開(kāi)發(fā)經(jīng)由 XML 所設(shè)計(jì)的最新應(yīng)用程序。
微軟留揚(yáng)左星巖吧曲傷陸附正式發(fā)布了其XML的核心服務(wù)組件—MSXML 4.0。和MSXML 3.0相比,MSXML 4.0提供了大量的新功能和功能改進(jìn)。其中包括:對(duì)XML模式語(yǔ)言的支持,更快的分析器和XSLT引擎,對(duì)XML流更好的處理,更好的一致性支持。 MSXML 4.0并不是MSXML 3.0的替代產(chǎn)品,因?yàn)樵?.0中的一些過(guò)時(shí)功能已經(jīng)在4.0中徹底去除了。所以4.0可以和3.0(甚至更早的版本)同時(shí)安裝。
MSXML 4.0 Service Pack 2 (SP2)是MSXML 4.0和MSXML 4.0 Service Pack 1 (SP1)的完全取代版本亮穩(wěn)。它提供了大量的安全和程序錯(cuò)誤修復(fù)。
MSXML 4.0 SP2并不能取代MSXML 3.0,因?yàn)樗巡辉僦С忠恍┡f的以及不一致的功能。所質(zhì)肥否掌權(quán)塊破以用戶可能必須同時(shí)運(yùn)行MSX嚴(yán)以合終屋業(yè)飯換ML 4.0和MSXML 3.0或更前版本。安裝Nero 8時(shí)要安裝最新版本MSXML 4.0,按正常的安裝就可以了對(duì)你的電腦沒(méi)有多大的影響
單機(jī)百萬(wàn)連接調(diào)優(yōu)
準(zhǔn)備兩臺(tái)Linux服務(wù)器,一個(gè)充當(dāng)服務(wù)端,一個(gè)充當(dāng)客戶端。
服務(wù)端
**作系統(tǒng):CentOS 7配置:4核8GIP:192.168.118.138
客戶端
**作系統(tǒng):CentOS 7配置:4核8GIP:192.168.118.139
服務(wù)端和客戶端均要配置java環(huán)境,基于jdk1.8。
如何模擬百萬(wàn)連接#
如果服務(wù)端只開(kāi)一個(gè)端口,客戶端連接的時(shí)候,端口號(hào)是有數(shù)量限制的(非root用戶,從1024到65535,大約6w),所以服務(wù)端開(kāi)啟一個(gè)端口,客戶端和服務(wù)端的連接最多6w個(gè)左右。
為了模擬單機(jī)百萬(wàn)連接,我們?cè)诜?wù)端開(kāi)啟多個(gè)端口,例如8000~8100,一共100個(gè)端口,客戶端還是6w的連接,但是可以連接服務(wù)端的不同端口,所以就可以模擬服務(wù)端百萬(wàn)連接的情況。
準(zhǔn)備服務(wù)端程序#
服務(wù)端程序的主要邏輯是:
綁定8000端口一直到8099端口,一共100個(gè)端口,每2s鐘統(tǒng)計(jì)一下連接數(shù)。
channelActive觸發(fā)的時(shí)候,連接+1, channelInactive觸發(fā)的時(shí)候,連接-1。
代碼見(jiàn):Server.java
準(zhǔn)備客戶端程序#
客戶端程序的主要邏輯是:
循環(huán)連接服務(wù)端的端口(從8000一直到8099)。
代碼見(jiàn):Client.java
準(zhǔn)備好客戶端和服務(wù)端的代碼后,打包成Client.jar和Server.jar并上傳到客戶端和服務(wù)端的/data/app目錄下。打包配置參考pom.xml
服務(wù)端和客戶端在/data/app下分別準(zhǔn)備兩個(gè)啟動(dòng)腳本,其中服務(wù)端準(zhǔn)備的腳本為startServer.sh, 客戶端準(zhǔn)備的腳本為startClient.sh,內(nèi)容如下:
startServer.sh
java -jar server.jar -Xms6.5g -Xmx6.5g -XX:NewSize=5.5g -XX:MaxNewSize=5.5g -XX:MaxDirectMemorySize=1g
startClient.sh
java -jar client.jar -Xms6.5g -Xmx6.5g -XX:NewSize=5.5g -XX:MaxNewSize=5.5g -XX:MaxDirectMemorySize=1g
腳本文件見(jiàn):startServer.sh 和 startClient.sh
先啟動(dòng)服務(wù)端
cd /data/app/
./startServer.sh
查看日志,待服務(wù)端把100個(gè)端口都綁定好以后。
在啟動(dòng)客戶端
cd /data/app/
./startClient.sh
第二查看服務(wù)端日志,服務(wù)端在支撐了3942個(gè)端口號(hào)以后,報(bào)了如下錯(cuò)誤:
Caused by: java.io.IOException: Too many open files
at sun.nio.ch.FileDispatcherImpl.init(Native Method)
at sun.nio.ch.FileDispatcherImpl.<clinit>(FileDispatcherImpl.java:35)
突破局部文件句柄限制#
使用ulimit -n命令可以查看一個(gè)jvm進(jìn)程最多可以打開(kāi)的文件個(gè)數(shù),這個(gè)是局部文件句柄限制,默認(rèn)是1024,我們可以修改這個(gè)值
vi /etc/security/limits.conf
增加如下兩行
* hard nofile 1000000
* soft nofile 1000000
以上配置表示每個(gè)進(jìn)程可以打開(kāi)的最大文件數(shù)是一百萬(wàn)。
突破全局文件句柄限制#
除了突破局部文件句柄數(shù)限制,還需要突破全局文件句柄數(shù)限制,修改如下配置文件
vi /proc/sys/fs/file-max
將這個(gè)數(shù)量修改為一百萬(wàn)
echo 1000000 > /proc/sys/fs/file-max
通過(guò)這種方式修改的配置在重啟后失效,如果要使重啟也生效,需要修改如下配置
vi /etc/sysctl.conf
在文件末尾加上
fs.file-max=1000000
服務(wù)端和客戶端在調(diào)整完局部文件句柄限制和全局文件句柄限制后,再次啟動(dòng)服務(wù)端,待端口綁定完畢后,啟動(dòng)客戶端。
查看服務(wù)端日志,可以看到,服務(wù)端單機(jī)連接數(shù)已經(jīng)達(dá)到百萬(wàn)級(jí)別。
…..
connections: 434703
connections: 438238
connections: 441195
connections: 444082
connections: 447596
…..
connections: 920435
connections: 920437
connections: 920439
connections: 920442
connections: 920443
connections: 920445
…..
Netty應(yīng)用級(jí)別調(diào)優(yōu)#場(chǎng)景#
服務(wù)端接受到客戶端的數(shù)據(jù),進(jìn)行一些相對(duì)耗時(shí)的**作(比如數(shù)據(jù)庫(kù)查詢,數(shù)據(jù)處理),第二把結(jié)果返回給客戶端。
模擬耗時(shí)**作#
在服務(wù)端,模擬通過(guò)sleep方法來(lái)模擬耗時(shí)**作,規(guī)則如下:
在90.0%情況下,處理時(shí)間為1ms在95.0%情況下,處理時(shí)間為10ms在99.0%情況下,處理時(shí)間為100ms在99.9%情況下,處理時(shí)間為1000ms
代碼如下
protected Object getResult(ByteBuf data) {
int level = ThreadLocalRandom.current().nextInt(1, 1000);
int time;
if (level <= 900) {
time = 1;
} else if (level <= 950) {
time = 10;
} else if (level <= 990) {
time = 100;
} else {
time = 1000;
}
try {
Thread.sleep(time);
} catch (InterruptedException e) {
}
return data;
}
客戶端統(tǒng)計(jì)QPS和AVG邏輯#
獲取當(dāng)前時(shí)間戳,客戶端在和服務(wù)端建立連接后,會(huì)每隔1s給服務(wù)端發(fā)送數(shù)據(jù),發(fā)送的數(shù)據(jù)就是當(dāng)前的時(shí)間戳,服務(wù)端獲取到這個(gè)時(shí)間戳以后,會(huì)把這個(gè)時(shí)間戳再次返回給客戶端,所以客戶端會(huì)拿到發(fā)送時(shí)候的時(shí)間戳,第二客戶端用當(dāng)前時(shí)間減去收到的時(shí)間戳,就是這個(gè)數(shù)據(jù)包的處理時(shí)間,記錄下這個(gè)時(shí)間,第二統(tǒng)計(jì)數(shù)據(jù)包發(fā)送的次數(shù),根據(jù)這兩個(gè)變量,可以求出QPS和AVG,其中:
QPS 等于
客戶端源碼參考:Client.java
服務(wù)端源碼參考:Server.java
服務(wù)端在不做任何優(yōu)化的情況下,關(guān)鍵代碼如下
…
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));
ch.pipeline().addLast(/*businessGroup,*/ ServerBusinessHandler.INSTANCE);
// ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);
}
});
…
@ChannelHandler.Sharable
public class ServerBusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {
public static final ChannelHandler INSTANCE = new ServerBusinessHandler();
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
ByteBuf data = Unpooled.directBuffer();
data.writeBytes(msg);
Object result = getResult(data);
ctx.channel().writeAndFlush(result);
}
protected Object getResult(ByteBuf data) {
int level = ThreadLocalRandom.current().nextInt(1, 1000);
int time;
if (level <= 900) {
time = 1;
} else if (level <= 950) {
time = 10;
} else if (level <= 990) {
time = 100;
} else {
time = 1000;
}
try {
Thread.sleep(time);
} catch (InterruptedException e) {
}
return data;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// ignore
}
}
運(yùn)行服務(wù)端和客戶端,查看客戶端日志
…..
qps: 1466, avg response time: 35.68182
qps: 832, avg response time: 214.28384
qps: 932, avg response time: 352.59363
qps: 965, avg response time: 384.59448
qps: 957, avg response time: 403.33804
qps: 958, avg response time: 424.5246
qps: 966, avg response time: 433.35272
qps: 980, avg response time: 484.2116
qps: 986, avg response time: 478.5395
…..
優(yōu)化方案一:使用自定義線程池處理耗時(shí)邏輯#
將服務(wù)端代碼做如下調(diào)整
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));
//ch.pipeline().addLast(/*businessGroup,*/ ServerBusinessHandler.INSTANCE);
ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);
}
});
其中ServerBusinessThreadPoolHandler中,使用了自定義的線程池來(lái)處理耗時(shí)的getResult方法。關(guān)鍵代碼如下:
private static ExecutorService threadPool = Executors.newFixedThreadPool(1000);
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
ByteBuf data = Unpooled.directBuffer();
data.writeBytes(msg);
threadPool.submit(() -> {
Object result = getResult(data);
ctx.channel().writeAndFlush(result);
});
}
再次運(yùn)行服務(wù)端和客戶端,可以查看客戶端日志,QPS和AVG指標(biāo)都有明顯的改善
….
qps: 1033, avg response time: 17.690498
qps: 1018, avg response time: 17.133448
qps: 1013, avg response time: 15.563113
qps: 1010, avg response time: 15.415672
qps: 1009, avg response time: 16.049961
qps: 1008, avg response time: 16.179882
qps: 1007, avg response time: 16.120466
qps: 1006, avg response time: 15.822202
qps: 1006, avg response time: 15.987518
….
實(shí)際生產(chǎn)過(guò)程中,Executors.newFixedThreadPool(1000);中配置的數(shù)量需要通過(guò)壓測(cè)來(lái)驗(yàn)證。
優(yōu)化方案二:使用Netty原生的線程池優(yōu)化#
我們可以通過(guò)Netty提供的線程池來(lái)處理耗時(shí)的Handler,這樣的話,無(wú)需調(diào)整Handler的邏輯(對(duì)原有Handler無(wú)代碼侵入),關(guān)鍵代碼:
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));
// ch.pipeline().addLast(ServerBusinessHandler.INSTANCE);
// 使用業(yè)務(wù)線程池方式
// ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);
// 使用Netty自帶線程池方式
ch.pipeline().addLast(businessGroup,ServerBusinessHandler.INSTANCE);
}
});
其中businessGroup是Netty自帶的線程池
EventLoopGroup businessGroup = new NioEventLoopGroup(1000);
ServerBusinessHandler中的所有方法,都會(huì)在businessGroup中執(zhí)行。
再次啟動(dòng)服務(wù)端和客戶端,查看客戶端日志
…..
qps: 1027, avg response time: 23.833092
qps: 1017, avg response time: 20.98855
qps: 1014, avg response time: 18.220013
qps: 1012, avg response time: 17.447332
qps: 1010, avg response time: 16.502508
qps: 1010, avg response time: 15.692251
qps: 1009, avg response time: 15.968423
qps: 1008, avg response time: 15.888149
…..更多優(yōu)化建議#
參考Netty性能調(diào)優(yōu)奇技淫巧還有其他的嗎?
1.如果QPS過(guò)高,數(shù)據(jù)傳輸過(guò)快的情況下,調(diào)用writeAndFlush可以考慮拆分成多次write,第二單次flush,也就是批量flush**作
2.分配和釋放內(nèi)存盡量在reactor線程內(nèi)部做,這樣內(nèi)存就都可以在reactor線程內(nèi)部管理
3.盡量使用堆外內(nèi)存,盡量減少內(nèi)存的copy**作,使用CompositeByteBuf可以將多個(gè)ByteBuf組合到一起讀寫(xiě)
4.外部線程連續(xù)調(diào)用eventLoop的異步調(diào)用方法的時(shí)候,可以考慮把這些**作封裝成一個(gè)task,提交到eventLoop,這樣就不用多次跨線程
5.盡量調(diào)用ChannelHandlerContext.writeXXX()方法而不是channel.writeXXX()方法,前者可以減少pipeline的遍歷
6.如果一個(gè)ChannelHandler無(wú)數(shù)據(jù)共享,那么可以搞成單例模式,標(biāo)注@Shareable,節(jié)省對(duì)象開(kāi)銷(xiāo)對(duì)象
7.如果要做網(wǎng)絡(luò)**類(lèi)似的功能,盡量復(fù)用eventLoop,可以避免跨reactor線程
拓展知識(shí):
原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請(qǐng)注明出處:http://xiesong.cn/39415.html