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

news2024/11/19 3:30:28

正文

我遇到了这个项目,它的目标是成为一个比Redis有着更好性能和更易用的克隆版。我发现它很有趣,因为它主要的卖点之一就是它是在多线程模式下运行(而不是像Redis那样是单线程)。他们使用memtier_benchmark(Redis项目的一部分)来测试性能。所以我很好奇,如果我使用C#来构建自己的Redis克隆版,会有怎么样的性能?

我构建的第一个版本非常简单。我的想法是使用高抽象的API来编写它,看看它的性能到底怎么样。为了使事情变得有趣,下面是它的测试方案:

  • 客户端:memtier_benchmark将在aws的c6g.2xlarge实例上运行,使用8核32G内存
  • 服务端:测试的实例将在aws的c6g.4xlarge上运行,使用16核64G内存
    客户端要运行的命令如下所示:
memtier_benchmark –s $SERVER_IP -t 8 -c 16 --test-time=30 --distinct-client-seed -d 256 --pipeline=30

上面的命令说明我们将使用8个线程(客户端实例上的CPU核心数),每个线程创建32个链接,20%的场景写入,80的场景读取,数据大小为256字节,将不断的把更多的数据推送到测试的实例中。
服务端使用以下命令运行:

dotnet run –c Release

以下是此测试在服务器的实例:


我选择30秒作为测试的持续时间,以收集更多的信息让我们感受正在发生的事情(比如GC周期),同时保持测试的持续时间足够短,这样我不会感觉到无聊。
以下是简单版本的测试结果:

因此,使用C#构建的简单版本,即使什么优化都不做,也有几乎100w/s的性能。从另外的角度来说,延时并不是那么的好。P99延时将近100ms。
现在我用数字和漂亮的图表引起了你的注意,让我向你展示我正在运行的实际代码。这是一个不到100行代码的“Redis克隆”。

using System.Collections.Concurrent;
using System.Net.Sockets;

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
{
    ConcurrentDictionary<string, string> _state = new();

    public async Task HandleConnection(TcpClient client)
    {
        using var _ = client;
        using var stream = client.GetStream();
        using var reader = new StreamReader(stream);
        using var writer = new StreamWriter(stream)
        {
            NewLine = "\r\n"
        };

        try
        {
            var args = new List<string>();
            while (true)
            {
                args.Clear();
                var line = await reader.ReadLineAsync();
                if (line == null) break;

                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);
                }
                var reply = ExecuteCommand(args);
                if(reply == null)
                {
                    await writer.WriteLineAsync("$-1");
                }
                else
                {
                    await writer.WriteLineAsync($"${reply.Length}\r\n{reply}");
                }
                await writer.FlushAsync();
            }
        }
        catch (Exception e)
        {
            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
            }
        }
    }

    string? ExecuteCommand(List<string> args)
    {
        switch (args[0])
        {
            case "GET":
                return _state.GetValueOrDefault(args[1]);
            case "SET":
                _state[args[1]] = args[2];
                return null;
            default:
                throw new ArgumentOutOfRangeException("Unknown command: " + args[0]);
        }
    }
}

只是关于实现的几个注意事项。我实际上并没有做太多事情。大部分代码用于解析 Redis 协议。代码充满了内存分配。每个命令解析都是使用多个字符串拆分和连接来完成的。对客户端的回复需要更多的连接。系统的“存储”实际上只是一个简单的 ConcurrentDictionary,没有任何避免锁竞争或高成本的东西。
我们处理I/O的方式非常糟糕,而且......我想你明白我的想法,对吧?我的目标是看看如何使用这个(非常简单的)示例来获得更高的性能,而不必处理很多额外的细节。
鉴于我最初的尝试已经接近100万QPS,这是一个非常好的开始,即使我自己这么说。
我想采取的下一步是处理这里多余的内存分配。我们也许可以在内存分配这方面做得更好,虽然我的目标只是尝试。但我将在下一篇文章中这样做。

 

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

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

相关文章

shell命令以及运行原理

shell命令以及运行原理shell命令以及运行原理shell命令以及运行原理 Linux严格意义上来说说的是操作系统&#xff0c;我们把操作系统称之为“内核”&#xff08;kernel&#xff09;&#xff0c;但是我们用户是不会直接与kernel直接打交道的&#xff0c;我们是通过一个“外壳程…

D. Fixed Point Guessing(二分+交互式问题)

Problem - D - Codeforces 这是一个互动问题。 最初&#xff0c;有一个数组a[1,2,...,n]&#xff0c;其中n是一个奇数正整数。陪审团选择了n-12对不相干的元素&#xff0c;然后对这些元素进行交换。例如&#xff0c;如果a[1,2,3,4,5]&#xff0c;对1↔4和3↔5进行互换&#xf…

吐血经验,怎么把OAK相机的镜头模组拆下来?

消息快播&#xff1a;OpenCV众筹了一款ROS2机器人rae&#xff0c;开源、功能强、上手简单。来瞅瞅~ 编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查…

[AI] LRTA*(K) 搜索算法

LRTA*[k]搜索算法 一、理论二、实际应用步骤一、理论 LRTA*(K) 是LRTA* 算法的进阶版&#xff0c;关于LRTA*的回顾请点此处&#xff0c; LRTA*(K) 论文请点此处 该文作者把LRTA算法归为 无界传播(unbounded propagation, 中文用谷歌翻译的。。。囧)&#xff0c;LRTA(K)归为有…

第四章:JVM运行时参数

一、JVM参数选项类型类型一&#xff1a;标准参数选项类型二&#xff1a;-X参数选项类型三&#xff1a; -XX参数选项二、常用的 JVM 参数选项打印设置的 XX 选项及值堆、栈、方法区的参数栈堆方法区垃圾回收器相关参数Serial回收器ParNew 回收器ParallelGcCMS回收器G1 回收器如何…

Win11杜比全景声无法正常运行的解决方法教学

Win11杜比全景声无法正常运行的解决方法教学。我们的电脑开启杜比声全景音效之后&#xff0c;无论是看电影&#xff0c;还是听音乐&#xff0c;都可以获得更加良好的视听体验。但是有用户却遇到了电脑无法正常的开启杜比声音效的问题&#xff0c;一起来看看如何去解决的方法吧。…

【Selenium】Selenium4 Grid

Selenium Grid: 将客户端发送的命令转发到远程浏览器上执行 WebDriver 脚本。 简介 Selenium Grid 是由一个 hub 服务和若干个 node 代理节点组成。 Hub 调度服务&#xff0c;管理各个代理节点的注册信息和状态信息&#xff0c;并且接收来自客户端代码的调用请求&#xff0c;…

13.练习题(年月日,打字游戏)

练习 1.任意给出一个年、月、日&#xff0c;判断是这一年的第几天&#xff1a;闰年算法&#xff1a;能被4整除且不能被100整除&#xff0c;或者能被400整除。 如&#xff1a;2012 5 10 是这一年的第131天。 提示&#xff1a;使用数组的方式计算&#xff0c;将每月的天数放在一…

面试学习总结

之前根据视频做的总结&#xff0c;备个份。 一、Volatile关键字 volatile是Java虚拟机提供的轻量级的同步机制。 三个特性&#xff1a; 保证可见性 线程修改了工作内存中的值并写回到主内存之后&#xff0c;主内存立刻通知所有线程。称为可见性。&#xff08;结合JMM理解&am…

蓝桥杯刷题(三)

蓝桥杯刷题一.等差素数列&#xff08;较难&#xff09;二.货物摆放&#xff08;思路新奇&#xff09;三.既约分数四.跳跃五.数值求值&#xff08;坑题&#xff09;蓝桥杯题库一.等差素数列&#xff08;较难&#xff09; 这道题有公式&#xff0c;其等差就是它长度内所有素数的乘…

Seata安装并注册集成到Nacos服务上

目录 一.工具地址与版本 二.解压与配置 三.创建数据库&#xff0c;执行脚本 四.启动与调试 五.seata高可用 六.查看启动情况 一.工具地址与版本 1.nacos-server-2.0.3 下载地址&#xff1a;Releases alibaba/nacos GitHub 2.seata-server-1.4.2 下载地址&#xff1…

小侃设计模式(十四)-职责链模式

1.概述 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是为请求创建了一个接收者对象的链&#xff0c;让请求沿着这条链进行传播&#xff0c;直到有对象处理它为止&#xff0c;它属于典型的行为型模式。这种方式降低了发送者和处理者之间的耦合关系&…

HTML+CSS简单的网页制作期末作业 关于我的家乡——四川文化网页介绍 DW大学生网页作业制作设计 Dreamweaver简单网页成品

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

chapter1——亚稳态的世界

目录1.亚稳态理论2.亚稳态窗口3.计算MTBF4.避免亚稳态多级同步器时钟倍频电路的多级同步器5.亚稳态测试电路6.同步器类型7.综合性建议在异步系统中&#xff0c;由于数据和时钟的关系不是固定的&#xff0c;因此有时会出现违反建立和保持时间的现象&#xff0c;此时便会输出介于…

OTA: Optimal Transport Assignment for Object Detection原理与代码解读

paper&#xff1a;OTA: Optimal Transport Assignment for Object Detection code&#xff1a;https://github.com/Megvii-BaseDetection/OTA 背景 标签分配&#xff08;Label Assignment&#xff09;是目标检测中重要的一环&#xff0c;经典的标签分配策略采用预定义的规则…

Type-challehges learning: pick

type-challenges/README.zh-CN.md at main TIMPICKLE/type-challenges GitHub 最近真的是迷上了type-challenges。无法自拔 TS真是太香了&#xff01; 今天手写一个pick 从类型定义的属性中&#xff0c;选取指定一组属性&#xff0c;返回一个新的类型定义。 type Pick<T…

Npm——发布依赖库并使用

1. 注册一个NPM账号 npm官方地址&#xff1a;https://www.npmjs.com/ 2. 在本地创建一个目录,输入npm初始化命令&#xff0c;并修改package.json,创建src/index.js入口文件 npm init --yespackage.json name npm发布时的包名&#xff0c;和已有的npm库不能重复version 包版本…

网络安全——SQL注入之安全狗bypass深度剖析

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

这些调试API技巧你熟悉吗?

通常&#xff0c;我们在调试第三方提供的API时&#xff0c;有时候并没那么顺畅&#xff0c;甚至可能本身就是API服务有问题&#xff0c;但是需要提供你结论的"依据"。下面整理了一些API调试技巧&#xff0c;也方便你甩锅 简单来说分为以下两点 检测状态信息检测返回…

MySQl(八):索引机制

MySQl&#xff1a;索引索引概述索引结构B树的演变过程B树Hash索引分类聚集索引和二级索引### 回表查询思考索引语法普通索引联合索引删除索引索引使用索引使用规则之验证索引效率索引使用规则之最左前缀法则索引使用规则之索引失效情况索引使用规则之SQL提示索引使用规则之覆盖…