Netty 入門與實戰:仿寫微信 IM 即時通信系統學習筆記

Netty 入門與實戰:仿寫微信 IM 即時通信系統學習筆記

  • 源代碼: github.com/lightningMa…html

  • NIO與OIO的區別java

    • 解決線程資源有限
      • 使用selector管理多個鏈接,減小建立線程的開銷。當有新鏈接進來時,將它註冊到一個selector上,批量監控是否有可讀的數據鏈接
    • 解決線程切換效率低下
      • 因爲NIO模型中線程數量大大下降,線程切換的開銷也變低了
    • 解決IO讀寫面向流
      • IO讀寫是面向流,意味着讀完以後流沒法再次讀取,須要本身緩存數據。而NIO是面向Buffer的,能夠隨意讀取Buffer中的任一字節,不須要本身緩存,只需移動指針。
  • NIO編程核心思想react

    • NIO 模型中一般會有兩個線程,每一個線程綁定一個輪詢器 selector ,例如 serverSelector負責輪詢是否有新的鏈接,clientSelector負責輪詢鏈接是否有數據可讀
    • 服務端監測到新的鏈接以後,再也不建立一個新的線程,而是直接將新鏈接綁定到clientSelector上,這樣就不用 IO 模型中 1w 個 while 循環在死等
    • clientSelector被一個 while 死循環包裹着,若是在某一時刻有多條鏈接有數據可讀,那麼經過clientSelector.select(1)方法能夠輪詢出來,進而批量處理
    • 數據的讀寫面向 Buffer
  • JAVA原始NIO相比Netty的缺點nginx

    • JDK 的 NIO 編程模型不友好,ByteBuffer 的 Api 簡直反人類
    • 對 NIO 編程來講,連簡單的自定義協議拆包都要你本身實現
    • JDK 的 NIO 底層由 epoll 實現,該實現飽受詬病的空輪詢 bug 會致使 cpu 飆升 100%
    • 項目龐大以後,自行實現的 NIO 很容易出現各種 bug,維護成本較高
  • 服務端的啓動git

    • ServerBootstrap
    • NioEventLoopGroup -> boss/worker
    • .channel -> NioServerSocketChannel
    • .group
    • .handler
    • .childHandler
    • ChannelInitializer
    • childOption
    • .option
    • .bind
  • 客戶端的啓動github

    • Bootstrap
    • channel -> NioSocketChannel
    • option
    • handler
    • attr
    • pipeline -> SocketChannel.pipeline
  • channelHandlerweb

    • ChannelInboundHandlerAdapter 主要用於實現其接口 ChannelInboundHandler的全部方法,這樣咱們只須要重寫咱們感興趣的方法,不用實現handler中的每個方法
    • ChannelOutboundHandlerAdapter 主要用於實現 ChannelOutboundHandler的全部方法
  • pipelineredis

    • ByteToMessageDecoder 實現自定義解碼 不用關心ByteBuf的強轉和解碼結構的傳遞
    • MessageToByteEncoder 實現自定義編碼 不用關係ByteBuf的建立,不用每次向對端寫Java對象都進行一次編碼
    • SimpleChannelInboundHandler 實現每一種指令的處理,將類型轉換操做交給編解碼處理器,必須實現channelRead0來完成request/response的處理
  • 粘包拆包編程

    • Netty 自帶的拆包器
      • FixedLengthFrameDecoder 固定長度的拆包器
        - 若是你的應用層協議很是簡單,每一個數據包的長度都是固定的,好比 100,那麼只須要把這個拆包器加到 pipeline 中,Netty 會把一個個長度爲 100 的數據包 (ByteBuf) 傳遞到下一個 channelHandler。
      • LineBasedFrameDecoder 行拆包器 - 從字面意思來看,發送端發送數據包的時候,每一個數據包之間以換行符做爲分隔,接收端經過 LineBasedFrameDecoder 將粘過的 ByteBuf 拆分紅一個個完整的應用層數據包。
      • DelimiterBasedFrameDecoder 分隔符拆包器 - DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不過咱們能夠自定義分隔符。
      • LengthFieldBasedFrameDecoder 基於長度域拆包器 《數據長度》 - 最後一種拆包器是最通用的一種拆包器,只要你的自定義協議中包含長度域字段,都可以使用這個拆包器來實現應用層拆包。
    • LengthFieldBasedFrameDecoder 長度域
      • new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 7, 4); 第一個參數指的是數據包的最大長度,第二個參數指的是長度域的偏移量,第三個參數指的是長度域的長度
      • 須要在 pipeline 的最前面加上這個拆包器
    • 拒絕非本協議鏈接
      • 解碼器解碼時判斷協議標識,若是非本協議,則拒絕處理該報文
  • channelHandler lifeCycle緩存

    • 開啓鏈接: handlerAdded -> channelRegister -> channelActive -> channelRead -> channelReadComplete
      • handlerAdded 指的是當檢測到新鏈接以後,調用 ch.pipeline().addLast(new LifeCyCleTestHandler()); 以後的回調,表示在當前的 channel 中,已經成功添加了一個 handler 處理器。
      • channelRegistered
        • 這個回調方法,表示當前的 channel 的全部的邏輯處理已經和某個 NIO 線程創建了綁定關係,相似 socket.accept 到新的鏈接,而後建立一個線程來處理這條鏈接的讀寫,只不過 Netty 裏面是使用了線程池的方式, 只須要從線程池裏面去抓一個線程綁定在這個 channel 上便可。
      • channelActive 當 channel 的全部的業務邏輯鏈準備完畢(也就是說 channel 的 pipeline 中已經添加完全部的 handler)以及綁定好一個 NIO 線程以後,這條鏈接算是真正激活了,接下來就會回調到此方法。
      • channelRead
      • channelReadComplete
    • 關閉鏈接: channelInactive -> channelUnregistered -> handlerRemoved
      • channelInactive 表面這條鏈接已經被關閉了,這條鏈接在 TCP 層面已經再也不是 ESTABLISH 狀態了
      • channelUnregistered 既然鏈接已經被關閉,那麼與這條鏈接綁定的線程就不須要對這條鏈接負責了,這個回調就代表與這條鏈接對應的 NIO 線程移除掉對這條鏈接的處理
      • handlerRemoved 咱們給這條鏈接上添加的全部的業務邏輯處理器都給移除掉
  • channelHandler lifeCycle應用舉例

    • ChannelInitializer 的實現原理
      • 仔細翻看一下咱們的服務端啓動代碼,咱們在給新鏈接定義 handler 的時候,其實只是經過 childHandler() 方法給新鏈接設置了一個 handler,這個 handler 就是 ChannelInitializer, 而在 ChannelInitializer 的 initChannel() 方法裏面,咱們經過拿到 channel 對應的 pipeline,而後往裏面塞 handler
      • ChannelInitializer 其實就利用了 Netty 的 handler 生命週期中 channelRegistered() 與 handlerAdded() 兩個特性,咱們簡單翻一翻 ChannelInitializer 這個類的源代碼
      • ChannelInitializer:initChannel、 handlerAdded、channelRegistered
    • handlerAdded() 與 handlerRemoved() 這兩個方法一般能夠用在一些資源的申請和釋放
    • channelActive() 與 channelInActive()
      • 對咱們的應用程序來講,這兩個方法代表的含義是 TCP 鏈接的創建與釋放,一般咱們在這兩個回調裏面統計單機的鏈接數,channelActive() 被調用,鏈接數加一,channelInActive() 被調用,鏈接數減一
      • 另外,咱們也能夠在 channelActive() 方法中,實現對客戶端鏈接 ip 黑白名單的過濾
      • 統計用戶鏈接數、流量
    • channelRead
    • channelReadComplete
      • 優化 ctx.channel().flush() 批量刷新
  • 處理優化

    • 共享handler
      • 對於沒有成員變量的handler可使用單例模式,提升效率,避免建立不少小的對象
      • @ChannelHandler.Sharable
      • LoginRequestHandler is not a @Sharable handler, so can't be added or removed multiple times.
    • 合併編解碼器
      • extends MessageToMessageCodec<ByteBuf,Packet>
      • 編碼器當outbound處理,解碼器當作inbound處理便可
    • 壓縮 handler 合併平行 handler
      • handler和handler之間無前後順序時,能夠放在一個map中,經過類型判斷處理
    • 更改事件傳播源
      • ctx.writeAndFlush() 替代 ctx.channel.writeAndFlush()
      • 若是明確知道後面的操做只須要解碼便可,就能夠直接使用ctx.writeAndFlush
    • 減小阻塞主線程的操做
      • channelRead0 異步處理
    • 統計處理時長
      • 使用addListener監聽channel信息 -> ChannelFuture
  • 心跳和空閒檢測

    • 鏈接假死緣由
      • 應用程序出現線程堵塞,沒法進行數據的讀寫。
      • 客戶端或者服務端網絡相關的設備出現故障,好比網卡,機房故障。
      • 公網丟包
    • 服務端空閒檢測
      • IdleStateHandler判斷是不是鏈接假死,channelIdle方法處理假死的鏈接,通常是手動關閉
    • 客戶端定時發心跳
      • heartbeat 定時發送
      • 主要用於排除客戶端確實沒有數據傳輸的正常狀況
    • 服務端回覆心跳與客戶端空閒檢測
      • heartbeatHandler、空閒檢測
  • netty-user-guide:netty.io/wiki/user-g…

  • netty-new-feature:netty.io/wiki/new-an…

  • netty-websocket-test-demo: github.com/netty/netty…

  • Netty源碼分析

  • 拓展問題

    • 集羣模式下多機器,netty 服務端如何工做?
      • nginx負載均衡,redis存儲channel關係和ip,服務器之間互相轉發消息
    • websocket-netty如何應用?
      • netty-sockio redsion 如何使用?
    • 使用netty實現消息推送,在生產環境須要處理那些常見問題?
    • dubbo等RPC服務是如何使用netty的?
相關文章
相關標籤/搜索