經驗之談:非關系型數據庫(NoSql)
最近了解了一點非關系型數據庫,覺得這是一個很好的方向,對于大數據方面的處理,非關系型數據庫能起到至關重要的地位。這里我主要是整理了一些前輩的經驗,僅供參考。
關系型數據庫的特點
1、關系型數據庫
關系型數據庫,是指采用了關系模型來組織數據的數據庫。 簡單來說,關系模型指的就是二維表格模型,而一個關系型數據庫就是由二維表及其之間的聯系所組成的一個數據組織。常見 的關系型數據庫有Oracle、Mysql、sql server等等。
2、關系型數據庫瓶頸
高并發讀寫需求
網站的用戶并發性非常高,往往達到每秒上萬次讀寫請求,對于傳統關系型數據庫來說,硬盤I/O是一個很大的瓶頸
海量數據的高效率讀寫 網站每天產生的數據量是巨大的,對于關系型數據庫來說,在一張包含海量數據的表中查詢,效率是非常低的
高擴展性和可用性
在基于web的結構當中,數據庫是最難進行橫向擴展的,當一個應用系統的用戶量和訪問量與日俱增的時候,數據庫卻沒有辦法像web server和app server那樣簡單的通過添加更多的硬件和服務節點來擴展性能和負載能力。對于很多需要提供24小時不間斷服務的網站來說,對數據庫系統進行升級和擴展是非常痛苦的事情,往往需要停機維護和數據遷移。
對網站來說,關系型數據庫的很多特性不再需要了:
事務一致性
關系型數據庫在對事物一致性的維護中有很大的開銷,而現在很多web2.0系統對事物的讀寫一致性都不高
讀寫實時性
對關系數據庫來說,插入一條數據之后立刻查詢,是肯定可以讀出這條數據的,但是對于很多web應用來說,并不要求這么高的實時性,比如發一條消息之后,過幾秒乃至十幾秒之后才看到這條動態是完全可以接受的
復雜SQL,特別是多表關聯查詢
任何大數據量的web系統,都非常忌諱多個大表的關聯查詢,以及復雜的數據分析類型的復雜SQL報表查詢,特別是SNS類型的網站,從需求以及產品階級角度,就避免了這種情況的產生。往往更多的只是單表的主鍵查詢,以及單表的簡單條件分頁查詢,SQL的功能極大的弱化了
在關系型數據庫中,導致性能欠佳的最主要原因是多表的關聯查詢,以及復雜的數據分析類型的復雜SQL報表查詢。為了保證數據庫的ACID特性,我們必須盡量按照其要求的范式進行設計,關系型數據庫中的表都是存儲一個格式化的數據結構。
每個元組字段的組成都是一樣,即使不是每個元組都需要所有的字段,但數據庫會為每個元組分配所有的字段,這樣的結構可以便于標語表之間進行鏈接等操作,但從另一個角度來說它也是關系型數據庫性能瓶頸的一個因素。
非關系型數據庫(NoSQL )
2009年初,Johan Oskarsson舉辦了一場關于開源分布式數據庫的討論,Eric Evans在這次討論中提出了NoSQL一詞,用于指代那些非關系型的,分布式的,且一般不保證遵循ACID原則的數據存儲系統。Eric Evans使用NoSQL這個詞,并不是因為字面上的“沒有SQL”的意思,他只是覺得很多經典的關系型數據庫名字都叫“**SQL”,所以為了表示跟這些關系型數據庫在定位上的截然不同,就是用了“NoSQL“一詞。
注:數據庫事務必須具備ACID特性,ACID是Atomic原子性,Consistency一致性,Isolation
- var script = document.createElement(‘script’); script.src = ‘http://static.pay.baidu.com/resource/baichuan/ns.js’; document.body.appendChild(script);
隔離性,Durability持久性。
非關系型數據庫提出另一種理念,例如,以鍵值對存儲,且結構不固定,每一個元組可以有不一樣的字段,每個元組可以根據需要增加一些自己的鍵值對,這樣就不會局限于固定的結構,可以減少一些時間和空間的開銷。使用這種方式,用戶可以根據需要去添加自己需要的字段,這樣,為了獲取用戶的不同信息,不需要像關系型數據庫中,要對多表進行關聯查詢。
僅需要根據id取出相應的value就可以完成查詢。但非關系型數據庫由于很少的約束,他也不能夠提供像SQL所提供的where這種對于字段屬性值情況的查詢。并且難以體現設計的完整性。他只適合存儲一些較為簡單的數據,對于需要進行較復雜查詢的數據,SQL數據庫顯的更為合適。
關系型數據庫與非關系型數據庫的區別
關系型數據庫的最大特點就是事務的一致性:傳統的關系型數據庫讀寫操作都是事務的,具有ACID的特點,這個特性使得關系型數據庫可以用于幾乎所有對一致性有要求的系統中,如典型的銀行系統。
但是,在網頁應用中,尤其是SNS應用中,一致性卻不是顯得那么重要,用戶A看到的內容和用戶B看到同一用戶C內容更新不一致是可以容忍的,或者說,兩個人看到同一好友的數據更新的時間差那么幾秒是可以容忍的,因此,關系型數據庫的最大特點在這里已經無用武之地,起碼不是那么重要了。
相反地,關系型數據庫為了維護一致性所付出的巨大代價就是其讀寫性能比較差,而像微博、facebook這類SNS的應用,對并發讀寫能力要求極高,關系型數據庫已經無法應付(在讀方面,傳統上為了克服關系型數據庫缺陷,提高性能,都是增加一級memcache來靜態化網頁,而在SNS中,變化太快,memchache已經無能為力了),因此,必須用新的一種數據結構存儲來代替關系數據庫。
關系數據庫的另一個特點就是其具有固定的表結構,因此,其擴展性極差,而在SNS中,系統的升級,功能的增加,往往意味著數據結構巨大變動,這一點關系型數據庫也難以應付,需要新的結構化數據存儲。
于是,非關系型數據庫應運而生,由于不可能用一種數據結構化存儲應付所有的新的需求,因此,非關系型數據庫嚴格上不是一種數據庫,應該是一種數據結構化存儲方法的集合。 必須強調的是,數據的持久存儲,尤其是海量數據的持久存儲,還是需要一種關系數據庫。
非關系型數據庫簡介
SQLite
- ACID事務
- 零配置 – 無需安裝和管理配置
- 儲存在單一磁盤文件中的一個完整的數據庫
- 數據庫文件可以在不同字節順序的機器間自由的共享
- 支持數據庫大小至2TB
- 足夠小, 大致3萬行C代碼, 250K
- 比一些流行的數據庫在大部分普通數據庫操作要快8. 簡單, 輕松的API
- 包含TCL綁定, 同時通過Wrapper支持其他語言的綁定
- 良好注釋的源代碼, 并且有著90%以上的測試覆蓋率
- 獨立: 沒有額外依賴
- Source完全的Open, 你可以用于任何用途, 包括出售它 13. 支持多種開發語言,C, PHP, Perl, Java, ASP .NET,Python
介紹下key相關的命令
- exits key 測試指定key是否存在,返回1表示存在,0不存在
- del key1 key2 ….keyN 刪除給定key,返回刪除key的數目,0表示給定key都不存在
- type key 返回給定key的value類型。返回 none 表示不存在key,string字符類型,list 鏈表類型 set 無序集合類型…
- keys pattern 返回匹配指定模式的所有key,下面給個例子 redis> set test dsf OK
- redis> set tast dsaf OK
- redis> set tist adff OK
- redis> keys t* 1. “tist” 2. “tast” 3. “test”
- redis> keys t[ia]st 1. “tist” 2. “tast”
- redis> keys t?st 1. “tist” 2. “tast” 3. “test”
- randomkey 返回從當前數據庫中隨機選擇的一個key,如果當前數據庫是空的,返回空串 rename oldkey newkey 原子的重命名一個key,如果newkey存在,將會被覆蓋,返回1表示成功,0失敗。可能是oldkey不存在或者和newkey相同
- renamenx oldkey newkey 同上,但是如果newkey存在返回失敗 dbsize 返回當前數據庫的key數量
- expire key seconds 為key指定過期時間,單位是秒。返回1成功,0表示key已經設置過過期時間或者不存在
- ttl key 返回設置過過期時間的key的剩余過期秒數 -1表示key不存在或者沒有設置過過期時間
- select db-index 通過索引選擇數據庫,默認連接的數據庫所有是0,默認數據庫數是16個。返回1表示成功,0失敗
- move key db-index 將key從當前數據庫移動到指定數據庫。返回1成功。0 如果key不存在,或者已經在指定數據庫中
- flushdb 刪除當前數據庫中所有key,此方法不會失敗。慎用
- flushall 刪除所有數據庫中的所有key,此方法不會失敗。更加慎用
string 類型
string是redis最基本的類型,而且string類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象
隨著互聯網web2.0網站的興起,非關系型的數據庫現在成了一個極其熱門的新領域, 非關系數據庫產品的發展非常迅速。而傳統的關系數據庫在應付web2.0網站,特別是超大規模和高并發的SNS類型的web2.0純動態網站已經顯得力不 從心,暴露了很多難以克服的問題,例如:
1、High performance – 對數據庫高并發讀寫的需求
web2.0網站要根據用戶個性化信息來實時生成動態頁面和提供動態信息,所以基本上無法使用動態頁面靜態化技術,因此數據庫并發負載非常高,往往要達到 每秒上萬次讀寫請求。關系數據庫應付上萬次SQL查詢還勉強頂得住,但是應付上萬次SQL寫數據請求,硬盤IO就已經無法承受了。
其實對于普通的BBS網 站,往往也存在對高并發寫請求的需求,例如像JavaEye網站的實時統計在線用戶狀態,記錄熱門帖子的點擊次數,投票計數等,因此這是一個相當普遍的需 求。
2、Huge Storage – 對海量數據的高效率存儲和訪問的需求
類似Facebook,twitter,Friendfeed這樣的SNS網站,每天用戶產生海量的用戶動態,以Friendfeed為例,一個月就達到 了2.5億條用戶動態,對于關系數據庫來說,在一張2.5億條記錄的表里面進行SQL查詢,效率是極其低下乃至不可忍受的。再例如大型web網站的用戶登 錄系統,例如騰訊,盛大,動輒數以億計的帳號,關系數據庫也很難應付。
3、High Scalability && High Availability- 對數據庫的高可擴展性和高可用性的需求
在基于web的架構當中,數據庫是最難進行橫向擴展的,當一個應用系統的用戶量和訪問量與日俱增的時候,你的數據庫卻沒有辦法像web server和app server那樣簡單的通過添加更多的硬件和服務節點來擴展性能和負載能力。對于很多需要提供24小時不間斷服務的網站來說,對數據庫系統進行升級和擴展 是非常痛苦的事情,往往需要停機維護和數據遷移,為什么數據庫不能通過不斷的添加服務器節點來實現擴展呢?
在上面提到的“三高”需求面前,關系數據庫遇到了難以克服的障礙,而對于web2.0網站來說,關系數據庫的很多主要特性卻往往無用武之地,例如:
1、數據庫事務一致性需求
很多web實時系統并不要求嚴格的數據庫事務,對讀一致性的要求很低,有些場合對寫一致性要求也不高。因此數據庫事務管理成了數據庫高負載下一個沉重的負 擔。
2、數據庫的寫實時性和讀實時性需求
對關系數據庫來說,插入一條數據之后立刻查詢,是肯定可以讀出來這條數據的,但是對于很多web應用來說,并不要求這么高的實時性,比方說發一條消息之 后,過幾秒乃至十幾秒之后,我的訂閱者才看到這條動態是完全可以接受的。
3、對復雜的SQL查詢,特別是多表關聯查詢的需求
任何大數據量的web系統,都非常忌諱多個大表的關聯查詢,以及復雜的數據分析類型的復雜SQL報表查詢,特別是SNS類型的網站,從需求以及產品設計角 度,就避免了這種情況的產生。往往更多的只是單表的主鍵查詢,以及單表的簡單條件分頁查詢,SQL的功能被極大的弱化了。
因此,關系數據庫在這些越來越多的應用場景下顯得不那么合適了,為了解決這類問題的非關系數據庫應運而生,現在這兩年,各種各樣非關系數據庫,特別是鍵值 數據庫(Key-Value Store DB)風起云涌,多得讓人眼花繚亂。前不久國外剛剛舉辦了NoSQL Conference,各路NoSQL數據庫紛紛亮相,加上未亮相但是名聲在外的,起碼有超過10個開源的NoSQLDB,例如:
Redis,Tokyo Cabinet,Cassandra,Voldemort,MongoDB,Dynomite,HBase,CouchDB,Hypertable, Riak,Tin, Flare, Lightcloud, KiokuDB,Scalaris, Kai, ThruDB , ……
這些NoSQL數據庫,有的是用C/C++編寫的,有的是用Java編寫的,還有的是用Erlang編寫的,每個都有自己的獨到之處,看都看不過來了,這 些NoSQL數據庫大致可以分為以下的三類:
1、滿足極高讀寫性能需求的Kye-Value數據庫:Redis,Tokyo Cabinet, Flare
高性能Key-Value數據庫的主要特點就是具有極高的并發讀寫性能,Redis,Tokyo Cabinet, Flare,這3個Key-Value DB都是用C編寫的,他們的性能都相當出色,但出了出色的性能,他們還有自己獨特的功能:
a、 Redis
Redis是一個很新的項目,剛剛發布了1.0版本。Redis本質上是一個Key-Value類型的內存數據庫,很像memcached,整個數據庫統 統加載在內存當中進行操作,定期通過異步操作把數據庫數據flush到硬盤上進行保存。因為是純內存操作,Redis的性能非常出色,每秒可以處理超過 10萬次讀寫操作,是我知道的性能最快的Key-Value DB。
Redis的出色之處不僅僅是性能,Redis最大的魅力是支持保存List鏈表和Set集合的數據結構,而且還支持對List進行各種操作,例如從 List兩端push和pop數據,取List區間,排序等等,對Set支持各種集合的并集交集操作,此外單個value的最大限制是1GB,不像 memcached只能保存1MB的數據。
因此Redis可以用來實現很多有用的功能,比方說用他的List來做FIFO雙向鏈表,實現一個輕量級的高性 能消息隊列服務,用他的Set可以做高性能的tag系統等等。另外Redis也可以對存入的Key-Value設置expire時間,因此也可以被當作一 個功能加強版的memcached來用。
Redis的主要缺點是數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,并且它沒有原生的可擴展機制,不具有scale(可擴展)能力,要 依賴客戶端來實現分布式讀寫,因此Redis適合的場景主要局限在較小數據量的高性能操作和運算上。目前使用Redis的網站有 github,Engine Yard。
b、Tokyo Cabinet和Tokoy Tyrant
TC和TT的開發者是日本人Mikio Hirabayashi,主要被用在日本最大的SNS網站mixi.jp上,TC發展的時間最早,現在已經是一個非常成熟的項目,也是Kye-Value 數據庫領域最大的熱點,現在被廣泛的應用在很多很多網站上。TC是一個高性能的存儲引擎,而TT提供了多線程高并發服務器,性能也非常出色,每秒可以處理 4-5萬次讀寫操作。
TC除了支持Key-Value存儲之外,還支持保存Hashtable數據類型,因此很像一個簡單的數據庫表,并且還支持基于column的條件查詢, 分頁查詢和排序功能,基本上相當于支持單表的基礎查詢功能了,所以可以簡單的替代關系數據庫的很多操作,這也是TC受到大家歡迎的主要原因之一,有一個 Ruby的項目miyazakiresistance將TT的hashtable的操作封裝成和ActiveRecord一樣的操作,用起來非常爽。
TC/TT在mixi的實際應用當中,存儲了2000萬條以上的數據,同時支撐了上萬個并發連接,是一個久經考驗的項目。TC在保證了極高的并發讀寫性能 的同時,具有可靠的數據持久化機制,同時還支持類似關系數據庫表結構的hashtable以及簡單的條件,分頁和排序操作,是一個很棒的NoSQL數據 庫。
TC主要的缺點是沒有scale的能力,如果單機無法滿足要求,只能通過主從復制的方式擴展,另外有人提到TC的性能會隨著數據量的增加而下降,當數據量 上億條以后,性能會有比較明顯的下降。
這個是Tim Yang做的一個Memcached,Redis和Tokyo Tyrant的簡單的性能評測,僅供參考
c、 Flare
TC是日本第一大SNS網站mixi開發的,而Flare是日本第二大SNS網站green.jp開發的,有意思吧。Flare簡單的說就是給TC添加了 scale功能。他替換掉了TT部分,自己另外給TC寫了網絡服務器,Flare的主要特點就是支持scale能力,他在網絡服務端之前添加了一個 node server,來管理后端的多個服務器節點,因此可以動態添加數據庫服務節點,刪除服務器節點,也支持failover。如果你的使用場景必須要讓TC可 以scale,那么可以考慮flare。
flare唯一的缺點就是他只支持memcached協議,因此當你使用flare的時候,就不能使用TC的table數據結構了,只能使用TC的 key-value數據結構存儲。
2、滿足海量存儲需求和訪問的面向文檔的數據庫:MongoDB,CouchDB
面向文檔的非關系數據庫主要解決的問題不是高性能的并發讀寫,而是保證海量數據存儲的同時,具有良好的查詢性能。MongoDB是用C++開發的,而 CouchDB則是Erlang開發的:
a、MongoDB
MongoDB是一個介于關系數據庫和非關系數據庫之間的產品,是非關系數據庫當中功能最豐富,最像關系數據庫的。他支持的數據結構非常松散,是類似 json的bjson格式,因此可以存儲比較復雜的數據類型。Mongo最大的特點是他支持的查詢語言非常強大,其語法有點類似于面向對象的查詢語言,幾 乎可以實現類似關系數據庫單表查詢的絕大部分功能,而且還支持對數據建立索引。
Mongo主要解決的是海量數據的訪問效率問題,根據官方的文檔,當數據量達到50GB以上的時候,Mongo的數據庫訪問速度是MySQL的10倍以 上。Mongo的并發讀寫效率不是特別出色,根據官方提供的性能測試表明,大約每秒可以處理0.5萬-1.5次讀寫請求。
因為Mongo主要是支持海量數據存儲的,所以Mongo還自帶了一個出色的分布式文件系統GridFS,可以支持海量的數據存儲,但我也看到有些評論認 為GridFS性能不佳,這一點還是有待親自做點測試來驗證了。
最后由于Mongo可以支持復雜的數據結構,而且帶有強大的數據查詢功能,因此非常受到歡迎,很多項目都考慮用MongoDB來替代MySQL來實現不是 特別復雜的Web應用,比方說why we migrated from MySQL to MongoDB就是一個真實的從MySQL遷移到MongoDB的案例,由于數據量實在太大,所以遷移到了Mongo上面,數據查詢的速度得到了非常顯著 的提升。
MongoDB也有一個ruby的項目MongoMapper,是模仿Merb的DataMapper編寫的MongoDB的接口,使用起來非常簡單,幾 乎和DataMapper一模一樣,功能非常強大易用。
b、CouchDB
CouchDB現在是一個非常有名氣的項目,似乎不用多介紹了。但是我卻對CouchDB沒有什么興趣,主要是因為CouchDB僅僅提供了基于HTTP REST的接口,因此CouchDB單純從并發讀寫性能來說,是非常糟糕的,這讓我立刻拋棄了對CouchDB的興趣。
3、滿足高可擴展性和可用性的面向分布式計算的數據庫:Cassandra,Voldemort
面向scale能力的數據庫其實主要解決的問題領域和上述兩類數據庫還不太一樣,它首先必須是一個分布式的數據庫系統,由分布在不同節點上面的數據庫共同 構成一個數據庫服務系統,并且根據這種分布式架構來提供online的,具有彈性的可擴展能力,例如可以不停機的添加更多數據節點,刪除數據節點等等。因 此像Cassandra常常被看成是一個開源版本的Google BigTable的替代品。Cassandra和Voldemort都是用Java開發的:
a、Cassandra
Cassandra項目是Facebook在2008年開源出來的,隨后Facebook自己使用Cassandra的另外一個不開源的分支,而開源出來 的Cassandra主要被Amazon的Dynamite團隊來維護,并且Cassandra被認為是Dynamite2.0版本。目前除了 Facebook之外,twitter和digg.com都在使用Cassandra。
Cassandra的主要特點就是它不是一個數據庫,而是由一堆數據庫節點共同構成的一個分布式網絡服務,對Cassandra的一個寫操作,會被復制到 其他節點上去,對Cassandra的讀操作,也會被路由到某個節點上面去讀取。對于一個Cassandra群集來說,擴展性能是比較簡單的事情,只管在 群集里面添加節點就可以了。我看到有文章說Facebook的Cassandra群集有超過100臺服務器構成的數據庫群集。
Cassandra也支持比較豐富的數據結構和功能強大的查詢語言,和MongoDB比較類似,查詢功能比MongoDB稍弱一些,twitter的平臺 架構部門領導Evan Weaver寫了一篇文章介紹Cassandra:http://blog.evanweaver.com/articles/2009/07/06 /up-and-running-with-cassandra/,有非常詳細的介紹。
Cassandra以單個節點來衡量,其節點的并發讀寫性能不是特別好,有文章說評測下來Cassandra每秒大約不到1萬次讀寫請求,我也看到一些對 這個問題進行質疑的評論,但是評價Cassandra單個節點的性能是沒有意義的,真實的分布式數據庫訪問系統必然是n多個節點構成的系統,其并發性能取 決于整個系統的節點數量,路由效率,而不僅僅是單節點的并發負載能力。
b、Voldemort
Voldemort是個和Cassandra類似的面向解決scale問題的分布式數據庫系統,Cassandra來自于Facebook這個SNS網 站,而Voldemort則來自于Linkedin這個SNS網站。說起來SNS網站為我們貢獻了n多的NoSQL數據庫,例如 Cassandar,Voldemort,Tokyo Cabinet,Flare等等。
Voldemort的資料不是很多,因此我沒有特別仔細去鉆研,Voldemort官方給出Voldemort的并發讀 寫性能也很不錯,每秒超過了1.5萬次讀寫。
從Facebook開發Cassandra,Linkedin開發Voldemort,我們也可以大致看出國外大型SNS網站對于分布式數據庫,特別是對 數據庫的scale能力方面的需求是多么殷切。前面提到,web應用的架構當中,web層和app層相對來說都很容易橫向擴展,唯有數據庫是單點的,極難 scale,現在Facebook和Linkedin在非關系型數據庫的分布式方面探索了一條很好的方向,這也是為什么現在Cassandra這么熱門的 主要原因。
補充說明:
- 實質。 非關系型數據庫的實質:非關系型數據庫產品是傳統關系型數據庫的功能閹割版本,通過減少用不到或很少用的功能,來大幅度提高產品性能。
- 價格。 目前基本上大部分主流的非關系型數據庫都是免費的。而比較有名氣的關系型數據庫,比如Oracle、DB2、MSSQL是收費的。雖然Mysql免費,但它需要做很多工作才能正式用于生產。
- 功能。 實際開發中,有很多業務需求,其實并不需要完整的關系型數據庫功能,非關系型數據庫的功能就足夠使用了。這種情況下,使用性能更高、成本更低的非關系型數據庫當然是更明智的選擇。
非關系型數據庫在某些特定的領域很好用,比如redis作為數據的緩存,數據是存儲在內存中,所以性能非常好,底層只有三萬條代碼,貌似知乎就用到了redis作為數據庫。
非關系數據庫只實現了關系數據庫一部分的功能,但因此很大程度上擴充了某些功能的性能。一般用關系數據庫就夠了。嚴格說mysql在關系數據庫兄是實現得也不是很完整的一類,從而在某些查詢上,mysql有超出嚴格關系數據庫很多的性能。具體應用需要權衡,特別是關聯條件很多的數據,非關系數據庫一般不合適,有時候甚至mysql也不合適。