cuteant / SpanNetty

Port of Netty(v4.1.51.Final) for .NET

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

句柄缓慢增长

MetSystem opened this issue · comments

commented

SpanNetty 0.7.2012.2221
940家设备1分钟传一次,大概整点传

Environment.SetEnvironmentVariable("io.netty.noPreferDirect", "true");

     var dispatcher = new DispatcherEventLoopGroup();
                bossGroup = dispatcher;
                workerGroup = new WorkerEventLoopGroup(dispatcher, workerEventThreadCount);
       bootstrap
                    .Option(ChannelOption.SoBacklog, 256)
                    .Option(ChannelOption.Allocator, PooledByteBufferAllocator.Default)
                    .Handler(new LoggingHandler("SEV-LITN", LogLevel.ERROR))
                    .ChildOption(ChannelOption.SoKeepalive, true)
                    .ChildOption(ChannelOption.TcpNodelay, true)
                    .ChildOption(ChannelOption.SoSndbuf, 8 * 1024)
                    .ChildOption(ChannelOption.SoRcvbuf, 16 * 1024)
                    .ChildOption(ChannelOption.Allocator, PooledByteBufferAllocator.Default)

                    .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
                    {
                        IChannelPipeline pipeline = channel.Pipeline;
                        pipeline.AddLast(new LoggingHandler("SEV-CONN", LogLevel.INFO));
                        pipeline.AddLast("timeout", new IdleStateHandler(3 * 60, 3 * 60, 3 * 60));

                        pipeline.AddFirst("device", new DeviceHandler());
                        pipeline.AddLast("LineBasedFrameDecoder", new LineBasedFrameDecoder(1024));
                        pipeline.AddLast("StringDecoder", new StringDecoder());
                        pipeline.AddLast("CustomEncoder", new CustomEncoder());
                        pipeline.AddLast("Check", new CheckChannelHandler());
                        pipeline.AddLast("default", new DefaultServerHandler());
                    }));

                IChannel boundChannel = await bootstrap.BindAsync(AppSettings.Port);
                Console.ReadLine();

服务端应答集成设备代码

  var data = BuildPacket(context.Message.Content.ToString());
                if (devcieInfo.Channel.IsWritable && devcieInfo.Channel.IsActive)
                {
                    devcieInfo?.Channel.WriteAndFlushAsync(data);
                }
                else if (!devcieInfo.Channel.IsWritable)
                {
                    devcieInfo?.Channel.WriteAndFlushAsync(data).Wait(TimeSpan.FromSeconds(5));
                }

流程:

接入设备=》Server 产生消息=>rabbitmq=>业务消费消息=》产生应答消息=》rabbitmq =》Server 消费消息=》设备接入

日志中常见异常

异常1

DotNetty.Transport.Channels.ChannelException: Exception of type 'DotNetty.Transport.Channels.ChannelException' was thrown.
 ---> DotNetty.Transport.Libuv.Native.OperationException: ECONNRESET (ECONNRESET) : connection reset by peer
   --- End of inner exception stack trace ---

异常2

00:00:50.032 [ERR] ExceptionCaught:Message=>远程主机强迫关闭了一个现有的连接。 ,StackTrace=>   at DotNetty.Transport.ThrowHelper.ThrowSocketException(SocketError err)
   at DotNetty.Transport.Channels.Sockets.AbstractSocketByteChannel`2.SocketByteChannelUnsafe.FinishRead(SocketChannelAsyncOperation`2 operation)
System.Net.Sockets.SocketException (10054): 远程主机强迫关闭了一个现有的连接。
   at DotNetty.Transport.ThrowHelper.ThrowSocketException(SocketError err)
   at DotNetty.Transport.Channels.Sockets.AbstractSocketByteChannel`2.SocketByteChannelUnsafe.FinishRead(SocketChannelAsyncOperation`2 operation)
commented

net 5.0

如果句柄数是在一定范围内反复增减, 不会一直涨到几十万的话, 而且是稳定的定时增长的话,

那可能就是我上次发现的那个问题, GC的时候还是会回收的, 只是看起来比较烦, 但应该不会造成太大的性能问题
dotnet/runtime#47752
https://github.com/cuteant/SpanNetty/blob/future/src/DotNetty.Common/Concurrency/XThread.cs#L124
这个主要是由于这里的Task.Delay().Wait()造成的, 根据版本历史, 最初用主要是因为netstandard2.0之前不支持Thread.Sleep().

如果想改的话, 因为现在不需要再支持netstandard1.x的原因, 这里可以考虑用回Thread.Sleep(ms), 或者改用using(var e = new ManualResetEventSlim()) {e.Wait(ms);}(这个netstandard1.3好像也支持, 参考https://stackoverflow.com/a/16374324)

commented

我尝试下,非常感谢

commented

delay = Task.Delay((int)sleepTimeMs, _owner.CancellationToken);

这个里面是不是也需要改。

今天用dotnetty源码改了XThread.cs 发现稍微缓解了增长速度,但还是存在(spannetty改了后生成环境找不到DLL,所以改成dotnetty)。

  1. HashedWheelTimer如果你用到了可以改下看看, 框架本身只是移植了, 但没有用这个类

其他句柄就不知道是什么了, 一般情况下句柄数基本上应该等于线程数+连接数+一个基本稳定的值, 如果没有一直启动新线程或者建立连接应该比较稳定的, 严格来说没有无限增长(也就是最终会稳定或在一段时间后下降)或者像过山车那样反复极速的上升下降都可以不管它

  1. 如果你现在是用的libuv也可以试试不开还会不会有句柄
    我对这行代码是如何工作的存在疑问, 感觉父类的实现才是正确的, 但没时间去测试

    protected override void TaskDelay(int millisecondsTimeout)
    {
    _ = _timerHandle.Start(millisecondsTimeout, 0);
    }

  2. 如果你想的话可以用procexp或者process hacker(均需要管理员权限, 属于系统工具, 部分杀软可能报毒)自己检查下对应一直在增加的句柄是什么东西
    process hacker, 注意关掉选项里的HIDE
    image
    procexp, View/Show Lower Pane+Lower Pane View=HANDLEs
    image

commented

大佬帮忙看看

IotServer.exe.txt

998个端口,6914个Event,1781个线程
感觉你这个线程数有点多得不正常了, 线程和Event的比例基本还算正常. 理论上DotNetty的EventLoopGroup应该是在不同客户端间全局共享的, 每次new的默认值线程数是CPU核数(libuv)/CPU核数*2(其他).
建议调试下看看这些线程都在跑什么, 有可能是业务代码本身的问题

commented

libuv 设置10,CPU 16核

1.消费应答队列(用的MassTransit库,加了失败重试)
2.一个hangfire的定时0点发送校准命令(我尝试把这个定时校准放到另一个进程处理)
3.主动发送命令(新开的后台线程跑)

现在句柄53K了
IotServer.exe.句柄53000.txt

commented

ProcessHacker显示的进程1万个
ProcessHacker.txt

这种只能你自己调试下了

如果开发环境, vs调试涨上去后(或者直接附加到已经涨上去的进程)直接暂停, 然后调试/窗口/并行堆栈 或者 线程/vscode看线程, 自己检查下大部分线程都在跑什么堆栈, 感觉应该是有大量的线程都在跑同一个东西的?
如果生产环境, 自己在服务器上抓个dump(任务管理器右键, 创建dump), 复制回开发环境, 丢vs然后步骤同上, 或者丢windbg里面看看吧(用法可以参考https://www.cnblogs.com/huangxincheng/p/14388296.html)

commented

好,多谢指导