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

前方:

其實完成這個功能以前,我就在思考:是先把想法寫了來,和大夥討論討論後再實現,仍是實現後再寫文論述本身的思惟。git

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

因此仍是靜下心,讓我輕輕地把代碼擼完再說。sql

最近這幾天,本身在大腦裏演練過各類技術難點,解決方案,推敲了各類該解決的問題,覺的差很少了,才決定擼碼。數據庫

突然發覺,原來代碼是能夠寫在大腦裏的。編程

要是你看到一個員工坐着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:我博客是有打贊插件的,哈。

相關文章
相關標籤/搜索