深入分析的工作機制_第1頁
深入分析的工作機制_第2頁
深入分析的工作機制_第3頁
深入分析的工作機制_第4頁
深入分析的工作機制_第5頁
已閱讀5頁,還剩13頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、Java 的 I/O 類庫的基本架構(gòu)I/O 問題是任何編程語言都無法回避的問題,可以說 I/O 問題是整個人機交互的問題,因為 I/O 是獲取和交換信息的主要。在這個數(shù)據(jù)大,I/O 問題尤其突出,很容易成為一個性能瓶頸。正因如此,所以 Java在 I/O 上也一直在做持續(xù)的優(yōu)化,如從 1.4 開始引入了 NIO,提升了 I/O 的性能。關(guān)于 NIO在后面詳細介紹。Java 的 I/O 操作類在包 java.io 下,大概有將近 80 個類,但是這些類大概可以分成四組,分別是:.基于字節(jié)操作的基于字符操作的基于磁盤操作的基于網(wǎng)絡操作的I/O I/O I/O I/O接口:InputS

2、tream 和 OutputStream接口:Writer 和 Reader接口:File 接口:Socket前兩組主要是根據(jù)傳輸數(shù)據(jù)的數(shù)據(jù)格式,后兩組主要是根據(jù)傳輸數(shù)據(jù)的方式,雖然 Socket 類并不在 java.io 包下,但是我仍然把它們劃分在一起,因為我個人認為 I/O 的問題要么是數(shù)據(jù)格式影響 I/O 操作,要么是傳輸方式影響I/O 操作,也就是將什么樣的數(shù)據(jù)寫到什么地方的問題,I/O 只是人與或者與交互的,除了在它們能夠完成這個交互功能外,我們關(guān)注的就是如何提高它的運行效率了,而數(shù)據(jù)格式和傳輸方式是影響效率最關(guān) 鍵的因素了。我們后面的分析也是基于這兩個因素來展開的?;谧止?jié)的 I

3、/O 操作接口基于字節(jié)的 I/O 操作接口輸入和輸出分別是:InputStream 和 OutputStream, InputStream 輸入流的類繼承層次如下圖所示:圖 1. InputStream 相關(guān)類層次結(jié)構(gòu)(查看大圖)輸入流根據(jù)數(shù)據(jù)類型和操作方式又被劃分成若干個子類,每個子類分別處理不同操作類型,OutputStream 輸出流的類層次結(jié)構(gòu)也是類似,如下圖所示:圖 2. OutputStream 相關(guān)類層次結(jié)構(gòu)(查看大圖)這里就不詳細解釋每個子類如何使用了,如果不清楚的話可以參考一下 JDK 的API 說明文檔,這里只想說明兩點,一個是操作數(shù)據(jù)的方式是可以組合使用的, 如這樣組合使

4、用OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream("fileName");還 有一點是流最終寫到什么地方必須要指定,要么是寫到磁盤要么是寫到網(wǎng)絡中,其實從上面的類圖中我們發(fā)現(xiàn),寫網(wǎng)絡實際上也是寫文件,只不過寫網(wǎng)絡還需 要處理就是底層操作系統(tǒng)再將數(shù)據(jù)傳送到其它地方而不是本地磁盤。關(guān)于網(wǎng)絡 I/O 和磁盤 I/O在后面詳細介紹。基于字符的 I/O 操作接口不磁盤還是網(wǎng)絡傳輸,最小的單元都是字節(jié),而不是字符,所以 I/O 操作的都是字節(jié)而不是字符,但是為

5、啥有操作字符的 I/O 接口呢?這是因為我們的通常操作的數(shù)據(jù)都是以字符形式,為了操作方然要提供一個直接寫字符的 I/O 接口,如此而已。我們知道字符到字節(jié)必須要經(jīng)過編碼轉(zhuǎn)換,而這個編碼又非常耗時,而且還會經(jīng)常出現(xiàn)亂碼問題,所以 I/O 的編碼問題經(jīng)常是讓人頭疼的問題。關(guān)于 I/O 編碼問題請參考另一篇文章 深入分析 Java 中的中文編碼問題。下圖是寫字符的 I/O 操作接口涉及到的類,Writer 類提供了一個抽象方法write(char cbuf, int off, int len) 由子類去實現(xiàn)。圖 3. Writer 相關(guān)類層次結(jié)構(gòu)(查看大圖)讀字符的操作接口也有類似的類結(jié)構(gòu),如下圖所

6、示:圖 4.Reader 類層次結(jié)構(gòu)(查看大圖)讀 字符的操作接口中也是 int read(char cbuf, int off, int len),返回讀到的 n 個字節(jié)數(shù),不Writer 還是 Reader 類它們都只定義了或?qū)懭氲臄?shù)據(jù)字符的方式,也就是怎么寫或讀,但是并沒有規(guī)定數(shù)據(jù)要寫到哪去,寫到哪去就是我們后面要討論的基于磁盤和網(wǎng)絡的工作機 制。字節(jié)與字符的轉(zhuǎn)化接口另外數(shù)據(jù)持久化或網(wǎng)絡傳輸都是以字節(jié)進行的,所以必須要有字符到字節(jié)或字節(jié)到字符的轉(zhuǎn)化。字符到字節(jié)需要轉(zhuǎn)化,其中讀的轉(zhuǎn)化過程如下圖所示:圖 5. 字符相關(guān)類結(jié)構(gòu)InputStreamReader 類是字節(jié)到字符的轉(zhuǎn)化橋梁,Inp

7、utStream 到 Reader 的過程要指定編碼字符集,否則將采用操作系統(tǒng)默認字符集,很可能會出現(xiàn)亂碼問題。StreamDecoder 正是完成字節(jié)到字符的的實現(xiàn)類。也就是當你用如下方式讀取一個文件時:1.文件try StringBuffer char buf = FileReader fstr = new StringBuffer(); new char1024;= new FileReader("file");while(f.read(buf)>0) str.append(buf);str.toString(); catch (IOException e) F

8、ileReader 類就是按照上面的工作方式文件的,F(xiàn)ileReader 是繼承了InputStreamReader 類,實際上是文件流,然后通過 StreamDecoder成 char,只不過這里的字符集是默認字符集。寫入也是類似的過程如下圖所示:圖 6. 字符編碼相關(guān)類結(jié)構(gòu)通過 OutputStreamWriter 類完成,字符到字節(jié)的編碼過程,由完成編碼過程。StreamEncoder回頁首磁盤 I/O 工作機制前面介紹了基本的 Java I/O 的操作接口,這些接口主要定義了如何操作數(shù)據(jù), 以及介紹了操作兩種數(shù)據(jù)結(jié)構(gòu):字節(jié)和字符的方式。還有一個關(guān)鍵問題就是數(shù)據(jù)寫到何處,其中一個主要方式

9、就是將數(shù)據(jù)持久化到物理磁盤,下面將介紹如何將數(shù)據(jù)持久化到物理磁盤的過程。我 們知道數(shù)據(jù)在磁盤的唯一最小描述就是文件,也就是說上層應用程序只能通過文件來操作磁盤上的數(shù)據(jù),文件也是操作系統(tǒng)和磁盤驅(qū)動器交互的一個最小單元。值得 注意的是 Java 中通常的 File 并不代表一個真實存在的文件對象, 當你通過指定一個路徑描述符時,它就會返回一個代表這個路徑相關(guān)聯(lián)的一個虛擬對象,這個可能是一個真實存在的文件或者是 一個包含多個文件的目錄。為何要這樣設計?因為大部分情況下,我們并不關(guān)心這個文件是否真的存在,而是關(guān)心這個文件到底如何操作。例如我們里通常存了 幾百個朋友的號碼,但是我們通常關(guān)心的是我有沒有這

10、個朋友的號碼,或者這個號碼是什么,但是這個號碼到底能不能打通,我們并不是時時刻刻 都去檢查,而只有在真正要給他打看這個能不能用。也就是使用這個要比打這個的次數(shù)多很多。何時真正會要檢查一個 文件存不存?就是在真正要這個文件時,例如FileInputStream 類都是操作一個文件的接口,注意到在創(chuàng)建一個FileInputStream 對象時,會創(chuàng)建一個 FileDescriptor 對象,其實這個對象就是真正代表一個存在的文件對象的描述,當我們在操作一個文件對象時可以通過getFD() 方法獲取真正操作的與底層操作系統(tǒng)關(guān)聯(lián)的文件描述。例如可以調(diào)用FileDescriptor.sync() 方法將

11、操作系統(tǒng)緩存中的數(shù)據(jù)強制刷新到物理磁盤中。下面以1的例,介紹下如何從磁盤一段文本字符。如下圖所示:圖 7.從磁盤文件當 傳入一個文件路徑,將會根據(jù)這個路徑創(chuàng)建一個File 對象來標識這個文件,然后將會根據(jù)這個 File 對象創(chuàng)建真正文件的操作對象,這時將會真正創(chuàng)建一個關(guān)聯(lián)真實存在的磁盤文件的文件描述符 FileDescriptor,通過這個對象可以直接這個磁盤文件。由于我們需要的是字符格式,所以需要StreamDecoder 類將 byte為 char 格式,至于如何從磁盤驅(qū)動器上一段數(shù)據(jù),由操作系統(tǒng)幫我們完成。至于操作系統(tǒng)是如何將數(shù)據(jù)持久化到磁盤以及如何建立數(shù)據(jù)結(jié)構(gòu)需要根據(jù)當前操作系統(tǒng)使用

12、何種文件系統(tǒng)來回答,至于文件系統(tǒng)的相關(guān)以參考另外的文章。回頁首Java Socket 的工作機制Socket 這個概念沒有對應到一個具體的實體,它是描述計算機之間完成相互通信一種抽象功能。打個比方,可以把 Socket 比作為兩個城市之間的交通工具, 有了它,就可以在城市之間來回穿梭了。交通工具有多種,每種交通工具也有相應的交通規(guī)則。Socket 也一樣,也有多種。大部分情況下我們使用的都是基于TCP/IP 的流套接字,它是一種穩(wěn)定的通信協(xié)議。下圖是典型的基于 Socket 的通信的場景:圖 8.Socket 通信示例主 機 A 的應用程序要能和主機 B 的應用程序通信,必須通過接,而建立 S

13、ocket 連接必須需要底層 TCP/IP 協(xié)議來建立 TCPSocket 建立連連接。建立 TCP連接需要底層 IP 協(xié)議來尋址網(wǎng)絡中的主機。我們知道網(wǎng)絡層使用的 IP 協(xié)議可以幫助我們根據(jù) IP 地址來找到目標主機,但是一臺主機上可能運行著多個應用程序,如何才能與指定的應用程序通信就要通過 TCP 或 UPD 的地址也就是端口號來指定。這樣就可以通過一個 Socket 實例唯一代表一個主機上的一個應用程序的通信鏈路了。建立通信鏈路當 客戶端要與服務端通信,客戶端首先要創(chuàng)建一個 Socket 實例,操作系統(tǒng)將為這個 Socket 實例分配一個沒有被使用的本地端,并創(chuàng)建一個包含本地和地址和端的

14、套接字數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)將一直保存在系統(tǒng)中直到這個連接關(guān)閉。在創(chuàng)建 Socket 實例的構(gòu)造函數(shù)正確返回之前,將要進行 TCP 的三次握手協(xié)議,TCP 握手協(xié)議完成后,Socket 實例對象將創(chuàng)建完成,否則將拋出 IOException 錯誤。與之對應的服務端將創(chuàng)建一個 ServerSocket 實例,ServerSocket 創(chuàng)建比較簡單只要指定的端沒有被占用,一般實例創(chuàng)建都會,同時操作系統(tǒng)也會為ServerSocket 實例創(chuàng)建一個底層數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)中包含指定的端和包含地址的通配符,通常情況下都是“*”即所有地址。之后當調(diào)用 accept() 方法時,將進入阻塞狀態(tài),等待客戶端

15、的請求。當一個新的請求到來時,將為這個連接創(chuàng)建一個新的套接字數(shù)據(jù)結(jié)構(gòu),該套接字數(shù)據(jù)的信息包含的地址和端口信息正 是請求源地址和端口。這個新創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)將會關(guān)聯(lián)到ServerSocket 實例的一個未完成的連接數(shù)據(jù)結(jié)構(gòu)列表中,注意這時服務端與之對應的 Socket 實例并沒有完成創(chuàng)建,而要等到與客戶端的三次握手完成后,這個服務端的 Socket 實例才會返回,并將這個 Socket 實例對應的數(shù)據(jù)結(jié)構(gòu)從未完成列表中移到已完成列表中。所以 ServerSocket 所關(guān)聯(lián)的列表中每個數(shù)據(jù)結(jié)構(gòu),都代表與一個客戶端的建立的 TCP 連接。數(shù)據(jù)傳輸傳輸數(shù)據(jù)是我們建立連接的主要目的,如何通過 Socke

16、t 傳輸數(shù)據(jù),下面將詳細介紹。當 連接已經(jīng)建立,服務端和客戶端都會擁有一個 Socket 實例,每個Socket 實例一個 InputStream 和 OutputStream,正是通過這兩個對象來交換數(shù)據(jù)。同時我們也知道網(wǎng)絡 I/O 都是以字節(jié)流傳輸?shù)?。?Socket 對象創(chuàng)建時,操作系統(tǒng)將會為 InputStream 和 OutputStream 分別分配一定大小的緩沖區(qū),數(shù)據(jù)的寫入和都是通過這個緩存區(qū)完成的。寫入端將數(shù)據(jù)寫到OutputStream 對應的 SendQ 隊列中,當隊列填滿時,數(shù)據(jù)將被到另一端InputStream 的 RecvQ 隊列中,如果這時 RecvQ 已經(jīng)滿了,

17、那么 OutputStream的 write 方法將會阻塞直到 RecvQ 隊列有足夠的空間容納 SendQ的數(shù)據(jù)。值得特別注意的是,這個緩存區(qū)的大小以及寫入端的速度和 端的速度非常影響這個連接的數(shù)據(jù)傳輸效率,由于可能會發(fā)生阻塞,所以網(wǎng)絡 I/O 與磁盤 I/O 在數(shù)據(jù)的寫入和 還要有一個協(xié)調(diào)的過程,如果兩邊同時傳送數(shù)據(jù)時可能會產(chǎn)生死鎖,在后面 NIO 部分將介紹避免這種情況?;仨撌譔IO 的工作方式BIO 帶來的BIO 即阻塞 I/O,不者從 InputStream磁盤 I/O 還是網(wǎng)絡 I/O,數(shù)據(jù)在寫入 OutputStream 或時可能會阻塞。一旦有線程阻塞將會失去 CPU 的使量和有

18、性能要求情況下是不能接受的。雖然當前的用權(quán),這在當前的大規(guī)模網(wǎng)絡 I/O 有一些解決辦法,如一個客戶端一個處理線程,出現(xiàn)阻塞時只是一個線程阻塞而影響其它線程工作,還有為了減少系統(tǒng)線程的開銷,采用線程池的辦法來減少線 程創(chuàng)建和回收的成本,但是有一些使用場景仍然是無法解決的。如當前一些需要大量 HTTP 長連接的情況,像淘寶現(xiàn)在使用的 Web項目,服務端需要同時保持幾百萬的 HTTP 連接,但是并不是每時每刻這些連接都在傳輸數(shù)據(jù),這種情況下不可能同時創(chuàng)建這么多線程來保持連接。即使線程的數(shù)量不是問題,仍然有一些問題還是無法避免 的。如這種情況,我們想給某些客戶端更高的服務優(yōu)先級,很難通過設計線程的優(yōu)

19、先級來完成,另外一種情況是,我們需要讓每個客戶端的請求在服務端可能需要一些競爭,由于這些客戶端是在不同線程中,因此需要同步,而往往要實現(xiàn)這些同步操作要遠遠比用單線程復雜很多。以上這些情況都說明,我們需要另外一種新的I/O操作方式。NIO 的工作機制我們先看一下 NIO涉及到的關(guān)聯(lián)類圖,如下:圖 9.NIO相關(guān)類圖上 圖中有兩個:Channel 和 Selector,它們是NIO 中兩個概念。我們還用前面的城市交通工具來繼續(xù)比喻 NIO 的工作方式,這里的 Channel 要比 Socket 更加具體,它可以比作為某種具體的交通工具,如汽車或是高鐵等, 而 Selector 可以比作為一個車站的

20、車輛運行調(diào)度系統(tǒng),它將負責每輛車的當前運行狀態(tài):是已經(jīng)出戰(zhàn)還是在路,也就是它可以輪詢每個 Channel 的狀態(tài)。這里還有一個 Buffer 類,它也比 Stream 更加具體化,我們可以將它比作為車上的座位,Channel 是汽車的話就是汽車上的座位,就是的座位,它始終是一個具體的概念,與 Stream 不同。Stream 只能代表是一個座位,至于是什么座位由你去想象,也就是你在去上車之前并不知道,這個車上是否還有沒有座位了,也不知道上的是什么車,因為你并不能選 擇,這些信息都已經(jīng)被封裝在了工具(Socket)里面了,對你是透明的。NIO 引入了Channel、Buffer 和 Select

21、or 就是想把這些信息具體化,讓程序員有機會它們,如:當我們調(diào)用 write() 往 SendQ 寫數(shù)據(jù)時,當一次寫的數(shù)據(jù)超過 SendQ長度是需要按照 SendQ 的長度進行分割,這個過程中需要有將用戶空間數(shù)據(jù)和內(nèi)核地址空間進行切換,而這個切換不是你可以的。而在 Buffer 中我們可以Buffer 的 capacity,并且是否擴容以及如何擴容都可以。理解了這些概念后我們看一下,實際上它們是如何工作的,下面是典型的一段NIO 代碼:2. NIO 工作代碼示例public void selector() throws IOException ByteBuffer buffer = Selec

22、tor selector = ServerSocketChannelByteBuffer.allocate(1024); Selector.open();ssc = ServerSocketChannel.open();ssc.configureBlocking(false);/設置為非阻塞方式ssc.socket().bind(new InetSocketAddress(8080); ssc.register(selector, SelectionKey.OP_ACCEPT);/的事件while (true) Set selectedKeys = selector.selectedKeys(

23、);/取得所有 key集合Iterator it = selectedKeys.iterator(); while (it.hasNext() SelectionKey key = (SelectionKey) it.next();if (key.readyOps() & SelectionKey.OP_ACCEPT) = SelectionKey.OP_ACCEPT) ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();SocketChannel sc = ssChannel.accept();/接受

24、到服務端的請求sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); it.remove(); else if(key.readyOps() & SelectionKey.OP_READ) = SelectionKey.OP_READ) SocketChannel sc = (SocketChannel) key.channel(); while (true) buffer.clear();int n = sc.read(buffer);/ if (n <= 0) 數(shù)據(jù)break;buff

25、er.flip();it.remove();調(diào)用 Selector 的靜態(tài)工廠創(chuàng)建一個選擇器,創(chuàng)建一個服務端的 Channel 綁定到一個 Socket 對象,并把這個通信信道 到選擇器上,把這個通信信道設置為非阻塞模式。然后就可以調(diào)用 Selector 的 selectedKeys 方法來檢查已經(jīng)注冊在這個選擇器上的所有通信信道是否有需要的 時,將會返回所有的 SelectionKey,通過這個對象發(fā)生,如果有某個發(fā)生Channel 方法就可以取得這的數(shù)據(jù)是 Buffer,這個個通信信道對象從而可以通信的數(shù)據(jù),而這里Buffer 是我們可以的緩沖器。在上面的這段,是將 Server 端的連接

26、請求的和處理請求的放在一個線程中,但是在實際應用中,我們通常會把它們放在兩個線程中,一個線程專門負責客戶端的連接請求,而 且是阻塞方式執(zhí)行的;另外一個線程專門來處理請求,這個專門處理請求的線程才會真正采用 NIO 的方式,像 Web 服務器 Tomcat 和 Jetty 都是這個處理方式,關(guān)于 Tomcat 和 Jetty 的 NIO 處理方式可以參考文章 Jetty 的工作原理和與 Tomcat 的比較。下圖是描述了基于 NIO 工作方式的 Socket 請求的處理過程:圖 10. 基于 NIO 的 Socket 請求的處理過程上 圖中的 Selector 可以同時前提是這個 Selecto

27、r 要已經(jīng)用 select() 方法檢查已經(jīng)一組通信信道(Channel)上的 I/O 狀態(tài), 到這些通信信道中。選擇器 Selector 可以調(diào)的通信信道上的是否有 I/O 已經(jīng)準備好,如果沒有至少一個信道 I/O 狀態(tài)有變化,那么 select 方阻塞等待或在超時時間后會返回 0。上圖中如果有多個信道有數(shù)據(jù),那么將會將這些數(shù)據(jù)分配到對應的數(shù)據(jù) Buffer 中。所以關(guān)鍵的地方是有一個線程來處理所有連接的數(shù)據(jù)交互, 每個連接的數(shù)據(jù)交互都不是阻塞方式,所以可以同時處理大量的連接請求。Buffer 的工作方式上面介紹了 Selector 將檢測到有通信信道 I/O 有數(shù)據(jù)傳輸時,通過 selel

28、ct()取得 SocketChannel,將數(shù)據(jù)如何接受和寫出數(shù)據(jù)?或?qū)懭?Buffer 緩沖區(qū)。下面討論一下 BufferBuffer 可以簡單的理解為一組基本數(shù)據(jù)類型的元素列表,它通過幾個變量來保存這個數(shù)據(jù)的當前位置狀態(tài),也就是有四個索引。如下表所示:表 1.Buffer 中的參數(shù)項索引capacity緩沖區(qū)數(shù)組的總長度說明position下一個要操作的數(shù)據(jù)元素的位置limit mark緩沖區(qū)數(shù)組中不可操作的下一個元素的位置,limit<=capacity用于當前 position 的前一個位置或者默認是 0在實際操作數(shù)據(jù)時它們有如下關(guān)系圖:我們通過 ByteBuffer.alloc

29、ate(11) 方法創(chuàng)建一個 11 個 byte 的數(shù)組緩沖區(qū),初始狀態(tài)如上圖所示,position 的位置為 0,capacity 和 limit 默認都是數(shù)組長度。當我們寫入 5 個字節(jié)時位置變化如下圖所示:這時我們需要將緩沖區(qū)的 5 個字節(jié)數(shù)據(jù)寫入 Channel 通信信道,所以我們需要調(diào)用byteBuffer.flip() 方法,數(shù)組的狀態(tài)又發(fā)生如下變化:這時底層操作系統(tǒng)就可以從緩沖區(qū)中正確這 5 個字節(jié)數(shù)據(jù)出去了。在下一次寫數(shù)據(jù)之前我們在調(diào)一下 clear() 方法。緩沖區(qū)的索引狀態(tài)又回到初始位置。這里還要說明一下 mark,當我們調(diào)用 mark() 時,它將當前 position

30、的前一個位置,當我們調(diào)用 reset 時,position 將恢復 mark下來的值。還 有一點需要說明,通過 Channel 獲取的 I/O 數(shù)據(jù)首先要經(jīng)過操作系統(tǒng)的 Socket 緩沖區(qū)再將數(shù)據(jù)到 Buffer 中,這個的操作系統(tǒng)緩沖區(qū)就是底層的 TCP 協(xié)議關(guān)聯(lián)的 RecvQ 或者SendQ 隊列,從操作系統(tǒng)緩沖區(qū)到用戶緩沖區(qū)數(shù)據(jù)比較耗性能,Buffer 提供了另外一種直接操作操作系統(tǒng)緩沖區(qū)的的方式即 ByteBuffer.allocateDirector(size),這個方法返回的byteBuffer 就是與底層操作方式類似??臻g關(guān)聯(lián)的緩沖區(qū),它的操作方式與 linux2.4 內(nèi)核的

31、sendfile回頁首I/O 調(diào)優(yōu)下面就磁盤 I/O 和網(wǎng)絡 I/O 的一些常用的優(yōu)化技巧進行總結(jié)如下:磁盤 I/O 優(yōu)化性能檢測我們的應用程序通常都需要磁盤數(shù)據(jù),而磁盤 I/O 通常都很耗時,我們要I/O 是否是一個瓶頸,我們有一些參數(shù)指標可以參考:如 我們可以測試應用程序看系統(tǒng)的 I/O wait 指標是否正常,例如測試機器有 4 個 CPU,那么理想的 I/O wait 參數(shù)不應該超過 25%,如果超過 25% 的話,I/O 很可能成為應用程序的性能瓶頸。Linux 操作系統(tǒng)下可以通過 iostat 命令查看。通常 我們在I/O 性能時還會看另外一個參數(shù)就是 IOPS,我們應用程序需要最

32、低的 IOPS 是多少,而我們的磁盤的 IOPS 能不能達到我們的要求。每個磁盤的 IOPS 通常是在一個范圍內(nèi),這和在磁盤的數(shù)據(jù)塊的大小和方式也有關(guān)。但是主要是由磁盤的轉(zhuǎn)速決定的,磁盤的轉(zhuǎn)速越高磁盤的 IOPS 也越高?,F(xiàn) 在為了提高磁盤 I/O 的性能,通常采用一種叫 RAID 的技術(shù),就是將不同的磁盤組合起來來提高 I/O 性能,目前有多種 RAID 技術(shù),每種 RAID 技術(shù)對 I/O 性能提升會有不同,可以用一個 RAID 因子來代表,磁盤的讀寫吞吐量可以通過iostat 命令來獲取,于是我們可以計算出一個理論的 IOPS 值,計算公式如下所以:( 磁盤數(shù) * 每塊磁盤的 IOPS)

33、/( 磁盤讀的吞吐量 +RAID 因子 * 磁盤寫的吞吐量 )=IOPS這個公式的詳細信息請查閱參考資料 Understanding Disk I/O。提升 I/O 性能提升磁盤 I/O 性能通常的方法有:1.2.增加緩存,減少磁盤次數(shù)優(yōu)化磁盤的管理系統(tǒng),設計最優(yōu)的磁盤這里是在底層操作系統(tǒng)層面考慮的。策略,以及磁盤的尋址策略,3.設計合理的磁盤數(shù)據(jù)塊,以及這些數(shù)據(jù)塊的策略,這里是在應用層面考慮的。如我們可以給存放的數(shù)據(jù)設計索引,通過尋址索引來加快和減少磁盤的應用合理的所示:,還有可以采用異步和非阻塞的方式加快磁盤的效率。4.RAID 策略提升磁盤 IO,每種 RAID 的區(qū)別我們可以用下表表

34、2.RAID 策略磁盤陣列說明RAID 數(shù)據(jù)被平均寫到多個磁盤陣列中,寫數(shù)據(jù)和讀數(shù)據(jù)都是并行的,所以磁盤0的 IOPS 可以提高一倍。磁盤陣列說明RAID 1 的主要作用是能夠提高數(shù)據(jù)的安全性,它將一份數(shù)據(jù)分別到多RAID 1個磁盤陣列中。并不能提升 IOPS 但是相同的數(shù)據(jù)有多個備份。通常用于對數(shù)據(jù)安全性較高的場合中。這中設計方式是前兩種的折中方式,它將數(shù)據(jù)平均寫到所有磁盤陣列總數(shù)減一的磁盤中,往另外一個磁盤中寫入這份數(shù)據(jù)的奇偶校驗信息。如果其中一個磁盤損壞,可以通過其它磁盤的數(shù)據(jù)和這個數(shù)據(jù)的奇偶校驗信息來恢復這份數(shù)據(jù)。如名字一樣,就是根據(jù)數(shù)據(jù)的備份情況進行分組,一份數(shù)據(jù)同時寫到多個備份磁盤

35、分組中,同時多個分組也會并行讀寫。RAID 5RAID 0+1網(wǎng)絡 I/O 優(yōu)化網(wǎng)絡 I/O 優(yōu)化通常有一些基本處理原則:1.一個是減少網(wǎng)絡交互的次數(shù):要減少網(wǎng)絡交互的次數(shù)通常我們在需要網(wǎng)絡交互的兩端會設置緩存,比如 Oracle 的 JDBC 驅(qū)動程序,就提供了對查詢的 SQL 結(jié)果的緩存,在客戶端和數(shù)據(jù)庫端 ,可以有效的減少對數(shù)據(jù)庫的。關(guān)于 Oracle JDBC 的內(nèi)存以參考 Oracle JDBC 內(nèi)存管理。除了設置緩存還有一個辦法是,合并請求:如在數(shù)據(jù)庫時,我們要查 10 個 id,我可以每次查一個 id,也可以一次查 10 個id。再比如在一個頁面時通過會有多個 js 或 css

36、的文件,我們可以將多個 js 文件合并在一個 HTTP到后端 Web 服務器根據(jù)這個 URL中,每個文件用逗號隔開,然后,再拆分出各個文件,然后打包再一并發(fā)回給前端瀏覽器。這些都是常用的減少網(wǎng)絡 I/O 的辦法。減少網(wǎng)絡傳輸數(shù)據(jù)量的大?。簻p少網(wǎng)絡數(shù)據(jù)量的辦法通常是將數(shù)據(jù)壓縮后2.再傳輸,如 HTTP 請求中,通常 Web 服務器將請求的 Web 頁面縮后在傳輸給瀏覽器。還有就是通過設計簡單的協(xié)議,盡量通過gzip 壓協(xié)議和 7 層頭來獲取有用的價值信息。比如在程序設計時,有 4 層盡量避免要整個通信數(shù)據(jù)來取得需要的信息。3.盡量減少編碼:通常在網(wǎng)絡 I/O 中數(shù)據(jù)傳輸都是以字節(jié)形式的,也就是通

37、常要序列化。但是我們要傳輸?shù)臄?shù)據(jù)都是字符形式的,從字符到字節(jié)必須編碼。但是這個編碼過程是比較耗時的,所以 在要經(jīng)過網(wǎng)絡 I/O傳輸時,盡量直接以字節(jié)形式或者減少字符到字節(jié)的轉(zhuǎn)化過程。也就是盡量提前將字符轉(zhuǎn)化為字節(jié),4.根據(jù)應用場景設計合適的交互方式:所謂的交互場景主要包括同步與異步阻塞與非阻塞方式,下面將詳細介紹。同步與異步所 謂同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成后,依賴的任務才能算完成,這是一種可靠的任務序列。要么都,失敗都 失敗,兩個任務的狀態(tài)可以保持一致。而異步是不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什么工作,依賴的任務也立即執(zhí)行,只要

38、完成了整 個任務就算完成了。至于被依賴的任務最終是否真正完成,依賴它的任務無法確定,所以它是不可靠的任務序列。我們可以用打很好的比喻同步與異 步操作。和發(fā)來在設計到 IO 處理時通常都會遇到一個是同步還是異步的處理方式的選擇問題。因為同步與異步的 I/O 處理方式對調(diào)用者的影響很大,在數(shù)據(jù)庫中都會遇到這個問題。因為 I/O 操作通常是一個非常耗時的操作,在一個任務序列中 I/O 通常都是性能瓶頸。但是同步與異步的處理方式對程序的可靠性影響非常大,同步能夠保證程序的可靠性,而異步可以提升程序的性能,必須在可靠性和性能之間做 個平衡,沒有完美的解決辦法。阻塞與非阻塞阻塞與非阻塞主要是從 CPU 的

39、消耗上來說的,阻塞就是 CPU 停下來等待一個慢的操作完成 CPU 才接著完成其它的事。非阻塞就是在這個慢的操作在執(zhí)行時CPU 去干其它別的事,等這個慢的操作完成時,CPU 再接著完成后續(xù)的操作。雖然表面上看非阻塞的方式可以明顯的提高 CPU 的利用率,但是也帶了另外一種后果就是系統(tǒng)的線程切換增加。增加的 CPU 使用時間能不能補償系統(tǒng)的切換成本需要好好評估。兩種的方式的組合組合的方式可以由四種,分別是:同步阻塞、同步非阻塞、異步阻塞、異步非阻塞,這四種方式都對 I/O 性能有影響。下面給出分析,并有一些常用的設計用例參考。表 3. 四種組合方式組合方式性能分析同步最常用的一種用法,使用也是最

40、簡單的,但是 I/O 性能一般很差,CPU 大阻塞 部分在提升 I/O同步網(wǎng)絡 I/O態(tài)。性能的常用,就是將 I/O 的阻塞改成非阻塞方式,尤其在是長連接,同時傳輸數(shù)據(jù)也不是很多的情況下,提升性能非常有非阻效。這種方式通常能提升 I/O 性能,但是會增加 CPU 消耗,要考慮增加的 I/O 性能能不能補償 CPU 的消耗,也就是系統(tǒng)的瓶頸是在 I/O 還是在 CPU 上。這種方式在分布式數(shù)據(jù)庫中經(jīng)常用到,例如在網(wǎng)一個分布式數(shù)據(jù)庫中寫一塞異步條,通常會有一份是同步阻塞的,而還有兩至三份是備份會阻塞 寫到其它上,這些備份通常都是采用異步阻塞的方式寫 I/O。異步阻塞對網(wǎng)絡 I/O 能夠提升效率,尤

41、其像上面這種同時寫多份相同數(shù)據(jù)的情況。異步這種組合方式用起來比較復雜,只有在一些非常復雜的分布式情況下使用,組合方式性能分析非阻像集群之間的消息同步機制一般用這種 I/O 組合方式。如 Cassandra 的塞Gossip 通信機制就是采用異步非阻塞的方式。它適合同時要傳多份相同的數(shù)據(jù)到集群中不同的,同時數(shù)據(jù)的傳輸量雖然不大,但是卻非常頻繁。這種網(wǎng)絡 I/O 用這個方式性能能達到最高。雖然異步和非阻塞能夠提升 I/O 的性能,但是也會帶來一些額外的性能成本, 例如會增加線程數(shù)量從而增加 CPU 的消耗,同時也會導致程序設計的復雜度上升。如果設計的不合理的話反而會導致性能下降。在實際設計時要根據(jù)

42、應用場景綜合評估一下。下面舉一些異步和阻塞的操作實例:在 Cassandra 中要數(shù)據(jù)通常會往多個數(shù)據(jù)節(jié)點命令,但是要檢查同步結(jié)果的應用場景,部分每個節(jié)點返回數(shù)據(jù)的完整性,所以需要一個異步代碼如下:3.異步同步結(jié)果class AsyncResult implements IAsyncResultprivate private private private privatebyte result_;AtomicBoolean done_ = new AtomicBoolean(false); Lock lock_ = new ReentrantLock();Condition condition_; long startTime_;public AsyncResult()condition_ = lock_.newCondition();/ 創(chuàng)建一個鎖startTime_ = System.currentTimeMillis();/* 檢查需要的數(shù)據(jù)是否已經(jīng)返回,如果沒有返回阻塞 */ public byte get()lock_.lock(); tryif (!done_.get()condition_.await();catch (InterruptedException ex) throw new AssertionError(ex);finallylock_.unloc

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論