Redis是一個 Key-Value 存儲系統(tǒng)绞呈。和 Memcached 類似,它支持存儲的 value 類型相對更多间景,包括 string(字符串)佃声、 list(鏈表)、 set(集合)和 zset(有序集合)倘要。這些數(shù)據(jù)類型都支持 push/pop圾亏、add/remove 及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的封拧。在此基礎上志鹃,Redis 支持各種不同方式的排序。與 memcached 一樣泽西,為了保證效率曹铃,數(shù)據(jù)都是緩存在內(nèi)存中。區(qū)別的是 Redis 會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件捧杉,并且在此基礎上實現(xiàn)了 master-slave(主從)同步陕见。
Key-Value存儲系統(tǒng)
Key-Value Store 是當下比較流行的話題,尤其在構建諸如搜索引擎糠溜、IM淳玩、P2P直撤、游戲服務器非竿、SNS 等大型互聯(lián)網(wǎng)應用以及提供云計算服務的時候,怎樣保證系統(tǒng)在海量數(shù)據(jù)環(huán)境下的高性能谋竖、高可靠性红柱、高擴展性承匣、高可用性、低成本成為所有系統(tǒng)架構們挖苦心思考慮的重點锤悄,而
怎樣解決數(shù)據(jù)庫服務器的性能瓶頸是最大的挑戰(zhàn)韧骗。
Key-Value Store 更加注重對海量數(shù)據(jù)存取的性能、分布式零聚、擴展性支持上袍暴,并不需要傳統(tǒng)關系數(shù)據(jù)庫的一些特征,例如:Schema隶症、事務政模、完整 SQL 查詢支持等等,因此在分布式環(huán)境下的性能相對于傳統(tǒng)的關系數(shù)據(jù)庫有較大的提升蚂会。
為什么要選擇Key-Value Store
大規(guī)牧苎互聯(lián)網(wǎng)應用
一類是仍然采用RDBMS,然后通過對數(shù)據(jù)庫的垂直和水平切分將整個數(shù)據(jù)庫部署到一個集群上胁住,缺點在于它是針對特定應用趁猴,通用型不足
另一類就是google采用的方法,拋棄RDBMS彪见,采用Key-Value形式存儲儡司,這樣可以極大增強系統(tǒng)的可擴展性。
云存儲
如果說上一個問題還有可以替代的解決方案(切割數(shù)據(jù)庫)的話余指,那么對于云存儲來說枫慷,也許 key-value 的 store 就是唯一的解決方案了。云存儲簡單點說就是構建一個大型的存儲平臺給別人用浪规,這也就意味著在這上面運行的應用其實是不可控的或听。如果其中某個客戶的應用隨著用戶的增長而不斷增長時,云存儲供應商是沒有辦法通過數(shù)據(jù)庫的切割來達到 scale 的笋婿,因為這個數(shù)據(jù)是客戶的誉裆,供應商不了解這個數(shù)據(jù)自然就沒法作出切割。在這種情況下缸濒,key-value 的 store 就是唯一的選擇了足丢,因為這種條件下的 scalability 必須是自動完成的,不能有人工干預庇配。這也是為什么幾乎所有的現(xiàn)有的云存儲都是 key-value 形式的斩跌,例如 Amazon的 smipleDB,底層實現(xiàn)就是 key-value捞慌,還有 google 的 GoogleAppEngine耀鸦,采用的是 BigTable的存儲形式。
Key-Value Store最大的特點就是它的可擴展性,這也就是它最大的優(yōu)勢袖订。所謂的可擴展性氮帐,
在我看來這里包括了兩方面內(nèi)容。一方面洛姑,是指 Key-Value Store 可以支持極大的數(shù)據(jù)的存儲上沐,它的分布式的架構決定了只要有更多的機器,就能夠保證存儲更多的數(shù)據(jù)楞艾。另一方面参咙,是指它可以支持數(shù)量很多的并發(fā)的查詢。對于 RDBMS硫眯,一般幾百個并發(fā)的查詢就可以讓它很吃
力了昂勒,而一個 Key-Value Store,可以很輕松的支持上千的并發(fā)查詢舟铜。下面而簡單的羅列了一些特點:
? Key-value store:一個 key-value 數(shù)據(jù)存儲系統(tǒng)戈盈,只支持一些基本操作,如: SET(key, value) 和 GET(key) 等谆刨;
? 分布式:多臺機器(nodes)同時存儲數(shù)據(jù)和狀態(tài)塘娶,彼此交換消息來保持數(shù)據(jù)一致,可視為一個完整的存儲系統(tǒng)痊夭。
? 數(shù)據(jù)一致:所有機器上的數(shù)據(jù)都是同步更新的刁岸、不用擔心得到不一致的結果;
? 冗余:所有機器(nodes)保存相同的數(shù)據(jù)她我,整個系統(tǒng)的存儲能力取決于單臺機器(node)的能力虹曙;
? 容錯:如果有少數(shù) nodes 出錯,比如重啟番舆、當機酝碳、斷網(wǎng)、網(wǎng)絡丟包等各種 fault/fail 都不影響整個系統(tǒng)的運行恨狈;
? 高可靠性:容錯疏哗、冗余等保證了數(shù)據(jù)庫系統(tǒng)的可靠性。
初識Redis
Redis是一個開源的使用ANSI C語言編寫禾怠,支持網(wǎng)絡返奉、可基于內(nèi)存且可持久化的日志型、Key-Value數(shù)據(jù)庫吗氏,并且提供多個語言的API芽偏,訪問十分便捷。
Redis數(shù)據(jù)類型:
作為 Key-value 型數(shù)據(jù)庫弦讽,Redis 也提供了鍵(Key)和鍵值(Value)的映射關系污尉。但是,除
了常規(guī)的數(shù)值或字符串,Redis 的鍵值還可以是以下形式之一:
? Lists (列表)
? Sets (集合)
? Sorted sets (有序集合)
? Hashes (哈希表)
鍵值的數(shù)據(jù)類型決定了該鍵值支持的操作十厢。Redis 支持諸如列表等太、集合或有序集合的交集捂齐、并集蛮放、查集等高級原子操作;同時奠宜,如果鍵值的類型是普通數(shù)字包颁,Redis 則提供自增等原子操作。
Redis持久化:
通常压真,Redis 將數(shù)據(jù)存儲于內(nèi)存中娩嚼,或被配置為使用虛擬內(nèi)存。通過兩種方式可以實現(xiàn)數(shù)據(jù)持久化:使用截圖的方式滴肿,將內(nèi)存中的數(shù)據(jù)不斷寫入磁盤岳悟;或使用類似 MySQL 的日志方式绷柒,記錄每次更新的日志伶授。前者性能較高,但是可能會引起一定程度的數(shù)據(jù)丟失铆惑;后者相反堆缘。
Redis主從同步:
Redis支持將數(shù)據(jù)同步到多臺從庫滔灶,這種特性對提高讀取性能非常有益
Redis性能:
相比需要依賴磁盤記錄每個更新的數(shù)據(jù)庫,基于內(nèi)存的特性無疑給Redis帶來了非常優(yōu)秀的性能吼肥,讀寫操作之間有顯著的性能差異
性能測試結果:
SET操作每秒鐘 110000 次录平,GET操作每秒鐘 81000 次,服務器配置如下:
Linux 2.6, Xeon X3320 2.5Ghz.
stackoverflow 網(wǎng)站使用 Redis 做為緩存服務器缀皱。
適用場合:
Redis其實開創(chuàng)了一種新的數(shù)據(jù)存儲思路斗这,使用Redis,我們不用再面對功能單調(diào)的數(shù)據(jù)庫時啤斗,把精力放在如何把大象放進冰箱的問題涝影,而是利用Redis提供的靈活多變的數(shù)據(jù)結構和數(shù)據(jù)操作,為不同的大象構建不同的冰箱争占。
下面是一些Redis常用的場景:
1. 取最新N個數(shù)據(jù)的操作
比如典型的取你網(wǎng)站的最新文章燃逻,通過下面方式,我們可以將最新的 5000 條評論的 ID 放在Redis 的 List 集合中臂痕,并將超出集合部分從數(shù)據(jù)庫獲取伯襟。使用 LPUSH latest.comments命令,向 list 集合中插入數(shù)據(jù)插入完成后再用 LTRIM latest.comments 0 5000 命令使其永遠只保存最近 5000 個 ID如果你還有不同的篩選維度握童,比如某個分類的最新 N 條姆怪,那么你可以再建一個按此分類的List,只存 ID 的話,Redis 是非常高效的稽揭。
2. 排行榜應用俺附,取TOP N操作
這個需求與上面需求的不同之處在于,前面操作以時間為權重溪掀,這個是以某一個條件為權重事镣,比如按購買的次數(shù)或者頂?shù)拇螖?shù),這時候就需要 sorted set 出馬揪胃,將你要排序的值設置為sorted set的score璃哟,將具體的數(shù)據(jù)設置為相應的value,每次只需要執(zhí)行一條ZADD命令即可喊递。
3. 需要精確設定過期時間的應用
比如你可以把上面說到的 sorted set 的 score 值設置成過期時間的時間戳随闪,那么就可以簡單地通過過期時間排序,定時清除過期數(shù)據(jù)了骚勘,不僅是清除 Redis 中的過期數(shù)據(jù)铐伴,你完全可以把 Redis 里這個過期時間當成是對數(shù)據(jù)庫中數(shù)據(jù)的索引,用 Redis 來找出哪些數(shù)據(jù)需要過期刪除俏讹,然后再精準地從數(shù)據(jù)庫中刪除相應的記錄当宴。
4.計數(shù)器應用
Redis的命令是原子性的,你可以輕松利用INCR藐石、DECR命令來構建計數(shù)器系統(tǒng)(底層的寫入是單線程模型即供,并發(fā)寫會按到位順序執(zhí)行)
5.Uniq操作,獲取某段時間所有數(shù)據(jù)去重值
這個使用Redis的Set數(shù)據(jù)結構最合適于微,只需要不斷將數(shù)據(jù)往Set中扔就行逗嫡,set就是集合,會自動去重
6.實時系統(tǒng)株依、發(fā)垃圾系統(tǒng)
通過上面說到的 set 功能驱证,你可以知道一個終端用戶是否進行了某個操作,可以找到其操作的集合并進行分析統(tǒng)計對比等恋腕。沒有做不到抹锄,只有想不到。
7.Pub荠藤、Sub構建實時消息系統(tǒng)
Redis 的 Pub/Sub 系統(tǒng)可以構建實時的消息系統(tǒng)伙单,比如很多用 Pub/Sub 構建的實時聊天系統(tǒng)的例子。
8. 構建隊列系統(tǒng)
使用list可以構建隊列系統(tǒng)哈肖,使用sorted set 甚至可以構建有優(yōu)先級的隊列系統(tǒng)吻育。
9. 緩存
性能優(yōu)于Memcached,并且更優(yōu)秀的在于數(shù)據(jù)結構更加多樣化
Redis作者的宣言
宣言中淤井,作者列舉了Redis的7大原則布疼,可以向大家闡明Redis的思想摊趾,看了之后我覺得Redis的作者的確牛叉,Redis這款產(chǎn)品這的是程序猿的福利:
1. Redis 是一個操作數(shù)據(jù)結構的語言工具游两,它提供基于 TCP 的協(xié)議以操作豐富的數(shù)據(jù)結構砾层。
在 Redis 中,數(shù)據(jù)結構這個詞的意義不僅表示在某種數(shù)據(jù)結構上的操作贱案,更包括了結構本身及這些操作的時間空間復雜度肛炮。
2. Redis 定位于一個內(nèi)存數(shù)據(jù)庫,正是由于內(nèi)存的快速訪問特性轰坊,才使得 Redis 能夠有如此高的性能铸董,才使得 Redis 能夠輕松處理大量復雜的數(shù)據(jù)結構祟印,Redis 會嘗試其它的存儲方面的選擇肴沫,但是永遠不會改變它是一個內(nèi)存數(shù)據(jù)庫的角色。
3. Redis 使用基礎的 API 操作基礎的數(shù)據(jù)結構蕴忆, Redis 的 API 與數(shù)據(jù)結構一樣颤芬,都是一些最基礎的元素,你幾乎可以將任何信息交互使用此 API 格式表示套鹅。作者調(diào)侃說站蝠,如果有其它非人類的智能生物存在,他們也能理解 Redis 的 API卓鹿。因為它是如此的基礎菱魔。(作者大大很有趣)
4. Redis 有著詩一般優(yōu)美的代碼,經(jīng)常有一些不太了解 Redis 有的人會建議 Redis 采用一些其它人的代碼吟孙,以實現(xiàn)一些 Redis 未實現(xiàn)的功能澜倦,但這對我們來說就像是非要給《紅樓夢》接上后四十回一樣。
5.Redis 始終避免復雜化杰妓,我們認為設計一個系統(tǒng)的本質(zhì)藻治,就是與復雜化作戰(zhàn)。我們不會為了一個小功能而往源碼里添加上千行代碼巷挥,解決復雜問題的方法就是讓復雜問題永遠不要提復雜的問題桩卵。
6.Redis 支持兩個層成的 API,第一個層面包含部分操作 API倍宾,但它支持用于分布式環(huán)境下的 Redis雏节。第二個層面的 API 支持更復雜的 multi-key 操作。它們各有所長高职,但是我們不會推出兩者都支持的 API钩乍,但我們希望能夠提供實例間數(shù)據(jù)遷移的命令,并執(zhí)行 multi-key 操作初厚。
7. 我們以優(yōu)化代碼為樂件蚕,我們相信編碼是一件辛苦的工作孙技,唯一對得起這辛苦的就是去享受它。如果我們在編碼中失去了樂趣排作,那最好的解決辦法就是停下來牵啦。我們決不會選擇讓Redis不好玩的開發(fā)模式。
實際MySQL是適合進行海量數(shù)據(jù)存儲的妄痪,通過Memcached將熱點數(shù)據(jù)加載到cache哈雏,加速訪問,很多公司都曾經(jīng)使用過這樣的架構衫生,但隨著業(yè)務數(shù)據(jù)量的不斷增加裳瘪,和訪問量的持續(xù)增長,我們遇到了很多問題:
MySQL需要不斷進行拆庫拆表罪针,Memcached也需不斷跟著擴容彭羹,擴容和維護工作占據(jù)大量開發(fā)時間。
Memcached與MySQL數(shù)據(jù)庫數(shù)據(jù)一致性問題泪酱。
Memcached數(shù)據(jù)命中率低或down機派殷,大量訪問直接穿透到DB,MySQL無法支撐墓阀。
跨機房cache同步問題毡惜。
眾多NoSQL百花齊放,如何選擇
最近幾年斯撮,業(yè)界不斷涌現(xiàn)出很多各種各樣的NoSQL產(chǎn)品经伙,那么如何才能正確地使用好這些產(chǎn)品,最大化地發(fā)揮其長處勿锅,是我們需要深入研究和思考的問題帕膜,實際歸根結底最重要的是了解這些產(chǎn)品的定位,并且了解到每款產(chǎn)品的tradeoffs粱甫,在實際應用中做到揚長避短泳叠,總體上這些NoSQL主要用于解決以下幾種問題
少量數(shù)據(jù)存儲,高速讀寫訪問茶宵。此類產(chǎn)品通過數(shù)據(jù)全部in-momery 的方式來保證高速訪問危纫,同時提供數(shù)據(jù)落地的功能,實際這正是Redis最主要的適用場景乌庶。
海量數(shù)據(jù)存儲种蝶,分布式系統(tǒng)支持,數(shù)據(jù)一致性保證瞒大,方便的集群節(jié)點添加/刪除螃征。
這方面最具代表性的是dynamo和bigtable 2篇論文所闡述的思路。前者是一個完全無中心的設計透敌,節(jié)點之間通過gossip方式傳遞集群信息盯滚,數(shù)據(jù)保證最終一致性踢械,后者是一個中心化的方案設計,通過類似一個分布式鎖服務來保證強一致性,數(shù)據(jù)寫入先寫內(nèi)存和redo log魄藕,然后定期compat歸并到磁盤上内列,將隨機寫優(yōu)化為順序?qū)懀岣邔懭胄阅堋?/p>
Schema free背率,auto-sharding等话瞧。比如目前常見的一些文檔數(shù)據(jù)庫都是支持schema-free的,直接存儲json格式數(shù)據(jù)寝姿,并且支持auto-sharding等功能交排,比如mongodb。
面對這些不同類型的NoSQL產(chǎn)品,我們需要根據(jù)我們的業(yè)務場景選擇最合適的產(chǎn)品饵筑。
Redis適用場景埃篓,如何正確的使用
相關廠商內(nèi)容
前面已經(jīng)分析過稻薇,Redis最適合所有數(shù)據(jù)in-momory的場景嫂冻,雖然Redis也提供持久化功能,但實際更多的是一個disk-backed的功能塞椎,跟傳統(tǒng)意義上的持久化有比較大的差別桨仿,那么可能大家就會有疑問,似乎Redis更像一個加強版的Memcached案狠,那么何時使用Memcached,何時使用Redis呢服傍?
Redis與Memcached的比較
網(wǎng)絡IO模型
Memcached是多線程,非阻塞IO復用的網(wǎng)絡模型骂铁,分為監(jiān)聽主線程和worker子線程吹零,監(jiān)聽線程監(jiān)聽網(wǎng)絡連接,接受請求后拉庵,將連接描述字pipe 傳遞給worker線程灿椅,進行讀寫IO, 網(wǎng)絡層使用libevent封裝的事件庫,多線程模型可以發(fā)揮多核作用钞支,但是引入了cache coherency和鎖的問題茫蛹,比如,Memcached最常用的stats 命令烁挟,實際Memcached所有操作都要對這個全局變量加鎖婴洼,進行計數(shù)等工作,帶來了性能損耗撼嗓。
(Memcached網(wǎng)絡IO模型)
Redis使用單線程的IO復用模型柬采,自己封裝了一個簡單的AeEvent事件處理框架欢唾,主要實現(xiàn)了epoll、kqueue和select粉捻,對于單純只有IO操作來說匈辱,單線程可以將速度優(yōu)勢發(fā)揮到最大,但是Redis也提供了一些簡單的計算功能杀迹,比如排序亡脸、聚合等,對于這些操作树酪,單線程模型實際會嚴重影響整體吞吐量浅碾,CPU計算過程中,整個IO調(diào)度都是被阻塞住的续语。
內(nèi)存管理方面
Memcached使用預分配的內(nèi)存池的方式垂谢,使用slab和大小不同的chunk來管理內(nèi)存,Item根據(jù)大小選擇合適的chunk存儲疮茄,內(nèi)存池的方式可以省去申請/釋放內(nèi)存的開銷滥朱,并且能減小內(nèi)存碎片產(chǎn)生,但這種方式也會帶來一定程度上的空間浪費力试,并且在內(nèi)存仍然有很大空間時徙邻,新的數(shù)據(jù)也可能會被剔除,原因可以參考Timyang的文章:http://timyang.net/data/Memcached-lru-evictions/
Redis使用現(xiàn)場申請內(nèi)存的方式來存儲數(shù)據(jù)畸裳,并且很少使用free-list等方式來優(yōu)化內(nèi)存分配缰犁,會在一定程度上存在內(nèi)存碎片,Redis跟據(jù)存儲命令參數(shù)怖糊,會把帶過期時間的數(shù)據(jù)單獨存放在一起帅容,并把它們稱為臨時數(shù)據(jù),非臨時數(shù)據(jù)是永遠不會被剔除的伍伤,即便物理內(nèi)存不夠并徘,導致swap也不會剔除任何非臨時數(shù)據(jù)(但會嘗試剔除部分臨時數(shù)據(jù)),這點上Redis更適合作為存儲而不是cache扰魂。
數(shù)據(jù)一致性問題
Memcached提供了cas命令麦乞,可以保證多個并發(fā)訪問操作同一份數(shù)據(jù)的一致性問題。 Redis沒有提供cas 命令阅爽,并不能保證這點路幸,不過Redis提供了事務的功能,可以保證一串 命令的原子性付翁,中間不會被任何操作打斷简肴。
存儲方式及其它方面
Memcached基本只支持簡單的key-value存儲,不支持枚舉百侧,不支持持久化和復制等功能
Redis除key/value之外砰识,還支持list,set,sorted set,hash等眾多數(shù)據(jù)結構能扒,提供了KEYS
進行枚舉操作,但不能在線上使用辫狼,如果需要枚舉線上數(shù)據(jù)初斑,Redis提供了工具可以直接掃描其dump文件,枚舉出所有數(shù)據(jù)膨处,Redis還同時提供了持久化和復制等功能见秤。
關于不同語言的客戶端支持
在不同語言的客戶端方面,Memcached和Redis都有豐富的第三方客戶端可供選擇真椿,不過因為Memcached發(fā)展的時間更久一些鹃答,目前看在客戶端支持方面,Memcached的很多客戶端更加成熟穩(wěn)定突硝,而Redis由于其協(xié)議本身就比Memcached復雜测摔,加上作者不斷增加新的功能等,對應第三方客戶端跟進速度可能會趕不上解恰,有時可能需要自己在第三方客戶端基礎上做些修改才能更好的使用锋八。
根據(jù)以上比較不難看出,當我們不希望數(shù)據(jù)被踢出护盈,或者需要除key/value之外的更多數(shù)據(jù)類型時挟纱,或者需要落地功能時,使用Redis比使用Memcached更合適黄琼。
關于Redis的一些周邊功能
Redis除了作為存儲之外還提供了一些其它方面的功能樊销,比如聚合計算、pubsub脏款、scripting等,對于此類功能需要了解其實現(xiàn)原理裤园,清楚地了解到它的局限性后撤师,才能正確的使用,比如pubsub功能拧揽,這個實際是沒有任何持久化支持的剃盾,消費方連接閃斷或重連之間過來的消息是會全部丟失的,又比如聚合計算和scripting等功能受Redis單線程模型所限淤袜,是不可能達到很高的吞吐量的痒谴,需要謹慎使用。
總的來說Redis作者是一位非常勤奮的開發(fā)者铡羡,可以經(jīng)郴担看到作者在嘗試著各種不同的新鮮想法和思路,針對這些方面的功能就要求我們需要深入了解后再使用烦周。
總結:
Redis使用最佳方式是全部數(shù)據(jù)in-memory尽爆。
Redis更多場景是作為Memcached的替代者來使用怎顾。
當需要除key/value之外的更多數(shù)據(jù)類型支持時,使用Redis更合適漱贱。
當存儲的數(shù)據(jù)不能被剔除時槐雾,使用Redis更合適。
后續(xù)關于Redis文章計劃:
Redis數(shù)據(jù)類型與容量規(guī)劃幅狮。
如何根據(jù)業(yè)務場景搭建穩(wěn)定募强,可靠,可擴展的Redis集群崇摄。
Redis參數(shù)钻注,代碼優(yōu)化及二次開發(fā)基礎實踐。