网络通信优化
如何優(yōu)雅的談?wù)揌TTP/1.0/1.1/2.0如何優(yōu)雅的談?wù)揌TTP/1.0/1.1/2.0
現(xiàn)在測試工具非常多,包括阿里云的 PTS 測試工具也很好用,但每款測試工具其實都有自
己的優(yōu)缺點。個人建議,還是在熟練掌握其中一款測試工具的前提下,再去探索其他測試工
具的使用方法會更好
網(wǎng)絡(luò)通信優(yōu)化之I/O模型:如何解決高并發(fā)下I/O瓶頸?
問題:什么是 I/O???
I/O 是機(jī)器獲取和交換信息的主要渠道,而流是完成 I/O 操作的主要方式。
在計算機(jī)中,流是一種信息的轉(zhuǎn)換。流是有序的,因此相對于某一機(jī)器或者應(yīng)用程序而言,我們通常
把機(jī)器或者應(yīng)用程序接收外界的信息稱為輸入流(InputStream),
從機(jī)器或者應(yīng)程序向外輸出的信息稱為輸出流(OutputStream),
合稱為輸入 / 輸出流(I/OStreams)。
機(jī)器間或程序間在進(jìn)行信息交換或者數(shù)據(jù)交換時,總是先將對象或數(shù)據(jù)轉(zhuǎn)換為某種形式的流,再通過流的傳輸,到達(dá)指定機(jī)器或程序后,再將流轉(zhuǎn)換為對象數(shù)據(jù)。因此,流就可以被看作是一種數(shù)據(jù)的載體,通過它可以實現(xiàn)數(shù)據(jù)交換和傳輸。
回顧我的經(jīng)歷,我記得在初次閱讀 Java I/O 流文檔的時候,我有過這樣一個疑問,在這里也分享給你,那就是:“不管是文件讀寫還是網(wǎng)絡(luò)發(fā)送接收,信息的最小存儲單元都是字節(jié),那為什么 I/O 流操作要分為字節(jié)流操作和字符流操作呢?”
“不管是文件讀寫還是網(wǎng)絡(luò)發(fā)送接收,信息的最小存儲單元都是字節(jié),那為什么 I/O 流操作要分為字節(jié)流操作和字符流操作呢?”
回答:我們知道字符到字節(jié)必須經(jīng)過轉(zhuǎn)碼,這個過程非常耗時,如果我們不知道編碼類型就很容易出現(xiàn)亂碼問題。所以 I/O 流提供了一個直接操作字符的接口,方便我們平時對字符進(jìn)行流操作。
I/O 操作分為磁盤 I/O 操作和網(wǎng)絡(luò) I/O 操作。
前者是從磁盤中讀取數(shù)據(jù)源輸入到內(nèi)存中,之后將讀取的信息持久化輸出在物理磁盤上;
后者是從網(wǎng)絡(luò)中讀取信息輸入到內(nèi)存,最終將信息輸出到網(wǎng)絡(luò)中。
但不管是磁盤 I/O 還是網(wǎng)絡(luò) I/O,在傳統(tǒng) I/O 中都存在嚴(yán)重的性能問題
JVM 會發(fā)出 read() 系統(tǒng)調(diào)用,并通過 read 系統(tǒng)調(diào)用向內(nèi)核發(fā)起讀請求;
內(nèi)核向硬件發(fā)送讀指令,并等待讀就緒;
內(nèi)核把將要讀取的數(shù)據(jù)復(fù)制到指向的內(nèi)核緩存中;
操作系統(tǒng)內(nèi)核將數(shù)據(jù)復(fù)制到用戶空間緩沖區(qū),然后 read 系統(tǒng)調(diào)用返回。
1. 多次內(nèi)存復(fù)制
在這個過程中,數(shù)據(jù)先從外部設(shè)備復(fù)制到內(nèi)核空間,再從內(nèi)核空間復(fù)制到用戶空間,這就發(fā)
生了兩次內(nèi)存復(fù)制操作。這種操作會導(dǎo)致不必要的數(shù)據(jù)拷貝和上下文切換,從而降低 I/O
的性能。
2. 阻塞
在傳統(tǒng) I/O 中,InputStream 的 read() 是一個 while 循環(huán)操作,它會一直等待數(shù)據(jù)讀取,
直到數(shù)據(jù)就緒才會返回。這就意味著如果沒有數(shù)據(jù)就緒,這個讀取操作將會一直被掛起,用
戶線程將會處于阻塞狀態(tài)。
在少量連接請求的情況下,使用這種方式?jīng)]有問題,響應(yīng)速度也很高。但在發(fā)生大量連接請
求時,就需要創(chuàng)建大量監(jiān)聽線程,這時如果線程沒有數(shù)據(jù)就緒就會被掛起,然后進(jìn)入阻塞狀
態(tài)。一旦發(fā)生線程阻塞,這些線程將會不斷地?fù)寠Z CPU 資源,從而導(dǎo)致大量的 CPU 上下
文切換,增加系統(tǒng)的性能開銷
傳統(tǒng) I/O 和 NIO 的最大區(qū)別就是傳統(tǒng) I/O 是面向流,NIO 是面向 Buffer。Buffer 可以將
文件一次性讀入內(nèi)存再做后續(xù)處理,而傳統(tǒng)的方式是邊讀文件邊處理數(shù)據(jù)。雖然傳統(tǒng) I/O
后面也使用了緩沖塊,例如 BufferedInputStream,但仍然不能和 NIO 相媲美。使用
NIO 替代傳統(tǒng) I/O 操作,可以提升系統(tǒng)的整體性能,效果立竿見影。
使用 DirectBuffer 減少內(nèi)存復(fù)制
提到了DirectBuffer,也就是零拷貝的實現(xiàn)
用DirectBuffer減少內(nèi)存復(fù)制,也就是避免了用戶空間與內(nèi)核空
間來回復(fù)制。
NIO 的 Buffer 除了做了緩沖塊優(yōu)化之外,還提供了一個可以直接訪問物理內(nèi)存的類
DirectBuffer。普通的 Buffer 分配的是 JVM 堆內(nèi)存,而 DirectBuffer 是直接分配物理內(nèi)
存。
我們知道數(shù)據(jù)要輸出到外部設(shè)備,必須先從用戶空間復(fù)制到內(nèi)核空間,再復(fù)制到輸出設(shè)備,
而 DirectBuffer 則是直接將步驟簡化為從內(nèi)核空間復(fù)制到外部設(shè)備,減少了數(shù)據(jù)拷貝。
這里拓展一點,由于 DirectBuffer 申請的是非 JVM 的物理內(nèi)存,所以創(chuàng)建和銷毀的代價很
高。DirectBuffer 申請的內(nèi)存并不是直接由 JVM 負(fù)責(zé)垃圾回收,但在 DirectBuffer 包裝
類被回收時,會通過 Java Reference 機(jī)制來釋放該內(nèi)存塊。
一個線程使用一個 Selector,通過輪詢的方式,可以監(jiān)聽多個 Channel 上的事件。我們可
以在注冊 Channel 時設(shè)置該通道為非阻塞,當(dāng) Channel 上沒有 I/O 操作時,該線程就不
會一直等待了,而是會不斷輪詢所有 Channel,從而避免發(fā)生阻塞。
目前操作系統(tǒng)的 I/O 多路復(fù)用機(jī)制都使用了 epoll,相比傳統(tǒng)的 select 機(jī)制,epoll 沒有最
大連接句柄 1024 的限制。所以 Selector 在理論上可以輪詢成千上萬的客戶端。
下面我用一個生活化的場景來舉例,看完你就更清楚 Channel 和 Selector 在非阻塞 I/O
中承擔(dān)什么角色,發(fā)揮什么作用了。
我們可以把監(jiān)聽多個 I/O 連接請求比作一個火車站的進(jìn)站口。以前檢票只能讓搭乘就近一
趟發(fā)車的旅客提前進(jìn)站,而且只有一個檢票員,這時如果有其他車次的旅客要進(jìn)站,就只能
在站口排隊。這就相當(dāng)于最早沒有實現(xiàn)線程池的 I/O 操作。
后來火車站升級了,多了幾個檢票入口,允許不同車次的旅客從各自對應(yīng)的檢票入口進(jìn)站。這就相當(dāng)于用多線程創(chuàng)建了多個監(jiān)聽線程,同時監(jiān)聽各個客戶端的 I/O 請求。
最后火車站進(jìn)行了升級改造,可以容納更多旅客了,每個車次載客更多了,而且車次也安排合理,乘客不再扎堆排隊,可以從一個大的統(tǒng)一的檢票口進(jìn)站了,這一個檢票口可以同時檢票多個車次。這個大的檢票口就相當(dāng)于 Selector,車次就相當(dāng)于 Channel,旅客就相當(dāng)于I/O 流。
在Linux中,AIO并未真正使用操作系統(tǒng)所提供的異步I/O,它仍然使用poll或epoll,并將
API封裝為異步I/O的樣子,但是其本質(zhì)仍然是同步非阻塞I/O,加上第三方產(chǎn)品的出現(xiàn),
Java網(wǎng)絡(luò)編程明顯落后,所以沒有成為主流。
DMA和Channel的區(qū)別, DMA需要占用總線, 那么Channel是如何跳過總線向內(nèi)存?zhèn)鬏敂?shù)據(jù)的????
回復(fù): 一個設(shè)備接口試圖通過總線直接向外部設(shè)備(磁盤)傳送數(shù)據(jù)時,它會先向CPU發(fā)送DMA請求信號。外部設(shè)備(磁盤)通過DMA的一種專門接口電路――DMA控制器(DMAC),向CPU提出接管總線控制權(quán)的總線請求,CPU收到該信號后,在當(dāng)前的總線周期結(jié)束后,會按DMA信號的優(yōu)先級和提出DMA請求的先后順序響應(yīng)DMA信號。CPU對某個設(shè)備接口響應(yīng)DMA請求時,會讓出總線控制權(quán)。于是在DMA控制器的管理下,磁盤和存儲器直接進(jìn)行數(shù)據(jù)交換,而不需CPU干預(yù)。數(shù)據(jù)傳送完畢后,設(shè)備接口會向CPU發(fā)送DMA結(jié)束信號,交還總線控制權(quán)。
而通道則是在DMA的基礎(chǔ)上增加了能執(zhí)行有限通道指令的I/O控制器,代替CPU管理控制外設(shè)。通道有自己的指令系統(tǒng),是一個協(xié)處理器,他實質(zhì)是一臺能夠執(zhí)行有限的輸入輸出指令,并且有專門通訊傳輸?shù)耐ǖ揽偩€完成控制。
網(wǎng)絡(luò)通信優(yōu)化之序列化:避免使用Java序列化
這個編碼和解碼過程我們稱之為序列化與反序列化
Java 序列化的缺陷
目前業(yè)內(nèi)優(yōu)秀的序列化框架有很多,而且大部分都避免了 Java 默認(rèn)序列化的一些缺陷。例
如,最近幾年比較流行的 FastJson、Kryo、Protobuf、Hessian 等。我們完全可以找一種
替換掉 Java 序列化,這里我推薦使用 Protobuf 序列化框架
網(wǎng)絡(luò)通信優(yōu)化之通信協(xié)議:如何優(yōu)化RPC網(wǎng)絡(luò)通信?
我們知道服務(wù)的拆分增加了通信的成本,特別是在一些搶購或者促銷的業(yè)務(wù)場景中,如果服
務(wù)之間存在方法調(diào)用,比如,搶購成功之后需要調(diào)用訂單系統(tǒng)、支付系統(tǒng)、券包系統(tǒng)等,這
種遠(yuǎn)程通信就很容易成為系統(tǒng)的瓶頸。所以,在滿足一定的服務(wù)治理需求的前提下,對遠(yuǎn)程
通信的性能需求就是技術(shù)選型的主要影響因素。
SpringCloud 是基于 Feign 組件實現(xiàn)的 RPC 通信(基于 Http+Json 序列化實現(xiàn)),
Dubbo 是基于 SPI 擴(kuò)展了很多 RPC 通信框架,包括 RMI、Dubbo、Hessian 等 RPC 通
信框架(默認(rèn)是 Dubbo+Hessian 序列化)
RPC(Remote Process Call),即遠(yuǎn)程服務(wù)調(diào)用,是通過網(wǎng)絡(luò)請求遠(yuǎn)程計算機(jī)程序服務(wù)的
通信技術(shù)。RPC 框架封裝好了底層網(wǎng)絡(luò)通信、序列化等技術(shù),我們只需要在項目中引入各
個服務(wù)的接口包,就可以實現(xiàn)在代碼中調(diào)用 RPC 服務(wù)同調(diào)用本地方法一樣
總結(jié)
- 上一篇: delphi mysql.pas_Del
- 下一篇: BPE, WordPiece, Sent