引言:
nosql 的興起和革命,在我看來已經開始逐漸影響到了傳統的sql的地位,但是僅僅是影響而已,取代是不太可能的。
正文:
兩年前,一個偶然的機會開始接觸到 nosql ( mongodb )。用來作數據挖掘的存儲容器,第一次接觸到nosql,真的被它驚艷到了。鄙人受到傳統的SQL的思維定勢,甚至一時間難以接受。
mongodb是一個非關系型文檔數據庫,非常適合文檔類型的數據的存儲,查詢也十分方便,支持動態的橫向和縱向的數據擴展。愛不釋手。下個用幾行shell來展示一下mongodb的魅力
show dbs; //無則會創建 use mydb; show collections; //新建集合,相當於mysql中的表 db.createCollection('mycollection'); //插入數據. db.mycollection.insert({'username':'chenqimiao','age':23}) db.mycollection.insert({'username':'cqm','age':23,'sex':'男'}) //查詢 db.mycollection.find({"age":23});
類比mysql的話,最大的區別可能在於表結構或者說集合結構的定義了,mysql的列是預定義的,而mongodb是在插入數據的時候才確定這條數據的列。
所以mongodb可以支持動態擴展列,在mongo中可能不叫列,要叫作域(field)吧。
后來花了一點時間了解了一下 HBase , Habase 誕生於 Hadoop的子項目,受大數據的遺傳。單表可以非常的大,同樣Habase也是基於列的。
印象中HBase 中有一個非常特別的特性,HBase 的數據覆蓋,並不是實際的覆蓋。HBase 有一個時間維度的概念,所有的數據都是基於這個維度進行存儲的,
簡單點說,同樣的key 可能在HBase 中存在兩個value。它是怎么做到的?因為它給每一條記錄都偷偷記錄了一個timestamp,每次你去覆蓋鍵值對的時候,你以為你已經刪除了舊值,替換成了新
值,而實際上只是再添加了一條記錄而已,兩條記錄共存在一個時間維度上。每次get(key)默認取最新的一個value,僅此而已。
你還可以手動設置失效時間TTL,這樣每一個值就會有一個有效期,過了有效期都值是不能get出來了,但是值還是存儲在HBase中並未丟失。
再后來接觸到了 redis ,相信大家對這個都相當的熟悉,基於內存的緩存數據庫,簡單的set() get()就可以了緩存一些經常使用到的值,之前我也專門介紹過 redis的安裝,shell命令,多實例部署,讀寫分離,主從復制,哨兵 等等問題。
當數據超過一定量 , redis會把數據swap到文件中去,如果使用到swap中的文件的值,redis會把文件在swap到內存中,進行讀寫,十分智能。支持的數據類型也比較多 ,除了k-v 還有hset ,hash ,zset等等
最近在做在線課堂,用到了 Memcache ,這個東西基本和redis的使用場景相似,基於內存的nosql。但是支持的數據類型只有k-v的形式。這是不同於redis的一點。
其二的話, Memcache 不支持文件持久化。
其三, memcache 的多實例,是基於客戶端的,這個比較有意思要好好聊一聊了,象我們平常接觸的mysql,redis,多實例同步基本是讀寫分離,主從復制,主機寫,從機讀,這樣的模式。可以說是基於服務端的多實例方案。但是 memcache 有點好玩了,它的多實例之間不進行同步,那它是怎么做到負載均衡的時候保證數據的完整性呢?
說到這里,我想先介紹一下memcache的主流的客戶端程序(JAVA)
- 官方提供的基於傳統阻塞io由Greg Whalin維護的客戶端
較早推出的客戶端,穩定,持久運行。
- Dustin Sallings實現的基於java nio的Spymemcached
A simple, asynchronous, single-threaded memcached client written in java. 支持異步,單線程的memcached客戶端,用到了java1.5版本的concurrent和nio,存取速度會高於前者,但是穩定性不好,測試中常 報timeOut等相關異常。
- XMemcache
Memcached同樣是基於java nio的客戶端,java nio相比於傳統阻塞io模型來說,有效率高(特別在高並發下)和資源耗費相對較少的優點。傳統阻塞IO為了提高效率,需要創建一定數量的連接形成連接 池,而nio僅需要一個連接即可(當然,nio也是可以做池化處理),相對來說減少了線程創建和切換的開銷,這一點在高並發下特別明顯。因此 XMemcached與Spymemcached在性能都非常優秀,在某些方面(存儲的數據比較小的情況下)Xmemcached比 Spymemcached的表現更為優秀,具體可以看這個Java Memcached Clients Benchmark。
根據上面的介紹,大概可以了解到,官方提供的是阻塞的客戶端,要利用線程池來實現並發,但是官方提供的包還有一個非常致命的問題,不提供 CAS 的同步功能。
什么是 CAS ?
了解過java下面的 java.util.concurrent.atomic; 下面的類的同學應該知道, CAS (check and swap),這是一種樂觀鎖的實現,程序陷入一個循環,得到舊值,執行CAS方法,傳入舊值,新值,若舊值未發生變化 ,則用舊值,換出新值。
while(true){ Object oldValue = atomicObject.get("key1") ; Object newValue = new Object(); Object value = checkAndSwap("key1",oldValue,newValue) ; if(value!=null&&oldValue.equals(value)) break; }
解釋完 CAS ,回到剛才的問題,為什么沒有實現 CAS ,是官方包一個致命的弱點,明白了 CAS 原理的同學,應該發現其實它就是一個同步的手段,那為什么不能使用 syncronized 的。那是因為數據並發發生在不同項目里面,沒有辦法給多個項目之前共用一個 syncronized 。這個時候只能利用memcache提供的鎖,利用 CAS 作為同步手段。
慶幸spyMemcache和xmemcache都提供了 CAS 的操作。
那么問題來了,剛剛最早提出的問題如何解答?( CAS 多實例同步問題。)其實啊memcache根本不需要進行多實例同步,它的多實例是依賴於客戶端程序實現的。
比如spymemcache:
para.memcache.server=192.168.202.121:11211,192.168.202.121:11210 配置兩個server即可。其余的操作和操作一個memcache是一摸一樣的,客戶端會根據Hash散列算法( HashMap 的實現算法)將鍵值對放到對應的 memcache 中,由於算法一致性,所以存取雙方都能得知鍵值在哪一個 memcache 中。這樣就實現了多實例了。
另外spymemcache客戶端還有一個比較厲害的地方,它能直接將java對象序列化,作為k-v中的v,get(k)的時候自動反序列化成對象,無需直接操作JSONObject,當然前提是對象實現
Serializable 接口,並給定一個 serialVersionUID 。redis也可以直接存儲二進制文件,但是官方提供的客戶端程序,並沒有封裝好序列化和反序列化,需要自己實現。
說了這么多nosql,好像傳統sql無用武之地一般。其實傳統的sql才是最穩定的,適用面積最廣,最安全,並且提供了事務回滾,保證數據一致性,這是nosql為了提高速度,增強擴展性,所要面臨的一部分舍棄。