使用.NET构建简单的高性能Redis(三)

译者注

该原文是Ayende Rahien大佬业余自己在使用C# 和 .NET构建一个简单、高性能兼容Redis协议的数据库的经历。首先这个"Redis"是非常简单的实现,但是他在优化这个简单"Redis"路程很有趣,也能给我们在从事性能优化工作时带来一些启示。原作者:Ayende Rahien 原链接:https://ayende.com/blog/197473-C/high-performance-net-building-a-redis-clone-architecture

构建Redis克隆版-架构

在之前的文章中,我们尝试用最简单的方式来完成一个Redis克隆版。打开一个套接字来监听,为每个客户端单独分配一个Task来从网络读取数据,解析命名并执行它。虽然在流水线上有一些小的改进,但也只仅此而已。

让我们退一步来构建一个与Redis架构更为接近的Redis克隆版。为此,我们需要在一个线程中完成所有工作。这在C#中是比较难实现的,没有用于执行Redis那样工作类型的API。更确切的来说是有Socket.Select()方法,但是需要我们自己在此基础上构建一切(比如我们必须写代码处理缓冲、字符串等等)。

考虑到这是通往最终建议的架构的一个中途站,我决定完全跳过这个。相反,我将首先专注于消除系统中的主要瓶颈,即ConcurrentDictionary

分析器的结果表明,我们这最大的开销就是ConcurrentDictionary的可伸缩性。即使我使用了1024个分片的锁,它仍然占用50%的时间开销。问题是,我们能做得更好吗?我们可以尝试一个更好的选择,就是我们不再使用ConcurrentDictionary,而是直接使用单独的Dictionary来分片,这样的话每个Dictionary都不需要并发就可以访问。

我的想法是这样的,我们将为客户端提供常规的读写操作。但是,我们不会直接在I/O上处理这些命令,而是将其路由到一个专用的线程(使用它自己的Dictionary)来完成这项工作。因为我是16核的机器,我将创建10个这样的线程(假设它们每个都能分配到1个核心),并且我能够将I/O处理放到其余的6个核心上。

以下是更改后的结果:7b0b8e37bee1310f124a62b7a2636f3b.png请注意,我们现在跑分的数据是125w/s,比上一次几乎增长了25%。下面是这一次新代码的分析器结果:1e0138ed3113f1cafa4a8fca763ade76.png因此在本例中,花费了大量的时间来处理各种各样的字符串,等待GC(大约占30%)。集合的成本下降了很多。还有一些其它的开销出现在我眼前,看看这里:f0a94ef86919cb79e9d38f65e0d470a4.png对于“简单”属性查找来说,这个开销非常惊人。另外SubString函数的调用开销也很大,超过整个系统开销的6%。在研究系统其它部分时,看到了这个:9d2cf7df420523bc816f47ce54c0dac5.png这真的很有趣,因为我们花了很多的时间在等待队列中是否有新的元素,其实我们可以做更多的事情,而不是就在那干等着。

我还尝试了其它的线程数量,如果只运行一个ExecWorker,我们的运行速度是40w/s,两个线程,我们的运行速度是70w/s。当使用4个专用于处理请求的线程时,我们的运行速度是106w/s。

因此,很明显,我们需要重新考虑这种方案,我们不能够正确地扩展到合适的数值。注意,这种方法也不利用流水线。我们分别处理每个命令和其他命令。我的下一步是添加对使用这种方法的流水线的支持,并测量这种影响。

从另一方面来说,我们现在的性能还是100w/s,考虑到我只花了很少的时间来实现方案,从这个方案可以获得25w/s的性能提升,这是令人激动人心的。从侧面说,我们还有更多的事情可以做,但我想把重点放在修复我们第一个方案上。

下面是当前的状态,因此您可以与原始代码比较。

using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Threading.Channels;var listener = new TcpListener(System.Net.IPAddress.Any, 6379);
listener.Start();var redisClone = new RedisClone();while (true)
{var client = listener.AcceptTcpClient();var _ = redisClone.HandleConnection(client); // run async
}public class RedisClone
{ShardedDictionary _state = new(Environment.ProcessorCount / 2);public async Task HandleConnection(TcpClient tcp){var _ = tcp;var stream = tcp.GetStream();var client = new Client{Tcp = tcp,Dic = _state,Reader = new StreamReader(stream),Writer = new StreamWriter(stream){NewLine = "\r\n"}};await client.ReadAsync();}}class Client
{public TcpClient Tcp;public StreamReader Reader;public StreamWriter Writer;public string Key;public string? Value;public ShardedDictionary Dic;List<string> Args = new();public async Task ReadAsync(){try{Args.Clear();var lineTask = Reader.ReadLineAsync();if (lineTask.IsCompleted == false){await Writer.FlushAsync();}var line = await lineTask;if (line == null){using (Tcp){return;}}if (line[0] != '*')throw new InvalidDataException("Cannot understand arg batch: " + line);var argsv = int.Parse(line.Substring(1));for (int i = 0; i < argsv; i++){line = await Reader.ReadLineAsync();if (line == null || line[0] != '$')throw new InvalidDataException("Cannot understand arg length: " + line);var argLen = int.Parse(line.Substring(1));line = await Reader.ReadLineAsync();if (line == null || line.Length != argLen)throw new InvalidDataException("Wrong arg length expected " + argLen + " got: " + line);Args.Add(line);}switch (Args[0]){case "GET":Key = Args[1];Value = null;break;case "SET":Key = Args[1];Value = Args[2];break;default:throw new ArgumentOutOfRangeException("Unknown command: " + Args[0]);}Dic.Run(this);}catch (Exception e){await HandleError(e);}}public async Task NextAsync(){try{if (Value == null){await Writer.WriteLineAsync("$-1");}else{await Writer.WriteLineAsync($"${Value.Length}\r\n{Value}");}await ReadAsync();}catch (Exception e){await HandleError(e);}}public async Task HandleError(Exception e){using (Tcp){try{string? line;var errReader = new StringReader(e.ToString());while ((line = errReader.ReadLine()) != null){await Writer.WriteAsync("-");await Writer.WriteLineAsync(line);}await Writer.FlushAsync();}catch (Exception){// nothing we can do}}}
}class ShardedDictionary
{Dictionary<string, string>[] _dics;BlockingCollection<Client>[] _workers;public ShardedDictionary(int shardingFactor){_dics = new Dictionary<string, string>[shardingFactor];_workers = new BlockingCollection<Client>[shardingFactor];for (int i = 0; i < shardingFactor; i++){var dic = new Dictionary<string, string>();var worker = new BlockingCollection<Client>();_dics[i] = dic;_workers[i] = worker;// readersnew Thread(() =>{ExecWorker(dic, worker);}){IsBackground = true,}.Start();}}private static void ExecWorker(Dictionary<string, string> dic, BlockingCollection<Client> worker){while (true){var client = worker.Take();if (client.Value != null){dic[client.Key] = client.Value;client.Value = null;}else{dic.TryGetValue(client.Key, out client.Value);}var _ = client.NextAsync();}}public void Run(Client c){var reader = _workers[c.GetHashCode() % _workers.Length];reader.Add(c);}}

公众号

之前一直有朋友让开通公众号,由于一直比较忙没有弄。现在终于抽空弄好了,译者公众号如下,欢迎大家关注。1b385c490eef061744f9fe6d79e022f7.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

海尔联手软银机器人,进军服务机器人领域

海尔此次将正式全面进入到服务机器人。 据悉&#xff0c;3月6日海尔公布了未来对于家用机器人的最新战略&#xff0c;同时与软银展开战略合作&#xff0c;将软银的人形机器人引入中国市场&#xff0c;正式进军服务机器人领域。 在本次发布会上&#xff0c;海尔与软银将联手从软…

.NET 7 中的 EndpointFilter

ASP.NET 7 中的 EndpointFilterIntro.NET 6 开始微软引入了 Minimal API&#xff0c;但是相比成熟的控制器模型&#xff0c;还是有很多的不足&#xff0c;.NET 7 针对于 Minimal API 也做了一些改进来让 Minimal API 功能更加丰富&#xff0c;其中 Filter 就是其中的一个更新从…

越来越火的图数据库到底能做什么?

作者 | 俞方桦 随着大数据时代的到来&#xff0c;传统的关系型数据库由于其在数据建模和存储方面的限制&#xff0c;变得越来越难以满足大量频繁变化的需求。关系型数据库&#xff0c;尽管其名称中有“关系”这个词&#xff0c;却并不擅长处理复杂关系的查询和分析。另外&…

piwik抓取用户交互行为

2019独角兽企业重金招聘Python工程师标准>>> https://github.com/matomo-org/matomo-sdk-ios/tree/version-3 http://piwik.org 首先下载demo&#xff0c;把文件拖到项目中去&#xff0c;在AppDelegate.m文件填写piwik服务器的URL和编号&#xff1b; 例如&#xff1…

k8s 读书笔记 - kubernetes 基本概念和术语(下)

DevOps前言上一篇文章 中&#xff0c;我们介绍了 k8s 中的 Master、Node、Pod、Label、RC & RS、Deployment、HPA & VPA、DaemonSet 这些资源对象信息&#xff0c;接下来我们继续介绍 k8s 中常用的资源对象。StatefulSet在 k8s 系统中&#xff0c;Pod 的管理对象 RC、D…

java数据类型后加三个点...

2019独角兽企业重金招聘Python工程师标准>>> 从Java5开始&#xff0c;Java语言对方法参数支持一种新写法&#xff0c;varargs&#xff08;可变长度参数列表&#xff09;&#xff0c;其语法就是类型后跟...&#xff0c;表示此处接受的参数为0到多个Object类型的对象&…

手把手教你用 Jenkins 自动部署 SpringBoot!

1. 什么是 CI/CD 1.1 CI&#xff08;Continuous Integration&#xff09; 1.2 CD&#xff08;Continuous Delivery/Continuous Deployment&#xff09; 2. 什么是 Jenkins 3. 准备工作 3.1 整体规划 3.2 准备代码 3.3 准备服务器 4. 搭建 Jenkins 5. 安装插件 6. 配置 …

bondat蠕虫传播与对抗

转载来自&#xff1a;http://www.mottoin.com/109730.html &#xff08;1&#xff09;可移动磁盘传播手段&#xff1a;隐藏U盘文件&#xff0c;创建快捷方式指向病毒bat文件。Bondat蠕虫主要通过可移动磁盘传播&#xff0c;并借助可移动磁盘中的文件隐蔽自身。Bondat蠕虫会检索…

vim 编译 Python 代码提示配置

2019独角兽企业重金招聘Python工程师标准>>> .vim 和.vimrc 拷贝到根目录 注意根目录下默认是没有.vim的&#xff0c;所以拷贝.vim 没问题&#xff0c;但是拷贝.vimrc 之前需要把原来的.vimrc备份 两个文件下载&#xff1a;http://pan.baidu.com/s/1eRRhakM 转载于:…

[转]Pinia与Vuex的对比:Pinia是Vuex的良好替代品吗?

文章目录 介绍设置 Pinia 设置Vuex 设置使用 Pinia使用Vuex使用社区和生态系统的力量学习曲线和文档GitHub 评分性能比较 Pinia 2 和 Vuex 4Vuex 和 Pinia 的优缺点何时使用Pinia&#xff0c;何时使用Vuex介绍 Pinia 是 Vue.js 的轻量级状态管理库&#xff0c;最近很受欢迎。它…

1.2开发文档简读,了解全貌.mp4

转载于:https://www.cnblogs.com/ZHONGZHENHUA/p/6910254.html

开源:一款开源的高颜值现代化桌面美化工具

背景在日常的工作或学习中&#xff0c;难免会有一些临时的文件夹&#xff0c;文件&#xff0c;应用&#xff0c;出现在你的桌面&#xff0c;但是呢你又不确定它是不是哪一天会突然用到&#xff0c;这样一天又一天&#xff0c;直至你的电脑桌面是一片狼藉&#xff0c;满屏的文件…

软件工程—团队作业1

软件工程—团队作业1 团队称号&#xff1a;Thanos &#xff08;灭霸&#xff0c;超叼的一个动漫人物&#xff09; 团队成员&#xff1a; 队长 成凯 1600802002 博客链接&#xff1a; http://www.cnblogs.com/ck03/ 党康 1600208004 博客链接&#xff1a; http://www.cnblogs…

k8s 读书笔记 - kubernetes 基本概念和术语(上)

k8s 资源控制系统k8s 中大部分概念如&#xff1a;Node、Pod、Replication Controller、RS、Deployment、Service 等都可以被看作一种资源对象&#xff0c;激活所有的资源对象都可以通过 k8s 提供 kubectl 工具&#xff08;或者 API 编程调用&#xff09;执行 CRUD 等操作并将其…

CentOs6.5下安装svn

1、检查是否已安装 rpm -qa subversion 1、1如果需要卸载旧版本&#xff08;如果想在一台机器安装不同svn&#xff0c;切记不要执行此步骤&#xff01;&#xff01;&#xff01;&#xff09; yum remove subversion 2、安装 yum install subversion 3、检查安装是否成功 svnser…

Android 升级到android studio 2.2项目死活run不起来

背景&#xff1a;升级到Android studio 2.2项目死活运行不起来 现象如下&#xff1a; run with --stacktrace --debug等等抛出的bug简直无法忍视 解决办法&#xff1a;把compileSdkVersion 改为23成功run起来了

【python】-- Django 中间件、缓存、信号

Django 中间件、缓存、信号 一、 Django 中间件 django 中的中间件&#xff08;middleware&#xff09;&#xff0c;在django中&#xff0c;中间件其实就是一个类&#xff0c;在请求到来和结束后&#xff0c;django会根据自己的规则在合适的时机执行中间件中相应的方法。 在d…

【温故知新】C#中 IEnumerable 与IQueryable

微信公众号&#xff1a;趣编程ACE关注可了解更多的.NET日常实战开发技巧&#xff0c;如需源码 后台回复 源码 即可;如果觉得对你有帮助&#xff0c;欢迎关注老生常谈 C#中 IEnumerable 与IQueryableIEnumerable 与 IQueryable 对于.Neter来说并不陌生&#xff0c;今天我就着重阐…

Scala基础 - _root_ package的作用

2019独角兽企业重金招聘Python工程师标准>>> 在Scala中引入类时支持相对路径&#xff0c;例如&#xff1a; import play.api.libs.json._ import play.api.libs.json.util.LazyHelper可以简写成&#xff1a; import play.api.libs.json._ import util.LazyHelper通常…

使用.NET简单实现一个Redis的高性能克隆版(六)

译者注该原文是Ayende Rahien大佬业余自己在使用C# 和 .NET构建一个简单、高性能兼容Redis协议的数据库的经历。首先这个"Redis"是非常简单的实现&#xff0c;但是他在优化这个简单"Redis"路程很有趣&#xff0c;也能给我们在从事性能优化工作时带来一些启…