.net core使用Pipelines进行消息IO合并
之前的文章讲述过通过IO合并实现百万级RPS和千万级消息推送,但这两篇文章只是简单地讲了一下原理和测试结果并没有在代码实现上的讲解,这一编文章主要通过代码的实现来讲述消息IO合并的原理。其实在早期的版本实现IO合并还是比较因难的,需要大量的代码和测试Beetlex是完全自己实现这套机制。不过这一章就不是从Beetlex的实现来讲解,因为MS已经提供了一个新东西给以支持,那就是System.IO.Pipelines.在Pipelines的支持下实现消息Buffer的合并变得非常简单的事情。
消息IO合并原理
其实消息IO合并的原理在这里再多说一遍,就是多个消息使用同一个网络IO写入,其实就是把原来一个消息对应一个Buffer,设计成多个消息写入同一个Buffer.从原理上实现可以看以下图解。

System.IO.Pipelines介绍
System.IO.Pipelines: High performance IO in .NET, 微软是这样说的了解详情 但我了解System.IO.Pipelines后发现其实是一个安全可靠的内存池读写+状态态通知机制;不过这套机制对普通开发者来说是件非常复杂的工作,主要原因是一但处理不好的情况那就导致内存泄露的可能!基于System.IO.Pipelines这套机制,可以非常方便地把消息和网络buffer分离出来。接下来就讲一下使用System.IO.Pipelines实现自动批量把消息合并到Buffer中。
Pipe类
针对System.IO.Pipelines的介绍说得还是挺神的,其实打开System.IO.Pipelines一看你就发现就几抽像类,真正使用的就只有Pipe一个类.Pipe看上去更像一个Stream提供一个Read和write属性。Writer属性是写入数据,而Reader则是读取消息,不过这两个属性对象基于状态交互所以两者可以分别在不同的线程进行处理。
消息队列和写入
前面的原理已经讲了,如果想消息能合并那就需要一个队列,然后确保同一时间只有一个线程来处理队列中的消息。如果当前线程检测到队列中有多个消息那就可以获取所有消息进行一个批序列化,接下来看一下这代码代码是怎样实现的.
private async void OnMergeWrite(object state)
{
while (true)
{
var memory = mWrite.GetMemory();
var length = memory.Length;
int offset = ;
int count = ;
while (_msgQueues.TryDequeue(out string msg))
{
if (length < msg.Length)
{
mWrite.Advance(count);
memory = mWrite.GetMemory();
length = memory.Length;
offset = ;
count = ;
}
var elen = System.Text.Encoding.ASCII.GetBytes(msg, memory.Slice(offset, msg.Length).Span);
count += elen;
offset += elen;
length -= elen;
}
if (count > )
mWrite.Advance(count);
await mWrite.FlushAsync();
lock (_workSync)
{
if (_msgQueues.IsEmpty)
{
_doingWork = false;
return;
}
}
}
}
代码并不复杂,进入线程不断地获取消息并序列化到Buffer中,当Buffer满了后提交给Writer后重新获取Buffer继续序列化。当没有消息的时候再一次检测队列如果又存在消息则继续,为什么需要两层While来检测呢,主要是和队列写入状态检测的一致性判断。
public void Enqueue(string message)
{
_msgQueues.Enqueue(message);
lock (_workSync)
{
if (!_doingWork)
{
System.Threading.ThreadPool.UnsafeQueueUserWorkItem(OnMergeWrite, this);
_doingWork = true;
}
}
}
以上是消息写入队列方法。
Pipe数据读取
由于Pipe的Write和Read是基于状态同步,所以Reader可以在任何意时间和任意线程中进行读取,以下是Read的代码:
private async static void Read(object state)
{
int count = ;
while (true)
{
var result = await pipe.Reader.ReadAsync();
var buffer = result.Buffer;
var end = buffer.End;
if (buffer.IsSingleSegment)
{
Console.WriteLine(System.Text.Encoding.ASCII.GetString(buffer.First.Span));
// SAEA.Memory=buffer;
}
else
{
foreach (var b in buffer)
{
Console.WriteLine(System.Text.Encoding.ASCII.GetString(b.Span));
}
//SAEA.BufferList=buffer;
}
pipe.Reader.AdvanceTo(end);
count++;
Console.WriteLine(count);
}
}
测试
代码写完了,接下来的工作就是通过测试看一下是不是达到合并的效果,以下开启两个线程分别连续写入1000个消息。
static void Main(string[] args)
{
pipe = new Pipe();
messageQueue = new MessageQueue(pipe.Writer);
System.Threading.ThreadPool.QueueUserWorkItem(Read);
System.Threading.ThreadPool.QueueUserWorkItem(Write, "AAAA");
System.Threading.ThreadPool.QueueUserWorkItem(Write, "BBBB");
Console.Read();
}
private static void Write(object state)
{
string name = (string)state;
for (int i = ; i < ; i++)
{
messageQueue.Enqueue($"[{name + i}]");
}
}
实际运行效果:

总结
通过以上示例相信大家对System.IO.Pipelines来对消息进行Buffer合并有一个很好的理解,不过实际情况处理的是对象消息则相对复杂一些,毕竟消息的大小是不可知的,不过可以针对最大消息长度来分析Buffer,确保一个Buffer能够序列化一个或多个消息即可。如果你想抛开System.IO.Pipelines更深入地了解实现原因可以查看Beetlex的源码,具体位置在:PipeStream
最后奉上以上示例的代码http://www.ikende.com/Files/SocketIOMerge.zip?tag=manager
.net core使用Pipelines进行消息IO合并的更多相关文章
- dotnet core使用IO合并技巧轻松实现千万级消息推送
之前讲述过多路复用实现单服百万级别RPS吞吐,但在文中有一点是没有说的就是消息IO合并,如果缺少了消息IO合并即使怎样多路复用也很难达到百万级别的请求响毕竟所有应用层面的网络IO读写都是非常损耗性能的 ...
- System.IO.Pipelines: .NET高性能IO
System.IO.Pipelines是一个新的库,旨在简化在.NET中执行高性能IO的过程.它是一个依赖.NET Standard的库,适用于所有.NET实现. Pipelines诞生于.NET C ...
- 本地化ASP.NET core模型绑定错误消息
默认错误消息: MissingBindRequiredValueAccessor A value for the '{0}' property was not provided. MissingKey ...
- Linux Block模块之IO合并代码解析
1 IO路径 从内核角度看,进程产生的IO路径主要有三条: 缓存IO:系统绝大部分IO走的这种形式,充分利用文件系统层的page cache所带来的优势.应用程序产生的IO经系统调用落入page ca ...
- ASP.NET Core SignalR :学习消息通讯,实现一个消息通知
什么是 SignalR 目前我用业余时间正在做一个博客系统,其中有个功能就是评论通知,就是假如A用户评论B用户的时候,如果B用户首页处于打开状态,那么就会提示B用户有未读消息.暂时用SignalR来实 ...
- .net core HttpClient 使用之消息管道解析(二)
一.前言 前面分享了 .net core HttpClient 使用之掉坑解析(一),今天来分享自定义消息处理HttpMessageHandler和PrimaryHttpMessageHandler ...
- ActiveMQ基础教程(四):.net core集成使用ActiveMQ消息队列
接上一篇:ActiveMQ基础教程(三):C#连接使用ActiveMQ消息队列 这里继续说下.net core集成使用ActiveMQ.因为代码比较多,所以放到gitee上:https://gitee ...
- Kafka基础教程(四):.net core集成使用Kafka消息队列
.net core使用Kafka可以像上一篇介绍的封装那样使用(Kafka基础教程(三):C#使用Kafka消息队列),但是我还是觉得再做一层封装比较好,同时还能使用它做一个日志收集的功能. 因为代码 ...
- Wireshark lua dissector 对TCP消息包合并分析
应用程序发送的数据报都是流式的,IP不保证同一个一个应用数据包会被抓包后在同一个IP数据包中,因此对于使用自制dissector的时候需要考虑这种情况. Lua Dissector相关资料可以见:ht ...
随机推荐
- quartz之CronExpression表达式
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素.按顺序依次为1.秒(~).分钟(~).小时(~).天(月)(~,但是你需要考虑你月的天数).月(~).天(星期)(~ =SUN 或 SU ...
- 07flask中session及cookie的用法。
一,基本概念. 1,session的概念. session和cookie的作用有点类似,都是为了存储用户相关的信息.不同的是,cookie是存储在本地浏览器,而session是存储在服务器.存储在服务 ...
- Catalan 数列的性质及其应用(转载)
转自:http://lanqi.org/skills/10939/ 卡特兰数 — 计数的映射方法的伟大胜利 发表于2015年11月8日由意琦行 卡特兰(Catalan)数来源于卡特兰解决凸$n+2$边 ...
- MyBatis 缓存机制
Mybatis 有两级缓存: 一级缓存: 也称为本地缓存,SqlSession级别的缓存.一级缓存是一直开启的: 与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从 ...
- SCOPE_IDENTITY() 和 @@identity
@@IDENTITY 和SCOPE_IDENTITY 返回在当前会话中的任何表内所生成的最后一个标识值.但是,SCOPE_IDENTITY 只返回插入到当前作用域中的值:@@IDENTITY 不受限于 ...
- Windows下安装Kafka
一.安装JDK 二.安装zooeleeper 下载安装包:http://zookeeper.apache.org/releases.html#download 下载后解压到一个目录: 1.进入Zook ...
- node 重新安装依赖模块
rm -rf node_modules rm package-lock.json npm cache clear --force npm install
- Address already in use: make_sock: could not bind to address 0.0.0.0:80
网上查了很多资料都是要杀进程,但是并没有找到占用的进程. 最后解决的方案是在apache配置文件下,去掉Listen 80 即可
- [Codeforces Round #516][Codeforces 1063B/1064D. Labyrinth]
题目链接:1063B - Labyrinth/1064D - Labyrinth 题目大意:给定一个\(n\times m\)的图,有若干个点不能走,上下走无限制,向左和向右走的次数分别被限制为\(x ...
- LeetCode笔记:140. Word Break II
题目描述 给定一个非空的字符串s,一个非空的字符串list作为字典.通过在s中添加空格可以将s变为由list中的word表示的句子,要求返回所有可能组成的句子.设定list中的word不重复,且每一个 ...