自從cyq.data架構出了資料庫讀寫分離、分布式緩存memcache、自動緩存等大功能之後,就進入了頻繁的細節打磨優化階段。
從以下的更新清單就可以看出來了,3個月更新了100條次功能:
<a href="http://www.cnblogs.com/cyq1162/p/6013140.html#">+ view code</a>
其實更多的時間,是放在asp.net aries 業務開發架構上,上裡下外全部重構了一遍。
前幾天,決定把redis內建進來,一鼓作氣,解決了。
下面分享一下經曆:
一開始我是拒絕的,不願動态調用第三方的用戶端(關聯依賴的dll太多)。
最近打算支援redis,有點妥協了,動态加載就動态加載了吧:
考慮着引入:stackexchange.redis或servicestack.redis?
看着這些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相關功能後才發現的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<code> </code><code>協定規範</code>
<code>redis允許用戶端以tcp方式連接配接,預設6379端口。傳輸資料都以\r\n結尾。</code>
<code>請求格式</code>
<code>*<number </code><code>of</code> <code>arguments>\r\n$<number </code><code>of</code> <code>bytes </code><code>of</code> <code>argument 1>\r\n<argument data>\r\n</code>
<code>例:*1\r\n$4\r\ninfo\r\n</code>
<code>響應格式</code>
<code>1:簡單字元串,非二進制安全字元串,一般是狀态回複。 +開頭,例:+ok\r\n</code>
<code>2: 錯誤資訊。 -開頭, 例:-err unknown command </code><code>'mush'</code><code>\r\n</code>
<code>3: 整型數字。 :開頭, 例::1\r\n</code>
<code>4:大塊回複值,最大512m。 $開頭+資料長度。 例:$4\r\mush\r\n</code>
<code>5:多條回複。 *開頭, 例:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n</code>
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的大小竟然沒變化,結果超出了預期,很好!
最後改造成的源碼結構是:
結果:
由于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次)。
3:當主機恢複時,從備份機裡恢複資料,并清空備份機的資料(未實作)
由于可能同時挂掉n台,是以備份機可能存檔多台主機的資訊。
于是算法的思路有3個:
至此,cyq.data已經支援上redis了,而且在分布式算法上,借了memcache的風,以及改進的算法,顯的更為實用!
當然,細節仍需打磨,代碼還可以改的更簡潔優美。
在分布式已經泛濫的今天,能正确的判斷并用好分布式架構是一種能力的展現。
剛剛群裡有人發了這條消息:
其實前面的問題都可以無視,因為最後解決方案他隻是把redis部署從windows轉移到linux就好了。
qps最大時聽說7萬多(兩台web分來就是3萬多,大部分是刷票造成的請求)
redis在windows上的表現并不如linux的好,這個可以了解。
但是如果在架構設計方案上稍為調整,其實也毫無壓力了。
最後我發現問題的根源不在于技術,在于人:.net缺少有足夠知識和思維的架構師。
不要遇到點問題就力不從心,在.net的陣營上堅持吧,少年!
本文原創發表于部落格園,作者為路過秋天,原文連結:http://www.cnblogs.com/cyq1162/p/6013140.html