长链接发送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里面也存在.

 public void RunSendLoopAsync(IChannel channel)
{
var allocator = channel.Allocator;
var reader = this.queue.Reader;
Task.Run(async () =>
{
while (!this.stop)
{
var more = await reader.WaitToReadAsync();
if (!more)
{
break;
} IOutboundMessage message = default;
var number = ;
try
{
while (number < && reader.TryRead(out message) && message != null)
{
Interlocked.Decrement(ref this.queueCount);
var msg = message.Inner as IMessage;
var buffer = msg.ToByteBuffer(allocator);
channel.WriteAsync(buffer);
number++;
}
channel.Flush();
number = ;
}
catch (Exception e) when(message != default)
{
logger.LogError("SendOutboundMessage Fail, SessionID:{0}, Exception:{1}",
this.sessionID, e.Message);
this.messageCenter.OnMessageFail(message);
}
}
this.logger.LogInformation("SessionID:{0}, SendingLoop Exit", this.sessionID);
});
}

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

 protected override void DoWrite(ChannelOutboundBuffer input)
{
List<ArraySegment<byte>> sharedBufferList = null;
try
{
while (true)
{
int size = input.Size;
if (size == )
{
// All written
break;
}
long writtenBytes = ;
bool done = false; // Ensure the pending writes are made of ByteBufs only.
int maxBytesPerGatheringWrite = ((TcpSocketChannelConfig)this.config).GetMaxBytesPerGatheringWrite();
sharedBufferList = input.GetSharedBufferList(, maxBytesPerGatheringWrite);
int nioBufferCnt = sharedBufferList.Count;
long expectedWrittenBytes = input.NioBufferSize;
Socket socket = this.Socket; List<ArraySegment<byte>> bufferList = sharedBufferList;
// Always us nioBuffers() to workaround data-corruption.
// See https://github.com/netty/netty/issues/2761
switch (nioBufferCnt)
{
case :
// We have something else beside ByteBuffers to write so fallback to normal writes.
base.DoWrite(input);
return;
default:
for (int i = this.Configuration.WriteSpinCount - ; i >= ; i--)
{
long localWrittenBytes = socket.Send(bufferList, SocketFlags.None, out SocketError errorCode);
if (errorCode != SocketError.Success && errorCode != SocketError.WouldBlock)
{
throw new SocketException((int)errorCode);
}

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

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

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

 private async Task ProcessOutgoing()
{
await Task.Yield(); Exception error = default;
PipeWriter output = default;
var serializer = this.serviceProvider.GetRequiredService<IMessageSerializer>();
try
{
output = this.Context.Transport.Output;
var reader = this.outgoingMessages.Reader;
if (this.Log.IsEnabled(LogLevel.Information))
{
this.Log.LogInformation(
"Starting to process messages from local endpoint {Local} to remote endpoint {Remote}",
this.LocalEndPoint,
this.RemoteEndPoint);
} while (true)
{
var more = await reader.WaitToReadAsync();
if (!more)
{
break;
} Message message = default;
try
{
while (inflight.Count < inflight.Capacity && reader.TryRead(out message) && this.PrepareMessageForSend(message))
{
inflight.Add(message);
var (headerLength, bodyLength) = serializer.Write(ref output, message);
MessagingStatisticsGroup.OnMessageSend(this.MessageSentCounter, message, headerLength + bodyLength, headerLength, this.ConnectionDirection);
}
}
catch (Exception exception) when (message != default)
{
this.OnMessageSerializationFailure(message, exception);
} var flushResult = await output.FlushAsync();
if (flushResult.IsCompleted || flushResult.IsCanceled)
{
break;
} inflight.Clear();
}

核心在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

DotNetty发送请求的最佳实践的更多相关文章

  1. ASP.NET MVC防范CSRF最佳实践

    XSS与CSRF 哈哈,有点标题党,但我保证这篇文章跟别的不太一样. 我认为,网站安全的基础有三块: 防范中间人攻击 防范XSS 防范CSRF 注意,我讲的是基础,如果更高级点的话可以考虑防范机器人刷 ...

  2. RESTful API 设计最佳实践

    背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API ...

  3. memcache的最佳实践方案

    1.memcached的基本设置 1)启动Memcache的服务器端 # /usr/local/bin/memcached -d -m 10 -u root -l 192.168.0.200 -p 1 ...

  4. Ubuntu14.04+RabbitMQ3.6.3+Golang的最佳实践

    目录 [TOC] 1.RabbitMQ介绍 1.1.什么是RabbitMQ?   RabbitMQ 是由 LShift 提供的一个 Advanced Message Queuing Protocol ...

  5. 基于AWS的云服务架构最佳实践

    ZZ from: http://blog.csdn.net/wireless_com/article/details/43305701 近年来,对于打造高度可扩展的应用程序,软件架构师们挖掘了若干相关 ...

  6. 【转】优化Web程序的最佳实践

    自动排版有点乱,看着蛋疼,建议下载中文PDF版阅读或阅读英文原文. Yahoo!的Exceptional Performance团队为改善Web性能带来最佳实践.他们为此进行了 一系列的实验.开发了各 ...

  7. 可伸缩性最佳实践:来自eBay的经验

    看到一篇关于系统可伸缩性(可扩展)的文章,eBay的架构师Randy Shoup写的,原文出处没找到,就不写转载的地址了.根据自己的理解对文章有修改剪切的地方. 在eBay,可伸缩性是我们每天奋力抵抗 ...

  8. Web前端优化最佳实践及工具集锦

    Web前端优化最佳实践及工具集锦 发表于2013-09-23 19:47| 21315次阅读| 来源Googe & Yahoo| 118 条评论| 作者王果 编译 Web优化Google雅虎P ...

  9. 45个实用的JavaScript技巧、窍门和最佳实践

    在这篇文章中,我将分享一组JavaScript的技巧.窍门和最佳实践,这些都是JavaScript程序员应该知晓的,不管他们是使用在浏览器/引擎上,还是服务器端(SSJS——Service Side ...

随机推荐

  1. ecshop数据结构

    ecshop数据结构  2.7.2版本,数据库表 共88张表 注: 1.颜色为蓝色的字,有待讨论验证的地方. 2.颜色为红色的字,是新增的字段.(改文档是基于网上下载的老版本的数据字典修改而成,已经检 ...

  2. 「Luogu P2508」[HAOI2008]圆上的整点 解题报告

    题面 给定圆的半径,求圆上整点数 这是一道很Nice的数学题!超爱!好吧,由于这道题,我去Study了一下复数(complex number)复杂的数 真棒!!! 有兴趣的戳这里!!!\(\huge ...

  3. Ubuntu管理软件源

    在Ubuntu环境下,我们经常会使用apt-get(apt)命令下载各种软件,当所需软件在官方软件库中找不到时,我们需要添加第三方的软件源,或者由于位于海外的官方软件源下载速度过于感人时,需要添加国内 ...

  4. ipaclient 4.5.4 requires jinja2, which is not installed. rtslib-fb 2.1.63 has requirement pyudev>=0.16.1, but you'll have pyudev 0.15 which is incompatible. ipapython 4.5.4 has requirement dnspython>=

  5. 单用户登陆demo-后者挤到前者,类似QQ

    单用户登陆demo ,采用的是Tp5. 流程是,当用户首次登陆是验证用户帐号密码,成功的,用当前时间戳加上用户id和ip 拼接成一个标识,暂且sign ,然后存入cookie ,时间戳存入缓存redi ...

  6. MySQL数据库(五)插入操作

    前提要述:参考书籍<MySQL必知必会> <MySQL必知必会>是先讲了查询,但是没有记录就无法查询,所以先将如何添加数据. 表已经知道怎么创建了,随便创两张. 5.1 插入数 ...

  7. Yolo V3理解bbox和label的关系

    假如一个bbox坐标为:[35 220 62 293 3] 第一步:将bbox转换为中心坐标和宽高形式(3种缩放比例进行缩放) 那么onehot:[0 0 0 1 0 0 0 0 0 0 ...... ...

  8. 你知道Java中的CopyOnWriteArrayList吗?

    CopyOnWrite CopyOnWrite是什么? CopyOnWriteArrayList源码分享? CopyOnWriteArrayList使用场景? CopyOnWriteArrayList ...

  9. vnpy源码阅读学习(2):学习PyQt5

    PyQt5的学习 花费了一个下午把PyQt5大概的学习了下.找了一个教程 PyQt5教程 跟着挨着把上面的案例做了一遍,大概知道PyQt5是如何生成窗体,以及控件的.基本上做到如果有需求要实现,查查手 ...

  10. Governing sand 贪心

    题目链接:https://ac.nowcoder.com/acm/contest/887/C 题目描述 The Wow village is often hit by wind and sand,th ...