ShardingCore

ShardingCore 一款ef-core下高性能、轻量级针对分表分库读写分离的解决方案,具有零依赖、零学习成本、零业务代码入侵。



介绍

在分表分库领域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

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过大会导致内存爆炸

解释

MEMORY_STRICTLY

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

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将是正则的最小化链接数限制

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的跳过页数过多每个节点储存的数据将会非常恐怖。

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

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

总结

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

分表分库组件求赞求star

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


博客

QQ群:771630778

个人QQ:326308290(欢迎技术支持提供您宝贵的意见)

个人邮箱:326308290@qq.com

.Net 下高性能分表分库组件-连接模式原理的更多相关文章

  1. .NETCore 下支持分表分库、读写分离的通用 Repository

    首先声明这篇文章不是标题党,我说的这个类库是 FreeSql.Repository,它作为扩展库现实了通用仓储层功能,接口规范参考 abp vnext 定义,实现了基础的仓储层(CURD). 安装 d ...

  2. 总结下Mysql分表分库的策略及应用

    上月前面试某公司,对于mysql分表的思路,当时简要的说了下hash算法分表,以及discuz分表的思路,但是对于新增数据自增id存放的设计思想回答的不是很好(笔试+面试整个过程算是OK过了,因与个人 ...

  3. efcore使用ShardingCore实现分表分库下的多租户

    efcore使用ShardingCore实现分表分库下的多租户 介绍 本期主角:ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本.零业 ...

  4. efcore分表分库原理解析

    ShardingCore ShardingCore 易用.简单.高性能.普适性,是一款扩展针对efcore生态下的分表分库的扩展解决方案,支持efcore2+的所有版本,支持efcore2+的所有数据 ...

  5. Abp VNext分表分库,拒绝手动,我们要happy coding

    Abp VNext 分表分库 ShardingCore ShardingCore 易用.简单.高性能.普适性,是一款扩展针对efcore生态下的分表分库的扩展解决方案,支持efcore2+的所有版本, ...

  6. Furion分表分库我也要happy coding

    Furion分表分库集成ShardingCore ShardingCore ShardingCore 易用.简单.高性能.普适性,是一款扩展针对efcore生态下的分表分库的扩展解决方案,支持efco ...

  7. NetCore框架WTM的分表分库实现

    介绍 本期主角: ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本.零业务代码入侵 WTM WalkingTec.Mvvm框架(简称W ...

  8. .Net下你不得不看的分表分库解决方案-多字段分片

    .Net下你不得不看的分表分库解决方案-多字段分片 介绍 本期主角:ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本.零业务代码入侵 ...

  9. efcore在Saas系统下多租户零脚本分表分库读写分离解决方案

    efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...

随机推荐

  1. 初步认识express,并创建web服务器,挂载静态资源

    1.Express简介 1.1什么是Express 官方给出的概念:Express 是基于 Node.js 平台,快速.开放.极简的 Web 开发框架,官方网址 相似用途:Express 的作用和 N ...

  2. mongodb安装教程(一)

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/fengtingYan/article/de ...

  3. Docker学习:起步篇

    Docker-概述 学习资源 最好的资源在官网! Docker官方: Docker 官方主页: https://www.docker.com(opens new window) Docker 官方博客 ...

  4. idea Mark Directory as 的几种文件类型

    1. Source roots (or source folders) 源文件夹 通过为该类别分配文件夹,可以告诉IntelliJ IDEA该文件夹及其子文件夹包含应在构建过程中进行编译的源代码. 2 ...

  5. 史上最全的Excel导入导出之easyexcel

    喝水不忘挖井人,感谢阿里巴巴项目组提供了easyexcel工具类,github地址:https://github.com/alibaba/easyexcel 文章目录 环境搭建 读取excel文件 小 ...

  6. Unity——有限状态机FSM修改

    FSM状态机改 一.前言 FSM状态机初版 之前写过一版有限状态机,后来发现很多问题: 前一个版本是记录了当前的状态,切换状态时,要等下一帧状态机Update的时候才会调动上个状态的退出,总会有一帧的 ...

  7. Spring Cloud Gateway实战之四:内置predicate小结

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. 菜鸡的Java笔记 - java 反射机制

    反射机制        1.观察 Class 类的使用        2.利用反射改善工程设计模式        3.反射操作类结构            content (内容)        1. ...

  9. 菜鸡的Java笔记 java数据库编程(JDBC)

    java数据库编程(JDBC)        介绍 JDBC 的基本功能            content (内容)        现在几乎所有的项目开发过程之中都不可能离开数据库,所以在java ...

  10. 菜鸡的Java笔记 第三十二 - java 静态导入的实现

    静态导入的实现        为了理解静态导入的操作产生的动机,下面通过一个具体的代码来观察        范例:现在有一个 Import 的类,这个类中的方法全部都是 static 方法 packa ...