之前的文章讲述过通过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. 如何修改build之后生成的文件结构和路径

    因为公司项目结构的原因, 被要求要build之后的文件夹结构要修改为: dist (文件夹) statics (文件夹) mobile (文件夹)  ---->  存放原本 build 之后存在 ...

  2. Win10家庭版WindowsUpdate属性为灰色

    一般的取消Windows更新只需要打开任务管理器,点击服务 然后点击左下角的打开服务 找到WindowsUpdate,右键属性 按照正常的电脑只要在启动类型中选择禁用,然后在恢复里的第一次操作选择无操 ...

  3. OI暑假集训游记

    莞中OI集训游记 Written BY Jum Leon. I        又是一载夏,本蒟蒻以特长生考入莞中,怀着忐忑的心情到了8月,是集训之际.怀着对算法学习的向往心情被大佬暴虐的一丝恐惧来到了 ...

  4. LPC 网络编程

    LPC有五种不同的通信模式(socket模式) ① MUD (面向连接的通信模式) 可以把除Object以外的所有LPC模型从一个MUD传到另一个MUD 弊端: 无法传送物件造成了穿越MUD的功能(即 ...

  5. Hadoop namenode节点无法启动的问题解决

    namenode是Hadoop集群HDFS的管理节点,管理着整个分布式文件系统的命名空间,以及文件与块的映射关系等,在Hadoop集群中扮演着至关重要的作用. 我之前安装的Hadoop集群中namen ...

  6. spark streaming中维护kafka偏移量到外部介质

    spark streaming中维护kafka偏移量到外部介质 以kafka偏移量维护到redis为例. redis存储格式 使用的数据结构为string,其中key为topic:partition, ...

  7. kettle web化

    kettle web化 通过Java API调用kettle核心代码,并基于Spring Boot提供简易的Web管理界面. 背景 在工作中,通过kettle这款ETL产品进行数据处理时,是通过kit ...

  8. 使用Ant Design的select组件时placeholder不生效/不起作用的解决办法

    先来说说使用Ant Design和Element-ui的感觉吧. 公司的项目开发中用的是vue+element-ui,使用了一通下来后,觉得element-ui虽然也有一些问题或坑,但这些小问题或坑凭 ...

  9. Java_Math/Date

    这篇文章我们来研究下java中两个常用的工具类,Math和Date Math: Math是java中各种函数计算集合类,进入到Math类中,可以看到Math是java.lang包下的,被final修饰 ...

  10. weex 学习: 添加图标(使用阿里吧吧-icon)

    添加图标(使用阿里吧吧-icon) <text slot="left" class="header-left"></text>   i ...