NoSQL架構(gòu)實(shí)踐 -電腦資料

電腦資料 時間:2019-01-01 我要投稿
【m.clearvueentertainment.com - 電腦資料】

    經(jīng)常有朋友遇到困惑,看到NoSQL的介紹,覺得很好,但是卻不知道如何正式用到自己的項(xiàng)目中,

NoSQL架構(gòu)實(shí)踐

。很大的原因就是思維固定在MySQL中了,他們問得最多的問題就是用了NoSQL,我如何做關(guān)系查詢。那么接下來,我們看下怎么樣在我們的系統(tǒng)中使用NoSQL。

    怎么樣把NoSQL引入到我們的系統(tǒng)架構(gòu)設(shè)計(jì)中,需要根據(jù)我們系統(tǒng)的業(yè)務(wù)場景來分析,什么樣類型的數(shù)據(jù)適合存儲在NoSQL數(shù)據(jù)庫中,什么樣類型的數(shù)據(jù)必須使用關(guān)系數(shù)據(jù)庫存儲。明確引入的NoSQL數(shù)據(jù)庫帶給系統(tǒng)的作用,它能解決什么問題,以及可能帶來的新的問題。下面我們分析幾種常見的NoSQL架構(gòu)。

(一)NoSQL作為鏡像

    不改變原有的以MySQL作為存儲的架構(gòu),使用NoSQL作為輔助鏡像存儲,用NoSQL的優(yōu)勢輔助提升性能。

   

    圖 1 -NoSQL為鏡像(代碼完成模式 )

//寫入數(shù)據(jù)的示例偽代碼 //data為我們要存儲的數(shù)據(jù)對象 data.title=”title”; data.name=”name”; data.time=”2009-12-01 10:10:01”; data.from=”1”; id=DB.Insert(data);//寫入MySQL數(shù)據(jù)庫 NoSQL.Add(id,data);//以寫入MySQL產(chǎn)生的自增id為主鍵寫入NoSQL數(shù)據(jù)庫

    如果有數(shù)據(jù)一致性要求,可以像如下的方式使用

//寫入數(shù)據(jù)的示例偽代碼 //data為我們要存儲的數(shù)據(jù)對象 bool status=false; DB.startTransaction();//開始事務(wù) id=DB.Insert(data);//寫入MySQL數(shù)據(jù)庫 if(id>0){     status=NoSQL.Add(id,data);//以寫入MySQL產(chǎn)生的自增id為主鍵寫入NoSQL數(shù)據(jù)庫 } if(id>0 && status==true){     DB.commit();//提交事務(wù) }else{     DB.rollback();//不成功,進(jìn)行回滾 }

    上面的代碼看起來可能覺得有點(diǎn)麻煩,但是只需要在DB類或者ORM層做一個統(tǒng)一的封裝,就能實(shí)現(xiàn)重用了,其他代碼都不用做任何的修改。

    這種架構(gòu)在原有基于MySQL數(shù)據(jù)庫的架構(gòu)上增加了一層輔助的NoSQL存儲,代碼量不大,技術(shù)難度小,卻在可擴(kuò)展性和性能上起到了非常大的作用。只需要程序在寫入MySQL數(shù)據(jù)庫后,同時寫入到NoSQL數(shù)據(jù)庫,讓MySQL和NoSQL擁有相同的鏡像數(shù)據(jù),在某些可以根據(jù)主鍵查詢的地方,使用高效的 NoSQL數(shù)據(jù)庫查詢,這樣就節(jié)省了MySQL的查詢,用NoSQL的高性能來抵擋這些查詢。

   

    圖 2 -NoSQL為鏡像(同步模式)

    這種不通過程序代碼,而是通過MySQL把數(shù)據(jù)同步到NoSQL中,這種模式是上面一種的變體,是一種對寫入透明但是具有更高技術(shù)難度一種模式。這種模式適用于現(xiàn)有的比較復(fù)雜的老系統(tǒng),通過修改代碼不易實(shí)現(xiàn),可能引起新的問題。同時也適用于需要把數(shù)據(jù)同步到多種類型的存儲中。

    MySQL到NoSQL同步的實(shí)現(xiàn)可以使用MySQL UDF函數(shù),MySQL binlog的解析來實(shí)現(xiàn)?梢岳矛F(xiàn)有的開源項(xiàng)目來實(shí)現(xiàn),比如:

MySQL memcached UDFs:從通過UDF操作Memcached協(xié)議。

國內(nèi)張宴開源的mysql-udf-http:通過UDF操作http協(xié)議。

    有了這兩個MySQL UDF函數(shù)庫,我們就能通過MySQL透明的處理Memcached或者Http協(xié)議,這樣只要有兼容Memcached或者Http協(xié)議的NoSQL數(shù)據(jù)庫,那么我們就能通過MySQL去操作以進(jìn)行同步數(shù)據(jù)。再結(jié)合lib_mysqludf_json,通過UDF和MySQL觸發(fā)器功能的結(jié)合,就可以實(shí)現(xiàn)數(shù)據(jù)的自動同步。

(二)MySQL和NoSQL組合

    MySQL中只存儲需要查詢的小字段,NoSQL存儲所有數(shù)據(jù)。

   

    圖 3 -MySQL和NoSQL組合

//寫入數(shù)據(jù)的示例偽代碼 //data為我們要存儲的數(shù)據(jù)對象 data.title=”title”; data.name=”name”; data.time=”2009-12-01 10:10:01”;data.from=”1”;bool status=false; DB.startTransaction();//開始事務(wù) id=DB.Insert(“INSERT INTO table (from) VALUES(data.from)”);//寫入MySQL數(shù)據(jù)庫,只寫from需要where查詢的字段 if(id>0){     status=NoSQL.Add(id,data);//以寫入MySQL產(chǎn)生的自增id為主鍵寫入NoSQL數(shù)據(jù)庫 } if(id>0 && status==true){     DB.commit();//提交事務(wù) }else{     DB.rollback();//不成功,進(jìn)行回滾 }

    把需要查詢的字段,一般都是數(shù)字,時間等類型的小字段存儲于MySQL中,根據(jù)查詢建立相應(yīng)的索引,其他不需要的字段,包括大文本字段都存儲在NoSQL中。在查詢的時候,我們先從MySQL中查詢出數(shù)據(jù)的主鍵,然后從NoSQL中直接取出對應(yīng)的數(shù)據(jù)即可。

    這種架構(gòu)模式把MySQL和NoSQL的作用進(jìn)行了融合,各司其職,讓MySQL專門負(fù)責(zé)處理擅長的關(guān)系存儲,NoSQL作為數(shù)據(jù)的存儲。它有以下優(yōu)點(diǎn):

節(jié)省MySQL的IO開銷。由于MySQL只存儲需要查詢的小字段,不再負(fù)責(zé)存儲大文本字段,這樣就可以節(jié)省MySQL存儲的空間開銷,從而節(jié)省MySQL的磁盤IO。我們曾經(jīng)通過這種優(yōu)化,把MySQL一個40G的表縮減到幾百M(fèi)。

提高M(jìn)ySQl Query Cache緩存命中率。我們知道query cache緩存失效是表級的,在MySQL表一旦被更新就會失效,經(jīng)過這種字段的分離,更新的字段如果不是存儲在MySQL中,那么對query cache就沒有任何影響。而NoSQL的Cache往往都是行級別的,只對更新的記錄的緩存失效。

提升MySQL主從同步效率。由于MySQL存儲空間的減小,同步的數(shù)據(jù)記錄也減小了,而部分?jǐn)?shù)據(jù)的更新落在NoSQL而不是MySQL,這樣也減少了MySQL數(shù)據(jù)需要同步的次數(shù)。

提高M(jìn)ySQL數(shù)據(jù)備份和恢復(fù)的速度。由于MySQL數(shù)據(jù)庫存儲的數(shù)據(jù)的減小,很容易看到數(shù)據(jù)備份和恢復(fù)的速度也將極大的提高。

比以前更容易擴(kuò)展。NoSQL天生就容易擴(kuò)展。經(jīng)過這種優(yōu)化,MySQL性能也得到提高。

    比如手機(jī)鳳凰網(wǎng)就是這種架構(gòu) http://www.cnblogs.com/sunli/archive/2010/12/20/imcp.html

   

   

   

   

    (三)純NoSQL架構(gòu)

    只使用NoSQL作為數(shù)據(jù)存儲。

   

    圖 4-純NoSQL架構(gòu)

    在一些數(shù)據(jù)結(jié)構(gòu)、查詢關(guān)系非常簡單的系統(tǒng)中,我們可以只使用NoSQL即可以解決存儲問題。這樣不但可以提高性能,還非常易于擴(kuò)展。手機(jī)鳳凰網(wǎng)的前端展示系統(tǒng)就使用了這種方案。

    在一些數(shù)據(jù)庫結(jié)構(gòu)經(jīng)常變化,數(shù)據(jù)結(jié)構(gòu)不定的系統(tǒng)中,就非常適合使用NoSQL來存儲。比如監(jiān)控系統(tǒng)中的監(jiān)控信息的存儲,可能每種類型的監(jiān)控信息都不太一樣。這樣可以避免經(jīng)常對MySQL進(jìn)行表結(jié)構(gòu)調(diào)整,增加字段帶來的性能問題。

    這種架構(gòu)的缺點(diǎn)就是數(shù)據(jù)直接存儲在NoSQL中,不能做關(guān)系數(shù)據(jù)庫的復(fù)雜查詢,如果由于需求變更,需要進(jìn)行某些查詢,可能無法滿足,所以采用這種架構(gòu)的時候需要確認(rèn)未來是否會進(jìn)行復(fù)雜關(guān)系查詢以及如何應(yīng)對。

    非常幸運(yùn)的是,有些NoSQL數(shù)據(jù)庫已經(jīng)具有部分關(guān)系數(shù)據(jù)庫的關(guān)系查詢特性,他們的功能介于key-value和關(guān)系數(shù)據(jù)庫之間,卻具有key-value數(shù)據(jù)庫的性能,基本能滿足絕大部分web 2.0網(wǎng)站的查詢需求。比如:

    MongoDB就帶有關(guān)系查詢的功能,能解決常用的關(guān)系查詢,所以也是一種非常不錯的選擇。下面是一些MongoDB的資料:

《視覺中國的NoSQL之路:從MySQL到MongoDB》

《Choosing a non-relational database; why we migrated from MySQL to MongoDB》

最近的一次Mongo Beijing 開發(fā)者聚會也有一部分資料。

    雖然Foursquare使用MongoDB的宕機(jī)事件的出現(xiàn)使人對MongoDB的自動Shard提出了質(zhì)疑,但是毫無疑問,MongoDB在NoSQL中,是一個優(yōu)秀的數(shù)據(jù)庫,其單機(jī)性能和功能確實(shí)是非常吸引人的。由于上面的例子有詳細(xì)的介紹,本文就不做MongoDB的使用介紹。

    Tokyo Tyrant數(shù)據(jù)庫帶有一個名為table的存儲類型,可以對存儲的數(shù)據(jù)進(jìn)行關(guān)系查詢和檢索。一個table庫類似于MySQL中的一個表。下面我們看一個小演示:

    我們要存儲一批用戶信息,用戶信息包含用戶名(name),年齡(age),email,最后訪問時間(lastvisit),地區(qū)(area)。下面為寫入的演示代碼:

   

</p><p>    <span><?</span><span>php</span><span>$tt</span><span></span><span>=</span><span></span><span>new</span><span>TokyoTyrantTable (</span><span>"</span><span>127.0.0.1</span><span>"</span><span>,</span><span></span><span>1978</span><span>);</span><span>$tt</span><span>-></span><span>vanish ();</span><span>//</span><span>清空</span><span></span><span>$id</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>genUid ();</span><span>//</span><span>獲取一個自增id//put方法提供數(shù)據(jù)寫入。 put ( string $key , array $columns );</span><span></span><span>$tt</span><span>-></span><span>put (</span><span>$id</span><span>,</span><span></span><span>array</span><span>(</span><span>"</span><span>id</span><span>"</span><span></span><span>=></span><span></span><span>$id</span><span>,</span><span></span><span>"</span><span>name</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>zhangsan</span><span>"</span><span>,</span><span></span><span>"</span><span>age</span><span>"</span><span></span><span>=></span><span></span><span>27</span><span>,</span><span></span><span>"</span><span>email</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>zhangsan@gmail.com</span><span>"</span><span>,</span><span></span><span>"</span><span>lastvisit</span><span>"</span><span></span><span>=></span><span>strtotime</span><span>(</span><span>"</span><span>2011-3-5 12:30:00</span><span>"</span><span>)</span><span>,</span><span></span><span>"</span><span>area</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>北京</span><span>"</span><span>) );</span><span>$id</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>genUid ();</span><span>$tt</span><span>-></span><span>put (</span><span>$id</span><span>,</span><span></span><span>array</span><span>(</span><span>"</span><span>id</span><span>"</span><span></span><span>=></span><span></span><span>$id</span><span>,</span><span></span><span>"</span><span>name</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>lisi</span><span>"</span><span>,</span><span></span><span>"</span><span>age</span><span>"</span><span></span><span>=></span><span></span><span>25</span><span>,</span><span></span><span>"</span><span>email</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>lisi@126.com</span><span>"</span><span>,</span><span></span><span>"</span><span>lastvisit</span><span>"</span><span></span><span>=></span><span></span><span>strtotime</span><span>(</span><span>"</span><span>2011-3-3 14:40:44</span><span>"</span><span>)</span><span>,</span><span></span><span>"</span><span>area</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>北京</span><span>"</span><span>) );</span><span>$id</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>genUid ();</span><span>$tt</span><span>-></span><span>put (</span><span>$id</span><span>,</span><span></span><span>array</span><span>(</span><span>"</span><span>id</span><span>"</span><span></span><span>=></span><span></span><span>$id</span><span>,</span><span></span><span>"</span><span>name</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>laowang</span><span>"</span><span>,</span><span></span><span>"</span><span>age</span><span>"</span><span></span><span>=></span><span></span><span>37</span><span>,</span><span></span><span>"</span><span>email</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>laowang@yahoo.com</span><span>"</span><span>,</span><span></span><span>"</span><span>lastvisit</span><span>"</span><span></span><span>=></span><span>strtotime</span><span>(</span><span>"</span><span>2011-3-5 08:30:12</span><span>"</span><span>)</span><span>,</span><span></span><span>"</span><span>area</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>成都</span><span>"</span><span>) );</span><span>$id</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>genUid ();</span><span>$tt</span><span>-></span><span>put (</span><span>$id</span><span>,</span><span></span><span>array</span><span>(</span><span>"</span><span>id</span><span>"</span><span></span><span>=></span><span></span><span>$id</span><span>,</span><span></span><span>"</span><span>name</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>tom</span><span>"</span><span>,</span><span></span><span>"</span><span>age</span><span>"</span><span></span><span>=></span><span></span><span>21</span><span>,</span><span></span><span>"</span><span>email</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>tom@hotmail.com</span><span>"</span><span>,</span><span></span><span>"</span><span>lastvisit</span><span>"</span><span></span><span>=></span><span>strtotime</span><span>(</span><span>"</span><span>2010-12-10 13:12:13</span><span>"</span><span>)</span><span>,</span><span></span><span>"</span><span>area</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>天津</span><span>"</span><span>) );</span><span>$id</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>genUid ();</span><span>$tt</span><span>-></span><span>put (</span><span>$id</span><span>,</span><span></span><span>array</span><span>(</span><span>"</span><span>id</span><span>"</span><span></span><span>=></span><span></span><span>$id</span><span>,</span><span></span><span>"</span><span>name</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>jack</span><span>"</span><span>,</span><span></span><span>"</span><span>age</span><span>"</span><span></span><span>=></span><span></span><span>21</span><span>,</span><span></span><span>"</span><span>email</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>jack@gmail.com</span><span>"</span><span>,</span><span></span><span>"</span><span>lastvisit</span><span>"</span><span></span><span>=></span><span>strtotime</span><span>(</span><span>"</span><span>2011-02-24 20:12:55</span><span>"</span><span>)</span><span>,</span><span></span><span>"</span><span>area</span><span>"</span><span></span><span>=></span><span></span><span>"</span><span>天津</span><span>"</span><span>) );</span><span>//</span><span>循環(huán)打印數(shù)據(jù)庫的所有數(shù)據(jù)庫</span><span></span><span>$it</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>getIterator ();</span><span>foreach</span><span>(</span><span>$it</span><span></span><span>as</span><span></span><span>$k</span><span></span><span>=></span><span></span><span>$v</span><span>) {</span><span>print_r</span><span>(</span><span>$v</span><span>);}</span><span>?></span></p>

    比如我們需要查詢年齡為21歲的所有用戶:

   

</p><p>    <span><?</span><span>php</span><span>$tt</span><span></span><span>=</span><span></span><span>new</span><span>TokyoTyrantTable (</span><span>"</span><span>127.0.0.1</span><span>"</span><span>,</span><span></span><span>1978</span><span>);</span><span>$query</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>getQuery ();</span><span>//</span><span>查詢年齡為21歲的用戶</span><span></span><span>$query</span><span>-></span><span>addCond ( “age”</span><span>,</span><span>TokyoTyrant</span><span>::</span><span>RDBQC_NUMEQ</span><span>,</span><span>“</span><span>21</span><span>” );</span><span>print_r</span><span>(</span><span>$query</span><span>-></span><span>search () );</span><span>?></span></p>

    查詢所有在2011年3月5日之后登陸的用戶:

   

</p><p>    <span><?</span><span>php</span><span>$tt</span><span></span><span>=</span><span></span><span>new</span><span>TokyoTyrantTable (</span><span>"</span><span>127.0.0.1</span><span>"</span><span>,</span><span></span><span>1978</span><span>);</span><span>$query</span><span></span><span>=</span><span></span><span>$tt</span><span>-></span><span>getQuery ();</span><span>$query</span><span>-></span><span>addCond ( “l(fā)astvisit”</span><span>,</span><span>TokyoTyrant</span><span>::</span><span>RDBQC_NUMGE</span><span>,</span><span></span><span>strtotime</span><span>(</span><span>"</span><span>2011-3-5 00:00:00</span><span>"</span><span>) );</span><span>print_r</span><span>(</span><span>$query</span><span>-></span><span>search () );</span><span>?></span></p>

    從上面的示例代碼可以看出,使用起來是非常簡單的,甚至比SQL語句還要簡單,

電腦資料

NoSQL架構(gòu)實(shí)踐》(http://m.clearvueentertainment.com)。Tokyo Tyrant的表類型存儲還提供了給字段建立普通索引和倒排全文索引,大大增強(qiáng)了其檢索功能和檢索的性能。

    所以,完全用NoSQL來構(gòu)建部分系統(tǒng),是完全可能的。配合部分帶有關(guān)系查詢功能的NoSQL,在開發(fā)上比MySQL數(shù)據(jù)庫更加快速和高效。

    (四)以NoSQL為數(shù)據(jù)源的架構(gòu)

    數(shù)據(jù)直接寫入NoSQL,再通過NoSQL同步協(xié)議復(fù)制到其他存儲。根據(jù)應(yīng)用的邏輯來決定去相應(yīng)的存儲獲取數(shù)據(jù)。

   

    圖 5 -以NoSQL為數(shù)據(jù)源

    純NoSQL的架構(gòu)雖然結(jié)構(gòu)簡單,易于開發(fā),但是在應(yīng)付需求的變更、穩(wěn)定性和可靠性上,總是給開發(fā)人員一種風(fēng)險難于控制的感覺。為了降低風(fēng)險,系統(tǒng)的功能不局限在NoSQL的簡單功能上,我們可以使用以NoSQL為數(shù)據(jù)源的架構(gòu)。

    在這種架構(gòu)中,應(yīng)用程序只負(fù)責(zé)把數(shù)據(jù)直接寫入到NoSQL數(shù)據(jù)庫就OK,然后通過NoSQL的復(fù)制協(xié)議,把NoSQL數(shù)據(jù)的每次寫入,更新,刪除操作都復(fù)制到MySQL數(shù)據(jù)庫中。同 時,也可以通過復(fù)制協(xié)議把數(shù)據(jù)同步復(fù)制到全文檢索實(shí)現(xiàn)強(qiáng)大的檢索功能。在海量數(shù)據(jù)下面,我們也可以根據(jù)不同的規(guī)則,把數(shù)據(jù)同步復(fù)制到設(shè)計(jì)好的分表分庫的 MySQL中。這種架構(gòu):

非常靈活。可以非常方便的在線上系統(tǒng)運(yùn)行過程中進(jìn)行數(shù)據(jù)的調(diào)整,比如調(diào)整分庫分表的規(guī)則、要添加一種新的存儲類型等等。

操作簡單。只需要寫入NoSQL數(shù)據(jù)庫源,應(yīng)用程序就不用管了。需要增加存儲類型或者調(diào)整存儲規(guī)則的時候,只需要增加同步的數(shù)據(jù)存儲,調(diào)整同步規(guī)則即可,無需更改應(yīng)用程序的代碼。

性能高。數(shù)據(jù)的寫入和更新直接操作NoSQL,實(shí)現(xiàn)了寫的高性能。而通過同步協(xié)議,把數(shù)據(jù)復(fù)制到各種適合查詢類型的存儲中(按照業(yè)務(wù)邏輯區(qū)分不同的存儲),能實(shí)現(xiàn)查詢的高性能,不像以前MySQL一種數(shù)據(jù)庫就全包了;蛘呔鸵粋表負(fù)責(zé)跟這個表相關(guān)的所有的查詢,現(xiàn)在可以把一個表的數(shù)據(jù)復(fù)制到各種存儲,讓各種存儲用自己的長處來對外服務(wù)。

易擴(kuò)展。開發(fā)人員只需要關(guān)心寫入NoSQL數(shù)據(jù)庫。數(shù)據(jù)的擴(kuò)展可以方便的在后端由復(fù)制協(xié)議根據(jù)規(guī)則來完成。

    這種架構(gòu)需要考慮數(shù)據(jù)復(fù)制的延遲問題,這跟使用MySQL的master-salve模式的延遲問題是一樣的,解決方法也一樣。

    在這種以NoSQL為數(shù)據(jù)源的架構(gòu)中,最核心的就是NoSQL數(shù)據(jù)庫的復(fù)制功能的實(shí)現(xiàn)。而當(dāng)前的幾乎所有的NoSQL都沒有提供比較易于使用的復(fù)制接口來完成這種架構(gòu),對NoSQL進(jìn)行復(fù)制協(xié)議的二次開發(fā),需要更高的技術(shù)水平,所以這種架構(gòu)看起來很好,但是卻不是非常容易實(shí)現(xiàn)的。我的開源項(xiàng)目PHPBuffer中有個實(shí)現(xiàn)TokyoTyrant復(fù)制的例子,雖然是PHP版本的,但是很容易就可以翻譯成其他語言。通過這個例子的代碼,可以實(shí)現(xiàn)從Tokyo Tyrant實(shí)時的復(fù)制數(shù)據(jù)到其他系統(tǒng)中。

    總結(jié)

    以NoSQL為主的架構(gòu)應(yīng)該算是對NoSQL的一種深度應(yīng)用,整個系統(tǒng)的架構(gòu)以及代碼都不是很復(fù)雜,但是卻需要一定的NoSQL使用經(jīng)驗(yàn)才行。

最新文章