.Net 下高性能分表分库组件-连ShardingCore接模式原理
ShardingCore?一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學(xué)習(xí)成本、零業(yè)務(wù)代碼入侵。
Github Source Code?助力dotnet 生態(tài)?Gitee Source Code
介紹
在分表分庫領(lǐng)域java有著很多的解決方案,尤其是客戶端解決方案(ShardingSphere),因為客戶端解決方案有著極高的性能,但是缺點也很明顯數(shù)據(jù)庫鏈接的消耗相對較高,使用語言的限制讓我們.Net望而卻步,但是哪怕是有著這些缺點其實也不足以掩蓋客戶端分表分庫帶來的便捷與高效。
目前本人所開發(fā)的ShardingCore?是.Net下基于efcore2+的所有版本的分表分庫很多都是借鑒了ShardingSphere,并且對其很多缺點進行了彌補。這邊可能有人就要說了,你為什么做個efcore的不做個ado.net的呢,說實話我這邊確實有一個ado.net版本的分表分庫,你可以理解為ShardingSphere的.Net復(fù)刻版本sharding-conector?最最最初版本的分表聚合已經(jīng)實現(xiàn)底層原理和ShardingSphere一致使用的Antlr4的分詞。為什么不對這個版本進行推進轉(zhuǎn)而對efcore的sharding-core版本進行升級維護呢,這邊主要有兩點,第一點如果我是在ado.net上進行的推進那么勢必可以支持更多的orm框架,但是orm框架下的很多特性將可能無法使用,并且需要維護各個數(shù)據(jù)庫版本之間的差異。比如efcore下的批量操作等一些列優(yōu)化語法是很難被支持的。第二點針對某個orm的擴展性能和使用體驗上遠遠可以大于通用性組件。這就是我為什么針對ShardingCore進行有段優(yōu)化和升級的原因。
性能
其實性能一直是大家關(guān)注的一個點,我用了ShardingCore那么針對特定的查詢他的損耗是多少是一個比較令人關(guān)注的話題。接下來我放出之前做的兩次性能比較,當然這兩次比較并不是特意準備的,是我邊開發(fā)邊跑的一個是sqlserver 一個是mysql
性能測試
以下所有數(shù)據(jù)均在開啟了表達式編譯緩存的情況下測試,并且電腦處于長時間未關(guān)機并且開著很多vs和idea的情況下僅供參考,所有測試都是基于ShardingCore x.3.1.63+ version
以下所有數(shù)據(jù)均在源碼中有案例
efcore版本均為6.0 表結(jié)構(gòu)為string型id的訂單取模分成5張表
N代表執(zhí)行次數(shù)
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
| 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
| 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前兩次結(jié)果來計算得出結(jié)論單次查詢的的損耗為0.2-0.3毫秒之間,通過數(shù)據(jù)聚合和數(shù)據(jù)路由的損耗單次在0.3ms-0.4ms,其中創(chuàng)建dbcontext為0.1毫秒目前沒有好的優(yōu)化方案,0.013毫秒左右是路由表達式解析和編譯,復(fù)雜表達式可能更加耗時,剩下的0.2毫秒為數(shù)據(jù)源和表后綴的解析等操作包括實例的反射創(chuàng)建和數(shù)據(jù)的聚合,
sqlserver的各項數(shù)據(jù)在分表和未分表的情況下都幾乎差不多可以得出在770w數(shù)據(jù)集情況下數(shù)據(jù)庫還并未是數(shù)據(jù)瓶頸的關(guān)鍵,但是mysql可以看到在分表和未分表的情況下如果涉及到?jīng)]有索引的全表掃描那么性能的差距將是分表后的表數(shù)目之多,測試中為5-6倍,也就是分表數(shù)目
如果你可以接受單次查詢的損耗在0.2ms-0.3ms的那相信這款框架將會是efcore下非常完美的一款分表分庫組件
鏈接模式
說了這么多這邊需要針對ShardingCore在查詢下面涉及到N表查詢后帶來的鏈接消耗是一個不容小覷的客觀因素。所以這邊參考ShardingSphere進行了類似原理的實現(xiàn)。就是如果查詢涉及不同庫那么直接并發(fā),如果是同庫的將根據(jù)用戶配置的單次最大鏈接進行串行查詢,并且動態(tài)選擇使用流式聚合和內(nèi)存聚合。
首先我們看下ShardingSphere的鏈接模式在限制鏈接數(shù)的情況下是如何進行處理的
針對不同的數(shù)據(jù)庫采用并行執(zhí)行,針對同一個數(shù)據(jù)庫根據(jù)用戶配置的最大連接數(shù)進行分庫串行執(zhí)行,并且因為需要控制鏈接數(shù)所以會將結(jié)果集保存在內(nèi)存中,最后通過合并返回給客戶端數(shù)據(jù)。
之后我們會講這個模式的缺點并且ShardingCore是如何進行優(yōu)化的
你可能已經(jīng)蒙了這么多名稱完全沒有一個概念。接下來我將一一進行講解,首先我們來看下鏈接模式下有哪些參數(shù)
MaxQueryConnectionsLimit
最大并發(fā)鏈接數(shù),就是表示單次查詢sharding-core允許使用的dbconnection,默認會加上1就是說如果你配置了MaxQueryConnectionsLimit=10那么實際sharding-core會在同一次查詢中開啟11條鏈接最多,為什么是11不是10因為sharding-core會默認開啟一個鏈接用來進行空dbconnection的使用。如果不設(shè)置本參數(shù)那么默認是cpu線程數(shù)Environment.ProcessorCount
ConnectionMode
鏈接模式,可以由用戶自行指定,使用內(nèi)存限制,和連接數(shù)限制或者系統(tǒng)自行選擇最優(yōu)
鏈接模式,有三個可選項,分別是:
MEMORY_STRICTLY
內(nèi)存限制模式最小化內(nèi)存聚合 流式聚合 同時會有多個鏈接
MEMORY_STRICTLY的意思是最小化內(nèi)存使用率,就是非一次性獲取所有數(shù)據(jù)然后采用流式聚合
CONNECTION_STRICTLY
連接數(shù)限制模式最小化并發(fā)連接數(shù) 內(nèi)存聚合 連接數(shù)會有限制
CONNECTION_STRICTLY的意思是最小化連接并發(fā)數(shù),就是單次查詢并發(fā)連接數(shù)為設(shè)置的連接數(shù)MaxQueryConnectionsLimit。因為有限制,所以無法一直掛起多個連接,數(shù)據(jù)的合并為內(nèi)存聚合采用最小化內(nèi)存方式進行優(yōu)化,而不是無腦使用內(nèi)存聚合
SYSTEM_AUTO
系統(tǒng)自動選擇內(nèi)存還是流式聚合
系統(tǒng)自行選擇會根據(jù)用戶的配置采取最小化連接數(shù),但是如果遇到分頁則會根據(jù)分頁策略采取內(nèi)存限制,因為skip過大會導(dǎo)致內(nèi)存爆炸
解釋
MEMORY_STRICTLY
MEMORY_STRICTLY內(nèi)存嚴格模式,用戶使用本屬性后將會嚴格控制查詢的聚合方式,將會采用流式聚合的迭代器模式,而不是一次性全部去除相關(guān)數(shù)據(jù)在內(nèi)存中排序獲取,通過用戶配置的MaxQueryConnectionsLimit連接數(shù)來進行限制,比如MaxQueryConnectionsLimit=2,并且本次查詢涉及到一個庫3張表,因為程序只允許單次查詢能并發(fā)2個鏈接,所以本次查詢會被分成2組每組兩個,其中第二組只有一個,在這種情況下第一次并發(fā)查詢2條語句因為采用內(nèi)存嚴格所以不會將數(shù)據(jù)獲取到內(nèi)存,第二次在進行一次查詢并將迭代器返回一共組合成3個迭代器后續(xù)通過流式聚合+優(yōu)先級隊列進行返回所要的數(shù)據(jù),在這種情況下程序的內(nèi)存是最少的但是消耗的鏈接也是最大的。當用戶手動選擇MEMORY_STRICTLY后MaxQueryConnectionsLimit將變成并行數(shù)目. 該模式下ShardingCore和ShardingSphere的處理方式類似基本一致
CONNECTION_STRICTLY
CONNECTION_STRICTLY連接數(shù)嚴格模式,用戶使用本屬性后將會嚴格控制查詢后的同一個數(shù)據(jù)庫下的同時查詢的鏈接數(shù),不會因為使用流式內(nèi)存而導(dǎo)致迭代器一致開著,因為一個迭代器查詢開著就意味著需要一個鏈接,如果查詢需要聚合3張表那么就需要同時開著三個鏈接來迭代保證流式聚合。通過用戶配置的MaxQueryConnectionsLimit連接數(shù)來進行限制,比如MaxQueryConnectionsLimit=2,并且本次查詢涉及到一個庫3張表,因為程序只允許單次查詢能并發(fā)2個鏈接,所以本次查詢會被分成2組每組兩個,其中第二組只有一個,在這種情況下第一次并發(fā)查詢2條語句因為采用連接數(shù)嚴格所以不會一直持有鏈接,會將鏈接結(jié)果進行每組進行合并然后將連接放回,合并時還是采用的流式聚合,會首先將第一組的兩個鏈接進行查詢之后將需要的結(jié)果通過流式聚合取到內(nèi)存,然后第二組會自行獨立查詢并且從第二次開始后會將上一次迭代的內(nèi)存聚合數(shù)據(jù)進行和本次查詢的流式聚合分別一起聚合,保證在分頁情況下內(nèi)存數(shù)據(jù)量最少。因為如果每組都是用獨立的內(nèi)存聚合那么你有n組就會有n*(skip+take)的數(shù)目,而ShardingSphere采用的是更加簡單的做法,就是將每組下面的各自節(jié)點都自行進行內(nèi)存聚合,那么如果在skip(10).take(10)的情況下sql會被改寫成各組的各個節(jié)點分別進行skip(0).take(20)的操作那么2組執(zhí)行器的第一組將會有40條數(shù)據(jù)第二組將會有20條數(shù)據(jù)一共會有60條數(shù)據(jù)遠遠操作了我們所需要的20條。所以在這個情況下ShardingCore第一組內(nèi)存流式聚合會返回20條數(shù)據(jù),第二組會將第一組的20條數(shù)據(jù)和第二組的進行流式聚合內(nèi)存中還是只有20條數(shù)據(jù),雖然是連接數(shù)嚴格但是也做到了最小化內(nèi)存單元。當用戶手動選擇CONNECTION_STRICTLY后MaxQueryConnectionsLimit將是正則的最小化鏈接數(shù)限制
SYSTEM_AUTO
SYSTEM_AUTO系統(tǒng)自行選擇,這是一個非常幫的選擇,因為在這個選擇下系統(tǒng)會自動根據(jù)用戶配置的MaxQueryConnectionsLimit來自行控制是采用流式聚合還是內(nèi)存聚合,并且因為我們采用的是同數(shù)據(jù)庫下面最小化內(nèi)存相比其他的解決方案可以更加有效和高性能的來應(yīng)對各種查詢。僅僅只需要配置一個最大連接數(shù)限制既可以適配好連接模式。
這邊極力推薦大家在不清楚應(yīng)該用什么模式的時候使用SYSTEM_AUTO并且手動配置MaxQueryConnectionsLimit來確定各個環(huán)境下的配置一直而不是采用默認的cpu線程數(shù)。
首先我們通過每個數(shù)據(jù)庫被路由到了多少張表進行計算期望用戶在配置了xx后應(yīng)該的并行數(shù)來進行分組,sqlCount :表示這個數(shù)據(jù)庫被路由到的表數(shù)目,exceptCount :表示計算出來的應(yīng)該的單次查詢并行數(shù)
//代碼本質(zhì)就是向上取整int exceptCount =Math.Max(0 == sqlCount % maxQueryConnectionsLimit? sqlCount / maxQueryConnectionsLimit: sqlCount / maxQueryConnectionsLimit + 1, 1);第二次我們通過判斷sqlCount和maxQueryConnectionsLimit的大小來確定鏈接模式的選擇
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的流程圖我們可以看到在獲取普通數(shù)據(jù)的時候是沒有什么問題的,但是如果遇到分頁也就是
select * from order limit 10,10這種情況下會被改寫成
select * from order limit 0,20我們可以看到如果是ShardingSphere的流程模式那么在各個節(jié)點處雖然已經(jīng)將連接數(shù)控制好了但是對于每個節(jié)點而言都有著20條數(shù)據(jù),這種情況下其實是一種非常危險的,因為一旦節(jié)點過多并且limit的跳過頁數(shù)過多每個節(jié)點儲存的數(shù)據(jù)將會非常恐怖。
所以針對這種情況ShardingCore將同庫下的各個節(jié)點組的查詢使用StreamMerge而不是MemoryMerge,并且會對各個節(jié)點間建立聯(lián)系進行聚合保證在同一個數(shù)據(jù)庫下只會有20條數(shù)據(jù)被加載到內(nèi)存中,大大降低了內(nèi)存的使用,提高了內(nèi)存使用率。
當然具體情況應(yīng)該還需要再次進行優(yōu)化并不是簡單的一次優(yōu)化就搞定的比如當跳過的頁數(shù)過多之后其實在內(nèi)存中的一部分數(shù)據(jù)也會再次進行迭代和新的迭代器比較,這個中間的性能差距可能需要不斷地嘗試才可以獲取一個比較可靠的值
總結(jié)
目前已經(jīng)有很多小伙伴已經(jīng)在使用SharidingCore了并且在使用的時候也是相對比較簡單的配置既可以“完美”目前她在使用的各種框架譬如:AbpVNext....基本上在繼承和使用方面可以說是目前efcore生態(tài)下最最最完美的了真正做到了三零的框架:零依賴,零學(xué)習(xí)成本,零業(yè)務(wù)代碼入侵
最后放一張圖
是我這邊給ShardingSphere提的建議,也證實了我對該聚合模型的優(yōu)化是可以有效解決在分頁下面聚合各數(shù)據(jù)庫節(jié)點下的內(nèi)存使用情況
分表分庫組件求贊求star
您的支持是開源作者能堅持下去的最大動力
Github?ShardingCore
Gitee?ShardingCore
ShardingCore?一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學(xué)習(xí)成本、零業(yè)務(wù)代碼入侵。
Github Source Code?助力dotnet 生態(tài)?Gitee Source Code
介紹
在分表分庫領(lǐng)域java有著很多的解決方案,尤其是客戶端解決方案(ShardingSphere),因為客戶端解決方案有著極高的性能,但是缺點也很明顯數(shù)據(jù)庫鏈接的消耗相對較高,使用語言的限制讓我們.Net望而卻步,但是哪怕是有著這些缺點其實也不足以掩蓋客戶端分表分庫帶來的便捷與高效。
目前本人所開發(fā)的ShardingCore?是.Net下基于efcore2+的所有版本的分表分庫很多都是借鑒了ShardingSphere,并且對其很多缺點進行了彌補。這邊可能有人就要說了,你為什么做個efcore的不做個ado.net的呢,說實話我這邊確實有一個ado.net版本的分表分庫,你可以理解為ShardingSphere的.Net復(fù)刻版本sharding-conector?最最最初版本的分表聚合已經(jīng)實現(xiàn)底層原理和ShardingSphere一致使用的Antlr4的分詞。為什么不對這個版本進行推進轉(zhuǎn)而對efcore的sharding-core版本進行升級維護呢,這邊主要有兩點,第一點如果我是在ado.net上進行的推進那么勢必可以支持更多的orm框架,但是orm框架下的很多特性將可能無法使用,并且需要維護各個數(shù)據(jù)庫版本之間的差異。比如efcore下的批量操作等一些列優(yōu)化語法是很難被支持的。第二點針對某個orm的擴展性能和使用體驗上遠遠可以大于通用性組件。這就是我為什么針對ShardingCore進行有段優(yōu)化和升級的原因。
性能
其實性能一直是大家關(guān)注的一個點,我用了ShardingCore那么針對特定的查詢他的損耗是多少是一個比較令人關(guān)注的話題。接下來我放出之前做的兩次性能比較,當然這兩次比較并不是特意準備的,是我邊開發(fā)邊跑的一個是sqlserver 一個是mysql
性能測試
以下所有數(shù)據(jù)均在開啟了表達式編譯緩存的情況下測試,并且電腦處于長時間未關(guān)機并且開著很多vs和idea的情況下僅供參考,所有測試都是基于ShardingCore x.3.1.63+ version
以下所有數(shù)據(jù)均在源碼中有案例
efcore版本均為6.0 表結(jié)構(gòu)為string型id的訂單取模分成5張表
N代表執(zhí)行次數(shù)
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
| 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
| 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前兩次結(jié)果來計算得出結(jié)論單次查詢的的損耗為0.2-0.3毫秒之間,通過數(shù)據(jù)聚合和數(shù)據(jù)路由的損耗單次在0.3ms-0.4ms,其中創(chuàng)建dbcontext為0.1毫秒目前沒有好的優(yōu)化方案,0.013毫秒左右是路由表達式解析和編譯,復(fù)雜表達式可能更加耗時,剩下的0.2毫秒為數(shù)據(jù)源和表后綴的解析等操作包括實例的反射創(chuàng)建和數(shù)據(jù)的聚合,
sqlserver的各項數(shù)據(jù)在分表和未分表的情況下都幾乎差不多可以得出在770w數(shù)據(jù)集情況下數(shù)據(jù)庫還并未是數(shù)據(jù)瓶頸的關(guān)鍵,但是mysql可以看到在分表和未分表的情況下如果涉及到?jīng)]有索引的全表掃描那么性能的差距將是分表后的表數(shù)目之多,測試中為5-6倍,也就是分表數(shù)目
如果你可以接受單次查詢的損耗在0.2ms-0.3ms的那相信這款框架將會是efcore下非常完美的一款分表分庫組件
鏈接模式
說了這么多這邊需要針對ShardingCore在查詢下面涉及到N表查詢后帶來的鏈接消耗是一個不容小覷的客觀因素。所以這邊參考ShardingSphere進行了類似原理的實現(xiàn)。就是如果查詢涉及不同庫那么直接并發(fā),如果是同庫的將根據(jù)用戶配置的單次最大鏈接進行串行查詢,并且動態(tài)選擇使用流式聚合和內(nèi)存聚合。
首先我們看下ShardingSphere的鏈接模式在限制鏈接數(shù)的情況下是如何進行處理的
針對不同的數(shù)據(jù)庫采用并行執(zhí)行,針對同一個數(shù)據(jù)庫根據(jù)用戶配置的最大連接數(shù)進行分庫串行執(zhí)行,并且因為需要控制鏈接數(shù)所以會將結(jié)果集保存在內(nèi)存中,最后通過合并返回給客戶端數(shù)據(jù)。
之后我們會講這個模式的缺點并且ShardingCore是如何進行優(yōu)化的
你可能已經(jīng)蒙了這么多名稱完全沒有一個概念。接下來我將一一進行講解,首先我們來看下鏈接模式下有哪些參數(shù)
MaxQueryConnectionsLimit
最大并發(fā)鏈接數(shù),就是表示單次查詢sharding-core允許使用的dbconnection,默認會加上1就是說如果你配置了MaxQueryConnectionsLimit=10那么實際sharding-core會在同一次查詢中開啟11條鏈接最多,為什么是11不是10因為sharding-core會默認開啟一個鏈接用來進行空dbconnection的使用。如果不設(shè)置本參數(shù)那么默認是cpu線程數(shù)Environment.ProcessorCount
ConnectionMode
鏈接模式,可以由用戶自行指定,使用內(nèi)存限制,和連接數(shù)限制或者系統(tǒng)自行選擇最優(yōu)
鏈接模式,有三個可選項,分別是:
MEMORY_STRICTLY
內(nèi)存限制模式最小化內(nèi)存聚合 流式聚合 同時會有多個鏈接
MEMORY_STRICTLY的意思是最小化內(nèi)存使用率,就是非一次性獲取所有數(shù)據(jù)然后采用流式聚合
CONNECTION_STRICTLY
連接數(shù)限制模式最小化并發(fā)連接數(shù) 內(nèi)存聚合 連接數(shù)會有限制
CONNECTION_STRICTLY的意思是最小化連接并發(fā)數(shù),就是單次查詢并發(fā)連接數(shù)為設(shè)置的連接數(shù)MaxQueryConnectionsLimit。因為有限制,所以無法一直掛起多個連接,數(shù)據(jù)的合并為內(nèi)存聚合采用最小化內(nèi)存方式進行優(yōu)化,而不是無腦使用內(nèi)存聚合
SYSTEM_AUTO
系統(tǒng)自動選擇內(nèi)存還是流式聚合
系統(tǒng)自行選擇會根據(jù)用戶的配置采取最小化連接數(shù),但是如果遇到分頁則會根據(jù)分頁策略采取內(nèi)存限制,因為skip過大會導(dǎo)致內(nèi)存爆炸
解釋
MEMORY_STRICTLY
MEMORY_STRICTLY內(nèi)存嚴格模式,用戶使用本屬性后將會嚴格控制查詢的聚合方式,將會采用流式聚合的迭代器模式,而不是一次性全部去除相關(guān)數(shù)據(jù)在內(nèi)存中排序獲取,通過用戶配置的MaxQueryConnectionsLimit連接數(shù)來進行限制,比如MaxQueryConnectionsLimit=2,并且本次查詢涉及到一個庫3張表,因為程序只允許單次查詢能并發(fā)2個鏈接,所以本次查詢會被分成2組每組兩個,其中第二組只有一個,在這種情況下第一次并發(fā)查詢2條語句因為采用內(nèi)存嚴格所以不會將數(shù)據(jù)獲取到內(nèi)存,第二次在進行一次查詢并將迭代器返回一共組合成3個迭代器后續(xù)通過流式聚合+優(yōu)先級隊列進行返回所要的數(shù)據(jù),在這種情況下程序的內(nèi)存是最少的但是消耗的鏈接也是最大的。當用戶手動選擇MEMORY_STRICTLY后MaxQueryConnectionsLimit將變成并行數(shù)目. 該模式下ShardingCore和ShardingSphere的處理方式類似基本一致
CONNECTION_STRICTLY
CONNECTION_STRICTLY連接數(shù)嚴格模式,用戶使用本屬性后將會嚴格控制查詢后的同一個數(shù)據(jù)庫下的同時查詢的鏈接數(shù),不會因為使用流式內(nèi)存而導(dǎo)致迭代器一致開著,因為一個迭代器查詢開著就意味著需要一個鏈接,如果查詢需要聚合3張表那么就需要同時開著三個鏈接來迭代保證流式聚合。通過用戶配置的MaxQueryConnectionsLimit連接數(shù)來進行限制,比如MaxQueryConnectionsLimit=2,并且本次查詢涉及到一個庫3張表,因為程序只允許單次查詢能并發(fā)2個鏈接,所以本次查詢會被分成2組每組兩個,其中第二組只有一個,在這種情況下第一次并發(fā)查詢2條語句因為采用連接數(shù)嚴格所以不會一直持有鏈接,會將鏈接結(jié)果進行每組進行合并然后將連接放回,合并時還是采用的流式聚合,會首先將第一組的兩個鏈接進行查詢之后將需要的結(jié)果通過流式聚合取到內(nèi)存,然后第二組會自行獨立查詢并且從第二次開始后會將上一次迭代的內(nèi)存聚合數(shù)據(jù)進行和本次查詢的流式聚合分別一起聚合,保證在分頁情況下內(nèi)存數(shù)據(jù)量最少。因為如果每組都是用獨立的內(nèi)存聚合那么你有n組就會有n*(skip+take)的數(shù)目,而ShardingSphere采用的是更加簡單的做法,就是將每組下面的各自節(jié)點都自行進行內(nèi)存聚合,那么如果在skip(10).take(10)的情況下sql會被改寫成各組的各個節(jié)點分別進行skip(0).take(20)的操作那么2組執(zhí)行器的第一組將會有40條數(shù)據(jù)第二組將會有20條數(shù)據(jù)一共會有60條數(shù)據(jù)遠遠操作了我們所需要的20條。所以在這個情況下ShardingCore第一組內(nèi)存流式聚合會返回20條數(shù)據(jù),第二組會將第一組的20條數(shù)據(jù)和第二組的進行流式聚合內(nèi)存中還是只有20條數(shù)據(jù),雖然是連接數(shù)嚴格但是也做到了最小化內(nèi)存單元。當用戶手動選擇CONNECTION_STRICTLY后MaxQueryConnectionsLimit將是正則的最小化鏈接數(shù)限制
SYSTEM_AUTO
SYSTEM_AUTO系統(tǒng)自行選擇,這是一個非常幫的選擇,因為在這個選擇下系統(tǒng)會自動根據(jù)用戶配置的MaxQueryConnectionsLimit來自行控制是采用流式聚合還是內(nèi)存聚合,并且因為我們采用的是同數(shù)據(jù)庫下面最小化內(nèi)存相比其他的解決方案可以更加有效和高性能的來應(yīng)對各種查詢。僅僅只需要配置一個最大連接數(shù)限制既可以適配好連接模式。
這邊極力推薦大家在不清楚應(yīng)該用什么模式的時候使用SYSTEM_AUTO并且手動配置MaxQueryConnectionsLimit來確定各個環(huán)境下的配置一直而不是采用默認的cpu線程數(shù)。
首先我們通過每個數(shù)據(jù)庫被路由到了多少張表進行計算期望用戶在配置了xx后應(yīng)該的并行數(shù)來進行分組,sqlCount :表示這個數(shù)據(jù)庫被路由到的表數(shù)目,exceptCount :表示計算出來的應(yīng)該的單次查詢并行數(shù)
//代碼本質(zhì)就是向上取整int exceptCount =Math.Max(0 == sqlCount % maxQueryConnectionsLimit? sqlCount / maxQueryConnectionsLimit: sqlCount / maxQueryConnectionsLimit + 1, 1);第二次我們通過判斷sqlCount和maxQueryConnectionsLimit的大小來確定鏈接模式的選擇
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的流程圖我們可以看到在獲取普通數(shù)據(jù)的時候是沒有什么問題的,但是如果遇到分頁也就是
select * from order limit 10,10這種情況下會被改寫成
select * from order limit 0,20我們可以看到如果是ShardingSphere的流程模式那么在各個節(jié)點處雖然已經(jīng)將連接數(shù)控制好了但是對于每個節(jié)點而言都有著20條數(shù)據(jù),這種情況下其實是一種非常危險的,因為一旦節(jié)點過多并且limit的跳過頁數(shù)過多每個節(jié)點儲存的數(shù)據(jù)將會非常恐怖。
所以針對這種情況ShardingCore將同庫下的各個節(jié)點組的查詢使用StreamMerge而不是MemoryMerge,并且會對各個節(jié)點間建立聯(lián)系進行聚合保證在同一個數(shù)據(jù)庫下只會有20條數(shù)據(jù)被加載到內(nèi)存中,大大降低了內(nèi)存的使用,提高了內(nèi)存使用率。
當然具體情況應(yīng)該還需要再次進行優(yōu)化并不是簡單的一次優(yōu)化就搞定的比如當跳過的頁數(shù)過多之后其實在內(nèi)存中的一部分數(shù)據(jù)也會再次進行迭代和新的迭代器比較,這個中間的性能差距可能需要不斷地嘗試才可以獲取一個比較可靠的值
總結(jié)
目前已經(jīng)有很多小伙伴已經(jīng)在使用SharidingCore了并且在使用的時候也是相對比較簡單的配置既可以“完美”目前她在使用的各種框架譬如:AbpVNext....基本上在繼承和使用方面可以說是目前efcore生態(tài)下最最最完美的了真正做到了三零的框架:零依賴,零學(xué)習(xí)成本,零業(yè)務(wù)代碼入侵
最后放一張圖
是我這邊給ShardingSphere提的建議,也證實了我對該聚合模型的優(yōu)化是可以有效解決在分頁下面聚合各數(shù)據(jù)庫節(jié)點下的內(nèi)存使用情況
分表分庫組件求贊求star
您的支持是開源作者能堅持下去的最大動力
Github?ShardingCore
Gitee?ShardingCoreShardingCore?一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學(xué)習(xí)成本、零業(yè)務(wù)代碼入侵。
Github Source Code?助力dotnet 生態(tài)?Gitee Source Code
介紹
在分表分庫領(lǐng)域java有著很多的解決方案,尤其是客戶端解決方案(ShardingSphere),因為客戶端解決方案有著極高的性能,但是缺點也很明顯數(shù)據(jù)庫鏈接的消耗相對較高,使用語言的限制讓我們.Net望而卻步,但是哪怕是有著這些缺點其實也不足以掩蓋客戶端分表分庫帶來的便捷與高效。
目前本人所開發(fā)的ShardingCore?是.Net下基于efcore2+的所有版本的分表分庫很多都是借鑒了ShardingSphere,并且對其很多缺點進行了彌補。這邊可能有人就要說了,你為什么做個efcore的不做個ado.net的呢,說實話我這邊確實有一個ado.net版本的分表分庫,你可以理解為ShardingSphere的.Net復(fù)刻版本sharding-conector?最最最初版本的分表聚合已經(jīng)實現(xiàn)底層原理和ShardingSphere一致使用的Antlr4的分詞。為什么不對這個版本進行推進轉(zhuǎn)而對efcore的sharding-core版本進行升級維護呢,這邊主要有兩點,第一點如果我是在ado.net上進行的推進那么勢必可以支持更多的orm框架,但是orm框架下的很多特性將可能無法使用,并且需要維護各個數(shù)據(jù)庫版本之間的差異。比如efcore下的批量操作等一些列優(yōu)化語法是很難被支持的。第二點針對某個orm的擴展性能和使用體驗上遠遠可以大于通用性組件。這就是我為什么針對ShardingCore進行有段優(yōu)化和升級的原因。性能
其實性能一直是大家關(guān)注的一個點,我用了ShardingCore那么針對特定的查詢他的損耗是多少是一個比較令人關(guān)注的話題。接下來我放出之前做的兩次性能比較,當然這兩次比較并不是特意準備的,是我邊開發(fā)邊跑的一個是sqlserver 一個是mysql
性能測試
以下所有數(shù)據(jù)均在開啟了表達式編譯緩存的情況下測試,并且電腦處于長時間未關(guān)機并且開著很多vs和idea的情況下僅供參考,所有測試都是基于ShardingCore x.3.1.63+ version
以下所有數(shù)據(jù)均在源碼中有案例
efcore版本均為6.0 表結(jié)構(gòu)為string型id的訂單取模分成5張表
N代表執(zhí)行次數(shù)
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 RyuJITMethodNMeanErrorStdDevMedian 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 RyuJITMethodNMeanErrorStdDevMedian 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前兩次結(jié)果來計算得出結(jié)論單次查詢的的損耗為0.2-0.3毫秒之間,通過數(shù)據(jù)聚合和數(shù)據(jù)路由的損耗單次在0.3ms-0.4ms,其中創(chuàng)建dbcontext為0.1毫秒目前沒有好的優(yōu)化方案,0.013毫秒左右是路由表達式解析和編譯,復(fù)雜表達式可能更加耗時,剩下的0.2毫秒為數(shù)據(jù)源和表后綴的解析等操作包括實例的反射創(chuàng)建和數(shù)據(jù)的聚合,
sqlserver的各項數(shù)據(jù)在分表和未分表的情況下都幾乎差不多可以得出在770w數(shù)據(jù)集情況下數(shù)據(jù)庫還并未是數(shù)據(jù)瓶頸的關(guān)鍵,但是mysql可以看到在分表和未分表的情況下如果涉及到?jīng)]有索引的全表掃描那么性能的差距將是分表后的表數(shù)目之多,測試中為5-6倍,也就是分表數(shù)目如果你可以接受單次查詢的損耗在0.2ms-0.3ms的那相信這款框架將會是efcore下非常完美的一款分表分庫組件
鏈接模式
說了這么多這邊需要針對ShardingCore在查詢下面涉及到N表查詢后帶來的鏈接消耗是一個不容小覷的客觀因素。所以這邊參考ShardingSphere進行了類似原理的實現(xiàn)。就是如果查詢涉及不同庫那么直接并發(fā),如果是同庫的將根據(jù)用戶配置的單次最大鏈接進行串行查詢,并且動態(tài)選擇使用流式聚合和內(nèi)存聚合。
首先我們看下ShardingSphere的鏈接模式在限制鏈接數(shù)的情況下是如何進行處理的
針對不同的數(shù)據(jù)庫采用并行執(zhí)行,針對同一個數(shù)據(jù)庫根據(jù)用戶配置的最大連接數(shù)進行分庫串行執(zhí)行,并且因為需要控制鏈接數(shù)所以會將結(jié)果集保存在內(nèi)存中,最后通過合并返回給客戶端數(shù)據(jù)。
之后我們會講這個模式的缺點并且ShardingCore是如何進行優(yōu)化的你可能已經(jīng)蒙了這么多名稱完全沒有一個概念。接下來我將一一進行講解,首先我們來看下鏈接模式下有哪些參數(shù)
MaxQueryConnectionsLimit
最大并發(fā)鏈接數(shù),就是表示單次查詢sharding-core允許使用的dbconnection,默認會加上1就是說如果你配置了MaxQueryConnectionsLimit=10那么實際sharding-core會在同一次查詢中開啟11條鏈接最多,為什么是11不是10因為sharding-core會默認開啟一個鏈接用來進行空dbconnection的使用。如果不設(shè)置本參數(shù)那么默認是cpu線程數(shù)Environment.ProcessorCount
ConnectionMode
鏈接模式,可以由用戶自行指定,使用內(nèi)存限制,和連接數(shù)限制或者系統(tǒng)自行選擇最優(yōu)
鏈接模式,有三個可選項,分別是:
MEMORY_STRICTLY
內(nèi)存限制模式最小化內(nèi)存聚合 流式聚合 同時會有多個鏈接
MEMORY_STRICTLY的意思是最小化內(nèi)存使用率,就是非一次性獲取所有數(shù)據(jù)然后采用流式聚合
CONNECTION_STRICTLY
連接數(shù)限制模式最小化并發(fā)連接數(shù) 內(nèi)存聚合 連接數(shù)會有限制
CONNECTION_STRICTLY的意思是最小化連接并發(fā)數(shù),就是單次查詢并發(fā)連接數(shù)為設(shè)置的連接數(shù)MaxQueryConnectionsLimit。因為有限制,所以無法一直掛起多個連接,數(shù)據(jù)的合并為內(nèi)存聚合采用最小化內(nèi)存方式進行優(yōu)化,而不是無腦使用內(nèi)存聚合
SYSTEM_AUTO
系統(tǒng)自動選擇內(nèi)存還是流式聚合
系統(tǒng)自行選擇會根據(jù)用戶的配置采取最小化連接數(shù),但是如果遇到分頁則會根據(jù)分頁策略采取內(nèi)存限制,因為skip過大會導(dǎo)致內(nèi)存爆炸
解釋
MEMORY_STRICTLY
MEMORY_STRICTLY內(nèi)存嚴格模式,用戶使用本屬性后將會嚴格控制查詢的聚合方式,將會采用流式聚合的迭代器模式,而不是一次性全部去除相關(guān)數(shù)據(jù)在內(nèi)存中排序獲取,通過用戶配置的MaxQueryConnectionsLimit連接數(shù)來進行限制,比如MaxQueryConnectionsLimit=2,并且本次查詢涉及到一個庫3張表,因為程序只允許單次查詢能并發(fā)2個鏈接,所以本次查詢會被分成2組每組兩個,其中第二組只有一個,在這種情況下第一次并發(fā)查詢2條語句因為采用內(nèi)存嚴格所以不會將數(shù)據(jù)獲取到內(nèi)存,第二次在進行一次查詢并將迭代器返回一共組合成3個迭代器后續(xù)通過流式聚合+優(yōu)先級隊列進行返回所要的數(shù)據(jù),在這種情況下程序的內(nèi)存是最少的但是消耗的鏈接也是最大的。當用戶手動選擇MEMORY_STRICTLY后MaxQueryConnectionsLimit將變成并行數(shù)目. 該模式下ShardingCore和ShardingSphere的處理方式類似基本一致
CONNECTION_STRICTLY
CONNECTION_STRICTLY連接數(shù)嚴格模式,用戶使用本屬性后將會嚴格控制查詢后的同一個數(shù)據(jù)庫下的同時查詢的鏈接數(shù),不會因為使用流式內(nèi)存而導(dǎo)致迭代器一致開著,因為一個迭代器查詢開著就意味著需要一個鏈接,如果查詢需要聚合3張表那么就需要同時開著三個鏈接來迭代保證流式聚合。通過用戶配置的MaxQueryConnectionsLimit連接數(shù)來進行限制,比如MaxQueryConnectionsLimit=2,并且本次查詢涉及到一個庫3張表,因為程序只允許單次查詢能并發(fā)2個鏈接,所以本次查詢會被分成2組每組兩個,其中第二組只有一個,在這種情況下第一次并發(fā)查詢2條語句因為采用連接數(shù)嚴格所以不會一直持有鏈接,會將鏈接結(jié)果進行每組進行合并然后將連接放回,合并時還是采用的流式聚合,會首先將第一組的兩個鏈接進行查詢之后將需要的結(jié)果通過流式聚合取到內(nèi)存,然后第二組會自行獨立查詢并且從第二次開始后會將上一次迭代的內(nèi)存聚合數(shù)據(jù)進行和本次查詢的流式聚合分別一起聚合,保證在分頁情況下內(nèi)存數(shù)據(jù)量最少。因為如果每組都是用獨立的內(nèi)存聚合那么你有n組就會有n*(skip+take)的數(shù)目,而ShardingSphere采用的是更加簡單的做法,就是將每組下面的各自節(jié)點都自行進行內(nèi)存聚合,那么如果在skip(10).take(10)的情況下sql會被改寫成各組的各個節(jié)點分別進行skip(0).take(20)的操作那么2組執(zhí)行器的第一組將會有40條數(shù)據(jù)第二組將會有20條數(shù)據(jù)一共會有60條數(shù)據(jù)遠遠操作了我們所需要的20條。所以在這個情況下ShardingCore第一組內(nèi)存流式聚合會返回20條數(shù)據(jù),第二組會將第一組的20條數(shù)據(jù)和第二組的進行流式聚合內(nèi)存中還是只有20條數(shù)據(jù),雖然是連接數(shù)嚴格但是也做到了最小化內(nèi)存單元。當用戶手動選擇CONNECTION_STRICTLY后MaxQueryConnectionsLimit將是正則的最小化鏈接數(shù)限制
SYSTEM_AUTO
SYSTEM_AUTO系統(tǒng)自行選擇,這是一個非常幫的選擇,因為在這個選擇下系統(tǒng)會自動根據(jù)用戶配置的MaxQueryConnectionsLimit來自行控制是采用流式聚合還是內(nèi)存聚合,并且因為我們采用的是同數(shù)據(jù)庫下面最小化內(nèi)存相比其他的解決方案可以更加有效和高性能的來應(yīng)對各種查詢。僅僅只需要配置一個最大連接數(shù)限制既可以適配好連接模式。
這邊極力推薦大家在不清楚應(yīng)該用什么模式的時候使用SYSTEM_AUTO并且手動配置MaxQueryConnectionsLimit來確定各個環(huán)境下的配置一直而不是采用默認的cpu線程數(shù)。
首先我們通過每個數(shù)據(jù)庫被路由到了多少張表進行計算期望用戶在配置了xx后應(yīng)該的并行數(shù)來進行分組,sqlCount :表示這個數(shù)據(jù)庫被路由到的表數(shù)目,exceptCount :表示計算出來的應(yīng)該的單次查詢并行數(shù)
//代碼本質(zhì)就是向上取整int exceptCount =Math.Max(0 == sqlCount % maxQueryConnectionsLimit? sqlCount / maxQueryConnectionsLimit: sqlCount / maxQueryConnectionsLimit + 1, 1);第二次我們通過判斷sqlCount和maxQueryConnectionsLimit的大小來確定鏈接模式的選擇
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的流程圖我們可以看到在獲取普通數(shù)據(jù)的時候是沒有什么問題的,但是如果遇到分頁也就是
select * from order limit 10,10這種情況下會被改寫成
select * from order limit 0,20我們可以看到如果是ShardingSphere的流程模式那么在各個節(jié)點處雖然已經(jīng)將連接數(shù)控制好了但是對于每個節(jié)點而言都有著20條數(shù)據(jù),這種情況下其實是一種非常危險的,因為一旦節(jié)點過多并且limit的跳過頁數(shù)過多每個節(jié)點儲存的數(shù)據(jù)將會非常恐怖。
所以針對這種情況ShardingCore將同庫下的各個節(jié)點組的查詢使用StreamMerge而不是MemoryMerge,并且會對各個節(jié)點間建立聯(lián)系進行聚合保證在同一個數(shù)據(jù)庫下只會有20條數(shù)據(jù)被加載到內(nèi)存中,大大降低了內(nèi)存的使用,提高了內(nèi)存使用率。
當然具體情況應(yīng)該還需要再次進行優(yōu)化并不是簡單的一次優(yōu)化就搞定的比如當跳過的頁數(shù)過多之后其實在內(nèi)存中的一部分數(shù)據(jù)也會再次進行迭代和新的迭代器比較,這個中間的性能差距可能需要不斷地嘗試才可以獲取一個比較可靠的值
總結(jié)
目前已經(jīng)有很多小伙伴已經(jīng)在使用SharidingCore了并且在使用的時候也是相對比較簡單的配置既可以“完美”目前她在使用的各種框架譬如:AbpVNext....基本上在繼承和使用方面可以說是目前efcore生態(tài)下最最最完美的了真正做到了三零的框架:零依賴,零學(xué)習(xí)成本,零業(yè)務(wù)代碼入侵
最后放一張圖
是我這邊給ShardingSphere提的建議,也證實了我對該聚合模型的優(yōu)化是可以有效解決在分頁下面聚合各數(shù)據(jù)庫節(jié)點下的內(nèi)存使用情況
分表分庫組件求贊求star
您的支持是開源作者能堅持下去的最大動力
Github?ShardingCore:?https://github.com/xuejmnet/sharding-core
Gitee?ShardingCore:?https://gitee.com/dotnetchina/sharding-core?
總結(jié)
以上是生活随笔為你收集整理的.Net 下高性能分表分库组件-连ShardingCore接模式原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 福利好礼现金大奖等你来→首届 .NET
- 下一篇: .NET 6 中 gRPC 的新功能