自從CYQ.Data框架出了數據庫讀寫分離、分佈式緩存MemCache、自動緩存等大功能以後,就進入了頻繁的細節打磨優化階段。html
從如下的更新列表就能夠看出來了,3個月更新了100條次功能:git
305:處理視圖名重複時的問題,同時簡化MDataTable的代碼,取消MDataTable的ReadFromDbDataReader(統一用CreateFrom(sdr)方法讀取。(2016-07-16) 306:優化經過Reader獲取列結構(該方法不靠譜,須要從新修正元數據的DataType、Size、Scale、DalType等參數)(2016-07-16) 307:爲MDataTable和MAction的Set方法增長重載Set(key,value,state),在循環賦值時,產生批量更新時,能夠對state賦值2(2016-07-21) 308:增長貼心功能:自定義參數化語句@符號,在各數據庫自動被替換成相應的?或:符號兼容多數據庫。(2016-07-22) 309:加強MDataTable綁定到Winform和WFP的功能(2016-07-23) 310:修正MProc的ExeMDataTableList的自動緩存問題2016-07-23) 311:DBTool的GetMapTable增長對錶名-中劃線符號和空格的兼容處理(2016-07-30) 312:CYQ.Data.ProjectTool 升級版本到V2.0(支持英文環境)(2016-07-30) 313:XHtmlAction大力調整升級(細節改動多)(2016-07-31) 314:MDataRow:SetToAll和MDataTable的Bind功能支持XHtmlAction對象(2016-07-31) 315:Dtd文件變成資源文件合在V5裏,用到時動態自動解壓提高使用體驗(爲減小文件大小,刪註釋扣到我差點眼瞎)(2016-07-31) 316:處理MDataTable的GetChange方法引起的Bug和CreateFrom產生的數據初始狀態置爲1(2016-08-02) 317:XHtmlAction處理對radio標籤的處理。(2016-08-02) 318:XHtmlAction增長html的clearflag標籤【值爲0(清除InnerXml)或1(節點移除)】(用於節點未處理時,處理掉標籤)(2016-08-02) 319:XHtmlAction處理html的img,select,input checkbox等節點的處理。(2016-08-02) 320:XHtmlActon重寫Load方法(優化加載,自動識別,並處理該<轉義的符號)(2016-08-03) 321:AppConfig減小一個Xml相關的配置項(UseFileLoadXml)(2016-08-03) 322:MDataTable修正Select方法(修正爲引用)(2016-08-04) 323:DBTool的GetTables方法增長Lock(2016-08-04) 324:修正失敗時仍緩存的問題(2016-08-08) 325:增長AppConfig.RunPath屬性,獲取框架運行的所在文件夾(2016-08-09) 326:處理配置工具ProjectTool升級(2016-08-09) 327:修正MDataCell對二進制數據二次賦值(2016-08-10) 328:調整MDataRow:CreateFrom(外部數據)的行狀態初始始爲1;LoadFrom(外部數據)的狀態和自身值有關(2016-08-10) 329:修正自動緩存(2016-08-10) 330:Oracle修正第1頁分頁問題【當排序條件爲字符串時】(2016-08-11) 331:AppConfig新增長NoCacheTables屬性,容許指定某些表不容許緩存(自動緩存開啓時)(2016-08-11) 332:XHtmlAction 增長對Xml文檔中&符號的處理(2016-08-15) 333:XHtmlAction 對SetForeachEventHandler事件作優化調整(2016-08-15) 334:MProc的SetCustom方法增長對MSSQL用戶自定義表類型的支持(2016-08-15) 335:StaticTool:提高了ChangeType方法的轉換性能(2016-08-18) 336:MDataTable的ToList<T>方法增長一個判斷條件,預防繼承OrmBase的遠程實體使用Emit(2016-08-18) 337:JsonHelper:優化提高了大數量下的ToString()的性能(2016-08-19) 338:AutoCache:當數據>10萬條時不自動緩存(2016-08-19) 339:MDataRow:修正索引取值(在字段名爲2個符號同時字段數>10時候產生的問題)(2016-08-23) 340:內部SQL語句優化(2016-08-23) 341:MAction:Select方法(優化查詢記錄總數的代碼,利用自動緩存功能,避免分頁時從新計算)(2016-08-24) 342:AppConfig.Cache.IgnoreCacheColumns,能夠指定表的某些列的更新操做時不更新緩存(2016-08-24) 343:JsonHelper增長對數組的檢測支持(2016-08-25) 344:JsonHelper支持對二進制和Base64的轉換(2016-08-26) 345:DBTool.CreateTable或DBTool.DropTable後的緩存處理(2016-08-27)。 346:MAction、MProc取消SetAopOn和SetAopOff方法,統一爲:SetAopState方法(簡化方法,同時能處理更多的狀態,包括關閉自動緩存)(2016-08-27) 347:MDataTable的Select方法加強(對浮點數的比較)(2016-08-27) 348:AutoCache的緩存時間,改爲DefaultCacheTime配置的時間,(用戶能夠本身配置自動緩存時間)(2016-08-27) 349:MProc的ExeMDataTableList方法增長對Oracle的批量語句的支持(2016-08-27) 350:優化Oracle拿表結構的語句(2016-08-27) 351:MDataTable的Merge方法修正(2016-08-27) 352:ThreadBreak的AddGlobalThread增長重載方法(2016-08-27) 353:CacheManage提供PreLoadDBSchemaToCache方法(2016-08-27) 354:JsonSplit處理IsJson判斷問題(2016-08-28) 355:MDataTable AcceptChanges(Update方法,處理當配置了AppConfig.DB.DeleteField時引起的問題)(2016-08-30) 356:DBTool.GetColumns方法處理"\r\nwhere"場景時產生的錯誤(2016-09-02) 357:文本數據庫(NoSqlCommand)增對select a as b 別名的支持(2016-09-02) 358:MAction處理屢次Fill時未清理舊值的問題(2016-09-02) 359:ORM(OrmBase和SimpleOrmBase)增長SetAopState方法(2016-09-02) 360:AutoCache:處理MAction的Fill方法的時的緩存引用(改爲克隆,避免屢次Fill指向同一緩存)(2016-09-02) 361:MDataTable增長Description屬性。(2016-09-03) 362:DBTool的GetColumns增長對錶映射的支持(2016-09-05) 363:修正文本數據庫的ResetTable方法(原表沒有清空)(2016-09-06) 364:改造並去掉內部的MD5(win2008下加密算法默認引起異常)(2016-09-08) 365:去掉映射表的條件限制(支持更多的外部映射)(2016-09-11) 366:修正讀寫分離時,insert into ...select語句處理到分庫的問題(2016-09-12) 367:SqlCreate處理Oracle日期條件的轉換問題。(2016-09-13) 368:SqlCreate增長對GUID類型的檢測(2016-09-20) 369:OrmBase、SimpleOrmBase延遲加載初始化(2016-09-20) 370:MAction在Insert時,對Oracle,Mysql等放置(獲取最大值)事務(2016-09-20) 371:MAction在Insert時的InsertOp默認選項變動爲:ID,原來爲(Fill)(2016-09-20) 372:JsonHelper.ToJson增長對List<MDataTable>和List<DataTable>的支持(2016-09-20) 373:DBBase處理Oracle下返回的DataBase名稱問題。(2016-09-21) 374:Oracle的加載方式進行小細節優化(2016-09-22) 375:StaticTool處理ChangeType中對於Guid的轉換(2016-09-22) 376:SqlCompatible增長對(+ ||)、Left和Right函數的處理(2016-09-24) 377:Oracle的ODP.NET參數添BindByName置爲true(2016-09-24) 378:MDataRowCollection AddNew方法,處理Winform(DataGrid綁定時)在空白行和數據行來回點擊時不斷添加空白數據的問題。(2016-09-29) 379:MAction SetPara增長重載方法(2016-09-29) 380:MAction Update的where條件Error時,RecordsAffected值從原來的0變動爲-2;(2016-09-30) 381:MDataTable 修正批量更新的返回值問題(2016-09-30) 382:MAction 內部增長IsIgnoreDeleteField 內部屬性(2016-09-30) 383:XHtmlBase 修正對Xml的加載(2016-10-08) 384:SqlValue 調整兩個名稱(GUID和ISNULL)的命名(2016-10-08) 385:MDataTable 修正Select條件爲<=的數字判斷問題(2016-10-08) 386:AutoCache(JsonHelper增長Escape屬性、MDataTable增長ToJson重載)不處理\n的轉義替換(2016-10-09) 387:MDataTable ToJson 對於null的數據,默認輸出 xx:null 值(2016-10-09) 388:Oracle(DBTool.GetTables) 增長對視圖的過濾(2016-10-10) 389:JsonHelper 修正實體嵌套的問題、同時增長對數組的支持(2016-10-14) 390:MDataTable AcceptChange 修正無主鍵時的的批量更新(2016-10-14) 391:MDataTable 增長 GetIndex 方法(統計知足條件的行所在的索引)(2016-10-16) 392:NoSqlAction 文本數據庫修正沒法刪除最後1條數據的問題(2016-10-16) 393:MDictionary增長索引取值或賦值。(2016-10-17) 394:XHtmlAction、RSS的OnForeach的參數由Dictionary變爲MDictionary(2016-10-17) 395:JsonHelper 修正對數組的輸出和還原(2016-10-17) 396:JsonHelper 修正Json嵌套問題。(2016-10-18) 397:MDataTable 優化批量更新問題。(2016-10-18) 398:MDataRow和MDataColumn 的ToTable() 調整適應(新增智能提示)(2016-10-19) 399:MySql 處理存儲過程Out值。(2016-10-19) 400:MySql 批量方法解決了Bit類型和空表時自增ID被置爲1的問題(2016-10-20) 401:JsonHelper、NoSqlAction小優化調整(2016-10-20) 402:MDataTable的AcceptChanges新增長Truncate屬性(2016-10-20) 403:JsonHelper的GetJosnValue(json寫錯順理)變動名稱爲GetValue(2016-10-21) 404:NoSqlAction 文本數據庫增強刪除最後一條數據時的併發處理問題(2016-10-23) 405:DBTool.GetColumns修正對於沒有where的group by語句拿表結構的問題(2016-10-24) 406:AppConfig增長SetConn方法(同時增長連接緩存)(2016-10-24) 407:SqlCreateForPager 處理分頁的order by aa,bb 沒帶asc的問題(2016-10-25) 408:NoSqlAction(修正第404修改產生刪除後沒法批量插入的問題)(2016-10-26) 409:MDataTable的AcceptChanges處理重複批量(同時外部沒有產生事務對象的條件下)的問題(2016-10-26) 410:SqlCompatible 對多語句兼容的關鍵詞(不區分大小寫)(2016-10-26) 411:MDataTable的Description增長表字段說明輸出(2016-10-27) 412:StaticTool優化處理GetDbName的細節(2016-10-28) 413:增長Redis分佈式緩存支持(配置AppConfig.Cache.RedisServers)(2016-10-30) 414:爲Redis和MemCache增長備份節點支持(配置AppConfig.Cache.RedisServersBak、AppConfig.Cache.MemCacheServersBak)(2016-10-30)
其實更多的時間,是放在ASP.NET Aries 業務開發框架上,上裏下外所有重構了一遍。github
前幾天,決定把Redis集成進來,一氣呵成,解決了。redis
下面分享一下經歷:算法
一開始我是拒絕的,不肯動態調用第三方的客戶端(關聯依賴的dll太多)。sql
最近打算支持Redis,有點妥協了,動態加載就動態加載了吧:數據庫
考慮着引入:StackExchange.Redis或ServiceStack.Redis?json
看着這些DLL,過重量級,方法反射起來也費勁!數組
中間思惟停頓了一會。。。緩存
在尋找Redis的API資料時,無心發現了這個開源的輕量級Bettle.Redis。
看到源碼編繹後才46K,感受就是它了。
不過才幾刻間,發現瞭如下幾個問題了:
1:自身雖然46K,但代碼引用了另外兩個3個dll(依賴太多):
2:使用的方法不符合使用習慣,一個命令類型就對應一個類。
3:不支持集羣的水平擴展(沒實現支持一致性Hash)。
4:代碼是用.NET 4.0 如下版本寫的,(CYQ.Data 框架是支持2.0起的,改代碼改到我手痛)
因此,以上緣由估計是它沒被普及的緣由,也是最終沒有被我選擇集成的緣由。
可是它開放了源碼、對我仍是有點啓發和參考意義。
在決定支持Redis的過程當中,花了很多時間掃了Redis的文檔:
更多命令詳情能夠看:http://doc.redisfans.com
從這麼一堆的命令中,找到基本命令:Get、Set、Exists、Expire、Info,可憐沒有Add。
其它的命令,多數都是能夠用基本命令實現的,就被無視了。
通過短期內大量的集中思考,決定本身實現了:
框架以前已經集成了MemCache,而Redis和MemCache又大同小異。
一些共性的東西,能夠複用:
1:hash算法。
2:一致性Hash(水平擴展)。
3:SocketPool。
4:ServerPool。
5:序列化(壓縮)
剩下的,就是完成Socket和Redis的交互及使用方式。
如下是Redis的協議規範,不過是我實現Redis相關功能後才發現的:
協議規範 redis容許客戶端以TCP方式鏈接,默認6379端口。傳輸數據都以\r\n結尾。 請求格式 *<number of arguments>\r\n$<number of bytes of argument 1>\r\n<argument data>\r\n 例:*1\r\n$4\r\nINFO\r\n 響應格式 1:簡單字符串,非二進制安全字符串,通常是狀態回覆。 +開頭,例:+OK\r\n 2: 錯誤信息。 -開頭, 例:-ERR unknown command 'mush'\r\n 3: 整型數字。 :開頭, 例::1\r\n 4:大塊回覆值,最大512M。 $開頭+數據長度。 例:$4\r\mush\r\n 5:多條回覆。 *開頭, 例:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
Bettle.Redis裏有源碼,看看實現就能夠了,因此沒找協議規範:
經過幾個小時的引進和代碼調整,測試。
覺得大功告成之際,測試到當Set的數據太大時,NetworkStream報異常:此流不支持Seek操做。
懷疑是Redis的Set有大小限制?:用Bettle.Redis自身試了下,發現正常,夢B了。
經代碼調試,發現Bettle的Socket實現(Socket.Send)和Socket池的實現(NetworkStream.Write)不同。
Bettle.Redis是把全部的協議構造好一次性Socket.Send(byte[])。
懷疑NetworkStream的默認緩存池過小引起的?:用memCache,Set了大量的數據,發現NetworkStream並無拋異常,又夢B了。
懷疑是Redis協議的問題了?:改造代碼,把協議分拆,先發送:$長度 ,再發送數據,發現居然正常了,無語問蒼天了!
通過一晚上一天的折騰,Cache目錄下補了4個類,同時進行了算法優化,清掉一些沒用的代碼。
支持Redis後,發現cyq.data.dll的大小居然沒變化,結果超出了預期,很好!
最後改形成的源碼結構是:
完整的源碼已經提交在:https://github.com/cyq1162/cyqdata
AppConfig.Cache.RedisServers = "127.0.0.1:6379,127.0.0.1:1121";//配置啓用, AppConfig.Cache.RedisServersBak = "127.0.0.1:6379";//備用配置。 CacheManage cache = CacheManage.RedisInstance;//操做對象 cache.Add("obj", cache.CacheTable);//添加DataTable MDataTable obj = cache.Get<MDataTable>("obj"); Console.WriteLine(obj.Rows.Count); Dictionary<string, string> dic = new Dictionary<string, string>(); dic.Add("路過秋天", "http://www.cnblogs.com/cyq1162"); cache.Add("dic", dic);//添加字段 Dictionary<string, string> dicObj = cache.Get<Dictionary<string, string>>("dic"); Console.WriteLine(dicObj["路過秋天"]); cache.Remove("dic");//移除Dic bool hasKey = cache.Contains("dic");//檢測是否存在 Console.WriteLine(hasKey); Console.Read();
結果:
因爲Redis的Get只支持字符串,爲了達到支持任意類型,我必須改進算法:
1:存檔:目標是對象時=》進行序列化(對於>128K的會進行壓縮)
2:數據的第1個字節:存檔數據類型。
3:獲取數據時:根據第1個字節,進行準確的數據類型還原。
(aaa是經過命令行Set的,而a0是經過代碼設置的,因此多了\x02的類型標識)
所以:框架靠Set與Get能支持任意類型的存取檔!
內部已經實現了一致性Hash算法,所以省了很多工做:
簡單的描述爲:把ip1產生N個hash ,ip2產生N個hash,... 而後排序(最後就看key的hash值離誰最新就粘誰了)
借用一張圖表示爲:
在測試的過程當中,我填寫了一臺異常的主機,發現被分配到異常的主機的key的讀寫都沒反應了:
(我潛意識默認覺得會自動轉移到相鄰的主機中)
1:沒有自動切換相鄰的主機【用思考代碼疑問:主動切換可能致使雪崩效應,(累積的壓力可能把全部的服務器都搞掛)】。
2:有重試鏈接機制(2分鐘試1次)。
1:根據Hash,每一臺主機都會指向一臺備份機。 2:主機異常時,由備份機代理服務器15分鐘(即每15分檢測主機是否正常一次,若是正常,則恢復主機服務)。
3:當主機恢復時,從備份機裏恢復數據,並清空備份機的數據(未實現)
因爲可能同時掛掉N臺,因此備份機可能存檔多臺主機的信息。
因而算法的思路有3個:
1:數據不要了(主機從新緩存便可) 2:主機被請求時(檢測是否掛過,若是是,讀自身(若沒有)=》讀備份機(同時發表移除指令)(如有數據)=》返回(同時寫入主機) 3:主機被請求時(檢測是否掛過,若是是開啓線程(讀備份機全部Key,檢測Hash是否符合自身,若是是,則從備份讀取並寫入,同時清除備份機的數據)
至此,CYQ.Data已經支持上Redis了,並且在分佈式算法上,借了memCache的風,以及改進的算法,顯的更爲實用!
固然,細節仍需打磨,代碼還能夠改的更簡潔優美。
在分佈式已經氾濫的今天,能正確的判斷並用好分佈式框架是一種能力的體現。
剛剛羣裏有人發了這條消息:
其實前面的問題均可以無視,由於最後解決方案他只是把Redis部署從Windows轉移到Linux就行了。
QPS最大時據說7萬多(兩臺Web分來就是3萬多,大部分是刷票形成的請求)
Redis在Windows上的表現並不如Linux的好,這個能夠理解。
可是若是在架構設計方案上稍爲調整,其實也毫無壓力了。
最後我發現問題的根源不在於技術,在於人:.NET缺乏有足夠知識和思惟的架構師。
不要遇到點問題就力不從心,在.NET的陣營上堅持吧,少年!