之前的文章讲述过通过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合并的更多相关文章

  1. dotnet core使用IO合并技巧轻松实现千万级消息推送

    之前讲述过多路复用实现单服百万级别RPS吞吐,但在文中有一点是没有说的就是消息IO合并,如果缺少了消息IO合并即使怎样多路复用也很难达到百万级别的请求响毕竟所有应用层面的网络IO读写都是非常损耗性能的 ...

  2. System.IO.Pipelines: .NET高性能IO

    System.IO.Pipelines是一个新的库,旨在简化在.NET中执行高性能IO的过程.它是一个依赖.NET Standard的库,适用于所有.NET实现. Pipelines诞生于.NET C ...

  3. 本地化ASP.NET core模型绑定错误消息

    默认错误消息: MissingBindRequiredValueAccessor A value for the '{0}' property was not provided. MissingKey ...

  4. Linux Block模块之IO合并代码解析

    1 IO路径 从内核角度看,进程产生的IO路径主要有三条: 缓存IO:系统绝大部分IO走的这种形式,充分利用文件系统层的page cache所带来的优势.应用程序产生的IO经系统调用落入page ca ...

  5. ASP.NET Core SignalR :学习消息通讯,实现一个消息通知

    什么是 SignalR 目前我用业余时间正在做一个博客系统,其中有个功能就是评论通知,就是假如A用户评论B用户的时候,如果B用户首页处于打开状态,那么就会提示B用户有未读消息.暂时用SignalR来实 ...

  6. .net core HttpClient 使用之消息管道解析(二)

    一.前言 前面分享了 .net core HttpClient 使用之掉坑解析(一),今天来分享自定义消息处理HttpMessageHandler和PrimaryHttpMessageHandler ...

  7. ActiveMQ基础教程(四):.net core集成使用ActiveMQ消息队列

    接上一篇:ActiveMQ基础教程(三):C#连接使用ActiveMQ消息队列 这里继续说下.net core集成使用ActiveMQ.因为代码比较多,所以放到gitee上:https://gitee ...

  8. Kafka基础教程(四):.net core集成使用Kafka消息队列

    .net core使用Kafka可以像上一篇介绍的封装那样使用(Kafka基础教程(三):C#使用Kafka消息队列),但是我还是觉得再做一层封装比较好,同时还能使用它做一个日志收集的功能. 因为代码 ...

  9. Wireshark lua dissector 对TCP消息包合并分析

    应用程序发送的数据报都是流式的,IP不保证同一个一个应用数据包会被抓包后在同一个IP数据包中,因此对于使用自制dissector的时候需要考虑这种情况. Lua Dissector相关资料可以见:ht ...

随机推荐

  1. java 堆、栈、方法区/ 类变量、成员变量、局部变量

    方法区:类信息.类变量(静态变量和常量).方法 堆:对象.成员变量 栈:局部变量 (1)当程序运行时,首先通过类装载器加载字节码文件,经过解析后装入方法区!在方法区中存了类的各种信息,包括类变量.类常 ...

  2. 研究比对搞定博客 canvas-nest.js

    经过比对网站源码,发现大的差异,复制代码添加成功. 参考:https://www.cnblogs.com/kexing/p/7264767.html 申请js权限 编辑   具体编辑请自行实验, 附上 ...

  3. Pycharm下同一目录的py文件不能相互调用的原因分析

    1.首先确保所在目录是Python Package而不是一般的New Stratch File Python Package下有__init___.py或自己建空的__init___.py 2.pyc ...

  4. CheckedTextView文字不居中的问题

    问题:CheckedTextView设置了android:gravity="center",但是不居中 解决方法:添加属性android:textAlignment="c ...

  5. 加固后,上传play store, 在 google play store 下载应用安装后,打开签名校验失败

    在Google Play Console. (Google Play App Signing )签署您的应用 在创建应用时: 会有个“ Google Play App Signing” 的东西,提示使 ...

  6. dubbo+zookeeper报错:com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method

    com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method可能的错误原因有三个前两个是从网上摘得, 第三个是自己解决的 1.需要进行 ...

  7. 根据dateFormatter创建NSDate类型数据

    根据dateFormatter 2000-01-01 创建NSDate类型数据 NSDateFormatter *dateFormatter = [NSDate shareDateFormatter] ...

  8. PHP 清除 Excel 导入的数据空格

    处理excel中的数据时,遇到了页面中显示为空格,审查元素时却显示为换行,使用replace函数也不管用,反正就是不知道是什么东西,看起来像空格 中文空格这里面有好几种:没有简单的解决问题的方式,比如 ...

  9. GLOG使用Demo

    GLOG使用Demo GLOG是Google开源的一个精简的日志系统,博主简单学习了一下并记录常见用法,以备日常查询 一.安装 照例是编译安装,不过没有使用cmake git clone https: ...

  10. vue的环境安装(一node环境)

    话不多说都知道vue是基于node环境的.肯定是要安装node环境的 1.node官网下载https://nodejs.org/en/download/对应的版本我的是win的 2.双击下载下来的安装 ...