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 ...
随机推荐
- schedule of 2016-09-19~2016-09-25(Monday~Sunday)——1st semester of 2nd Grade
2016/9/19 Monday 1.make ppt for today's group meeting 2.recite 100 words 3.review <图解机器学习>ch1~ ...
- C++ | C++ 概览 基础知识 | 01
一.基本概念 1.1 类型.变量和算术运算 1.2 常量 1.3 检验和循环 1.4 指针,数组和循环 二.用户自定义类型 2.1 结构 2.2 类 2.3 枚举 三.模块化 3.1 分离编译 3.2 ...
- bootstrapValidator JS修改内容无法验证
需求: form表单输入中有坐标,坐标可以输入也可以从地图中获取,验证插件使用的是 bootstrapValidator 问题: 当输入错误的值时会触发验证,有错误提示.当在地图上选择坐标,通过js修 ...
- 使用gravatar生成头像
avatar代表您在线的图像,当你与网站互动时,你的名字旁边会出现一张图片. Gravatar是一个全球通用的头像.你只需上传一次并创建自己的个人资料,然后当你加入任何支持Gravatar的网站时,你 ...
- 一些常用查询SQL语句以及显示格式
1.查询当前年.月.周相关时间 1.1.查询当前年份 SELECT TO_CHAR(SYSDATE,'YYYY') AS YEAR FROM DUAL--查询当前年份 SELECT TO_CHAR(S ...
- Android 平台JS调试技术
1. 测试技术简介 Android平台微信公众号一般以H5的形式开发,测试发现流量一般都通过js进行加密传输,导致无法对越权.SQL注入等风险点进行测试.针对此难点,本手册会介绍包括Android环 ...
- 重拾c++第一天(3):数据处理
1.short至少16位:int至少与short一样长:long至少32位,且至少与int一样长:long long至少64位,且至少与long一样长 2.sizeof 变量 返回变量长度 或者s ...
- Spring Boot2 系列教程 (十三) | 整合 MyBatis (XML 版)
前言 如题,今天介绍 SpringBoot 与 Mybatis 的整合以及 Mybatis 的使用,之前介绍过了 SpringBoot 整合MyBatis 注解版的使用,上一篇介绍过 MyBatis ...
- map文件分析
1.MAP文件基本概念 段(section):描述映像文件的代码和数据块 RO:Read-Only的缩写,包括RO-data(只读数据)和RO-code(代码) RW:Read-Write的缩写,主要 ...
- C语言编译成dll
首先c语言在开始要加上 #ifdef __cplusplus extern "C" { #endif …被导出的方法名称 #ifdef __cplusplus } #endif 不 ...