ShardingCore
一款ef-core
下高性能、轻量级针对分表分库读写分离的解决方案,具有零依赖、零学习成本、零业务代码入侵。
Github Source Code 助力dotnet 生态 Gitee Source Code
介绍
在分表分库领域java有着很多的解决方案,尤其是客户端解决方案(ShardingSphere),因为客户端解决方案有着极高的性能,但是缺点也很明显数据库链接的消耗相对较高,使用语言的限制让我们.Net望而却步,但是哪怕是有着这些缺点其实也不足以掩盖客户端分表分库带来的便捷与高效。
目前本人所开发的ShardingCore
是.Net下基于efcore2+的所有版本的分表分库很多都是借鉴了ShardingSphere
,并且对其很多缺点进行了弥补。这边可能有人就要说了,你为什么做个efcore的不做个ado.net的呢,说实话我这边确实有一个ado.net版本的分表分库,你可以理解为ShardingSphere的.Net复刻版本sharding-conector 最最最初版本的分表聚合已经实现底层原理和ShardingSphere
一致使用的Antlr4的分词。为什么不对这个版本进行推进转而对efcore
的sharding-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
Method | N | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
NoShardingIndexFirstOrDefaultAsync | 10 | 2.154 ms | 0.1532 ms | 0.4443 ms | 1.978 ms |
ShardingIndexFirstOrDefaultAsync | 10 | 4.293 ms | 0.1521 ms | 0.4485 ms | 4.077 ms |
NoShardingNoIndexFirstOrDefaultAsync | 10 | 823.382 ms | 16.0849 ms | 18.5233 ms | 821.221 ms |
ShardingNoIndexFirstOrDefaultAsync | 10 | 892.276 ms | 17.8131 ms | 16.6623 ms | 894.880 ms |
NoShardingNoIndexCountAsync | 10 | 830.754 ms | 16.5309 ms | 38.6405 ms | 821.736 ms |
ShardingNoIndexCountAsync | 10 | 915.630 ms | 8.8511 ms | 7.3911 ms | 914.107 ms |
NoShardingNoIndexLikeToListAsync | 10 | 7,008.918 ms | 139.4664 ms | 166.0248 ms | 6,955.674 ms |
ShardingNoIndexLikeToListAsync | 10 | 7,044.168 ms | 135.3814 ms | 132.9626 ms | 7,008.057 ms |
NoShardingNoIndexToListAsync | 10 | 787.129 ms | 10.5812 ms | 8.8357 ms | 785.798 ms |
ShardingNoIndexToListAsync | 10 | 935.880 ms | 16.3354 ms | 15.2801 ms | 940.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
Method | N | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
NoShardingIndexFirstOrDefaultAsync | 10 | 5.020 ms | 0.1245 ms | 0.3672 ms | 4.855 ms |
ShardingIndexFirstOrDefaultAsync | 10 | 7.960 ms | 0.1585 ms | 0.2514 ms | 7.974 ms |
NoShardingNoIndexFirstOrDefaultAsync | 10 | 11,336.083 ms | 623.8044 ms | 1,829.5103 ms | 11,185.590 ms |
ShardingNoIndexFirstOrDefaultAsync | 10 | 5,422.259 ms | 77.5386 ms | 72.5296 ms | 5,390.019 ms |
NoShardingNoIndexCountAsync | 10 | 14,229.819 ms | 82.8929 ms | 77.5381 ms | 14,219.773 ms |
ShardingNoIndexCountAsync | 10 | 3,085.268 ms | 55.5942 ms | 49.2828 ms | 3,087.704 ms |
NoShardingNoIndexLikeToListAsync | 10 | 27,046.390 ms | 71.2034 ms | 59.4580 ms | 27,052.316 ms |
ShardingNoIndexLikeToListAsync | 10 | 5,707.009 ms | 106.8713 ms | 99.9675 ms | 5,672.453 ms |
NoShardingNoIndexToListAsync | 10 | 26,001.850 ms | 89.2787 ms | 69.7030 ms | 25,998.407 ms |
ShardingNoIndexToListAsync | 10 | 5,490.659 ms | 71.8199 ms | 67.1804 ms | 5,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
的链接模式在限制链接数的情况下是如何进行处理的
针对不同的数据库采用并行执行,针对同一个数据库根据用户配置的最大连接数进行分库串行执行,并且因为需要控制链接数所以会将结果集保存在内存中,最后通过合并返回给客户端数据。
之后我们会讲这个模式的缺点并且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过大会导致内存爆炸