Netty學習二:Java IO與序列化


1 Java IO

1.1 Java IO

1.1.1 IO

IO,即輸入(Input)輸出(Output)的簡寫,是描述計算機軟硬件對二進制數據的傳輸、讀寫等操作的統稱。

按照軟硬件可分為:

  • 磁盤IO
  • 內存IO
  • 網絡IO

按照處理的方式可分為:

  • 同步IO
  • 非阻塞IO
  • 異步IO

按照數據類型可分為:

  • 字節流
  • 字符流

隨着軟硬件技術的飛速發展,IO性能也有了很大的發展,但IO還是影響現代計算機系統性能最重要的因素之一

  1. 磁盤技術還嚴重影響讀寫性能
  2. 網絡傳輸還存在很大的延遲
  3. 數據庫的IO已經成為計算機應用系統的主要瓶頸

1.1.2 IO相關指標

1.1.2.1 IOPS
IOPS,IO系統每秒所執行IO操作的次數,是一個重要的用來衡量系統IO能力的一個參數。對於單個磁盤組成的IO系統來說,計算它的IOPS不是一件很難的事情,只要我們知道了系統完成一次IO所需要的時間的話我們就能推算出系統IOPS來。

磁盤IOPS的計算:

IO Time = 尋址時間 + 60s/轉速/2 + IO塊大小/傳輸速率

IOPS = 1/IO Time = 1/(尋址時間 + 60s/轉速/2 + IO塊大小/傳輸速率)

不同轉速的磁盤IO:

  • 3600轉:1000/(5ms + 60000/3600/2 + 4K/40MB)=75
  • 7200轉:1000/(5ms + 60000/7200/2 + 4K/40MB)=108
1.1.2.2 IO響應時間
IO響應時間也被稱為IO延時(IO Latency),IO響應時間就是從操作系統內核發出的一個讀或者寫的IO命令到操作系統內核接收到IO回應的時間,注意不要和單個IO時間混淆了,單個IO時間僅僅指的是IO操作在磁盤內部處理的時間,而IO響應時間還要包括IO操作在IO等待隊列中所花費的等待時間。
  • 隨着系統實際IOPS越接近理論的最大值,IO的響應時間會成非線性的增長,越是接近最大值,響應時間就變得越大
1.1.2.3 吞吐量
吞吐量是指單位時間內傳輸的數據量的總和.

一個系統吞吐量通常由QPS(TPS)、並發數兩個因素決定,每套系統這兩個值都有一個相對極限值,在應用場景訪問壓力下,只要某一項達到系統最高值,系統的吞吐量就上不去了,如果壓力繼續增大,系統的吞吐量反而會下降,原因是系統超負荷工作,上下文切換、內存等等其它消耗導致系統性能下降。

QPS(TPS)=並發數/平均響應時間

1.1.3 Java的IO

IO是包括Java在內所有編程語言最重要的特性和模塊之一,因為不管是讀寫文件,分配回收內存和網絡通信都離不開IO。
同時IO也是計算機系統最主要的性能瓶頸和問題之一,特別是在分布式系統中IO問題更顯得突出。

Java最開始只支持BIO,到了JDK1.4開始支持NIO,在JDK7中支持NIO2.0(AIO)

JavaIO按照數據類型分為:

  • 字節流:InputStream/OutputStream
  • 字符流:Writer/Reader

1.2 BIO

BIO即Blocking IO,同步並且阻塞。

在BIO中,用戶線程發起一個IO操作以后,必須等待IO操作的完成,只有當真正的IO完成以后,用戶線程才能繼續操作。

如上圖,用戶發起一個請求到服務器,服務器接收后分配一個處理線程來進行處理,同時用戶線程阻塞以等待處理線程處理完成后,返回數據到用戶線程,用戶線程繼續往下執行。

BIO模型的問題:

  • 處理線程執行速度影響用戶線程的性能
  • 用戶線程與處理線程一對一的模式,隨着用戶線程的增多,處理線程也將持續增加

1.3 偽異步IO

為了解決BIO模型中線程一對一的問題,通過偽異步IO進行處理。

如上圖,服務器接收到用戶線程的請求后,通過后端的線程池分配線程進行處理。由於線程池的大小可以設置,因此可以限制服務端的資源使用。

偽異步IO實際上只是對一對一線程模型進行改進,沒有解決同步阻塞的問題

1.4 NIO

NIO即Non-Blocking IO,是通過非阻塞的方式實現IO的技術,Java在1.4后提供該技術的支持。

1.4.1 Java NIO

  1. 用戶線程將請求發送到服務端后,如果服務端不能馬上准備好數據,則即可返回,用戶線程繼續執行其他操作;
  2. 用戶線程主動詢問服務端是否准備好數據,如果服務端准備好數據,則將數據返回給用戶線程,用戶線程繼續處理。

1.4.2 Buffer、Channel和Selector

1.4.2.1 緩沖區Buffer
在Java NIO中,所有的數據操作都時在緩沖區中完成的。在讀取數據時,它直接讀緩沖區的數據;寫入數據時,也是將數據寫入緩存去。

緩沖區實際上是一個數組,包括:

  • 字節緩沖區:ByteBuffer
  • 字符緩沖區:CharBuffer
  • 短整型緩沖區:ShortBuffer
  • 整形緩沖區:IntBuffer
  • 長整形緩沖區:LongBuffer
  • 浮點緩沖區:FloatBuffer
  • 雙精度浮點型緩沖區:DoubleBuffer

一個 Buffer 主要由 position、limit、capacity 三個變量來控制讀寫的過程。這三個變量在讀和寫時分別代表的含義如下:

  1. position:當前寫入/讀取的單位數據的數量
  2. limit:代表最多能寫入/讀取多少單位的數據量
  3. capacity:Buffer的容量

在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數據。當切換Buffer到讀模式時, limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數據.

1.4.2.2 通道Channel
Channel是一個全雙工的雙向通道,可以通過它讀取和寫入數據。Channel總是從Buffer讀取數據或者向Buffer寫入數據.

Java Channel包括:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

1.4.2.3 多路復用器Selector
Selector類是NIO的核心類,Selector能夠檢測多個注冊的通道上是否有事件發生,如果有事件發生,便獲取事件然后針對每個事件進行相應的響應處理。這樣一來,只是用一個單線程就可以管理多個通道,也就是管理多個連接。這樣使得只有在連接真正有讀寫事件發生時,才會調用函數來進行讀寫,就大大地減少了系統開銷,並且不必為每個連接都創建一個線程,不用去維護多個線程,並且避免了多線程之間的上下文切換導致的開銷。

從圖中可以看出,當有讀或寫等任何注冊的事件發生時,可以從Selector中獲得相應的SelectionKey,同時從 SelectionKey中可以找到發生的事件和該事件所發生的具體的SelectableChannel,以獲得客戶端發送過來的數據。

使用NIO中非阻塞I/O編寫服務器處理程序,大體上可以分為下面三個步驟:

  1. 向Selector對象注冊感興趣的事件
  2. 從Selector中獲取感興趣的事件
  3. 根據不同的事件進行相應的處理

1.4.3 服務端處理

1. 打開ServerSocketChannel,用於監聽客戶端連接
2. 綁定監聽端口,設置連接為非阻塞模式
3. 創建Reactor線程,創建多路復用器並啟動線程
4. 將SeverSocketChannel注冊Reactor線程的多路復用器Selector上,監聽ACCEPT事件
5. 多路復用器在線程run方法的無限循環體內輪詢准備就緒的Key
6. 多路復用器監聽到有新的客戶端接入,處理新的接入請求,完成TCP三次握手,建立物理鏈路
7. 設置客戶端鏈路為非阻塞模式
8. 將新接入的客戶端連接到Reactor線程的多路復用器上,監聽讀操作,用來讀取客戶端發送的網絡消息
9. 異步讀取客戶端請求消息到緩沖區
10. 對Buffer進行編輯碼,將解碼成功的消息封裝成Task,投遞到業務線程池,進行業務邏輯編排
11. 將POJO對象編碼為Buffer,調用channel的異步write接口,將消息異步發送給客戶端

1.4.4 客戶端處理

1. 打開SocketChannel,綁定本地地址和端口
2. 設置SocketChannel為非阻塞模式,設置TCP參數
3. 異步連接服務端
4. 判斷是否連接成功,如果連接成功,則直接注冊讀狀態位到多路復用器中,如果沒有連接成功返回false
5. 向Reactor線程的多路復用器注冊OP_CONNECT狀態位
6. 創建Reactor線程,創建多路復用器並且啟動線程
7. 多路復用器在線程run方法的無限循環體內輪詢准備就緒的Key
8. 接收connect事件進行處理
9. 判斷連接結果,如果連接成功,注冊讀事件到多路復用器
10. 注冊讀事件到多路復用器
11. 異步讀客戶端請求消息到緩沖區
12. 對Buffer進行編解碼,將解碼成功的消息封裝成Task,投遞到業務線程池,進行業務邏輯編排
13. 將POJO對象編碼為Buffer,調用channel的異步write接口,將消息異步發送給客戶端

1.5 AIO

在JAVA NIO中用戶線程會主動的詢問數據是否准備完成,不是真正的異步

Java AIO即Java NIO2.0,是在JDK1.7中引入的新概念,並提供了異步文件通道和異步套接字通道的實現。
NIO2.0的異步套接字通道是真正的異步非阻塞IO,它對應UNIX網絡編程中的事件驅動IO,它不需要通過多路復用器(Selector)對注冊的通道進行輪詢操作即可實現異步讀寫,從而簡化了NIO的編程模型。

1.6 比較

  • BIO

    同步阻塞IO,性能低,編程較容器,適用於連接數目比較小且固定的架構

  • NIO

    同步非阻塞IO,性能高,編程較復雜,適用於連接數目多且連接比較短(輕操作)的架構

  • AIO

    異步非阻塞IO,性能高,編程較復雜,使用於連接數目多且連接比較長(重操作)的架構

2 序列化

2.1 序列化與反序列化

序列化 (Serialization)將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。在序列化期間,對象將其當前狀態寫入到臨時或持久性存儲區。以后,可以通過從存儲區中讀取或反序列化對象的狀態,重新創建該對象。
序列化使其他代碼可以查看或修改那些不序列化便無法訪問的對象實例數據。

什么情況下需要序列化:

  • 當你想把的內存中的對象保存到一個文件中或者數據庫中時候
  • 當你想用套接字在網絡上傳送對象的時候
  • 當你想通過RMI傳輸對象的時候

2.2 Java序列化

Java序列化是指把Java對象轉換為字節序列的過程;而Java反序列化是指把字節序列恢復為Java對象的過程。

2.2.1 Serializable

實現Serializable接口的Java類表示可以被Java默認序列化或者其他第三方序列化工具序列化,但有些第三方序列化工具序列化類時不需要該標識。

2.2.2 serialVersionUID

如果沒有設置這個值,你在序列化一個對象之后,改動了該類的字段或者方法名之類的,那如果你再反序列化想取出之前的那個對象時就可能會拋出異常,因為你改動了類中間的信息,serialVersionUID是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,當修改后的類去反序列化的時候發現該類的serialVersionUID值和之前保存在問價中的serialVersionUID值不一致,所以就會拋出異常。而顯示的設置serialVersionUID值就可以保證版本的兼容性,如果你在類中寫上了這個值,就算類變動了,它反序列化的時候也能和文件中的原值匹配上。而新增的值則會設置成null,刪除的值則不會顯示。

2.5.3 注意事項

  • 序列化只能保存對象的非靜態成員交量,不能保存任何的成員方法和靜態的成員變量,而且序列化保存的只是變量的值,對於變量的任何修飾符都不能保存。

  • 對於某些類型的對象,其狀態是瞬時的,這樣的對象是無法保存其狀態的。例如一個Thread對象或一個FileInputStream對象 ,對於這些字段,我們必須用transient關鍵字標明,否則編譯器將報措。

  • 當一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化。

2.3 序列化實現

  • Netty 序列化

    Netty通過ObjectEncoder和ObjectDecoder對對象進行序列化編解碼以便在網絡中傳輸數據

  • Dubbo 序列化

    Dubbo序列化是阿里在Dubbo框架中用於對象序列化的技術

  • Hessian 序列化

    Hessian是一種跨語言的高效二進制序列化方式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM