程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> CYQ.Data V5 分布式緩存Redis應用開發及實現算法原理介紹,cyq.dataredis

CYQ.Data V5 分布式緩存Redis應用開發及實現算法原理介紹,cyq.dataredis

編輯:關於.NET

CYQ.Data V5 分布式緩存Redis應用開發及實現算法原理介紹,cyq.dataredis


前言:

自從CYQ.Data框架出了數據庫讀寫分離、分布式緩存MemCache、自動緩存等大功能之後,就進入了頻繁的細節打磨優化階段。

從以下的更新列表就可以看出來了,3個月更新了100條次功能:

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 業務開發框架上,上裡下外全部重構了一遍。

前幾天,決定把Redis集成進來,一鼓作氣,解決了。

下面分享一下經歷:

最初的想法:

一開始我是拒絕的,不願動態調用第三方的客戶端(關聯依賴的dll太多)。

最近打算支持Redis,有點妥協了,動態加載就動態加載了吧:

考慮著引入:StackExchange.Redis或ServiceStack.Redis?

看著這些DLL,太重量級,方法反射起來也費勁!

中間思維停頓了一會。。。

發現輕量級:Bettle.Redis

在尋找Redis的API資料時,無意發現了這個開源的輕量級Bettle.Redis。

看到源碼編繹後才46K,感覺就是它了。

不過才幾刻間,發現了以下幾個問題了:

1:自身雖然46K,但代碼引用了另外兩個3個dll(依賴太多):

2:使用的方法不符合使用習慣,一個命令類型就對應一個類。

3:不支持集群的水平擴展(沒實現支持一致性Hash)。

4:代碼是用.NET 4.0 以下版本寫的,(CYQ.Data 框架是支持2.0起的,改代碼改到我手痛)

所以,以上原因估計是它沒被普及的原因,也是最終沒有被我選擇集成的原因。

但是它開放了源碼、對我還是有點啟發和參考意義。

Redis API 掃盲:

在決定支持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

Redis使用方式:

        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能支持任意類型的存取檔!

對於分布式算法的改進:

1:對於水平增加節點的擴展:

內部已經實現了一致性Hash算法,因此省了不少工作:

簡單的描述為:把ip1產生N個hash ,ip2產生N個hash,... 然後排序(最後就看key的hash值離誰最新就粘誰了)

借用一張圖表示為:

2:對於節點故障的轉移:

在測試的過程中,我填寫了一台異常的主機,發現被分配到異常的主機的key的讀寫都沒反應了:

(我潛意識默認以為會自動轉移到相鄰的主機中)

默認的算法:

1:沒有自動切換相鄰的主機【用思考代碼疑問:主動切換可能導致雪崩效應,(累積的壓力可能把所有的服務器都搞掛)】。

2:有重試連接機制(2分鐘試1次)。

改進了算法:增加了一個備份機的配置(AppConfig.Cache.RedisServersBak)

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的陣營上堅持吧,少年!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved