主頁 > .NET開發 > .Net 下高性能分表分庫組件-連接模式原理

.Net 下高性能分表分庫組件-連接模式原理

2021-12-09 06:01:26 .NET開發

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的分詞,為什么不對這個版本進行推進轉而對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將變成并行數目. 該模式下ShardingCoreShardingSphere的處理方式類似基本一致

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生態下最最最完美的了真正做到了三零的框架:零依賴,零學習成本,零業務代碼入侵

最后放一張圖

是我這邊給ShardingSphere提的建議,也證實了我對該聚合模型的優化是可以有效解決在分頁下面聚合各資料庫節點下的記憶體使用情況

分表分庫組件求贊求star

您的支持是開源作者能堅持下去的最大動力

  • Github ShardingCore
  • Gitee ShardingCore

博客

QQ群:771630778

個人QQ:326308290(歡迎技術支持提供您寶貴的意見)

個人郵箱:[email protected]

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/376805.html

標籤:.NET Core

上一篇:堆疊的條形意外地用條形高度的總和進行了注釋

下一篇:重新認識Docker Compose之Sidecar模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more