【IT168技術分析評論】
我個人很喜歡反範式這種應用,每次資料庫設計用到時心裡都有點欣喜,或許是因為它之間夾雜著那麼點美感吧。時而提醒我計算機不僅僅是一門科學,也是一門藝術。We are not just Coder. We are all Designer! 不是嗎?謝謝Dylan Tang帶來的的好文章。下面通過Dylan的文章來側重討論一下反範式的使用,同時也談了點memcached使用的個人想法。首先申明,對於Dylan的最終方案我是贊同的。
反範式確實違反了三個範式的關係數據理論,那我們為什麼不做個乖孩子而要去違反範式設計理論呢?下面從Dylan舉的那個例子來說起什麼場景下應該使用反範式。假如我們現在要查詢大眾點評網社區裡面最新的10個帖子,如果按照範式設計,那麼可能要關聯兩個表,一張是帖子表,另外一張是會員表,整個查詢如下:
SELECT TOP 10 N.帖子標題, U.會員暱稱,N.會員ID FROM 帖子表 N
JOIN 會員表 U
ON N.會員ID=U.會員ID
ORDER BY N.帖子ID DESC
上面的sample如果你懶的仔細看或者沒怎麼看懂,那麼我再給你簡單解釋一下,講的就是當我拿出來的10個最新帖子以後,在顯示這幾個帖子的時候我也要顯示會員的暱稱,但是帖子那張表沒有暱稱,只要再次到會員表拿用戶暱稱了,所以這裡就要涉及到兩張表了。常用的方法就是像上面sql語句一樣用一個自然連接查詢來搞定。作為一個solution,這個方案挺好。如果拿出10個新帖子,然後根據每個帖子的會員ID再去會員表拿會員暱稱,因為會員ID是加了index的,性能應該還不錯。但是Dylan gg嫌這個還是太慢了。當我們的業務邏輯涉及的表要在百萬數量級以上時候,當我們在寫sql語句時候就一定要小心了,慢點的操作可能幾個小時結果都出不來。特別是遇到像join, order, group by, distinct這樣的操作,一定要小心一點。這時對算法的設計要求還是挺高的。但實際上上面這個查詢應該是挺快的,所以說這個sample舉的不大好,雖然可以很清楚讓人明白而且它還是個實際的問題,但是它對性能提升的要求還不夠強烈。我們姑且假設用這個join慢的如老牛吧,那我們該怎麼辦?
這不,Dylan Tang提出了第二種解決方案,這次是利用反範式,在帖子表裡面添加冗餘欄位——會員暱稱,這樣我們就可以通過下面的查詢達到同樣的目的:
SELECT TOP 10 帖子標題, 會員暱稱,會員ID FROM 帖子表 ORDER BY 帖子ID DESC
這個方案不錯,把反範式的大概使用場景說出來了。反範式在這種場景下,對於boost performance忒有用。你可以避免連接兩張表才就完成了這種要經常操作的查詢。僅僅通過一張表就可以搞定,這對於性能提升是很顯著的。但是Dylan還是不大滿意,他說,」每當一個會員去更新自己的暱稱的時候,我們會執行一個存儲過程,這個存儲過程的目的就是去更新大量的會員暱稱的冗餘欄位,這些更新對於一個活躍會員來說,將是非常耗時的,因為需要更新的數據實在太多,而Web 2.0的精髓之一就是個性化,這樣的設計對於個性化來說,有著不可調和的矛盾」,這個理由講的很清楚。他擔心的不是一致性,而是維護一致性需要block修改暱稱用戶太長時間。這裡我們是不是可以深入的討論一下,有沒有其它的方案來解決這個問題?我們是不是可以有選擇性的來同步更新影響比較大的冗餘欄位,其它的就可以通過異步操作來解決。這樣我們就不會長時間blocked住修改暱稱的用戶。實際上這裡是表現了一個經典的理論就是Dylan希望通過類似採用事務一樣的ACID(Atomicity, Consistency, Isolation, Durability)來保證一致性,但是對於高可用架構網站的架構師更多的考慮應該是採用BASE(Basically Available, Soft-state, Eventual Consistency)吧。BASE 策略是 Inktomi 公司的 Eric A. Brewer 在 1988 年提出的。更多關於BASE請查看文章後面的參考文章。可以通過暫時的不一致來保證用戶的可用性,這對於用戶來說也是基本可以接受的,對吧。所以這裡的反範式方案的Core Problem也從作者遇到的事務操作的可用性問題轉變成反範式帶來的潛在的不一致風險問題。
為了對比,我說一個新的的反範式使用場景,就舉一個我們都熟悉的場景,你現在已經可以看到我這篇文章有多少人閱讀了,在博客園後臺伺服器上有個記錄我這篇文章信息的資料庫表,這個表裡面可能有個欄位來統計閱讀人數。這時候同樣會遇到上面說的問題,當你讀我這篇文章的時候,是不是應該在那個欄位上加1然後再返回給你我這篇文章內容的響應?我想博客園也同樣是屈服於BASE而放棄ACID。它通過ajax來發異步request到一臺新的域名伺服器上來統計閱讀數,它後臺可能採用延遲寫來避免高頻度的寫操作。只有當閱讀計數器到一定閥值或者每隔x分鐘後,才將內存中這個計數flush到那個欄位裡。這樣就可以減輕反範式可能遇到的頻繁寫操作。我想延遲寫算是最常用的性能調優的方法之一吧,提前讀也算一種,google ditu裡面就充分應用了。
這裡插一個問題,為什麼cnblogs要用ajax來記錄閱讀數?關於這個話題,你還可以參考這篇文章<< 網站日誌收集方式簡介>>。使用這種方式的確有很多優點,首先新的域名是的requests可以突破常用瀏覽器同一域名的並發數的限制,其次可以使得這種記log操作和後臺邏輯脫離開來,類似AOP的思想。再者也可以在爬蟲訪問時不會被統計,還有就是這種方式容易scale out,可以放在專門的機器上來做這種統計,因為統計的計算比較簡單,所以主要遇到的問題還是I/O瓶頸。
遇到上述這個I/O瓶頸怎麼辦?加新的機器來分攤I/O操作?我們先可以延緩這個加機器的欲望,而採用延遲寫來減少一點運營成本。就是我上面說的,當閱讀計數器到一定閥值或者每隔x分鐘後,才將內存中這個計數flush到統計閱讀數那個欄位中。你可以自己實現內存管理或者使用memcached等軟體來幫助你來管理閱讀計數器的存儲,但由於memcached重啟或者其它操作可能使得內存中的那些緩衝會丟掉,這裡你就需要自己來選擇符合你需求的方案了。對於memcached你可以在每次重啟前先把這些數據flush到DB中。這個方案對統計閱讀數應該是可以接受的,畢竟memcached的重啟不會那麼頻繁吧,而且如果閥值不是太高,即使丟了某些數值,還是能夠接受的。如果能夠給memcached加上persistent功能就可以輕鬆搞定這個問題了,國內sina做了一個開源的項目Memcachedb,感興趣可以了解一下,它是結合BDB做的。Sina的blog的訪問數好像就這麼做的,也佐證了我這個想法。總之,如果我們能夠接受帶來的不一致風險的話,可以考慮採用延遲寫來提升性能。
現在你對反範式使用優缺點應該有更為清晰的了解了吧。小結一下,反範式主要問題是採用ACID時候可用性有問題,而採用BASE時候,可能導致冗餘欄位的不一致性。優點當然是能夠避免很多實時計算來提高性能。後臺還可能有這麼一個表來詳細記錄哪些用戶看了我這篇文章,它的用途可以是避免重複記錄閱讀數來保證閱讀數的準確,這估計也要記錄閱讀者的IP吧,不然很難防止用戶作弊,同時也可以為網站的數據挖掘提供點基礎數據。如果要統計我的文章被閱讀次數每次都從這個閱讀者記錄表重新統計一次,這成本根本沒有辦法接受,沒有人會傻的這麼幹的。依賴於反範式可以很好解決這個問題,它僅僅通過一點的存儲成本就節省了很多的計算,把統計需要的計算細分到每個用戶訪問時進行的。這裡也有可能ajax的http request發送失敗。同樣,這裡可能會出現結果最終不一致。但是我們並不care,丟一個兩個沒關係。所以我想說反範式某些場景很有用的。Dylan使用反範式的遇到問題你知道怎麼克服了嗎?什麼?你還不知道,我要打你pp。哎,那我再說一遍,選擇BASE, not ACID。記住了。我們可以異步更新帖子表那個欄位,名字丟失概率應該很小很小,而且我不知道有幾個人閒著沒事幹天天改名字。同時對於這種想保證結果的一致性,你可以在後臺有專門的服務來驗證表之間的一致性。我想我們是不是應該避免使用外鍵,避免使用存儲過程,資料庫表的一致性由外圍服務來保證。這應該也是未來的計算模式吧。
由於這一篇文章是想向大家推薦反範式的應用,所以寫的比較多。希望沒有思考過這種方案的同志們可以正視這個解決方案。偶真的真的很喜歡她,希望你的審美觀和我類似。
下面繼續分析分析那篇文章,看看作者提出的」王者歸來」 解決方案。他的意思是把帖子先拿出來,然後看每個帖子的會員號,然後看他們memcached裡面有沒有,有就直接拿不需要通過db,沒有就需要從DB取,然後塞入memcached,同時得到會員暱稱。這個方案挺好,很容易理解。這個方案的確是比那個反範式方案完美的多。我承認這一點。特別是對於涉及會員表的這個應用很合適,為什麼說呢,因為用戶表是Identity系統以及整個網站應用的核心表,使用很頻繁,很多應用都得依賴於用戶基本信息。但是我對於他所說的,」 通過緩存系統封裝的批量讀取的方法得到這些會員的暱稱,再顯示到網頁上」有點疑惑,這個」批量」是什麼意思?這對於memcached的命中率應該是影響不小吧,難道是單獨用個memcached機器來幹這個。我說說我思考的兩種方案。第一,如果內存比較緊張的話,可以把最近活躍的用戶放在memcached,採用類似LRU的算法,這應該是memcached內存利用很好的方案吧。第二,點評網應該有不少收入了,有銀子當然要多買點機器了,那內存就不是問題了。那麼我的想法是把所有用戶的基本信息load到memcached中,這樣就不存在到底該把哪些用戶信息放到memcached中的問題了。這時候設計到用戶操作都可以飛快,降低了I/O的負載。點評網的餐館等信息也可以這樣幹,當然關於memcached的更新策略問題也就出現了,不過還算比較簡單的。我來大概把整個用戶表放到內存中需要的大概內存量,假設是500萬用戶,打算放用戶ID,假設是個int, 4個字節,用戶暱稱假設16個字節,漢字得看編碼情況了,英文平均8個字母綽綽有餘吧,你可以統計一下長度然後調整為更為合適的數值。主要用到的就是這兩個欄位吧,其它的看情況再加上把。(4+16)*500*10^4bytes/(1024*1024)結果大約95M左右。才用了這麼點內存。。。。。。
最後,為了檢驗一下大家是不是還記得清楚三大範式的定義和統計一下有多人認真看完了這篇文章,請大家跟貼說出你對三個範式的理解。
作者:劉守照
出處:http://liushouzhao.cnblogs.com