netty优化 (-) websocket解码异常后自动断开连接处理
背景:
公司需要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,跳过了正常解析 。