长链接发送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. $Noip2015/Luogu2661$ 信息传递 并查集

    Luogu $Description$ 给定一个有向图,每个点只有一条出边.求图里的最小环. $Sol$ 使得这个题不难的地方就在于每个点只有一条出边叭. 一边连边一边更新答案.首先当然是初始$f[i ...

  2. java poi ppt 接口的基本操作

    依赖 在 pom.xml中增加以下依赖 <dependency> <groupId>org.apache.poi</groupId> <artifactId& ...

  3. Linux Centos7 环境搭建Docker部署Zookeeper分布式集群服务实战

    Zookeeper完全分布式集群服务 准备好3台服务器: [x]A-> centos-helios:192.168.19.1 [x]B-> centos-hestia:192.168.19 ...

  4. 机器学习之路--KNN算法

    机器学习实战之kNN算法   机器学习实战这本书是基于python的,如果我们想要完成python开发,那么python的开发环境必不可少: (1)python3.52,64位,这是我用的python ...

  5. NB的程序员,亮瞎了你的眼吗?

    郑重声明: 本文首发于人工博客 1.导读 你能想象到1K的代码能写出什么样的功能强大.效果炫酷的作品吗?来吧,今天小编带领大家认识下下面这位大神的作品. 西班牙程序员Roman Cortes用纯Jav ...

  6. 再也不怕和老外交流了!我用python实现一个微信聊天翻译助手!

    前言 在前面的一篇文章如何用python“优雅的”调用有道翻译中咱们清楚的写过如何一层一层的解开有道翻译的面纱,并且笔者说过那只是脑洞的开始.现在笔者又回来了.当你遇到一些外国小哥哥小姐姐很心动.想结 ...

  7. ORA-00903表名无效关于${}和#{}的使用

    相当于对数据 加上 双引号,$相当于直接显示数据 ${xxx}这样格式的参数会直接参与SQL编译,从而不能避免注入攻击 但可以使用在from之后传入表名 {}占位符? where之后 防止注入

  8. 从N个元素中抽取K个不重复元素(抽奖问题)

    核心就是 把N数组抽中的元素给K数组 把N数组最后一位给N数组被抽走的那一位(这时候N数组最后一位元素和被抽走的那位元素值相等) 把N数组长度减一,去除最后一位

  9. 《C++Primer》第五版习题解答--第四章【学习笔记】

    [C++Primer]第五版习题解答--第四章[学习笔记] ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/11 第四章:表达式 练习4. ...

  10. 机器学习新手必看:Jupyter Notebook入门指南

    参考网址:https://blog.csdn.net/guleileo/article/details/80490921