最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Netty 實(shí)現(xiàn)百萬(wàn)級(jí)連接服務(wù)的難點(diǎn)和優(yōu)點(diǎn)分析總結(jié)!

2022-10-31 16:32 作者:網(wǎng)星軟件  | 我要投稿

推送服務(wù)

還記得一年半前,做的一個(gè)項(xiàng)目需要用到 Android 推送服務(wù)。和 iOS 不同,Android 生態(tài)中沒(méi)有統(tǒng)一的推送服務(wù)。Google 雖然有 Google Cloud Messaging ,但是連國(guó)外都沒(méi)統(tǒng)一,更別說(shuō)國(guó)內(nèi)了,直接被墻。

所以之前在 Android 上做推送大部分只能靠輪詢。而我們之前在技術(shù)調(diào)研的時(shí)候,搜到了 jPush 的博客,上面介紹了一些他們的技術(shù)特點(diǎn),他們主要做的其實(shí)就是移動(dòng)網(wǎng)絡(luò)下的長(zhǎng)連接服務(wù)。單機(jī) 50W-100W 的連接的確是嚇我一跳!后來(lái)我們也采用了他們的免費(fèi)方案,因?yàn)槭且粋€(gè)受眾面很小的產(chǎn)品,所以他們的免費(fèi)版夠我們用了。一年多下來(lái),運(yùn)作穩(wěn)定,非常不錯(cuò)!

時(shí)隔兩年,換了部門后,竟然接到了一項(xiàng)任務(wù),優(yōu)化公司自己的長(zhǎng)連接服務(wù)端。

再次搜索網(wǎng)上技術(shù)資料后才發(fā)現(xiàn),相關(guān)的很多難點(diǎn)都被攻破,網(wǎng)上也有了很多的總結(jié)文章,單機(jī) 50W-100W 的連接完全不是夢(mèng),其實(shí)人人都可以做到。但是光有連接還不夠,QPS 也要一起上去。

所以,這篇文章就是匯總一下利用 Netty 實(shí)現(xiàn)長(zhǎng)連接服務(wù)過(guò)程中的各種難點(diǎn)和可優(yōu)化點(diǎn)。

Netty 是什么

Netty: http://netty.io/

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

官方的解釋最精準(zhǔn)了,其中最吸引人的就是高性能了。但是很多人會(huì)有這樣的疑問(wèn):直接用 NIO 實(shí)現(xiàn)的話,一定會(huì)更快吧?就像我直接手寫 JDBC 雖然代碼量大了點(diǎn),但是一定比 iBatis 快!

但是,如果了解 Netty 后你才會(huì)發(fā)現(xiàn),這個(gè)還真不一定!

利用 Netty 而不用 NIO 直接寫的優(yōu)勢(shì)有這些:

  • 高性能高擴(kuò)展的架構(gòu)設(shè)計(jì),大部分情況下你只需要關(guān)注業(yè)務(wù)而不需要關(guān)注架構(gòu)

  • Zero-Copy?技術(shù)盡量減少內(nèi)存拷貝

  • 為 Linux 實(shí)現(xiàn) Native 版 Socket

  • 寫同一份代碼,兼容 java 1.7 的 NIO2 和 1.7 之前版本的 NIO

  • Pooled Buffers?大大減輕?Buffer?和釋放?Buffer?的壓力

特性太多,大家可以去看一下《Netty in Action》這本書(shū)了解更多。

另外,Netty 源碼是一本很好的教科書(shū)!大家在使用的過(guò)程中可以多看看它的源碼,非常棒!

瓶頸是什么

想要做一個(gè)長(zhǎng)鏈服務(wù)的話,最終的目標(biāo)是什么?而它的瓶頸又是什么?

其實(shí)目標(biāo)主要就兩個(gè):

  1. 更多的連接

  2. 更高的 QPS

所以,下面就針對(duì)這兩個(gè)目標(biāo)來(lái)說(shuō)說(shuō)他們的難點(diǎn)和注意點(diǎn)吧。

更多的連接

非阻塞 IO

其實(shí)無(wú)論是用 Java NIO 還是用 Netty,達(dá)到百萬(wàn)連接都沒(méi)有任何難度。因?yàn)樗鼈兌际欠亲枞?IO,不需要為每個(gè)連接創(chuàng)建一個(gè)線程了。

欲知詳情,可以搜索一下BIO,NIO,AIO的相關(guān)知識(shí)點(diǎn)。

Java NIO 實(shí)現(xiàn)百萬(wàn)連接

ServerSocketChannel?ssc?=?ServerSocketChannel.open();??
Selector?sel?=?Selector.open();??
??
ssc.configureBlocking(false);??
ssc.socket().bind(new?InetSocketAddress(8080));??
SelectionKey?key?=?ssc.register(sel,?SelectionKey.OP_ACCEPT);??
??
while(true)?{??
????sel.select();??
????Iterator?it?=?sel.selectedKeys().iterator();??
????while(it.hasNext())?{??
????????SelectionKey?skey?=?(SelectionKey)it.next();??
????????it.remove();??
????????if(skey.isAcceptable())?{??
????????????ch?=?ssc.accept();??
????????}??
????}??
}??

這段代碼只會(huì)接受連過(guò)來(lái)的連接,不做任何操作,僅僅用來(lái)測(cè)試待機(jī)連接數(shù)極限。

大家可以看到這段代碼是 NIO 的基本寫法,沒(méi)什么特別的。

Netty 實(shí)現(xiàn)百萬(wàn)連接

NioEventLoopGroup?bossGroup?=??new?NioEventLoopGroup();??
NioEventLoopGroup?workerGroup=?new?NioEventLoopGroup();??
ServerBootstrap?bootstrap?=?new?ServerBootstrap();??
bootstrap.group(bossGroup,?workerGroup);??
??
bootstrap.channel(?NioServerSocketChannel.class);??
??
bootstrap.childHandler(new?ChannelInitializer<SocketChannel>()?{??
????@Override?protected?void?initChannel(SocketChannel?ch)?throws?Exception?{??
????????ChannelPipeline?pipeline?=?ch.pipeline();??
????????//todo:?add?handler??
????}});??
bootstrap.bind(8080).sync();??

這段其實(shí)也是非常簡(jiǎn)單的 Netty 初始化代碼。同樣,為了實(shí)現(xiàn)百萬(wàn)連接根本沒(méi)有什么特殊的地方。

瓶頸到底在哪

上面兩種不同的實(shí)現(xiàn)都非常簡(jiǎn)單,沒(méi)有任何難度,那有人肯定會(huì)問(wèn)了:實(shí)現(xiàn)百萬(wàn)連接的瓶頸到底是什么?

其實(shí)只要 java 中用的是非阻塞 IO(NIO 和 AIO 都算),那么它們都可以用單線程來(lái)實(shí)現(xiàn)大量的 Socket 連接。不會(huì)像 BIO 那樣為每個(gè)連接創(chuàng)建一個(gè)線程,因?yàn)榇a層面不會(huì)成為瓶頸。

其實(shí)真正的瓶頸是在 Linux 內(nèi)核配置上,默認(rèn)的配置會(huì)限制全局最大打開(kāi)文件數(shù)(Max Open Files)還會(huì)限制進(jìn)程數(shù)。所以需要對(duì) Linux 內(nèi)核配置進(jìn)行一定的修改才可以。

這個(gè)東西現(xiàn)在看似很簡(jiǎn)單,按照網(wǎng)上的配置改一下就行了,但是大家一定不知道第一個(gè)研究這個(gè)人有多難。

如何驗(yàn)證

讓服務(wù)器支持百萬(wàn)連接一點(diǎn)也不難,我們當(dāng)時(shí)很快就搞定了一個(gè)測(cè)試服務(wù)端,但是最大的問(wèn)題是,我怎么去驗(yàn)證這個(gè)服務(wù)器可以支撐百萬(wàn)連接呢?

我們用 Netty 寫了一個(gè)測(cè)試客戶端,它同樣用了非阻塞 IO ,所以不用開(kāi)大量的線程。但是一臺(tái)機(jī)器上的端口數(shù)是有限制的,用root權(quán)限的話,最多也就 6W 多個(gè)連接了。所以我們這里用 Netty 寫一個(gè)客戶端,用盡單機(jī)所有的連接吧。

NioEventLoopGroup?workerGroup?=??new?NioEventLoopGroup();??
Bootstrap?b?=?new?Bootstrap();??
b.group(workerGroup);??
b.channel(?NioSocketChannel.class);??
??
b.handler(new?ChannelInitializer<SocketChannel>()?{??
????@Override??
????public?void?initChannel(SocketChannel?ch)?throws?Exception?{??
????????ChannelPipeline?pipeline?=?ch.pipeline();??
????????//todo:add?handler??
????}??
????});??
??
for?(int?k?=?0;?k?<?60000;?k++)?{??
????//請(qǐng)自行修改成服務(wù)端的IP??
????b.connect(127.0.0.1,?8080);??
}??

代碼同樣很簡(jiǎn)單,只要連上就行了,不需要做任何其他的操作。

這樣只要找到一臺(tái)電腦啟動(dòng)這個(gè)程序即可。這里需要注意一點(diǎn),客戶端最好和服務(wù)端一樣,修改一下 Linux 內(nèi)核參數(shù)配置。

怎么去找那么多機(jī)器

按照上面的做法,單機(jī)最多可以有 6W 的連接,百萬(wàn)連接起碼需要17臺(tái)機(jī)器!

如何才能突破這個(gè)限制呢?其實(shí)這個(gè)限制來(lái)自于網(wǎng)卡。我們后來(lái)通過(guò)使用虛擬機(jī),并且把虛擬機(jī)的虛擬網(wǎng)卡配置成了橋接模式解決了問(wèn)題。

根據(jù)物理機(jī)內(nèi)存大小,單個(gè)物理機(jī)起碼可以跑4-5個(gè)虛擬機(jī),所以最終百萬(wàn)連接只要4臺(tái)物理機(jī)就夠了。

討巧的做法

除了用虛擬機(jī)充分壓榨機(jī)器資源外,還有一個(gè)非常討巧的做法,這個(gè)做法也是我在驗(yàn)證過(guò)程中偶然發(fā)現(xiàn)的。

根據(jù) TCP/IP 協(xié)議,任何一方發(fā)送FIN后就會(huì)啟動(dòng)正常的斷開(kāi)流程。而如果遇到網(wǎng)絡(luò)瞬斷的情況,連接并不會(huì)自動(dòng)斷開(kāi)。

那我們是不是可以這樣做?

  1. 啟動(dòng)服務(wù)端,千萬(wàn)別設(shè)置 Socket 的keep-alive屬性,默認(rèn)是不設(shè)置的

  2. 用虛擬機(jī)連接服務(wù)器

  3. 強(qiáng)制關(guān)閉虛擬機(jī)

  4. 修改虛擬機(jī)網(wǎng)卡的 MAC 地址,重新啟動(dòng)并連接服務(wù)器

  5. 服務(wù)端接受新的連接,并保持之前的連接不斷

我們要驗(yàn)證的是服務(wù)端的極限,所以只要一直讓服務(wù)端認(rèn)為有那么多連接就行了,不是嗎?

經(jīng)過(guò)我們的試驗(yàn)后,這種方法和用真實(shí)的機(jī)器連接服務(wù)端的表現(xiàn)是一樣的,因?yàn)榉?wù)端只是認(rèn)為對(duì)方網(wǎng)絡(luò)不好罷了,不會(huì)將你斷開(kāi)。

另外,禁用keep-alive是因?yàn)槿绻唤?,Socket 連接會(huì)自動(dòng)探測(cè)連接是否可用,如果不可用會(huì)強(qiáng)制斷開(kāi)。

更高的 QPS

由于 NIO 和 Netty 都是非阻塞 IO,所以無(wú)論有多少連接,都只需要少量的線程即可。而且 QPS 不會(huì)因?yàn)檫B接數(shù)的增長(zhǎng)而降低(在內(nèi)存足夠的前提下)。

而且 Netty 本身設(shè)計(jì)得足夠好了,Netty 不是高 QPS 的瓶頸。那高 QPS 的瓶頸是什么?

是數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)!

如何優(yōu)化數(shù)據(jù)結(jié)構(gòu)

首先要熟悉各種數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)是必需的,但是在復(fù)雜的項(xiàng)目中,不是用了一個(gè)集合就可以搞定的,有時(shí)候往往是各種集合的組合使用。

既要做到高性能,還要做到一致性,還不能有死鎖,這里難度真的不小…

我在這里總結(jié)的經(jīng)驗(yàn)是,不要過(guò)早優(yōu)化。優(yōu)先考慮一致性,保證數(shù)據(jù)的準(zhǔn)確,然后再去想辦法優(yōu)化性能。

因?yàn)橐恢滦员刃阅苤匾枚?,而且很多性能?wèn)題在量小和量大的時(shí)候,瓶頸完全會(huì)在不同的地方。所以,我覺(jué)得最佳的做法是,編寫過(guò)程中以一致性為主,性能為輔;代碼完成后再去找那個(gè) TOP1,然后去解決它!

解決 CPU 瓶頸

在做這個(gè)優(yōu)化前,先在測(cè)試環(huán)境中去狠狠地壓你的服務(wù)器,量小量大,天壤之別。

有了壓力測(cè)試后,就需要用工具來(lái)發(fā)現(xiàn)性能瓶頸了!

我喜歡用的是 VisualVM,打開(kāi)工具后看抽樣器(Sample),根據(jù)自用時(shí)間(Self Time (CPU))倒序,排名第一的就是你需要去優(yōu)化的點(diǎn)了!

備注:Sample 和 Profiler 有什么區(qū)別?前者是抽樣,數(shù)據(jù)不是最準(zhǔn)但是不影響性能;后者是統(tǒng)計(jì)準(zhǔn)確,但是非常影響性能。如果你的程序非常耗 CPU,那么盡量用 Sample,否則開(kāi)啟 Profiler 后降低性能,反而會(huì)影響準(zhǔn)確性。

圖片

還記得我們項(xiàng)目第一次發(fā)現(xiàn)的瓶頸竟然是ConcurrentLinkedQueue這個(gè)類中的size()方法。量小的時(shí)候沒(méi)有影響,但是Queue很大的時(shí)候,它每次都是從頭統(tǒng)計(jì)總數(shù)的,而這個(gè)size()方法我們又是非常頻繁地調(diào)用的,所以對(duì)性能產(chǎn)生了影響。

size()的實(shí)現(xiàn)如下:

public?int?size()?{??
????int?count?=?0;??
????for?(Node<E>?p?=?first();?p?!=?null;?p?=?succ(p))??
????if?(p.item?!=?null)??
????//?Collection.size()?spec?says?to?max?out??
????if?(++count?==?Integer.MAX_VALUE)??
????break;??
????return?count;??
}??

后來(lái)我們通過(guò)額外使用一個(gè)AtomicInteger來(lái)計(jì)數(shù),解決了問(wèn)題。但是分離后豈不是做不到高一致性呢?沒(méi)關(guān)系,我們的這部分代碼關(guān)心最終一致性,所以只要保證最終一致就可以了。

總之,具體案例要具體分析,不同的業(yè)務(wù)要用不同的實(shí)現(xiàn)。

解決 GC 瓶頸

GC 瓶頸也是 CPU 瓶頸的一部分,因?yàn)椴缓侠淼?GC 會(huì)大大影響 CPU 性能。

這里還是在用 VisualVM,但是你需要裝一個(gè)插件:VisualGC

圖片

有了這個(gè)插件后,你就可以直觀的看到 GC 活動(dòng)情況了。

按照我們的理解,在壓測(cè)的時(shí)候,有大量的 New GC 是很正常的,因?yàn)橛写罅康膶?duì)象在創(chuàng)建和銷毀。

但是一開(kāi)始有很多 Old GC 就有點(diǎn)說(shuō)不過(guò)去了!

后來(lái)發(fā)現(xiàn),在我們壓測(cè)環(huán)境中,因?yàn)?Netty 的 QPS 和連接數(shù)關(guān)聯(lián)不大,所以我們只連接了少量的連接。內(nèi)存分配得也不是很多。

而 JVM 中,默認(rèn)的新生代和老生代的比例是1:2,所以大量的老生代被浪費(fèi)了,新生代不夠用。

通過(guò)調(diào)整?-XX:NewRatio?后,Old GC 有了顯著的降低。

但是,生產(chǎn)環(huán)境又不一樣了,生產(chǎn)環(huán)境不會(huì)有那么大的 QPS,但是連接會(huì)很多,連接相關(guān)的對(duì)象存活時(shí)間非常長(zhǎng),所以生產(chǎn)環(huán)境更應(yīng)該分配更多的老生代。

總之,GC 優(yōu)化和 CPU 優(yōu)化一樣,也需要不斷調(diào)整,不斷優(yōu)化,不是一蹴而就的。

其他優(yōu)化

如果你已經(jīng)完成了自己的程序,那么一定要看看《Netty in Action》作者的這個(gè)網(wǎng)站:Netty Best Practices a.k.a Faster == Better。

相信你會(huì)受益匪淺,經(jīng)過(guò)里面提到的一些小小的優(yōu)化后,我們的整體 QPS 提升了很多。

最后一點(diǎn)就是,java 1.7 比 java 1.6 性能高很多!因?yàn)?Netty 的編寫風(fēng)格是事件機(jī)制的,看似是 AIO???java 1.6 是沒(méi)有 AIO 的,java 1.7 是支持 AIO 的,所以如果用 java 1.7 的話,性能也會(huì)有顯著提升。

最后成果

經(jīng)過(guò)幾周的不斷壓測(cè)和不斷優(yōu)化了,我們?cè)谝慌_(tái)16核、120G內(nèi)存(JVM只分配8G)的機(jī)器上,用 java 1.6 達(dá)到了60萬(wàn)的連接和20萬(wàn)的QPS。

其實(shí)這還不是極限,JVM 只分配了8G內(nèi)存,內(nèi)存配置再大一點(diǎn)連接數(shù)還可以上去;

QPS 看似很高,System Load Average 很低,也就是說(shuō)明瓶頸不在 CPU 也不在內(nèi)存,那么應(yīng)該是在 IO 了!上面的 Linux 配置是為了達(dá)到百萬(wàn)連接而配置的,并沒(méi)有針對(duì)我們自己的業(yè)務(wù)場(chǎng)景去做優(yōu)化。

因?yàn)槟壳靶阅芡耆珘蛴?,線上單機(jī) QPS 最多才 1W,所以我們先把精力放在了其他地方。相信后面我們還會(huì)去繼續(xù)優(yōu)化這塊的性能,期待 QPS 能有更大的突破!

Netty 實(shí)現(xiàn)百萬(wàn)級(jí)連接服務(wù)的難點(diǎn)和優(yōu)點(diǎn)分析總結(jié)!的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
教育| 怀安县| 庄河市| 景东| 同仁县| 抚远县| 徐汇区| 晋中市| 遂平县| 阳信县| 庆安县| 手机| 稷山县| 深水埗区| 开封市| 资讯 | 邵阳市| 万载县| 长治县| 萨嘎县| 罗源县| 斗六市| 台前县| 玉田县| 林周县| 闻喜县| 耒阳市| 渭源县| 赣州市| 万山特区| 阳原县| 临城县| 潍坊市| 墨竹工卡县| 金堂县| 宁蒗| 荥经县| 固原市| 长治市| 和田县| 锡林郭勒盟|