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