DotNetty发送请求的最佳实践

长链接发送request/response时, 绝大部分包都是小包, 而每个小包都要消耗一个IP包, 成本大约是20-30us, 普通千兆网卡的pps大约是60Wpps, 所以想要提高长链接密集IO的应用性能, 需要做包的合并, 也称为了scatter/gather io或者vector io.

在linux下有readv/writev就是对应这个需求的, 减少系统调用, 减少pps, 提高网卡的吞吐量. 关于readv提高读的速度, 可以看看陈硕muduo里面对于readv的使用, 思路是就是在栈上面弄一个64KB的数组, 组成readv的第二块buffer, 从而尽可能一次性把socket缓冲区的内容全部出来(参见5). 这里不再赘述, 重点描述DotNetty下面怎么做Gathering Write.

首先得有一个Channel<IMessage>, 用来做写的缓冲, 让业务关心业务, 网络关心网络, 否则每个业务都WriteAndFlushAsync, 那是不太可能有合并发送的.

然后就是SendingLoop的主循环, 里面不停的从Channel里面TryRead包, 然后WriteAsync, 隔几个包Flush一次. 类似的思想在Orleans Network里面也存在.

1 public void RunSendLoopAsync(IChannel channel) 2 { 3     var allocator = channel.Allocator; 4     var reader = this.queue.Reader; 5     Task.Run(async () =>  6     { 7         while (!this.stop)  8         { 9             var more = await reader.WaitToReadAsync();10             if (!more) 11             {12                 break;13             }14 15             IOutboundMessage message = default;16             var number = 0;17             try 18             {19                 while (number < 4 && reader.TryRead(out message) && message != null) 20                 {21                     Interlocked.Decrement(ref this.queueCount);22                     var msg = message.Inner as IMessage;23                     var buffer = msg.ToByteBuffer(allocator);24                     channel.WriteAsync(buffer);25                     number++;26                 }27                 channel.Flush();28                 number = 0;29             }30             catch (Exception e)  when(message != default)31             {32                 logger.LogError("SendOutboundMessage Fail, SessionID:{0}, Exception:{1}",33                     this.sessionID, e.Message);34                 this.messageCenter.OnMessageFail(message);35             }36         }37         this.logger.LogInformation("SessionID:{0}, SendingLoop Exit", this.sessionID);38     });39 }

第19-27行是关键, 这边每4个包做一下flush, 然后flush会触发DotNetty的DoWrite:

1 protected override void DoWrite(ChannelOutboundBuffer input) 2 { 3     List<ArraySegment<byte>> sharedBufferList = null; 4     try 5     { 6         while (true) 7         { 8             int size = input.Size; 9             if (size == 0)10             {11                 // All written12                 break;13             }14             long writtenBytes = 0;15             bool done = false;16 17             // Ensure the pending writes are made of ByteBufs only.18             int maxBytesPerGatheringWrite = ((TcpSocketChannelConfig)this.config).GetMaxBytesPerGatheringWrite();19             sharedBufferList = input.GetSharedBufferList(1024, maxBytesPerGatheringWrite);20             int nioBufferCnt = sharedBufferList.Count;21             long expectedWrittenBytes = input.NioBufferSize;22             Socket socket = this.Socket;23 24             List<ArraySegment<byte>> bufferList = sharedBufferList;25             // Always us nioBuffers() to workaround data-corruption.26             // See https://github.com/netty/netty/issues/276127             switch (nioBufferCnt)28             {29                 case 0:30                     // We have something else beside ByteBuffers to write so fallback to normal writes.31                     base.DoWrite(input);32                     return;33                 default:34                     for (int i = this.Configuration.WriteSpinCount - 1; i >= 0; i--)35                     {36                         long localWrittenBytes = socket.Send(bufferList, SocketFlags.None, out SocketError errorCode);37                         if (errorCode != SocketError.Success && errorCode != SocketError.WouldBlock)38                         {39                             throw new SocketException((int)errorCode);40                         }

DotNetty TcpSocketChannel类的DoWrite函数, 19行获取当前ChannelOutboundBuffer的Segment<byte>数组, 然后在36行调用Socket.Send一次性发出去, 这个是Gathering Write的关键. 有了这个, 就可以不在业务层用CompositeByteBuffer.

DotNetty Libuv Transport的实现可以看6, 思想是类似的.

实际上Orleans 3.x做的网络优化, 也有类似的思想:

1 private async Task ProcessOutgoing() 2 { 3     await Task.Yield(); 4  5     Exception error = default;    6     PipeWriter output = default; 7     var serializer = this.serviceProvider.GetRequiredService<IMessageSerializer>(); 8     try 9     {10         output = this.Context.Transport.Output;11         var reader = this.outgoingMessages.Reader;12         if (this.Log.IsEnabled(LogLevel.Information))13         {14             this.Log.LogInformation(15                 "Starting to process messages from local endpoint {Local} to remote endpoint {Remote}",16                 this.LocalEndPoint,17                 this.RemoteEndPoint);18         }19 20         while (true)21         {22             var more = await reader.WaitToReadAsync();23             if (!more)24             {25                 break;26             }27 28             Message message = default;29             try30             {31                 while (inflight.Count < inflight.Capacity && reader.TryRead(out message) && this.PrepareMessageForSend(message))32                 {33                     inflight.Add(message);34                     var (headerLength, bodyLength) = serializer.Write(ref output, message);35                     MessagingStatisticsGroup.OnMessageSend(this.MessageSentCounter, message, headerLength + bodyLength, headerLength, this.ConnectionDirection);36                 }37             }38             catch (Exception exception) when (message != default)39             {40                 this.OnMessageSerializationFailure(message, exception);41             }42 43             var flushResult = await output.FlushAsync();44             if (flushResult.IsCompleted || flushResult.IsCanceled)45             {46                 break;47             }48 49             inflight.Clear();50         }

核心在31行, 开始写, 43行开始flush, 只不过Orleans用的pipelines io, DotNetty是传统模型.

这样做, 可以在有限的pps下, 支撑更高的吞吐量.

个人感觉DotNetty更好用一些.

参考:

1. https://github.com/Azure/DotNetty/blob/dev/src/DotNetty.Transport/Channels/Sockets/TcpSocketChannel.cs#L271-L288

2. https://github.com/dotnet/orleans/blob/master/src/Orleans.Core/Networking/Connection.cs#L282-L294

3. https://docs.microsoft.com/zh-cn/windows/win32/winsock/scatter-gather-i-o-2

4. https://linux.die.net/man/2/writev

5. https://github.com/chenshuo/muduo/blob/d980315dc054b122612f423ee2e1316cb14bd3b5/muduo/net/Buffer.cc#L28-L38

6. https://github.com/Azure/DotNetty/blob/dev/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs#L106-L128

(0)

相关推荐

  • 如何使用thrift 服务引擎组件

    在本文中将介绍如何通过thrift 组件集成到surging 微服务引擎中,然后可以选择dotnetty 或thrift作为服务远程调用RPC,也可以通过其它语言的thrift 调用surging 服 ...

  • 这些新开发的R包尝起来确实鲜

    写在前面 翻了翻最近一年来cran上的R包,找了一些对我们很有用的,让当然看不完,进行了简单的了解,这里送给大家,看看是否可以帮助自己做好数据呢? ClinReport 提供输出wrd格式的表格,这个 ...

  • Go语言网络编程入门不走弯路最佳案例(写Api接口)

    Go语言是Google领导开发的一门编程语言,国内可访问的官网 https://golang.google.cn/ image-20201213123438844 只要选对了框架,用Go语言完成网络编 ...

  • 接口鉴权之sign签名校验与JWT验证

    优质文章,第一时间送达 作者 |  那一片蓝海 来源 |  urlify.cn/FJjmqa 需求描述: 项目里的几个Webapi接口需要进行鉴权,同接口可被小程序或网页调用,小程序里没有用户登录的概 ...

  • 7种人力资源最佳实践

    什么是人力资源最佳实践? 最佳实践是一套通用的人力资源管理流程和行动.在人力资源管理研究中,有两种关于如何管理人员的思想流派:第一个是最合适的,第二个是最佳实践. 最合适的观点指出,为了增加价值,人力 ...

  • 培训的定义、作用和最佳实践

    一.什么是人力资源开发? 人力资源开发一词最早是在1969年提出的,指的是劳动力的培训,教育和发展.它旨在弥合学校教育和工作场所要求之间的差距. 在早期,HRD会进行严格的动手培训,重点是掌握硬技能. ...

  • 从最佳实践的焦虑中解脱出来

    几乎每天都会出现关于最佳实践的头条新闻.例如: 1.参加一个会议:"XX公司会议创新管理提高效率,我们现在没有." 2.行业文章:"人力资源部即将消失." 3. ...

  • 中国电信MEC最佳实践白皮书

    大数据.云计算.AI 等新一代信息技术的高速发展,在为新兴互联网行业提供强劲驱动之外,也在引领传统行业实施数字化.智能化转型,并催生出智能制造.智慧金融等一系列全新智能产业生态.在中国电信 MEC 平 ...

  • go-zero解读与最佳实践(上)

    本文有『Go开源说』第三期 go-zero 直播内容修改整理而成,视频内容较长,拆分成上下篇,本文内容有所删减和重构. 大家好,很高兴来到"GO开源说" 跟大家分享开源项目背后的一 ...

  • 160页PPT学习华为集成产品研发最佳实践之IPD(附下载)

    华为在整个企业内部改革中最重要的两个项目一个是ISC(集成供应链),另外一个就是IPD.用任正非的话讲,这两项改革关系到华为的生死存亡.其中IPD的项目是先行项目也是重点. 今天我们学习下华为的IPD ...

  • 远程软件工程师的10个最佳实践

    从表面上看,当考虑软件工程师研发效率的时候,我们可能会想到时间管理.沟通和任务完成的有效性.问题是完成任务或者有一个预期的时间表并不一定等同于生产力.对于远程工作的软件工程师而言,正面临着常规思考.责 ...

  • 雇主品牌最佳实践——UPS公益项目赋能雇主品牌创新实践

    能与年轻人产生共鸣的雇主品牌,一定不只有认知上的肯定,还有情感上的喜欢.当争夺优秀年轻人才取得竞争优势成为一种市场共识时,一个有态度的雇主品牌,如何运用差异化和情感上的打法,与同行同业形成区隔,成为了 ...

  • IoT产品的10个最佳实践

    如果经历过,有时候就会被人回忆起来.上周末,经过和友人的友人深入地讨论,自己梳理了实现IoT产品的10条经验,并自以为是地称之为"最佳实践". 制造业花了数年甚至数十年时间来磨练他 ...