15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > .net 有没有比较好用的redis 集群解决方案?

.net 有没有比较好用的redis 集群解决方案?

时间:2023-10-23 13:12:01 | 来源:网站运营

时间:2023-10-23 13:12:01 来源:网站运营

.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都不需要并发就可以访问。

Vue3+.NET6+C#10,最近优质前后端分离项目汇总

据说80%的WEB开发都是管理后台,一套开源的优秀管理后台开发模板堪称福音!分享一套Vue3+ Axios+ TS+ Vite +Element Plus+ .NET 6 WebAPI+ JWT+ SqlSugar的前后端分离架构的通用管理后台源码+数据库脚本,还有与之配套录制的一组视频教程,全部打包免费分享!

核心功能概览

管理后台千千万,核心几乎没咋变,权限模块、数据增删改查、日志预警、流程审批、数据报表等,剩下的就是填充各种业务。本后台是提供了部分功能的实现,剩下的可以参考实现了,后续也会升级。

全套资料预览

光有代码和工具是不够的,真的要学会,就一定得动手!这里是全套的视频课件代码打包分享的,强烈建议对照视频教程动手试试!

资料免费自取:

由于内容过多不便呈现,需要视频教程和配套源码的小伙伴,可点击这里,添加我本站主页个人说明处号码 免费分享

也可直接点击下方卡片:点击后可自动复制威芯号,并跳转到威芯。得辛苦大家自行搜索威芯号添加。内容已做打包,添加后直接发送注意查收!

兴致上来了做了张图,也是干货清单,需要的小伙伴直接来领就是了。

包含VS2022安装包 / C#基础 .NET6/WPF/Winform零基础到各类实战!

免费资料免费送!完整还附源码...

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

以下是更改后的结果:

请注意,我们现在跑分的数据是125w/s,比上一次几乎增长了25%。下面是这一次新代码的分析器结果:

因此在本例中,花费了大量的时间来处理各种各样的字符串,等待GC(大约占30%)。集合的成本下降了很多。还有一些其它的开销出现在我眼前,看看这里:

对于“简单”属性查找来说,这个开销非常惊人。另外SubString函数的调用开销也很大,超过整个系统开销的6%。在研究系统其它部分时,看到了这个:

这真的很有趣,因为我们花了很多的时间在等待队列中是否有新的元素,其实我们可以做更多的事情,而不是就在那干等着。

我还尝试了其它的线程数量,如果只运行一个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; // readers new 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); }}学习离不开答疑交流,跳槽高薪离不开信息分享,这里给大家推荐个学习群,里面有大佬技术答疑、有直播技术分享、有资料定期分享、还有高薪内推资源,强烈建议大家进群一起学习交流,来年拿高薪。也欢迎大家加入最活跃的编程技术交流群 (.NET/WPF )进学习群,一起学习进步!

关键词:解决,方案

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭