背景:
公司需要25台設備組網,用戶通過客戶端登錄后對25台機子進行監控操作(包括視頻播放)。
技術方案:
產品分為設備端、客戶端、服務端。為兼容以后的瀏覽器訪問,選java搭建服務器。服務器主要業務包括客戶端用戶管理、客戶端業務指令、權限;設備端登記、發現、在線監測、分組管理、權限。
由於環境比較簡單,后台服務采用netty的websocket協議進行通信,消息指令進行權限管理。
問題描述:
1、25台設備搭建后進行壓力測試,百兆路由可25路視頻的2個客戶端,3個客戶端同時打開會導致設備掉線頻繁,(添加重連限制客戶端個數)。
2、OOM,outof direct memory,此問題很懵逼。netty中derectmemory 是框架中進行計數處理的,測試中計數增長到一定值后保持穩定不存在超出;channelread0方法中會自動釋放bytebuf; 此問題無法重現,只好添加jvm內存待以后重現再處理!
3、長時間掛機無任何操作出現客戶端或者設備掉線問題,查看日志多是和decode解碼有關,消息異常解碼出錯,netty自動關閉通道斷開了連接。
測試結果:設備端掉線明顯;消息解析錯誤后直接關閉了連接;偶爾出現一個大的數據包接收一半后斷開連接;
websocket基於TCP協議,在不穩定的網絡環境下發送大量數據,並且發送頻率非常高,很可能會出現錯誤(1、程序處理邏輯錯誤;2、多線程同步問題;3、緩沖區溢出等)。這掉線的頻率讓人很難接收,抓包也是抓的崩潰, 放棄了! 幾個同事之間可能也都踢了好幾周的皮球,呵呵,感覺對不起公司的同事們。首先讓客戶端和設備端全部添加了斷線重連、優化設備端發送頻率、服務端緩存一些消息。
業務上做了優化之后,掉線有所緩解,但是偶爾一次的掉線的確讓人抓狂,尤其是這么小的局域網中,為了從這個鍋中脫離, 決定還是要有所優化, 可怕的框架bug ~~
A.下netty參數,消息隊列默認128 ,加到1024 ; 避免數據包的緩存
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.TCP_NODELAY,true)
B. 異常不關閉
重寫WebSocketDecoderConfig.closeOnProtocolViolation修改默認值。
ByteToMessageDecoder 中解析完后,
callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) 方法中 將 WebSocket08FrameDecoder的 state 重置為 WebSocket08FrameDecoder.State.READING_FIRST
重寫ByteToMessageDecoder .java 中callDecode 添加抽象方法initState
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try { while(true) { if (in.isReadable()) { int outSize = out.size(); if (outSize > 0) { fireChannelRead(ctx, out, outSize); out.clear(); if (ctx.isRemoved()) { return; } outSize = 0; } int oldInputLength = in.readableBytes(); this.decodeRemovalReentryProtection(ctx, in, out); if (!ctx.isRemoved()) { if (outSize == out.size()) { if (oldInputLength != in.readableBytes()) { continue; } } else { if (oldInputLength == in.readableBytes()) { throw new DecoderException(StringUtil.simpleClassName(this.getClass()) + ".decode() did not read anything but decoded a message."); } if (!this.isSingleDecode()) { continue; } } } } if ( ctx.name().equals("wsdecoder")){ try{ this.initState(ctx, in, out); }catch (Exception E){ } } return; } } catch (DecoderException var6) { throw var6; } catch (Exception var7) { throw new DecoderException(var7); } } protected abstract void initState(ChannelHandlerContext var1, ByteBuf var2, List<Object> var3) throws Exception;
重寫WebSocket08FrameDecoder.java 中添加initState
protected void initState(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if(this.state == WebSocket08FrameDecoder.State.CORRUPT){ this.state = WebSocket08FrameDecoder.State.READING_FIRST; } }
不將state 設置為READING_FIRST ,通道解析出現異常后,WebSocket08FrameDecoder每次消息解析都會走CORRUPT,跳過了正常解析 。