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里面也存在.
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发送请求的最佳实践的更多相关文章
- ASP.NET MVC防范CSRF最佳实践
XSS与CSRF 哈哈,有点标题党,但我保证这篇文章跟别的不太一样. 我认为,网站安全的基础有三块: 防范中间人攻击 防范XSS 防范CSRF 注意,我讲的是基础,如果更高级点的话可以考虑防范机器人刷 ...
- RESTful API 设计最佳实践
背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API ...
- memcache的最佳实践方案
1.memcached的基本设置 1)启动Memcache的服务器端 # /usr/local/bin/memcached -d -m 10 -u root -l 192.168.0.200 -p 1 ...
- Ubuntu14.04+RabbitMQ3.6.3+Golang的最佳实践
目录 [TOC] 1.RabbitMQ介绍 1.1.什么是RabbitMQ? RabbitMQ 是由 LShift 提供的一个 Advanced Message Queuing Protocol ...
- 基于AWS的云服务架构最佳实践
ZZ from: http://blog.csdn.net/wireless_com/article/details/43305701 近年来,对于打造高度可扩展的应用程序,软件架构师们挖掘了若干相关 ...
- 【转】优化Web程序的最佳实践
自动排版有点乱,看着蛋疼,建议下载中文PDF版阅读或阅读英文原文. Yahoo!的Exceptional Performance团队为改善Web性能带来最佳实践.他们为此进行了 一系列的实验.开发了各 ...
- 可伸缩性最佳实践:来自eBay的经验
看到一篇关于系统可伸缩性(可扩展)的文章,eBay的架构师Randy Shoup写的,原文出处没找到,就不写转载的地址了.根据自己的理解对文章有修改剪切的地方. 在eBay,可伸缩性是我们每天奋力抵抗 ...
- Web前端优化最佳实践及工具集锦
Web前端优化最佳实践及工具集锦 发表于2013-09-23 19:47| 21315次阅读| 来源Googe & Yahoo| 118 条评论| 作者王果 编译 Web优化Google雅虎P ...
- 45个实用的JavaScript技巧、窍门和最佳实践
在这篇文章中,我将分享一组JavaScript的技巧.窍门和最佳实践,这些都是JavaScript程序员应该知晓的,不管他们是使用在浏览器/引擎上,还是服务器端(SSJS——Service Side ...
随机推荐
- SpringBoot拦截器没有生效的原因
最近的一个项目中,需要实现一个记录系统请求日志的功能,我通过拦截器实现了这个功能,但是,测试的时候发现拦截器没有发挥作用,检查了很久,没有发现错的地方,系统也没有报错,最终在网上找打了答案. 原来之前 ...
- openlayers中实现点的拖拽(modify),在layer中增加修改删除point。
最近忙着整地图,都忘记了总结来沉淀自己,自我检讨一下. 总结一下最近使用openlayer时学习的内容,先说下我的业务逻辑吧,在室内地图中 1,点击新增在地图上新增一个可以拖拽的点,拖拽完成后确定位置 ...
- 2017 ACM-ICPC亚洲区域赛北京站J题 Pangu and Stones 题解 区间DP
题目链接:http://www.hihocoder.com/problemset/problem/1636 题目描述 在中国古代神话中,盘古是时间第一个人并且开天辟地,它从混沌中醒来并把混沌分为天地. ...
- 「BZOJ4510」「Usaco2016 Jan」Radio Contact 解题报告
无线电联系 Radio Contact 题目描述 Farmer John has lost his favorite cow bell, and Bessie the cow has agreed t ...
- 7.netty内存管理-ByteBuf
ByteBuf ByteBuf是什么 ByteBuf重要API read.write.set.skipBytes mark和reset duplicate.slice.copy retain.rele ...
- Spark设置Kryo序列化缓冲区大小
背景 今天在开发SparkRDD的过程中出现Buffer Overflow错误,查看具体Yarn日志后发现是因为Kryo序列化缓冲区溢出了,日志建议调大spark.kryoserializer.buf ...
- C++ 模板常见特性(函数模板、类模板)
背景 C++ 是很强大,有各种特性来提高代码的可重用性,有助于减少开发的代码量和工作量. C++ 提高代码的可重用性主要有两方面: 继承 模板 继承的特性我已在前面篇章写过了,本篇主要是说明「模板」的 ...
- docker 批量删除 镜像 容器
我们在docker构建和测试时,经常会产生很多无用的镜像或者容器,我们可用如下两条命令一个一个删除. docker container rm 容器id #删除容器 可简写: docker rm 容器i ...
- Spring Boot2 系列教程 (三) | 使用 LomBok 提高开发效率
微信公众号:一个优秀的废人 如有问题或建议,请后台留言,我会尽力解决你的问题. 前言 上周去了开年会,去的地方是温泉度假村.老实说,我是无感的,90% 是因为没中奖(老板太抠,两百人只抽三个奖),10 ...
- JS中Cookie、localStorage、sessionStorage三者的区别
cookie:大小4k,一般由服务器生成,可设置失效时间,关闭浏览器后失效,与服务器通信时:每次都会携带HTTP头中,如果使用cookie保存过多数据会带来性能问题 localhostStorage: ...