.Net 下高性能分表分库组件 (类似ShardingSphere原理)


.Net 下高性能分表分库组件 (类似ShardingSphere原理)



介绍

在分表分库领域java有着很多的解决方案,尤其是客户端解决方案(ShardingSphere),因为客户端解决方案有着极高的性能,但是缺点也很明显数据库链接的消耗相对较高,使用语言的限制让我们.Net望而却步,但是哪怕是有着这些缺点其实也不足以掩盖客户端分表分库带来的便捷与高效。
目前本人所开发的ShardingCore 是.Net下基于efcore2+的所有版本的分表分库很多都是借鉴了ShardingSphere,并且对其很多缺点进行了弥补。这边可能有人就要说了,你为什么做个efcore的不做个ado.net的呢,说实话我这边确实有一个ado.net版本的分表分库,你可以理解为ShardingSphere的.Net复刻版本sharding-conector 最最最初版本的分表聚合已经实现底层原理和ShardingSphere一致使用的Antlr4的分词。为什么不对这个版本进行推进转而对efcoresharding-core版本进行升级维护呢,这边主要有两点,第一点如果我是在ado.net上进行的推进那么势必可以支持更多的orm框架,但是orm框架下的很多特性将可能无法使用,并且需要维护各个数据库版本之间的差异。比如efcore下的批量操作等一些列优化语法是很难被支持的。第二点针对某个orm的扩展性能和使用体验上远远可以大于通用性组件。这就是我为什么针对ShardingCore进行推进、优化和升级的原因。

性能

其实性能一直是大家关注的一个点,我用了ShardingCore那么针对特定的查询他的损耗是多少是一个比较令人关注的话题。接下来我放出之前做的两次性能比较,当然这两次比较并不是特意准备的,是我边开发边跑的一个是sqlserver 一个是mysql

性能测试

以下所有数据均在开启了表达式编译缓存的情况下测试,并且电脑处于长时间未关机并且开着很多vs和idea的情况下仅供参考,所有测试都是基于ShardingCore x.3.1.63+ version

以下所有数据均在源码中有案例

efcore版本均为6.0 表结构为string型id的订单取模分成5张表

N代表执行次数

sql server 2012,data rows 7734363 =773w

// * Summary *

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1500 (1909/November2019Update/19H2)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK=6.0.100
[Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT

MethodNMeanErrorStdDevMedian
NoShardingIndexFirstOrDefaultAsync102.154 ms0.1532 ms0.4443 ms1.978 ms
ShardingIndexFirstOrDefaultAsync104.293 ms0.1521 ms0.4485 ms4.077 ms
NoShardingNoIndexFirstOrDefaultAsync10823.382 ms16.0849 ms18.5233 ms821.221 ms
ShardingNoIndexFirstOrDefaultAsync10892.276 ms17.8131 ms16.6623 ms894.880 ms
NoShardingNoIndexCountAsync10830.754 ms16.5309 ms38.6405 ms821.736 ms
ShardingNoIndexCountAsync10915.630 ms8.8511 ms7.3911 ms914.107 ms
NoShardingNoIndexLikeToListAsync107,008.918 ms139.4664 ms166.0248 ms6,955.674 ms
ShardingNoIndexLikeToListAsync107,044.168 ms135.3814 ms132.9626 ms7,008.057 ms
NoShardingNoIndexToListAsync10787.129 ms10.5812 ms8.8357 ms785.798 ms
ShardingNoIndexToListAsync10935.880 ms16.3354 ms15.2801 ms940.369 ms

mysql 5.7,data rows 7553790=755w innerdb_buffer_size=3G

// * Summary *

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1500 (1909/November2019Update/19H2)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK=6.0.100
[Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT

MethodNMeanErrorStdDevMedian
NoShardingIndexFirstOrDefaultAsync105.020 ms0.1245 ms0.3672 ms4.855 ms
ShardingIndexFirstOrDefaultAsync107.960 ms0.1585 ms0.2514 ms7.974 ms
NoShardingNoIndexFirstOrDefaultAsync1011,336.083 ms623.8044 ms1,829.5103 ms11,185.590 ms
ShardingNoIndexFirstOrDefaultAsync105,422.259 ms77.5386 ms72.5296 ms5,390.019 ms
NoShardingNoIndexCountAsync1014,229.819 ms82.8929 ms77.5381 ms14,219.773 ms
ShardingNoIndexCountAsync103,085.268 ms55.5942 ms49.2828 ms3,087.704 ms
NoShardingNoIndexLikeToListAsync1027,046.390 ms71.2034 ms59.4580 ms27,052.316 ms
ShardingNoIndexLikeToListAsync105,707.009 ms106.8713 ms99.9675 ms5,672.453 ms
NoShardingNoIndexToListAsync1026,001.850 ms89.2787 ms69.7030 ms25,998.407 ms
ShardingNoIndexToListAsync105,490.659 ms71.8199 ms67.1804 ms5,477.891 ms

具体可以通过first前两次结果来计算得出结论单次查询的的损耗为0.2-0.3毫秒之间,通过数据聚合和数据路由的损耗单次在0.3ms-0.4ms,其中创建dbcontext为0.1毫秒目前没有好的优化方案,0.013毫秒左右是路由表达式解析和编译,复杂表达式可能更加耗时,剩下的0.2毫秒为数据源和表后缀的解析等操作包括实例的反射创建和数据的聚合,
sqlserver的各项数据在分表和未分表的情况下都几乎差不多可以得出在770w数据集情况下数据库还并未是数据瓶颈的关键,但是mysql可以看到在分表和未分表的情况下如果涉及到没有索引的全表扫描那么性能的差距将是分表后的表数目之多,测试中为5-6倍,也就是分表数目

如果你可以接受单次查询的损耗在0.2ms-0.3ms的那相信这款框架将会是efcore下非常完美的一款分表分库组件


链接模式

说了这么多这边需要针对ShardingCore在查询下面涉及到N表查询后带来的链接消耗是一个不容小觑的客观因素。所以这边参考ShardingSphere进行了类似原理的实现。就是如果查询涉及不同库那么直接并发,如果是同库的将根据用户配置的单次最大链接进行串行查询,并且动态选择使用流式聚合和内存聚合。

首先我们看下ShardingSphere的链接模式在限制链接数的情况下是如何进行处理的

.Net 下高性能分表分库组件-连接模式原理


针对不同的数据库采用并行执行,针对同一个数据库根据用户配置的最大连接数进行分库串行执行,并且因为需要控制链接数所以会将结果集保存在内存中,最后通过合并返回给客户端数据。
之后我们会讲这个模式的缺点并且ShardingCore是如何进行优化的

你可能已经蒙了这么多名称完全没有一个概念。接下来我将一一进行讲解,首先我们来看下链接模式下有哪些参数

MaxQueryConnectionsLimit

最大并发链接数,就是表示单次查询sharding-core允许使用的dbconnection,默认会加上1就是说如果你配置了MaxQueryConnectionsLimit=10那么实际sharding-core会在同一次查询中开启11条链接最多,为什么是11不是10因为sharding-core会默认开启一个链接用来进行空dbconnection的使用。如果不设置本参数那么默认是cpu线程数Environment.ProcessorCount

ConnectionMode

链接模式,可以由用户自行指定,使用内存限制,和连接数限制或者系统自行选择最优

链接模式,有三个可选项,分别是:

MEMORY_STRICTLY

内存限制模式最小化内存聚合 流式聚合 同时会有多个链接

MEMORY_STRICTLY的意思是最小化内存使用率,就是非一次性获取所有数据然后采用流式聚合

CONNECTION_STRICTLY

连接数限制模式最小化并发连接数 内存聚合 连接数会有限制

CONNECTION_STRICTLY的意思是最小化连接并发数,就是单次查询并发连接数为设置的连接数MaxQueryConnectionsLimit。因为有限制,所以无法一直挂起多个连接,数据的合并为内存聚合采用最小化内存方式进行优化,而不是无脑使用内存聚合

SYSTEM_AUTO

系统自动选择内存还是流式聚合

系统自行选择会根据用户的配置采取最小化连接数,但是如果遇到分页则会根据分页策略采取内存限制,因为skip过大会导致内存爆炸

解释

MEMORY_STRICTLY

MEMORY_STRICTLY内存严格模式,用户使用本属性后将会严格控制查询的聚合方式,将会采用流式聚合的迭代器模式,而不是一次性全部去除相关数据在内存中排序获取,通过用户配置的MaxQueryConnectionsLimit连接数来进行限制,比如MaxQueryConnectionsLimit=2,并且本次查询涉及到一个库3张表,因为程序只允许单次查询能并发2个链接,所以本次查询会被分成2组每组两个,其中第二组只有一个,在这种情况下第一次并发查询2条语句因为采用内存严格所以不会将数据获取到内存,第二次在进行一次查询并将迭代器返回一共组合成3个迭代器后续通过流式聚合+优先级队列进行返回所要的数据,在这种情况下程序的内存是最少的但是消耗的链接也是最大的。当用户手动选择MEMORY_STRICTLYMaxQueryConnectionsLimit将变成并行数目. 该模式下ShardingCoreShardingSphere的处理方式类似基本一致

.Net 下高性能分表分库组件-连接模式原理

CONNECTION_STRICTLY

CONNECTION_STRICTLY连接数严格模式,用户使用本属性后将会严格控制查询后的同一个数据库下的同时查询的链接数,不会因为使用流式内存而导致迭代器一致开着,因为一个迭代器查询开着就意味着需要一个链接,如果查询需要聚合3张表那么就需要同时开着三个链接来迭代保证流式聚合。通过用户配置的MaxQueryConnectionsLimit连接数来进行限制,比如MaxQueryConnectionsLimit=2,并且本次查询涉及到一个库3张表,因为程序只允许单次查询能并发2个链接,所以本次查询会被分成2组每组两个,其中第二组只有一个,在这种情况下第一次并发查询2条语句因为采用连接数严格所以不会一直持有链接,会将链接结果进行每组进行合并然后将连接放回,合并时还是采用的流式聚合,会首先将第一组的两个链接进行查询之后将需要的结果通过流式聚合取到内存,然后第二组会自行独立查询并且从第二次开始后会将上一次迭代的内存聚合数据进行和本次查询的流式聚合分别一起聚合,保证在分页情况下内存数据量最少。因为如果每组都是用独立的内存聚合那么你有n组就会有n*(skip+take)的数目,而ShardingSphere采用的是更加简单的做法,就是将每组下面的各自节点都自行进行内存聚合,那么如果在skip(10).take(10)的情况下sql会被改写成各组的各个节点分别进行skip(0).take(20)的操作那么2组执行器的第一组将会有40条数据第二组将会有20条数据一共会有60条数据远远操作了我们所需要的20条。所以在这个情况下ShardingCore第一组内存流式聚合会返回20条数据,第二组会将第一组的20条数据和第二组的进行流式聚合内存中还是只有20条数据,虽然是连接数严格但是也做到了最小化内存单元。当用户手动选择CONNECTION_STRICTLYMaxQueryConnectionsLimit将是正则的最小化链接数限制

.Net 下高性能分表分库组件-连接模式原理

SYSTEM_AUTO

SYSTEM_AUTO系统自行选择,这是一个非常帮的选择,因为在这个选择下系统会自动根据用户配置的MaxQueryConnectionsLimit来自行控制是采用流式聚合还是内存聚合,并且因为我们采用的是同数据库下面最小化内存相比其他的解决方案可以更加有效和高性能的来应对各种查询。仅仅只需要配置一个最大连接数限制既可以适配好连接模式。

这边极力推荐大家在不清楚应该用什么模式的时候使用SYSTEM_AUTO并且手动配置MaxQueryConnectionsLimit来确定各个环境下的配置一直而不是采用默认的cpu线程数。

首先我们通过每个数据库被路由到了多少张表进行计算期望用户在配置了xx后应该的并行数来进行分组,sqlCount :表示这个数据库被路由到的表数目,exceptCount :表示计算出来的应该的单次查询并行数

//代码本质就是向上取整
    int exceptCount =
                Math.Max(
                    0 == sqlCount % maxQueryConnectionsLimit
                        ? sqlCount / maxQueryConnectionsLimit
                        : sqlCount / maxQueryConnectionsLimit + 1, 1);

第二次我们通过判断sqlCountmaxQueryConnectionsLimit的大小来确定链接模式的选择


        private ConnectionModeEnum CalcConnectionMode(int sqlCount)
        {
            switch (_shardingConfigOption.ConnectionMode)
            {
                case ConnectionModeEnum.MEMORY_STRICTLY:
                case ConnectionModeEnum.CONNECTION_STRICTLY: return _shardingConfigOption.ConnectionMode;
                default:
                {
                    return _shardingConfigOption.MaxQueryConnectionsLimit < sqlCount
                        ? ConnectionModeEnum.CONNECTION_STRICTLY
                        : ConnectionModeEnum.MEMORY_STRICTLY; ;
                }
            }
        }

比较

针对ShardingSphere的流程图我们可以看到在获取普通数据的时候是没有什么问题的,但是如果遇到分页也就是

select * from order limit 10,10

这种情况下会被改写成

select * from order limit 0,20

我们可以看到如果是ShardingSphere的流程模式那么在各个节点处虽然已经将连接数控制好了但是对于每个节点而言都有着20条数据,这种情况下其实是一种非常危险的,因为一旦节点过多并且limit的跳过页数过多每个节点储存的数据将会非常恐怖。

.Net 下高性能分表分库组件-连接模式原理

所以针对这种情况ShardingCore将同库下的各个节点组的查询使用StreamMerge而不是MemoryMerge,并且会对各个节点间建立联系进行聚合保证在同一个数据库下只会有20条数据被加载到内存中,大大降低了内存的使用,提高了内存使用率。

.Net 下高性能分表分库组件-连接模式原理

当然具体情况应该还需要再次进行优化并不是简单的一次优化就搞定的比如当跳过的页数过多之后其实在内存中的一部分数据也会再次进行迭代和新的迭代器比较,这个中间的性能差距可能需要不断地尝试才可以获取一个比较可靠的值

总结

目前已经有很多小伙伴已经在使用SharidingCore了并且在使用的时候也是相对比较简单的配置既可以“完美”目前她在使用的各种框架譬如:AbpVNext....基本上在继承和使用方面可以说是目前efcore生态下最最最完美的了真正做到了三零的框架:零依赖,零学习成本,零业务代码入侵

最后放一张图

是我这边给ShardingSphere提的建议,也证实了我对该聚合模型的优化是可以有效解决在分页下面聚合各数据库节点下的内存使用情况

.Net 下高性能分表分库组件-连接模式原理

分表分库组件求赞求star

您的支持是开源作者能坚持下去的最大动力

 

版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:ASP.NET Web API入门介绍(一)
下一篇:C#.NET LINQ入门基础
评论列表

发表评论

评论内容
昵称:
关联文章

.Net 高性能分库组件类似ShardingSphere原理
.NET 自定义图片拉框组件ImageComboBox
CSFramework.WebApiV3.分库
CSFramework.WebApiV3.分库
C#.NET理解Task和async await原理
理解C#.NET泛型运作原理
C# DevExpress会计凭证表格金额录入组件(支持元角
基于DevExpress C# Winform 财务会计凭证表格金额录入组件(支持元角
SQL注入攻击的原理及其防范措施
C# C/S架构客户、供应商等大批量数据单条记录选取组件
C/S框架-WebService架构页查询数据解决方案
基于C# Winform财务会计凭证表格金额录入组件(支持亿仟佰拾萬仟佰拾圆角
SQL Server连接池基本原理
.NET 开源免费图表组件库 - ScottPlot(Winform,WPF 通用)
【原创】WebApi开发框架:Token生成、Token缓存原理、Token验证、令牌机制与原理
【原创】C# Dev用户自定义组件,自动拉弹框表格过滤数据组件ucMyLookUpEdit
C# Winform 按回车键查找一个可设置焦点的组件
C# DevExpress教学 GridLookUpEdit组件表格组件使用
标准主从界面增加一个明细表解决方案(不变更BusinessData模型的情况
【原创】C# 深度拷贝对象 使用.NET反射+递归原理实现深度克隆

热门标签
.NET5 .NET6 .NET7 APP Auth-软件授权注册系统 Axios B/S B/S开发框架 Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 DAL数据访问层 Database datalock DbFramework Demo教学 Demo下载 DevExpress教程 DOM EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS NavBarControl Node.JS NPM OMS ORM PaaS POS Promise API Redis SAP SEO SQL SQLConnector TMS系统 Token令牌 VS2022 VSCode VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 版本区别 报表 踩坑日记 操作手册 代码生成器 迭代开发记录 基础资料窗体 架构设计 角色权限 开发sce 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 秘钥 密钥 权限设计 软件报价 软件测试报告 软件简介 软件开发框架 软件开发平台 软件开发文档 软件体系架构 软件下载 软著证书 三层架构 设计模式 生成代码 实用小技巧 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序