背景:
公司需要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,跳過了正常解析 。
