Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文由作者“大白菜”分享,有較多修訂和改動。注意:本系列是給IM初學者的文章,IM老油條們還望海涵,勿噴!

          1、引言

          前兩篇《編碼實踐篇(單聊功能)》、《編碼實踐篇(群聊功能)》分別實現了控制臺版本的IM單聊和群聊的功能。

          通過前兩篇這兩個小案例來體驗的只是Netty在IM系統(tǒng)這種真實的開發(fā)實踐,但對比在真實的Netty應用開發(fā)當中,本系列的案例是非常的簡單的,主要目的其實是讓大家可以更好地了解其原理,從而寫出更高質量的 Netty 代碼。

          不過,雖然 Netty 的性能很高,但是也不能保證隨意寫出來的項目就是性能很高的,所以本篇將主要講解幾個基于Netty的IM系統(tǒng)的優(yōu)化實戰(zhàn)技術點。

          學習交流:

          本文同步發(fā)布于:http://www.52im.net/thread-3988-1-1.html

          2、寫在前面

          建議你在閱讀本文之前,務必先讀本系列的前三篇《IM系統(tǒng)設計篇》、《編碼實踐篇(單聊功能)》、《編碼實踐篇(群聊功能)》。

          最后,在開始本文之前,請您務必提前了解Netty的相關基礎知識,可從本系列首篇《IM系統(tǒng)設計篇》中的“知識準備”一章開始。

          3、系列文章

          本文是系列文章的第3篇,以下是系列目錄:

          4、基于Netty的IM系統(tǒng)常見優(yōu)化方向

          常見優(yōu)化方向腦圖:

          我們逐條詳細解釋一下這些優(yōu)化的目的:

          • 1)心跳檢測:主要是避免連接假死現象;
          • 2)連接斷開:則刪除通道綁定屬性、刪除對應的映射關系,這些信息都是保存在內存當中的,如果不刪除則造成資源浪費;
          • 3)性能問題:用戶 ID 和 Channel 的關系綁定存在內存當中,比如:Map,key 是用戶 ID,value 是 Channel,如果用戶量多的情況(客戶端數量過多),那么服務端的內存將被消耗殆盡;
          • 4)性能問題:每次服務端往客戶端推送消息,都需從Map里查找到對應的Channel,如果數量較大和查詢頻繁的情況下如何保證查詢性能;
          • 5)安全問題:HashMap 是線程不安全的,并發(fā)情況下,我們如何去保證線程安全;
          • 6)身份校驗:如何 LoginHandler 是負責登錄認證的業(yè)務 Handler,AuthHandler 是負責每次請求時校驗該請求是否已經認證了,這些 Handler 在鏈接就緒時已經被添加到 Pipeline 管道當中,其實,我們可以采用熱插拔的方式去把一些在做業(yè)務操作時用不到的 Handler 給剔除掉。

          以上是基于Netty的IM系統(tǒng)開發(fā)當中,需要去注意的技術優(yōu)化點,當然還有很多其他的細節(jié),比如:線程池這塊,需要大家慢慢去從實戰(zhàn)中積累。

          5、本篇優(yōu)化方向

          本篇主要的優(yōu)化內容主要是在第二篇單聊功能第三篇群聊功能的基礎上繼續(xù)完善幾點。

          具體的優(yōu)化方向如下:

          • 1)無論客戶端還是服務端都分別只有一個 Handler,這樣的話,業(yè)務越來越多,Handler 里面的代碼就會越來越臃腫,我們應該想辦法把 Handler 拆分成各個獨立的 Handler;
          • 2)如果拆分的 Handler 很多,每次有連接進來,那么都會觸發(fā) initChannel () 方法,所有的 Handler 都得被 new 一遍,我們應該把這些 Handler 改成單例模式(不需要每次都 new,提高效率);
          • 3)發(fā)送消息時,無論是單聊還是群聊,對方不在線,則把消息緩存起來,等待其上線再推送給他;
          • 4)連接斷開時,無論是主動和被動,需要刪除 Channel 屬性、刪除用戶和 Channel 映射關系。

          6、業(yè)務拆分以及單例模式優(yōu)化

          6.1 概述

          主要優(yōu)化細節(jié)如下:

          • 1)自定義 Handler 繼承 SimpleChannelInboundHandler,那么解碼的時候,會自動根據數據格式類型轉到相應的 Handler 去處理;
          • 2)@Shareable 修飾 Handler,保證 Handler 是可共享的,避免每次都創(chuàng)建一個實例。

          6.2 登錄Handler優(yōu)化

          @ChannelHandler.Sharable

          public class ClientLogin2Handler extends SimpleChannelInboundHandler<LoginResBean> {

              //1.構造函數私有化,避免創(chuàng)建實體

              private ClientLogin2Handler(){}

              //2.定義一個靜態(tài)全局變量

              public static ClientLogin2Handler instance=null;

              //3.獲取實體方法

              public static ClientLogin2Handler getInstance(){

                  if(instance==null){

                      synchronized(ClientLogin2Handler.class){

                          if(instance==null){

                              instance=new ClientLogin2Handler();

                          }

                      }

                  }

                  return instance;

              }

           

              protected void channelRead0(

                  ChannelHandlerContext channelHandlerContext,

                  LoginResBean loginResBean) throws Exception {

           

                  //具體業(yè)務代碼,參考之前

              }

          }

          6.3 消息發(fā)送Handler優(yōu)化

          @ChannelHandler.Sharable

          public class ClientMsgHandler extends SimpleChannelInboundHandler<MsgResBean> {

              //1.構造函數私有化,避免創(chuàng)建實體

              private ClientMsgHandler(){}

              //2.定義一個靜態(tài)全局變量

              public static ClientMsgHandler instance=null;

              //3.獲取實體方法

              public static ClientMsgHandler getInstance(){

                  if(instance==null){

                      synchronized(ClientMsgHandler.class){

                          if(instance==null){

                              instance=new ClientMsgHandler();

                          }

                      }

                  }

                  return instance;

              }

           

              protected void channelRead0(

                  ChannelHandlerContext channelHandlerContext,

                  MsgResBean msgResBean) throws Exception {

           

                  //具體業(yè)務代碼,參考之前

              }

          }

          6.4 initChannel方法優(yōu)化

          .handler(newChannelInitializer<SocketChannel>() {

              @Override

              public void initChannel(SocketChannel ch) {

                  //1.拆包器

                  ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,5,4));

                  //2.解碼器

                  ch.pipeline().addLast(new MyDecoder());

                  //3.登錄Handler,使用單例獲取

                  ch.pipeline().addLast(ClientLogin2Handler.getInstance());

                  //4.消息發(fā)送Handler,使用單例獲取

                  ch.pipeline().addLast(ClientMsgHandler.getInstance());

                  //5.編碼器

                  ch.pipeline().addLast(new MyEncoder());

              }

          });

          6.5 小結

          這種業(yè)務拆分以及單例模式優(yōu)優(yōu)化是Netty開發(fā)當中很常用的,可以更好的維護基于Netty的代碼并提高應用性能。

          7、數據緩存優(yōu)化

          為了提高用戶體驗,在發(fā)送消息(推送消息)時,如果接收方不在線,則應該把消息緩存起來,等對方上線時,再推送給他。

          7.1 數據緩存到集合

          //1.定義一個集合存放數據(真實項目可以存放數據庫或者redis緩存),這樣數據比較安全。

          private List<Map<Integer,String>> datas=new ArrayList<Map<Integer,String>>();

           

          //2.服務端推送消息

          private void pushMsg(MsgReqBean bean,Channel channel){

              Integer touserid=bean.getTouserid();

              Channel c=map.get(touserid);

           

              if(c==null){//對方不在線

                  //2.1存放到list集合

                  Map<Integer,String> data=new HashMap<Integer, String>();

                  data.put(touserid,bean.getMsg());

                  datas.add(data);

           

                  //2.2.給消息“發(fā)送人”響應

                  MsgResBean res=new MsgResBean();

                  res.setStatus(1);

                  res.setMsg(touserid+">>>不在線");

                  channel.writeAndFlush(res);

           

              }else{//對方在線

                  //2.3.給消息“發(fā)送人”響應

                  MsgResBean res=new MsgResBean();

                  res.setStatus(0);

                  res.setMsg("發(fā)送成功);

                  channel.writeAndFlush(res);

           

                  //2.4.給接收人推送消息

                  MsgRecBean res=new MsgRecBean();

                  res.setFromuserid(bean.getFromuserid());

                  res.setMsg(bean.getMsg());

                  c.writeAndFlush(res);

              }

          }

          7.2 上線推送

          private void login(LoginReqBean bean, Channel channel){

              Channel c=map.get(bean.getUserid());

              LoginResBean res=new LoginResBean();

              if(c==null){

                  //1.添加到map

                  map.put(bean.getUserid(),channel);

                  //2.給通道賦值

                  channel.attr(AttributeKey.valueOf("userid")).set(bean.getUserid());

                  //3.登錄響應

                  res.setStatus(0);

                  res.setMsg("登錄成功");

                  res.setUserid(bean.getUserid());

                  channel.writeAndFlush(res);

           

                  //4.根據user查找是否有尚未推送消息

                  //思路:根據userid去lists查找.......

           

              }else{

                  res.setStatus(1);

                  res.setMsg("該賬戶目前在線");

                  channel.writeAndFlush(res);

              }

          }

          8、連接斷開事件處理優(yōu)化

          如果客戶端網絡故障導致連接斷開了(非主動下線),那么服務端就應該能監(jiān)聽到連接的斷開,且此時應刪除對應的 map 映射關系。但是映射關系如果沒有刪除掉,將導致服務器資源沒有得到釋放,進而影響客戶端的下次同一個賬號登錄以及大量的客戶端掉線時性能。

          8.1 正確寫法

          實例:

          public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

              //映射關系

              private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

              //連接斷開,觸發(fā)該事件

              @Override

              public void channelInactive(ChannelHandlerContext ctx) throws Exception {

                  //1.獲取Channel

                  Channel channel=ctx.channel();

           

                  //2.從map里面,根據Channel找到對應的userid

                  Integer userid=null;

                  for(Map.Entry<Integer, Channel> entry : map.entrySet()){

                      Integer uid=entry.getKey();

                      Channel c=entry.getValue();

                      if(c==channel){

                          userid=uid;

                      }

                  }

                  //3.如果userid不為空,則需要做以下處理

                  if(userid!=null){

                      //3.1.刪除映射

                      map.remove(userid);

                      //3.2.移除標識

                      ctx.channel().attr(AttributeKey.valueOf("userid")).remove();

                  }

              }

          }

          8.2 錯誤寫法

          Channel 斷開,服務端監(jiān)聽到連接斷開事件,但是此時 Channel 所綁定的屬性已經被移除掉了,因此這里無法直接獲取的到 userid。

          實例:

          public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

              //映射關系

              private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

           

              //連接斷開,觸發(fā)該事件

              @Override

              public void channelInactive(ChannelHandlerContext ctx) throws Exception {

                  //1.獲取Channel綁定的userid

                  Object userid=channel.attr(AttributeKey.valueOf("userid")).get();

           

                  //2.如果userid不為空

                  if(userid!=null){

                      //1.刪除映射

                      map.remove(userid);

                      //2.移除標識

                      ctx.channel().attr(AttributeKey.valueOf("userid")).remove();

                  }

              }

          }

          9、本篇小結

          本篇內容還是相對容易理解的,主要是優(yōu)化前面兩篇實現的IM聊天功能,優(yōu)化內容是業(yè)務 Handler 的拆分以及使用單例模式、接受人不在線則緩存數據、等其上線再推送、監(jiān)聽連接斷開刪除對應的映射關系。

          限于篇幅,本系列文章文章沒辦法真正講解開發(fā)一個完整IM系統(tǒng)所涉及的方方面面,如果有興趣,可以繼續(xù)閱讀更有針對性的IM開發(fā)文章,比如IM架構設計IM通信協(xié)議IM通信安全群聊優(yōu)化弱網優(yōu)化網絡保活等。

          10、參考資料

          [1] 新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析

          [2] 理論聯系實際:一套典型的IM通信協(xié)議設計詳解

          [3] 淺談IM系統(tǒng)的架構設計

          [4] 簡述移動端IM開發(fā)的那些坑:架構設計、通信協(xié)議和客戶端

          [5] 一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)

          [6] 一套原創(chuàng)分布式即時通訊(IM)系統(tǒng)理論架構方案

          [7]  一套高可用、易伸縮、高并發(fā)的IM群聊、單聊架構方案設計實踐

          [8] 一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等

          [9] 從新手到專家:如何設計一套億級消息量的分布式IM系統(tǒng)

          [10] 基于實踐:一套百萬消息量小規(guī)模IM系統(tǒng)技術要點總結

          [11] 探探的IM長連接技術實踐:技術選型、架構設計、性能優(yōu)化

          [12] 拿起鍵盤就是干,教你徒手開發(fā)一套分布式IM系統(tǒng)

          [13] 萬字長文,手把手教你用Netty打造IM聊天

          [14] 基于Netty實現一套分布式IM系統(tǒng)

          [15] SpringBoot集成開源IM框架MobileIMSDK,實現即時通訊IM聊天功能

          本文同步發(fā)布于:http://www.52im.net/thread-3988-1-1.html



          作者:Jack Jiang (點擊作者姓名進入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發(fā)表評論。


          網站導航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 蕲春县| 麻城市| 日照市| 安徽省| 清远市| 孟村| 兰溪市| 乐陵市| 庆城县| 海口市| 临沧市| 确山县| 桦甸市| 外汇| 喀什市| 建瓯市| 高州市| 大同市| 叶城县| 德州市| 合肥市| 濮阳市| 甘孜县| 崇阳县| 新源县| 桃园市| 义马市| 同德县| 山丹县| 黎川县| 横峰县| 介休市| 平和县| 韶关市| 宜兴市| 来宾市| 武穴市| 商城县| 保康县| 和硕县| 南皮县|