使用.NET System.IO.Pipelines和Kestrel套接字库创建Redis客户端

目录

背景

介绍

Redis协议

软件设计


本文是关于为Redis服务器创建异步客户端的系列文章中的第一篇,该客户端低分配,因此GC压力小,数据复制最少。这是使用技术完成的,这些技术使Kestrel成为TechEmpower 纯文本性能测试13轮中记录的每秒原始请求数排名前十的Web服务器之一。

背景

前段时间,我开始编写一个异步的.NET Core Redis客户端。当时没有一个Redis客户端支持.NET Core,我想写一篇关于如何为一个简单的协议实现一个客户端的文章。

不幸的是,VS2015 RC1RC2的变化表明该平台将在一段时间内不稳定,虽然我有一个相当完整的实现,但我把它放在架子上,直到.NETVisual Studio世界变得更加稳定。

随着即将发布的VS2019.NET 3.0以及CLINetStandard和工具的稳定,我认为是时候重新审视这个项目了。最让我对.NET Core产生兴趣的一件事是性能提升了多少,尤其是在Kestrel Web服务器性能方面。

.NET Core团队,尤其是David Fowler,利用他们学到的知识改进Kestrel,并创建了一组库,允许以很少或没有内存分配和最少数据复制的方式处理数据流。这是通过反转现有的 Stream范式来完成的,这样数据缓冲区就不是将数据缓冲区推入和拉出流,而是由低级 API管理并推送到应用程序。这些使用高效的内存缓冲池和结构来实现性能,使Kestrel成为可用的最快的Web服务器之一。

话虽如此,似乎Kestrel现在使用System.IO.Pipelines NuGet包,并且它也在SignalR 中使用。作为Kestrel项目的一部分,创建了许多基于低级流水线的库,以实现低分配、高性能的网络IO,以取代基于流的IO。有一个基于SocketIO的实现。这可以在Nuget.org Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets上找到。

介绍

几年前,我们查看了我们的网页响应时间的性能,发现它严重不足。对于每个请求,我们都会对常见请求的数据进行数据库请求,并对内容进行复杂且昂贵的清理和格式化。

我们开始了一个项目,使用缓存各种信息和视图模型来提高站点的性能。这种缓存需要分布式,以便我们网络场中的所有服务器都与最新数据保持一致和最新。在评估了几个选项后,我们决定使用Redis因为它的速度、成本、广泛采用、好评,以及其数据结构和API的强大功能。

由此产生的性能提升超出了我们的预期,需要几秒甚至几十秒的页面在不到一秒的时间内返回,通常不到500毫秒,大大降低了我们SQL ServerCPU负载。通过添加后台事件处理和优化一些昂贵且大量使用的算法,已经实现了进一步的性能改进,但我怀疑我们能做的任何事情都不能产生我们使用Redis获得的改进。

我们当前的实现使用ServiceStack Redis Client V3。我们也有一个使用StackExchange.Redis客户端的实现,但我也遇到了一些问题。我不得不研究代码以解决许多问题,并且像任何程序员一样,决定我可以做得更好,或者至少是不同的。这主要是由于C#语言的改进,例如扩展方法。这允许我创建一个小客户端,它只是向Redis服务器发送和从Redis服务器接收内容。实际的命令是使用扩展方法实现的。这消除了服务堆栈实现中的巨大类和StackExchange实现中的一些代码重复,允许每个类具有更大的单一职责。

这两个库中有许多很棒的想法,例如StackExchange客户端中的ConnectionMultiplexer 允许共享单个套接字,而不必在每次需要访问Redis服务器时创建新的套接字连接。将在本系列文章的后面部分实现这一点。

此实现的目标是

  • 简单
  • 性能
  • 效率
  • 稳健性
  • 完成单元测试

Redis协议

客户端使用Redis序列化协议 (RESP) Redis服务器通信,详见Redis 协议规范。正如规范所述:

Redis客户端使用名为RESP的协议与Redis服务器通信。(Redis序列化协议)。虽然该协议是专为Redis设计的,但它也可用于其他客户端-服务器软件项目。

RESP是以下各项之间的折衷:

  • 实现简单。
  • 解析速度快。
  • 人类可读。

RESP可以序列化不同的数据类型,如整数、字符串、数组。还有一种特定的错误类型。请求以字符串数组的形式从客户端发送到Redis服务器,这些字符串表示要执行的命令的参数。Redis使用特定于命令的数据类型进行回复。

RESP是二进制安全的,不需要处理从一个进程传输到另一个进程的批量数据,因为它使用前缀长度来传输批量数据。

如果您需要澄清有关我正在做什么的任何事情,我将把它留给读者参考规范,而不是详细介绍协议。它小巧、简单且易于理解。当我解释使用它们的代码时,我会解释具体的协议细节。

软件设计

基于管道的套接字传输的神奇之处在于它为一对管道公开了PipeReadersPipeWriters。一个管道(OutputPipe)将数据从应用程序传输到传输程序,而另一个管道(InputPipe)将数据从传输程序传输到应用程序。

Connection公开了一个IDuplexPipe Application,它有一个Input PipeReader和一个Output PipeWriterInput设置为InputPipe.Reader,而Output设置为OutputPipe.Writer。该连接有两个任务,一个从Socket读取数据并将其写入InputPipe,第二个任务从OutputPipe读取数据并将其写入Socket

管道使用一组内存块来提供和重用缓冲区来存储数据。这与Streams范式不同,在这种范式中,用户负责分配和管理用于读取和写入Stream的数据缓冲区。结果是管道传输需要很少或不需要缓冲区分配和垃圾收集来从Socket读取和写入。事实上,在大多数情况下,很少需要将数据从一个缓冲区复制到另一个缓冲区,直到需要进行这种复制才能从接收到的数据中反序列化某个对象。

这意味着我们的Redis协议处理程序需要做两件事:

  • Redis命令序列化为写入PipeWriter的字节
  • PipeWReader读取字节并将它们反序列化为Redis响应

因此,从两个管道创建测试传输层很简单。被测代码连接到应用程序端,测试读取和写入传输端,允许测试预期的功能,而实际上不需要为单元测试设置Redis实例。

当然,在某些时候需要与真实的Redis服务器进行实际通信以验证单元测试的假设。为此,我将使用Redis Docker容器。

https://www.codeproject.com/Articles/5274503/Creating-a-Redis-Client-using-the-NET-System-IO-Pi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
System.IO.Pipelines是Microsoft开发的用于处理高性能I/O操作的。它提供了一种简化的方式来读写数据流,并且在处理大量数据时具有出色的性能。 串口通信是一种用于在计算机和外部设备之间传输数据的通信方式。通过串口,计算机可以与各种外部设备进行通信,如传感器、机器人、打印机等。 System.IO.Pipelines可以在串口通信中发挥重要作用。使用,我们可以通过创建一个Pipeline对象,来轻松处理从串口接收到的数据,并对接收和发送的数据进行高效的处理。 首先,我们可以使用System.IO.Pipelines.PipelineReader从串口中读取数据。通过调用ReadAsync方法,我们可以异步地读取串口中的数据,并将其放入到一个缓冲区中。然后,我们可以通过提供的Read方法来处理这些数据,比如解析、处理或存储。 其次,我们可以使用System.IO.Pipelines.PipelineWriter向串口发送数据。通过调用WriteAsync方法,我们可以异步地将数据写入到串口中。该方法会返回一个可用于链式编程的WritableBuffer对象,我们可以使用其提供的方法来构建数据流,并最终将数据发送到串口。 最后,System.IO.Pipelines还提供了一些高级功能,以帮助我们更好地处理串口通信。例如,我们可以使用PipeScheduler来调度读写操作,以充分利用系统资源。我们还可以使用MemoryPoolOptions来自定义内存池的大小和数量,以适应不同的数据量和性能要求。 总之,System.IO.Pipelines是一个非常有用且高性能的,可用于处理串口通信。它提供了简单易用的API,并具有出色的性能和灵活性,可以帮助我们更好地处理和管理串口数据的读写操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值