Netty學習二:Java IO與序列化

1 Java IO

1.1 Java IO

1.1.1 IO

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

按照軟硬件可分爲:java

  • 磁盤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是一種跨語言的高效二進制序列化方式

相關文章
相關標籤/搜索