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 ...
随机推荐
- 洛谷$P5038\ [SCOI2012]$奇怪的游戏 二分+网络流
正解:二分+网络流 解题报告: 传送门$QwQ$ 这种什么,"同时增加",长得就挺网络流的$QwQ$?然后看到问至少加多少次,于是考虑加个二分呗?于是就大体确定了做题方向,用的网络 ...
- 开箱即用~基于.NET Core的统一应用逻辑分层框架设计
目前公司系统多个应用分层结构各不相同,给运维和未来的开发带来了巨大的成本,分层架构看似很简单,但保证整个研发中心都使用统一的分层架构就不容易了. 那么如何保证整个研发中心都使用统一的分层架构,以达到提 ...
- Spring事务失效的 8 大原因,这次可以吊打面试官了!
今天再来一篇<吊打面试官>系列,这次真的要吊打了,哈哈!(看往期吊打系列请在后台回复:吊打,我会陆续更新--) 前几天栈长不是发了一篇文章,里面有一个关于事务失效的问题: 用 Spring ...
- 钱包开发经验分享:ETH篇
# 钱包开发经验分享:ETH篇 [TOC] ## 开发前的准备 > 工欲善其事,必先利其器 一路开发过来,积累了一些钱包的开发利器和网站,与大家分享一下.这些东西在这行开发过的人都知道,只是给行 ...
- docker+mysql 构建数据库的主从复制
docker+mysql 构建数据库的主从复制 在最近的项目中,决定将项目改造成数据库读写分离的架构,后续会有博文详细讲述我的开发改造,本文主要记录我是如何一步步的构建数据库的主从复制. 为什么使用d ...
- bootstrap:图片轮播
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...
- 基于Jenkins的持续交付全流程设计与实践
1 从理论开始 什么是DevOps? 近年来,随着DevOps理念的逐渐深入人心,企业逐渐意识到从看似重复的手工劳动中实现自动化流程处理,对于提高企业劳动生产力已经非常重要,尤其是面向互联网的开发者, ...
- 【Java基础总结】数据库编程
MySQL数据库查询 import java.sql.*; public class JdbcDemo1{ public static void main(String[] args){ try{ / ...
- a标签点击触发 layer open 只显示背景解决
问题:公司网站突然说有个查看信息的点击不好使了,有时候点击无反应,但是href执行了,有时候弹出只有背景,不显示内容.网上找了a标签的各种方法尝试后,均不能解决. 代码:类似如下,method()方法 ...
- 区间dp - 送外卖
When we are focusing on solving problems, we usually prefer to stay in front of computers rather tha ...