CYQ.Data V5 分散式自動化緩存設計介紹

来源:http://www.cnblogs.com/cyq1162/archive/2016/07/13/5659077.html
-Advertisement-
Play Games

其實完成這個功能之前,我就在思考:是先把想法寫了來,和大伙討論討論後再實現,還是實現後再寫文論述自己的思維。忽然腦後傳來一個聲音說:你發文後會進入發呆階段。所以還是靜下心,讓我輕輕地把代碼擼完再說。最近這幾天,自己在大腦里演練過各種技術難點,解決方案,推敲了各種該解決的問題,覺的差不多了,才決定擼碼... ...


前方:

其實完成這個功能之前,我就在思考:是先把想法寫了來,和大伙討論討論後再實現,還是實現後再寫文論述自己的思維。

忽然腦後傳來一個聲音說:你發文後會進入發呆階段。

所以還是靜下心,讓我輕輕地把代碼擼完再說。

最近這幾天,自己在大腦里演練過各種技術難點,解決方案,推敲了各種該解決的問題,覺的差不多了,才決定擼碼。

忽然發覺,原來代碼是可以寫在大腦里的。

要是你看到一個員工坐著2天沒寫一行代碼,說明人家是高手,正在大腦編程。

好,不扯,回正文!

傳統ORM的二級緩存為何失效?

有些ORM會提供:如Hibernate。

有些不提供:如EF,不提供是因為知道提供了也沒啥鳥用,因為:

1:你不能強迫一個項目全部用單實體編程,多表時,用戶更偏向於執行SQL語句。

2:沒有分散式緩存做為基礎,解決不了多應用程式部署的緩存策略問題。

因此:

1:若控制不了整個項目用戶的SQL語句,單機的搞不了。

2:沒有分散式緩存做基礎,分散式的都搞不了。

這也是為啥EF一直不提供,是因為看到Hibernate雖然提供但並沒多大卵用的原因吧!

 

疑惑資料庫已有緩存,為何框架還要造孽?

主要原因:

1:資料庫從請求到建立緩存,需要時間(框架緩存可以減緩資料庫緩存失效時壓力)

2:資料庫是有鏈接數限制的,不可能允許大量併發的直連,需要外界分壓。

3:資料庫的緩存是單機性。

4:資料庫發數據往伺服器的時間比本機緩存的長。

自動緩存設計前的一些思考:

1:一開始我思考的緩存策略,是細化到行或列,於是瞭解資料庫自身緩存後發現資料庫目前也只是做了以表為單位。

2:MSSQL是有提供SqlDependency的緩存依賴項的,它可以從資料庫層面通知你的數據何時失效。

3:但是SqlDependency和SqlCommand依賴太深,無法在所有資料庫層面通用。

4:SqlDependency的緩存依賴只能在本地緩存。

5:其它資料庫不支持依賴通知。

6:所以方案只能通過全局執行與分析,來處理緩存及失效策略。

7:單機時:全局攔截分析,如何分析出表?

8:應用分散式時:緩存及時失效?

9:用戶直接修改資料庫時:緩存如何失效?

還有好多好多問題,一直在思考......

緩存什麼?

1:緩存單個對象時,是直接存檔對象的,Cache返回時會根據本地或是遠程選擇是否Clone返回。。

2:緩存列表時:只存檔欄位類型僅包含:(數字、布爾、字元、時間、GUID)的欄位,並轉成Json字元串存檔。

技術細節:

  A:一個對象存檔在本機時,存檔的是引用(可能出現誤寫操作);存檔在分散式時,存檔的不是引用,這會在使用時出現不確定性。

  B:大對象的存檔,在緩存來去間需要序列化和反序列化,性能上降低很多。

因此:將列表轉成Json存檔,拿到時再還原,可以同時解決A和B的問題。

 

簡單的說,如果對象有長欄位,或者有二進位數據,是不會被緩存的,所以MSSQL的Timestamp欄位就不要用了;

如果要用:AppConfig.DB.HiddenFields="欄位名",把它隱藏了也行。

 

緩存時間?

考慮到通常訪問量低時都是在中午和晚上的時間,因此,將緩存的對象的時間隨機分佈(早上的分佈在中午失效,下午的分佈在晚上失效)

考慮到分頁時的查詢,通常都關註前面幾頁,因此前面幾頁的數據,時間如上的時間段分佈。

分頁後面的數據,只預設2分鐘的緩存時間。

其它規則有待討論......

 

緩存多大?

1:在單機狀態,檢測到記憶體的可用比例低於15%時,則不再接受緩存。

2:在分散式緩存狀態,暫時有多少扔多少。

緩存如何失效:

1:攔截請求:(包括(MAction)增刪改查+(MProc)執行自定義語句+(MDataTable)批量方法)

2:分析語句的關聯表(單表的可以拿表名,視圖的拿關聯結構涉及的表名,存儲過程(目前沒法),自定義SQL(語句分析出表名),批量(直接拿表名)

3:技術難點:如何從未知的SQL或視圖中準確的分析出所有關聯的表。

4:緩存失效:執行以下方法應該失效:增刪改,執行ExeNonQuery,批量語句。

5:技術難點:

  1:對於視圖(關聯了多個表,如何根據一個表名,關聯到相應涉及的視圖語句失效?)

  2:對於分散式的應用,A服務更新,如何B伺服器也失效。

 

如何處理修改頻繁的表:

1:一開始想增加配置,讓用戶設置不參與緩存的表,認真思考後,發現根據緩存失效的時間和次數,可自動分析判斷一個表是否修改頻繁。

2:表操作相關增刪查時,該表被置為失效(相關緩存會被移除),此時設置好時間間隔(6秒),在此時間段對該表相關的不緩存,同時提交的緩存刪命令也可以無視。

3:對被分析出為修改頻繁的表該如何處理?延長相應的不緩存時間,或是??還需要思考!!!

 

緩存失效的粒度能不能小?

1:目前的失效,和資料庫一樣,是以表為單位的。

2:對於插入操作,不會影響某一條數據的讀取(所以單條數據的查詢,是不應該受到插入操作的影響的)

3:還有其它情形是必然不會影響的?

 

框架有自動緩存,業務需不需要緩存?

1:資料庫有自動緩存,框架的也可以自動緩存。

2:框架有自動緩存,同理業務也可以有緩存。

 

框架能處理的粒度是有限的,不能細到具體的行或列的緩存級別,因此在業務複雜和併發到一定量後,業務緩存是必要的。

 

資料庫有緩存,業務也可做緩存,為何還思考往框架增加自動緩存?

1:資料庫的預設緩存是固定的,需要配置,各種資料庫環境不一致。

2:資料庫鏈接池預設是固定的。

3:業務加緩存的事,往往是後期的動作。

現另一個現狀是:

1:.NET 群體,存在很多初中級的開發人員,這部分人員的技術成長相對較慢,對緩存或性能調優並不熟。

2:國內有很多的中小網站,預設都抗不起併發,攻擊成本很小,幾百上千個併發就可以掛你站了。

因此,既然有現實的問題,就可以有對應的解決方案。

V5框架此功能的出現,就是為了從基礎層面統一解決這些問題。

只有當.NET行業不在有慢網站的存在,整體提升檔次了,有良好的口碑,才會引進更多的BOSS選用 .NET,大伙所期待的.NET春天也就近了

 

V5的目前解決的問題:

總體而言,要實現這個功能,核心要解決以下問題:

下麵我來將技術一點一點出賣:

5:AOP攔截問題:

首先,要實現這功能,就得全局攔截,掃蕩過源碼或用過V5的同學,聽說過框架本身就有AOP的吧;

其次,得改造這個AOP:框架預設有一個空AOP,當外部有AOP裝載的時候,會替換掉這個空AOP。

要實現這個自動緩存:本想在空AOP里實現,放著浪費,但若用戶自定義的Aop被裝載,又會被替換掉,走不通...

方案想了三四個,思考了三四夜,最後還是在擼碼時才確定了現在的模式(這個告訴我們,想的差不多了就該擼碼了,要100%想通再擼不太靠譜):

於是,我這樣做了:

原有的Aop,改名成InterAop,不過是掛名的,因為它沒有繼承IAOP介面,而且從原本的單例變更成多例模式。

這裡可以貼兩行代碼,意思是:在Bengin和End方法調用了外部AOP的介面,並根據外部AOP的狀態決定後續的執行流程:

完整的源碼你們自己SVN了:https://github.com/cyq1162/cyqdata.git

 public AopResult Begin(AopEnum action)
        {
            AopResult ar = AopResult.Continue;
            if (outerAop != null)
            {
                ar = outerAop.Begin(action, Para);
                if (ar == AopResult.Return)
                {
                    return ar;
                }
            }
            if (AppConfig.Cache.IsAutoCache && !IsTxtDataBase) // 只要不是直接返回
            {
                isHasCache = AutoCache.GetCache(action, Para); //找看有沒有Cache
            }
            if (isHasCache)  //找到Cache
            {
                if (outerAop == null || ar == AopResult.Default)//不執行End
                {
                    return AopResult.Return;
                }
                return AopResult.Break;//外部Aop說:還需要執行End
            }
            else // 沒有Cache,預設返回
            {
                return ar;
            }
        }

        public void End(AopEnum action)
        {
            if (outerAop != null)
            {
                outerAop.End(action, Para);
            }
            if (!isHasCache && !IsTxtDataBase)
            {
                AutoCache.SetCache(action, Para); //找看有沒有Cache
            }
        }

代碼最後很少,但沒想出來之前,2天都搞不定。

1:基礎單表、視圖操作

A:單表,這個是最簡單的,傳遞進來的就是表名;

B:視圖,這個麻煩一點,傳遞的是視圖名;

於是,如何從視圖獲取相關參與的表名?你現在應該不知道,我來告訴你吧:

DBDataReader sdr=....
DataTable dt = sdr.GetSchemaTable();

這條語句,可以通殺所有的資料庫,不用去N種資料庫里搜各種元數據藏在哪了!!!

2:多表SQL語句操作:

對於SQL語句,可以用上面的方法,執行一個DataReader再拿,但我弄了一個簡單的方法來找關聯表:

 internal static List<string> GetTableNamesFromSql(string sql)
        {
            List<string> nameList = new List<string>();

            //獲取原始表名
            string[] items = sql.Split(' ');
            if (items.Length == 1) { return nameList; }//單表名
            if (items.Length > 3) // 總是包含空格的select * from xxx
            {
                bool isKeywork = false;
                foreach (string item in items)
                {
                    if (!string.IsNullOrEmpty(item))
                    {
                        string lowerItem = item.ToLower();
                        switch (lowerItem)
                        {
                            case "from":
                            case "update":
                            case "into":
                            case "join":
                            case "table":
                                isKeywork = true;
                                break;
                            default:
                                if (isKeywork)
                                {
                                    if (item[0] == '(' || item.IndexOf('.') > -1) { isKeywork = false; }
                                    else
                                    {
                                        isKeywork = false;
                                        nameList.Add(NotKeyword(item));
                                    }
                                }
                                break;
                        }
                    }
                }
            }
            return nameList;
        }

有可能會找多,找到後,再過濾一下名稱是不是資料庫里的表就可以了。

3:直接操作資料庫

一開始設置的思維,是動態創建一個表,欄位大概是這樣的:

表名    更新時間

然後如果手工操作資料庫,可以手工更改時間,也可以用觸發器引發這裡的更新。

然後後臺線程定時掃這個表,就知道有沒有表被更新了。

不過--------V5目前並木有實現它,只是開放了一個介面,可以讓你在代碼里調用移除緩存。

這個方法就是:

 public abstract partial class CacheManage
    {
        /// <summary>
        /// 獲取系統內部緩存Key
        /// </summary>
        public static string GetKey(CacheKeyType ckt, string tableName)
        {
            return GetKey(ckt, tableName, AppConfig.DB.DefaultDataBase, AppConfig.DB.DefaultDalType);
        }
        /// <summary>
        /// 獲取系統內部緩存Key
        /// </summary>
        public static string GetKey(CacheKeyType ckt, string tableName, string dbName, DalType dalType)
        {
            switch (ckt)
            {
                case CacheKeyType.Schema:
                    return TableSchema.GetSchemaKey(tableName, dbName, dalType);
                case CacheKeyType.AutoCache:
                    return AutoCache.GetBaseKey(dalType, dbName, tableName);
            }
            return string.Empty;
        }
    }

4:跨伺服器操作

這個本來是簡單的,後來又想麻煩了,因為要兼顧性能問題,緩存移除可能會頻繁的問題。

後來,通過增加了緩存類型,來識別本地緩存或分散式緩存,來區別寫代碼:

private static void SetBaseKey(string baseKey, string key)
        {
            //baseKey是表的,不包括視圖和自定義語句
            if (_MemCache.CacheType == CacheType.LocalCache)
            {
                if (cacheKeys.ContainsKey(baseKey))
                {
                    cacheKeys[baseKey] = cacheKeys[baseKey].Append("," + key);
                }
                else
                {
                    cacheKeys.Add(baseKey, new StringBuilder(key));
                }
            }
            else
            {
                StringBuilder sb = _MemCache.Get<StringBuilder>(baseKey);
                if (sb == null)
                {
                    _MemCache.Set(baseKey, new StringBuilder(key));
                }
                else
                {
                    sb.Append("," + key);
                    _MemCache.Set(baseKey, sb);
                }
            }
        }

6:緩存失效問題  

 這個問題,流程本來很簡單的:

但思考到Cache多,而且分散式時,返回會卡,所以刪除Cache操作就變成線程處理了。

後來為了避免線程多開,又把類改成了單例(一開始是多實例的)

現在,又把這線程的線程開啟,放到LocalCache里和另一個線程作伴了,然後這個單例類又變更成了靜態類。

 

V5框架怎麼使用這功能:

升級版本到最新版即可!

 

總結:

1:沒有這個功能之前:框架解決了三大問題:編程架構的統一(自動化)、資料庫壓力(讀寫分離)、伺服器壓力(分散式緩存)。

2:此功能的存在:是針對從基礎層面提升行業項目的整體水平。

3:最近大腦有點發春,一個個創新Idea不斷的從我大腦冒出來,折騰的我好累:

要思考架構、落實框架代碼、要寫文分享,寫框架Demo、群里解答。

4:開源不賺錢,又投入這麼多精力,只能把它當理想了,希望它有天成為.NET項目的標配數據層。

5:我博客是有打贊插件的,哈。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 嘗試在程式去訪問遠程的Web API,它在運行時,出現異常: TypeError: invalid 'in' operand obj TypeError: invalid 'in' operand obj 由於從伺服器返回的數據是json。當我們需要得到這些數據時,還得需要Parse。因此我們把代碼 ...
  • Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:9001/api/size/get. (Reason: CORS header ...
  • 1 WebForm優點 1)支持事件模型開發,得益於豐富的服務端組件,WebForm開發可以迅速的搭建Web應用 2)使用方便,入門容易 3)控制項豐富的WebForm 2 WebForm缺點 1)封裝太強,很多地層東西讓我們初學者不是很明白 2)入門容易,提升很難。 3)複雜的生命周期模型學習起來並 ...
  • 前言,此方法利用反射將DataRow轉成實體,由於反射SetValue據說性能不行,大家就看看就行了吧。 後話, 1.可以通過緩存提高下性能。 每次typeof(T)後,將其對象相關信息(泛型屬性等)存儲起來,下次從緩存讀取。 2.對SetValue改進。 可以使用泛型委托對其賦值。 3.用Emit ...
  • Stopwatch watch = new Stopwatch();//初始化一個對象 watch.Start();//開始計時 //測試代碼 watch.Stop();//停止計時 System.Diagnostics.Debug.WriteLine("耗時:" + watch.Elapsed); ...
  • 摘要 雖然ASP.NET的伺服器控制項一直被大家所詬病,但是用戶控制項(ACSX)在某些場景下還是非常有用的。 在一些極特珠的情況下,我們會使用JavaScript動態的構建頁面中的控制項,但假設遇到了我要用JavaScript構建一個服務端控制項、用戶控制項時,該怎麼辦? 我們常常說,服務端控制項運行在服端器 ...
  • 根據公司目前的業務情況,進行分散式雲平臺基礎服務建設的架構,現狀,取捨,概述以及展望。 包含資料庫中間件,TCP服務框架,認證中心,服務中心,統一監控,配置中心,消息隊列,任務調度平臺,分散式緩存,文件服務,日誌平臺,開發介面平臺,分散式部署平臺,開發Api網關相關內容。 ...
  • 個人網站地址:nee32.com 一、實體框架(EF)簡介 EF框架是一個數據持久層框架,它的全稱是ADO.NET Entity Framework,是微軟開發的基於ADO.NET的ORM(Object Relational Mapping,對象關係映射)框架,常見的數據持久層框架有還有Nhiber ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...