mqtt连接失败_Netty实战:如何让单机下Netty支持百万长连接?
單機(jī)下能不能讓我們的網(wǎng)絡(luò)應(yīng)用支持百萬連接?可以,但是有很多的工作要做。而且要考慮到單機(jī)的系統(tǒng)資源消耗能否支撐百萬并發(fā)
一、操作系統(tǒng)優(yōu)化
首先就是要突破操作系統(tǒng)的限制。
在Linux平臺(tái)上,無論編寫客戶端程序還是服務(wù)端程序,在進(jìn)行高并發(fā)TCP連接處理時(shí),最高的并發(fā)數(shù)量都要受到系統(tǒng)對(duì)用戶單一進(jìn)程同時(shí)可打開文件數(shù)量的限制(這是因?yàn)橄到y(tǒng)為每個(gè)TCP連接都要?jiǎng)?chuàng)建一個(gè)socket句柄,每個(gè)socket句柄同時(shí)也是一個(gè)文件句柄)。
可使用ulimit命令查看系統(tǒng)允許當(dāng)前用戶進(jìn)程打開的文件數(shù)限制:$ ulimit -n 1024
這表示當(dāng)前用戶的每個(gè)進(jìn)程最多允許同時(shí)打開1024個(gè)文件,這1024個(gè)文件中還得除去每個(gè)進(jìn)程必然打開的標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯(cuò)誤,服務(wù)器監(jiān)聽 socket,進(jìn)程間通訊的unix域socket等文件,那么剩下的可用于客戶端socket連接的文件數(shù)就只有大概1024-10=1014個(gè)左右。也就是說缺省情況下,基于Linux的通訊程序最多允許同時(shí)1014個(gè)TCP并發(fā)連接。
對(duì)于想支持更高數(shù)量的TCP并發(fā)連接的通訊處理程序,就必須修改Linux對(duì)當(dāng)前用戶的進(jìn)程同時(shí)打開的文件數(shù)量。
修改單個(gè)進(jìn)程打開最大文件數(shù)限制的最簡單的辦法就是使用ulimit命令:$ ulimit –n 1000000
如果系統(tǒng)回顯類似于"Operation not permitted"之類的話,說明上述限制修改失敗,實(shí)際上是因?yàn)樵谥兄付ǖ臄?shù)值超過了Linux系統(tǒng)對(duì)該用戶打開文件數(shù)的軟限制或硬限制。因此,就需要修改Linux系統(tǒng)對(duì)用戶的關(guān)于打開文件數(shù)的軟限制和硬限制。
軟限制(soft limit):是指Linux在當(dāng)前系統(tǒng)能夠承受的范圍內(nèi)進(jìn)一步限制用戶同時(shí)打開的文件數(shù);
硬限制(hardlimit):是根據(jù)系統(tǒng)硬件資源狀況(主要是系統(tǒng)內(nèi)存)計(jì)算出來的系統(tǒng)最多可同時(shí)打開的文件數(shù)量。
第一步,修改/etc/security/limits.conf文件,在文件中添加如下行:
* soft nofile 1000000* hard nofile 1000000'*'號(hào)表示修改所有用戶的限制;
soft或hard指定要修改軟限制還是硬限制;1000000則指定了想要修改的新的限制值,即最大打開文件數(shù)(請(qǐng)注意軟限制值要小于或等于硬限制)。修改完后保存文件。
第二步,修改/etc/pam.d/login文件,在文件中添加如下行:
session required /lib/security/pam_limits.so這是告訴Linux在用戶完成系統(tǒng)登錄后,應(yīng)該調(diào)用pam_limits.so模塊來設(shè)置系統(tǒng)對(duì)該用戶可使用的各種資源數(shù)量的最大限制(包括用戶可打開的最大文件數(shù)限制),而pam_limits.so模塊就會(huì)從/etc/security/limits.conf文件中讀取配置來設(shè)置這些限制值。修改完后保存此文件。
第三步,查看Linux系統(tǒng)級(jí)的最大打開文件數(shù)限制,使用如下命令:
[root@VM_0_15_centos ~]# cat /proc/sys/fs/file-max98566這表明這臺(tái)Linux系統(tǒng)最多允許同時(shí)打開(即包含所有用戶打開文件數(shù)總和)98566個(gè)文件,是Linux系統(tǒng)級(jí)硬限制,所有用戶級(jí)的打開文件數(shù)限制都不應(yīng)超過這個(gè)數(shù)值。通常這個(gè)系統(tǒng)級(jí)硬限制是Linux系統(tǒng)在啟動(dòng)時(shí)根據(jù)系統(tǒng)硬件資源狀況計(jì)算出來的最佳的最大同時(shí)打開文件數(shù)限制,如果沒有特殊需要,不應(yīng)該修改此限制,除非想為用戶級(jí)打開文件數(shù)限制設(shè)置超過此限制的值。
如何修改這個(gè)系統(tǒng)最大文件描述符的限制呢?修改sysctl.conf文件
vi /etc/sysctl.conf# 在末尾添加fs.file_max = 1000000# 立即生效sysctl -p二、Netty調(diào)優(yōu)
1、設(shè)置合理的線程數(shù)
對(duì)于線程池的調(diào)優(yōu),主要集中在用于接收海量設(shè)備TCP連接、TLS握手的 Acceptor線程池( Netty通常叫 boss NioEventLoop Group)上,以及用于處理網(wǎng)絡(luò)數(shù)據(jù)讀寫、心跳發(fā)送的1O工作線程池(Nety通常叫 work Nio EventLoop Group)上。
對(duì)于Nety服務(wù)端,通常只需要啟動(dòng)一個(gè)監(jiān)聽端口用于端側(cè)設(shè)備接入即可,但是如果服務(wù)端集群實(shí)例比較少,甚至是單機(jī)(或者雙機(jī)冷備)部署,在端側(cè)設(shè)備在短時(shí)間內(nèi)大量接入時(shí),需要對(duì)服務(wù)端的監(jiān)聽方式和線程模型做優(yōu)化,以滿足短時(shí)間內(nèi)(例如30s)百萬級(jí)的端側(cè)設(shè)備接入的需要。
服務(wù)端可以監(jiān)聽多個(gè)端口,利用主從 Reactor線程模型做接入優(yōu)化,前端通過SLB做4層門7層負(fù)載均衡。
主從 Reactor線程模型特點(diǎn)如下:服務(wù)端用于接收客戶端連接的不再是一個(gè)單獨(dú)的NO線程,而是一個(gè)獨(dú)立的NIO線程池; Acceptor接收到客戶端TCP連接請(qǐng)求并處理后(可能包含接入認(rèn)證等),將新創(chuàng)建的 Socketchanne注冊(cè)到I/O線程池(subReactor線程池)的某個(gè)IO線程,由它負(fù)責(zé) Socketchannel的讀寫和編解碼工作; Acceptor線程池僅用于客戶端的登錄、握手和安全認(rèn)證等,一旦鏈路建立成功,就將鏈路注冊(cè)到后端 sub reactor線程池的IO線程,由IO線程負(fù)責(zé)后續(xù)的IO操作。
對(duì)于IO工作線程池的優(yōu)化,可以先采用系統(tǒng)默認(rèn)值(即CPU內(nèi)核數(shù)×2)進(jìn)行性能測試,在性能測試過程中采集IO線程的CPU占用大小,看是否存在瓶頸對(duì)于O工作線程池的優(yōu)化,可以先采用系統(tǒng)默認(rèn)值(即CPU內(nèi)核數(shù)×2)進(jìn)行性能
測試,在性能測試過程中采集IO線程的CPU占用大小,看是否存在瓶頸, 具體可以觀察線程堆棧,如果連續(xù)采集幾次進(jìn)行對(duì)比,發(fā)現(xiàn)線程堆棧都停留在 Selectorlmpl. lock AndDoSelect,則說明IO線程比較空閑,無須對(duì)工作線程數(shù)做調(diào)整。
如果發(fā)現(xiàn)IO線程的熱點(diǎn)停留在讀或者寫操作,或者停留在 Channelhandler的執(zhí)行處,則可以通過適當(dāng)調(diào)大 Nio EventLoop線程的個(gè)數(shù)來提升網(wǎng)絡(luò)的讀寫性能。
2、心跳優(yōu)化
針對(duì)海量設(shè)備接入的服務(wù)端,心跳優(yōu)化策略如下。
當(dāng)設(shè)備突然掉電、連接被防火墻擋住、長時(shí)間GC或者通信線程發(fā)生非預(yù)期異常時(shí),會(huì)導(dǎo)致鏈路不可用且不易被及時(shí)發(fā)現(xiàn)。特別是如果異常發(fā)生在凌晨業(yè)務(wù)低谷期間,當(dāng)早晨業(yè)務(wù)高峰期到來時(shí),由于鏈路不可用會(huì)導(dǎo)致瞬間大批量業(yè)務(wù)失敗或者超時(shí),這將對(duì)系統(tǒng)的可靠性產(chǎn)生重大的威脅。
從技術(shù)層面看,要解決鏈路的可靠性問題,必須周期性地對(duì)鏈路進(jìn)行有效性檢測。目前最流行和通用的做法就是心跳檢測。心跳檢測機(jī)制分為三個(gè)層面
心跳檢測的目的就是確認(rèn)當(dāng)前鏈路是否可用,對(duì)方是否活著并且能夠正常接收和發(fā)送消息。作為高可靠的NIO框架,Nety也提供了心跳檢測機(jī)制。
一般的心跳檢測策略如下。
Nety提供了三種鏈路空閑檢測機(jī)制,利用該機(jī)制可以輕松地實(shí)現(xiàn)心跳檢測
對(duì)于百萬級(jí)的服務(wù)器,一般不建議很長的心跳周期和超時(shí)時(shí)長
3、接收和發(fā)送緩沖區(qū)調(diào)優(yōu)
在一些場景下,端側(cè)設(shè)備會(huì)周期性地上報(bào)數(shù)據(jù)和發(fā)送心跳,單個(gè)鏈路的消息收發(fā)量并不大,針對(duì)此類場景,可以通過調(diào)小TCP的接收和發(fā)送緩沖區(qū)來降低單個(gè)TCP連接的資源占用率
當(dāng)然對(duì)于不同的應(yīng)用場景,收發(fā)緩沖區(qū)的最優(yōu)值可能不同,用戶需要根據(jù)實(shí)際場景,結(jié)合性能測試數(shù)據(jù)進(jìn)行針對(duì)性的調(diào)優(yōu)
4、合理使用內(nèi)存池
隨著JVM虛擬機(jī)和JT即時(shí)編譯技術(shù)的發(fā)展,對(duì)象的分配和回收是一個(gè)非常輕量級(jí)的工作。但是對(duì)于緩沖區(qū) Buffer,情況卻稍有不同,特別是堆外直接內(nèi)存的分配和回收,是一個(gè)耗時(shí)的操作。
為了盡量重用緩沖區(qū),Nety提供了基于內(nèi)存池的緩沖區(qū)重用機(jī)制。
在百萬級(jí)的情況下,需要為每個(gè)接入的端側(cè)設(shè)備至少分配一個(gè)接收和發(fā)送緩沖區(qū)對(duì)象,采用傳統(tǒng)的非池模式,每次消息讀寫都需要?jiǎng)?chuàng)建和釋放 ByteBuf對(duì)象,如果有100萬個(gè)連接,每秒上報(bào)一次數(shù)據(jù)或者心跳,就會(huì)有100萬次/秒的 ByteBuf對(duì)象申請(qǐng)和釋放,即便服務(wù)端的內(nèi)存可以滿足要求,GC的壓力也會(huì)非常大。
以上問題最有效的解決方法就是使用內(nèi)存池,每個(gè) NioEventLoop線程處理N個(gè)鏈路,在線程內(nèi)部,鏈路的處理是串行的。假如A鏈路首先被處理,它會(huì)創(chuàng)建接收緩沖區(qū)等對(duì)象,待解碼完成,構(gòu)造的POJO對(duì)象被封裝成任務(wù)后投遞到后臺(tái)的線程池中執(zhí)行,然后接收緩沖區(qū)會(huì)被釋放,每條消息的接收和處理都會(huì)重復(fù)接收緩沖區(qū)的創(chuàng)建和釋放。如果使用內(nèi)存池,則當(dāng)A鏈路接收到新的數(shù)據(jù)報(bào)時(shí),從 NioEventLoop的內(nèi)存池中申請(qǐng)空閑的 ByteBuf,解碼后調(diào)用 release將 ByteBuf釋放到內(nèi)存池中,供后續(xù)的B鏈路使用。
Nety內(nèi)存池從實(shí)現(xiàn)上可以分為兩類:堆外直接內(nèi)存和堆內(nèi)存。由于 Byte Buf主要用于網(wǎng)絡(luò)IO讀寫,因此采用堆外直接內(nèi)存會(huì)減少一次從用戶堆內(nèi)存到內(nèi)核態(tài)的字節(jié)數(shù)組拷貝,所以性能更高。由于 DirectByteBuf的創(chuàng)建成本比較高,因此如果使用 DirectByteBuf,則需要配合內(nèi)存池使用,否則性價(jià)比可能還不如 Heap Byte。
Netty默認(rèn)的IO讀寫操作采用的都是內(nèi)存池的堆外直接內(nèi)存模式,如果用戶需要額外使用 ByteBuf,建議也采用內(nèi)存池方式;如果不涉及網(wǎng)絡(luò)IO操作(只是純粹的內(nèi)存操作),可以使用堆內(nèi)存池,這樣內(nèi)存的創(chuàng)建效率會(huì)更高一些。
5、IO線程和業(yè)務(wù)線程分離
如果服務(wù)端不做復(fù)雜的業(yè)務(wù)邏輯操作,僅是簡單的內(nèi)存操作和消息轉(zhuǎn)發(fā),則可以通過調(diào)大 NioEventLoop工作線程池的方式,直接在IO線程中執(zhí)行業(yè)務(wù) Channelhandler,這樣便減少了一次線程上下文切換,性能反而更高。
如果有復(fù)雜的業(yè)務(wù)邏輯操作,則建議IO線程和業(yè)務(wù)線程分離,對(duì)于IO線程,由于互相之間不存在鎖競爭,可以創(chuàng)建一個(gè)大的 NioEvent Loop Group線程組,所有 Channel都共享同一個(gè)線程池。
對(duì)于后端的業(yè)務(wù)線程池,則建議創(chuàng)建多個(gè)小的業(yè)務(wù)線程池,線程池可以與IO線程綁定,這樣既減少了鎖競爭,又提升了后端的處理性能。
針對(duì)端側(cè)并發(fā)連接數(shù)的流控
無論服務(wù)端的性能優(yōu)化到多少,都需要考慮流控功能。當(dāng)資源成為瓶頸,或者遇到端側(cè)設(shè)備的大量接入,需要通過流控對(duì)系統(tǒng)做保護(hù)。流控的策略有很多種,比如針對(duì)端側(cè)連接數(shù)的流控:
在Nety中,可以非常方便地實(shí)現(xiàn)流控功能:新增一個(gè)FlowControlchannelhandler,然后添加到 ChannelPipeline靠前的位置,覆蓋 channelActiveO方法,創(chuàng)建TCP鏈路后,執(zhí)行流控邏輯,如果達(dá)到流控閾值,則拒絕該連接,調(diào)用 ChannelHandler Context的 close(方法關(guān)閉連接。
三、JVM層面相關(guān)性能優(yōu)化
當(dāng)客戶端的并發(fā)連接數(shù)達(dá)到數(shù)十萬或者數(shù)百萬時(shí),系統(tǒng)一個(gè)較小的抖動(dòng)就會(huì)導(dǎo)致很嚴(yán)重的后果,例如服務(wù)端的GC,導(dǎo)致應(yīng)用暫停(STW)的GC持續(xù)幾秒,就會(huì)導(dǎo)致海量的客戶端設(shè)備掉線或者消息積壓,一旦系統(tǒng)恢復(fù),會(huì)有海量的設(shè)備接入或者海量的數(shù)據(jù)發(fā)送很可能瞬間就把服務(wù)端沖垮。
JVM層面的調(diào)優(yōu)主要涉及GC參數(shù)優(yōu)化,GC參數(shù)設(shè)置不當(dāng)會(huì)導(dǎo)致頻繁GC,甚至OOM異常,對(duì)服務(wù)端的穩(wěn)定運(yùn)行產(chǎn)生重大影響。
1、確定GC優(yōu)化目標(biāo)
GC(垃圾收集)有三個(gè)主要指標(biāo)。
JVM GC調(diào)優(yōu)的三個(gè)基本原則如下。
2、確定服務(wù)端內(nèi)存占用
在優(yōu)化GC之前,需要確定應(yīng)用程序的內(nèi)存占用大小,以便為應(yīng)用程序設(shè)置合適的內(nèi)存,提升GC效率。內(nèi)存占用與活躍數(shù)據(jù)有關(guān),活躍數(shù)據(jù)指的是應(yīng)用程序穩(wěn)定運(yùn)行時(shí)長時(shí)間存活的Java對(duì)象。活躍數(shù)據(jù)的計(jì)算方式:通過GC日志采集GC數(shù)據(jù),獲取應(yīng)用程序穩(wěn)定時(shí)老年代占用的Java堆大小,以及永久代(元數(shù)據(jù)區(qū))占用的Java堆大小,兩者之和就是活躍數(shù)據(jù)的內(nèi)存占用大小。
3、GC優(yōu)化過程
GC調(diào)優(yōu)會(huì)是一個(gè)需要多次調(diào)整的過程,期間不僅有參數(shù)的變化,更重要的是需要調(diào)整業(yè)務(wù)代碼。
作者:Dark_King_原文鏈接:https://blog.csdn.net/b379685397/article/details/104042536
總結(jié)
以上是生活随笔為你收集整理的mqtt连接失败_Netty实战:如何让单机下Netty支持百万长连接?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vb.net怎么调用fastreport
- 下一篇: 单例模式双重校验锁_被面试官虐过之后,他