ShardingCore
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過大會導致記憶體爆炸
解釋
MEMORY_STRICTLY
MEMORY_STRICTLY記憶體嚴格模式,用戶使用本屬性后將會嚴格控制查詢的聚合方式,將會采用流式聚合的迭代器模式,而不是一次性全部去除相關資料在記憶體中排序獲取,通過用戶配置的MaxQueryConnectionsLimit連接數來進行限制,比如MaxQueryConnectionsLimit=2,并且本次查詢涉及到一個庫3張表,因為程式只允許單次查詢能并發2個鏈接,所以本次查詢會被分成2組每組兩個,其中第二組只有一個,在這種情況下第一次并發查詢2條陳述句因為采用記憶體嚴格所以不會將資料獲取到記憶體,第二次在進行一次查詢并將迭代器回傳一共組合成3個迭代器后續通過流式聚合+優先級佇列進行回傳所要的資料,在這種情況下程式的記憶體是最少的但是消耗的鏈接也是最大的,當用戶手動選擇MEMORY_STRICTLY后MaxQueryConnectionsLimit將變成并行數目. 該模式下ShardingCore和ShardingSphere的處理方式類似基本一致

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_STRICTLY后MaxQueryConnectionsLimit將是正則的最小化鏈接數限制

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);
第二次我們通過判斷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的流程圖我們可以看到在獲取普通資料的時候是沒有什么問題的,但是如果遇到分頁也就是
select * from order limit 10,10
這種情況下會被改寫成
select * from order limit 0,20
我們可以看到如果是ShardingSphere的流程模式那么在各個節點處雖然已經將連接數控制好了但是對于每個節點而言都有著20條資料,這種情況下其實是一種非常危險的,因為一旦節點過多并且limit的跳過頁數過多每個節點儲存的資料將會非常恐怖,

所以針對這種情況ShardingCore將同庫下的各個節點組的查詢使用StreamMerge而不是MemoryMerge,并且會對各個節點間建立聯系進行聚合保證在同一個資料庫下只會有20條資料被加載到記憶體中,大大降低了記憶體的使用,提高了記憶體使用率,

當然具體情況應該還需要再次進行優化并不是簡單的一次優化就搞定的比如當跳過的頁數過多之后其實在記憶體中的一部分資料也會再次進行迭代和新的迭代器比較,這個中間的性能差距可能需要不斷地嘗試才可以獲取一個比較可靠的值
總結
目前已經有很多小伙伴已經在使用SharidingCore了并且在使用的時候也是相對比較簡單的配置既可以“完美”目前她在使用的各種框架譬如:AbpVNext....基本上在繼承和使用方面可以說是目前efcore生態下最最最完美的了真正做到了三零的框架:零依賴,零學習成本,零業務代碼入侵
最后放一張圖
是我這邊給ShardingSphere提的建議,也證實了我對該聚合模型的優化是可以有效解決在分頁下面聚合各資料庫節點下的記憶體使用情況

分表分庫組件求贊求star
您的支持是開源作者能堅持下去的最大動力
- Github ShardingCore
- Gitee ShardingCore
博客
QQ群:771630778
個人QQ:326308290(歡迎技術支持提供您寶貴的意見)
個人郵箱:[email protected]
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/376805.html
標籤:.NET Core
