怎么樣把NoSQL引入到我們的系統架構設計中,需要根據我們系統的業務場景來分析,什么樣類型的數據適合存儲在NoSQL數據庫中,什么樣類型的數據必須使用關系數據庫存儲。明確引入的NoSQL數據庫帶給系統的作用,它能解決什么問題,以及可能帶來的新的問題。下面我們分析幾種常見的NoSQL架構。
(一)NoSQL作為鏡像
不改變原有的以MySQL作為存儲的架構,使用NoSQL作為輔助鏡像存儲,用NoSQL的優勢輔助提升性能。
圖 1 -NoSQL為鏡像(代碼完成模式 )
//寫入數據的示例偽代碼
//data為我們要存儲的數據對象 data.title=”title”; data.name=”name”; data.time=”2009-12-01 10:10:01”; data.from=”1”; id=DB.Insert(data);//寫入MySQL數據庫 NoSQL.Add(id,data);//以寫入MySQL產生的自增id為主鍵寫入NoSQL數據庫
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果有數據一致性要求,可以像如下的方式使用
//寫入數據的示例偽代碼 //data為我們要存儲的數據對象 bool status=false; DB.startTransaction();//開始事務 id=DB.Insert(data);//寫入MySQL數據庫 if(id>0){ status=NoSQL.Add(id,data);//以寫入MySQL產生的自增id為主鍵寫入NoSQL數據庫 } if(id>0 && status==true){ DB.commit();//提交事務 }else{ DB.rollback();//不成功,進行回滾 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
上面的代碼看起來可能覺得有點麻煩,但是只需要在DB類或者ORM層做一個統一的封裝,就能實現重用了,其他代碼都不用做任何的修改。
這種架構在原有基於MySQL數據庫的架構上增加了一層輔助的NoSQL存儲,代碼量不大,技術難度小,卻在可擴展性和性能上起到了非常大的作用。只需要程序在寫入MySQL數據庫后,同時寫入到NoSQL數據庫,讓MySQL和NoSQL擁有相同的鏡像數據,在某些可以根據主鍵查詢的地方,使用高效的NoSQL數據庫查詢,這樣就節省了MySQL的查詢,用NoSQL的高性能來抵擋這些查詢。
圖 2 -NoSQL為鏡像(同步模式)
這種不通過程序代碼,而是通過MySQL把數據同步到NoSQL中,這種模式是上面一種的變體,是一種對寫入透明但是具有更高技術難度一種模式。這種模式適用於現有的比較復雜的老系統,通過修改代碼不易實現,可能引起新的問題。同時也適用於需要把數據同步到多種類型的存儲中。
MySQL到NoSQL同步的實現可以使用MySQL UDF函數,MySQL binlog的解析來實現。可以利用現有的開源項目來實現,比如:
MySQL memcached UDFs:從通過UDF操作Memcached協議。
國內張宴開源的mysql-udf-http:通過UDF操作http協議。
有了這兩個MySQL UDF函數庫,我們就能通過MySQL透明的處理Memcached或者Http協議,這樣只要有兼容Memcached或者Http協議的NoSQL數據庫,那么我們就能通過MySQL去操作以進行同步數據。再結合lib_mysqludf_json,通過UDF和MySQL觸發器功能的結合,就可以實現數據的自動同步。
(二)MySQL和NoSQL組合
MySQL中只存儲需要查詢的小字段,NoSQL存儲所有數據。
圖 3 -MySQL和NoSQL組合
//寫入數據的示例偽代碼 //data為我們要存儲的數據對象 data.title=”title”; data.name=”name”; data.time=”2009-12-01 10:10:01”; data.from=”1”; bool status=false; DB.startTransaction();//開始事務 id=DB.Insert(“INSERT INTO table (from) VALUES(data.from)”);//寫入MySQL數據庫,只寫from需要where查詢的字段 if(id>0){ status=NoSQL.Add(id,data);//以寫入MySQL產生的自增id為主鍵寫入NoSQL數據庫 } if(id>0 && status==true){ DB.commit();//提交事務 }else{ DB.rollback();//不成功,進行回滾 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
把需要查詢的字段,一般都是數字,時間等類型的小字段存儲於MySQL中,根據查詢建立相應的索引,其他不需要的字段,包括大文本字段都存儲在NoSQL中。在查詢的時候,我們先從MySQL中查詢出數據的主鍵,然后從NoSQL中直接取出對應的數據即可。
這種架構模式把MySQL和NoSQL的作用進行了融合,各司其職,讓MySQL專門負責處理擅長的關系存儲,NoSQL作為數據的存儲。簡單的說,就是在mysql中存儲1對多/多對多的映射關系,然后根據關系查詢出主鍵,再查nosql。或者在nosql直接維護映射關系,實現范圍查詢。它有以下優點:
* 節省MySQL的IO開銷。由於MySQL只存儲需要查詢的小字段,不再負責存儲大文本字段,這樣就可以節省MySQL存儲的空間開銷,從而節省MySQL的磁盤IO。我們曾經通過這種優化,把MySQL一個40G的表縮減到幾百M。
* 提高MySQl Query Cache緩存命中率。我們知道query cache緩存失效是表級的,在MySQL表一旦被更新就會失效,經過這種字段的分離,更新的字段如果不是存儲在MySQL中,那么對query cache就沒有任何影響。而NoSQL的Cache往往都是行級別的,只對更新的記錄的緩存失效。
* 提升MySQL主從同步效率。由於MySQL存儲空間的減小,同步的數據記錄也減小了,而部分數據的更新落在NoSQL而不是MySQL,這樣也減少了MySQL數據需要同步的次數。
* 提高MySQL數據備份和恢復的速度。由於MySQL數據庫存儲的數據的減小,很容易看到數據備份和恢復的速度也將極大的提高。
比以前更容易擴展。NoSQL天生就容易擴展。經過這種優化,MySQL性能也得到提高。
比如手機鳳凰網就是這種架構 http://www.cnblogs.com/sunli/archive/2010/12/20/imcp.html
總結:上述以NoSQL為輔的架構還是以MySQL架構的思想為中心,只是在以前的架構上輔助增加了NoSQL來提高其性能和可擴展性。這種架構實現起來比較容易,卻能取得不錯的效果。如果正想在項目中引入NoSQL,或者你的以MySQL架構的系統目前正出現相關的瓶頸,希望本文可以為你帶來幫助。
第一種鏡像方式,一份數據在RDBMS和NoSQL中都有存儲,主要是使用NoSQL中的高效查詢,例如當緩存用的那種key-value,查詢時顯然性能要高很多。第二種組合使用方式,RDBMS存儲映射關系,NoSQL存儲數據,以發揮兩者的優勢。
接下來我們繼續深入下去,換另外一個角度,“以NoSQL為主”來架構系統。
(三)純NoSQL架構
只使用NoSQL作為數據存儲。
圖 4-純NoSQL架構
在一些數據結構、查詢關系非常簡單的系統中,我們可以只使用NoSQL即可以解決存儲問題。這樣不但可以提高性能,還非常易於擴展。手機鳳凰網的前端展示系統就使用了這種方案。
在一些數據庫結構經常變化,數據結構不定的系統中,就非常適合使用NoSQL來存儲。比如監控系統中的監控信息的存儲,可能每種類型的監控信息都不太一樣。這樣可以避免經常對MySQL進行表結構調整,增加字段帶來的性能問題。
這種架構的缺點就是數據直接存儲在NoSQL中,不能做關系數據庫的復雜查詢,如果由於需求變更,需要進行某些查詢,可能無法滿足,所以采用這種架構的時候需要確認未來是否會進行復雜關系查詢以及如何應對。
非常幸運的是,有些NoSQL數據庫已經具有部分關系數據庫的關系查詢特性,他們的功能介於key-value和關系數據庫之間,卻具有key-value數據庫的性能,基本能滿足絕大部分web 2.0網站的查詢需求。比如:MongoDB就帶有關系查詢的功能,能解決常用的關系查詢,所以也是一種非常不錯的選擇。下面是一些MongoDB的資料:
* 視覺中國的NoSQL之路:從MySQL到MongoDB
* Choosing a non-relational database; why we migrated from MySQL to MongoDB
* 最近的一次Mongo Beijing 開發者聚會也有一部分資料。
雖然Foursquare的MongoDB宕機事件使人們對MongoDB的自動Shard提出了質疑,但是毫無疑問,MongoDB在NoSQL中,是一個優秀的數據庫,其單機性能和功能確實是非常吸引人的。由於上面的例子有詳細的介紹,本文就不做MongoDB的使用介紹。
Tokyo Tyrant數據庫帶有一個名為table的存儲類型,可以對存儲的數據進行關系查詢和檢索。一個table庫類似於MySQL中的一個表。下面我們看一個小演示。我們要存儲一批用戶信息,用戶信息包含用戶名(name),年齡(age),email,最后訪問時間(lastvisit),地區(area)。下面為寫入的演示代碼:
[PHP] view plaincopy
<?php $tt = new TokyoTyrantTable ( "127.0.0.1", 1978 ); $tt->vanish ();//清空 $id = $tt->genUid ();//獲取一個自增id //put方法提供數據寫入。 put ( string $key , array $columns ); $tt->put ( $id, array ("id" => $id, "name" => "zhangsan", "age" => 27, "email" => "zhangsan@gmail.com", "lastvisit" =>strtotime ( "2011-3-5 12:30:00" ), "area" => "北京" ) ); $id = $tt->genUid (); $tt->put ( $id, array ("id" => $id, "name" => "lisi", "age" => 25, "email" => "lisi@126.com", "lastvisit" => strtotime( "2011-3-3 14:40:44" ), "area" => "北京" ) ); $id = $tt->genUid (); $tt->put ( $id, array ("id" => $id, "name" => "laowang", "age" => 37, "email" => "laowang@yahoo.com", "lastvisit" =>strtotime ( "2011-3-5 08:30:12" ), "area" => "成都" ) ); $id = $tt->genUid (); $tt->put ( $id, array ("id" => $id, "name" => "tom", "age" => 21, "email" => "tom@hotmail.com", "lastvisit" =>strtotime ( "2010-12-10 13:12:13" ), "area" => "天津" ) ); $id = $tt->genUid (); $tt->put ( $id, array ("id" => $id, "name" => "jack", "age" => 21, "email" => "jack@gmail.com", "lastvisit" =>strtotime ( "2011-02-24 20:12:55" ), "area" => "天津" ) ); //循環打印數據庫的所有數據庫 $it = $tt->getIterator (); foreach ( $it as $k => $v ) { print_r ( $v ); } ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
比如我們需要查詢年齡為21歲的所有用戶:
<?php $tt = new TokyoTyrantTable ( "127.0.0.1", 1978 ); $query = $tt->getQuery (); //查詢年齡為21歲的用戶 $query->addCond ( “age”, TokyoTyrant::RDBQC_NUMEQ, “21” ); print_r ( $query->search () ); ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
查詢所有在2011年3月5日之后登陸的用戶:
<?php $tt = new TokyoTyrantTable ( "127.0.0.1", 1978 ); $query = $tt->getQuery (); $query->addCond ( “lastvisit”, TokyoTyrant::RDBQC_NUMGE, strtotime ( "2011-3-5 00:00:00" ) ); print_r ( $query->search () ); ?>
- 1
- 2
- 3
- 4
- 5
- 6
從上面的示例代碼可以看出,使用起來是非常簡單的,甚至比SQL語句還要簡單。Tokyo Tyrant的表類型存儲還提供了給字段建立普通索引和倒排全文索引,大大增強了其檢索功能和檢索的性能。
所以,完全用NoSQL來構建部分系統,是完全可能的。配合部分帶有關系查詢功能的NoSQL,在開發上比MySQL數據庫更加快速和高效。
(四)以NoSQL為數據源的架構
數據直接寫入NoSQL,再通過NoSQL同步協議復制到其他存儲。根據應用的邏輯來決定去相應的存儲獲取數據。
圖 5 -以NoSQL為數據源的架構
純NoSQL的架構雖然結構簡單,易於開發,但是在應付需求的變更、穩定性和可靠性上,總是給開發人員一種風險難於控制的感覺。為了降低風險,系統的功能不局限在NoSQL的簡單功能上,我們可以使用以NoSQL為數據源的架構。
在這種架構中,應用程序只負責把數據直接寫入到NoSQL數據庫就OK,然后通過NoSQL的復制協議,把NoSQL數據的每次寫入,更新,刪除操作都復制到MySQL數據庫中。同 時,也可以通過復制協議把數據同步復制到全文檢索實現強大的檢索功能。在海量數據下面,我們也可以根據不同的規則,把數據同步復制到設計好的分表分庫的 MySQL中。這種架構:
* 非常靈活。可以非常方便的在線上系統運行過程中進行數據的調整,比如調整分庫分表的規則、要添加一種新的存儲類型等等。
* 操作簡單。只需要寫入NoSQL數據庫源,應用程序就不用管了。需要增加存儲類型或者調整存儲規則的時候,只需要增加同步的數據存儲,調整同步規則即可,無需更改應用程序的代碼。
* 性能高。數據的寫入和更新直接操作NoSQL,實現了寫的高性能。而通過同步協議,把數據復制到各種適合查詢類型的存儲中(按照業務邏輯區分不同的存儲),能實現查詢的高性能,不像以前MySQL一種數據庫就全包了。或者就一個表負責跟這個表相關的所有的查詢,現在可以把一個表的數據復制到各種存儲,讓各種存儲用自己的長處來對外服務。
* 易擴展。開發人員只需要關心寫入NoSQL數據庫。數據的擴展可以方便的在后端由復制協議根據規則來完成。
這種架構需要考慮數據復制的延遲問題,這跟使用MySQL的master-salve模式的延遲問題是一樣的,解決方法也一樣。包括:
-
嚴格不能延遲的,讀取操作路由到主庫進行。
- 使用部分復制,提高復制速度。(復制部分表)–限於MySQL。
- 采用異步復制,復制到從庫的時候,是復制到緩沖區的,提高復制速度。
- 結合寫緩沖。
在這種以NoSQL為數據源的架構中,最核心的就是NoSQL數據庫的復制功能的實現。而當前的幾乎所有的NoSQL都沒有提供比較易於使用的復制接口來完成這種架構,對NoSQL進行復制協議的二次開發,需要更高的技術水平,所以這種架構看起來很好,但是卻不是非常容易實現的。我的開源項目PHPBuffer中有個實現TokyoTyrant復制的例子,雖然是PHP版本的,但是很容易就可以翻譯成其他語言。通過這個例子的代碼,可以實現從Tokyo Tyrant實時的復制數據到其他系統中。
總結:以NoSQL為主的架構應該算是對NoSQL的一種深度應用,整個系統的架構以及代碼都不是很復雜,但是卻需要一定的NoSQL使用經驗才行。第三種純NoSQL架構是把NoSQL當普通的數據庫來用,存儲和查詢在一起,即包含NoSQL的優點,也包含NoSQL的缺點。適用於數據不是非常地結構化,查詢關系簡單的應用系統。第四種以NoSQL為數據源的構架相當於把NoSQL作為數據庫集群的前端,簡化系統的數據存儲和讀寫,提高性能和系統的擴展性,后台數據到各個RDBMS的復制可以異步進行。
(五)以NoSQL作為緩存的架構
由於NoSQL數據庫天生具有高性能、易擴展的特點,所以我們常常結合關系數據庫,存儲一些高性能的、海量的數據。從另外一個角度看,根據NoSQL的高性能特點,它同樣適合用於緩存數據。用NoSQL緩存數據可以分為內存模式和磁盤持久化模式。
1、內存模式
說起內存模式緩存,我們自然就會想起大名鼎鼎的Memcached。在互聯網發展過程中,Memcached曾經解救了數據庫的大部分壓力,做出了巨大的貢獻,直到今天,它依然是緩存服務器的首選。Memcached的常見使用方式類似下面的代碼:
Memcached提供了相當高的讀寫性能,一般情況下,都足夠應付應用的性能要求。但是基於內存的Memcached緩存的總數據大小受限於內存的大小。
當前如日中天、討論得異常火熱的NoSQL數據庫Redis又為我們提供了功能更加強大的內存存儲功能。跟Memcached比,Redis的一個key的可以存儲多種數據結構Strings、Hashes、Lists、Sets、Sorted sets。Redis不但功能強大,而且它的性能完全超越大名鼎鼎的Memcached。Redis支持List、hashes等多種數據結構的功能,提供了更加易於使用的api和操作性能,比如對緩存的list數據的修改。
同樣,其他一些NoSQL數據庫也提供了內存存儲的功能,所以也適合用來做內存緩存。比如Tokyo Tyrant就提供了內存hash數據庫、內存tree數據庫功能,內存tree數據可根據key的順序進行遍歷。你可以通過使用其提供的兼容Memcached協議或自定義的協議來使用。
2、持久化模式
雖然基於內存的緩存服務器具有高性能,低延遲的特點,但是內存成本高、內存數據易失卻不容忽視。幾十GB內存的服務器,在很多公司看來,還比較奢侈。所以,我們應該根據應用的特點,盡量的提高內存的利用率,降低成本。
大部分互聯網應用的特點都是數據訪問有熱點,也就是說,只有一部分數據是被頻繁訪問的。如果全部都cache到內存中,無疑是對內存的浪費。
這時,我們可以利用NoSQL來做數據的緩存。其實NoSQL數據庫內部也是通過內存緩存來提高性能的,通過一些比較好的算法,把熱點數據進行內存cache,非熱點數據存儲到磁盤以節省內存占用。由於其數據庫結構的簡單,從磁盤獲取一次數據也比從數據庫一次耗時的查詢划算很多。用NoSQL數據庫做緩存服務器不但具有不錯的性能。而且還能夠Cache比內存大的數據。
使用NoSQL來做緩存,由於其不受內存大小的限制,我們可以把一些不常訪問、不怎么更新的數據也緩存起來。比如論壇、新聞的老數據、數據列表的靠后的頁面,雖然用戶訪問不多,但是搜索引擎爬蟲會訪問,也可能導致系統負載上升。
如果NoSQL持久化緩存也使用類似基於內存的memcached設置過期時間的方式,那么持久化緩存就失去了意義。所以用NoSQL做緩存的過期策略最好不使用時間過期,而是數據是否被更新過,如果數據沒有更新,那么就永久不過期。下面我們用代碼(php)演示一種實現這種策略的方法:
場景:新聞站點的評論系統。用戶對新聞頁面的url進行評論,然后根據url進行查詢展示。
我把上面代碼演示的緩存使用方式稱為基於版本的緩存。這種方式同樣適用於基於內存的Memcached。它能實現緩存數據的實時性,讓用戶感覺不到延遲。只要用戶一發表評論,該新聞的評論緩存就會失效。用戶很少去評論一些過時的新聞,那么緩存就一直存在於NoSQL中,避免了爬蟲訪問過時新聞的評論數據而沖擊數據庫。
總結:目前國內的新浪微博已經在大量的使用Redis緩存數據,趕集網也在大量的使用Redis。Redis作為一些List,Hashes等數據結構的緩存,非常適合。
把NoSQL當持久化Cache使用的模式,在很多大數據量、有熱點、查詢非熱點數據比較消耗資源的場景下比較有用。
NoSQL架構實踐總結
到這里,關於NoSQL架構實踐的三篇文章就結束了。NoSQL架構並不局限於我介紹的三種模式,他們之間也可以進行組合,應該根據你具體的應用場景靈活使用。不管是什么模式,都是為了解決我們的問題而出現的,所以在系統架構的時候,要問下自己,我為什么要用NoSQL;在對NoSQL架構模式選型的時候,要問下自己,我為什么要這么用NoSQL。
參考鏈接:
關於Redis的一些介紹: http://blog.nosqlfan.com/tags/redis
分布式緩存還是NoSQL(英文): http://goo.gl/VLECa
NoSQL微群: http://q.t.sina.com.cn/127870
我的微博: http://t.sina.com.cn/sunli1223
關於作者
孫立,目前為去哪兒網(qunar.com)高級系統架構師。曾就職於鳳凰網、ku6和搜狐。多年互聯網從業經驗和程序開發,對分布式搜索引擎的開發,高並發,大數據量網站系統架構優化,高可用性,可伸縮性,分布式系統緩存,數據庫分表分庫(sharding)等有豐富的經驗,並且對運維監控和自動化運維控制有經驗。是開源項目phplock,phpbuffer的作者。近期開發了一個NOSQL數據庫存儲INetDB,是NoSQL數據庫愛好者。
本文整理自:http://www.cnblogs.com/sunli/category/250681.html
源文件:http://blog.csdn.net/zhoudaxia/article/details/8942131 為了方便自己瀏覽,所以復制過來了。