之前的文章讲述过通过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对象引用和对象赋值

    关于对象与引用之间的一些基本概念. 初学Java时,在很长一段时间里,总觉得基本概念很模糊.后来才知道,在许多Java书中,把对象和对象的引用混为一谈.可是,如果我分不清对象与对象引用,那实在没法很好 ...

  2. I - Infinite Improbability Drive

    I - Infinite Improbability Drivehttp://codeforces.com/gym/241750/problem/I不断构造,先填n-1个0,然后能放1就放1,最后这个 ...

  3. Spring Cloud微服务笔记(四)客户端负载均衡:Spring Cloud Ribbon

    客户端负载均衡:Spring Cloud Ribbon 一.负载均衡概念 负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容.因为负载均衡对系统的高可用性. 网络压力的缓解和处理能力的扩容的 ...

  4. 微信跳转ticket值怎么得到?浏览器跳到微信?哪里有微信跳转接口?跳转功能能用多久?

    目前很多实用微信跳转技术的电商朋友,网站文章头部或者文章中部出现了点击关注微信关注的二维码,用户点击进去直接跳转到微信内打开指定的二维码,识别即可关注,方便省事,比以往的一键复制—粘贴微信号,转化效果 ...

  5. python学习笔记(5)

    .................................................................................................... ...

  6. myeclipse中的HTML页面在浏览器中显示为乱码

    myeclipse中的HTML页面在浏览器中显示为乱码 在通过myeclipse开发项目的过程中,如果用HTML页面书写前端,可能出现中文乱码现象,需要怎么解决呢?下面是我从网上搜的方法: 解决办法: ...

  7. sortable的基本属性

    所有的事件回调函数都有两个参数:event和ui,浏览器自有event对象,和经过封装的ui对象   ui.helper - 表示sortable元素的JQuery对象,通常是当前元素的克隆对象    ...

  8. Android APT

    APT APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理. Annota ...

  9. swust oj 1051

    输出利用先序遍历创建的二叉树中的指定结点的孩子结点 1000(ms) 10000(kb) 2432 / 5430 利用先序递归遍历算法创建二叉树并输出该二叉树中指定结点的儿子结点.约定二叉树结点数据为 ...

  10. mongodb怎么创建数据库和配置用户

    mongodb怎么创建数据库和配置用户,远程连接是不够的,还要上去操作和放数据的. 系统:centos 5.x 环境:mongodb 1.安装mongodb 这步就不说了,大家自己去看Centos安装 ...