常見NoSQL的對比及使用場景(Redis,memcached,mongodb)

1. NoSQL

NoSQL(Not Only SQL),泛指非關係型的數據庫(mysql、oracle、sqlserver都是關係型數據庫)。html

1.1 NoSQL特色

  • 數據之間無關係,隨意擴展node

  • 數據存儲簡單,能夠存在內存中,讀寫速度快mysql

  • 不須要建表、字段。自定義格式redis

1.2 NoSQL數據庫的分類

分類算法

Examples舉例sql

典型應用場景數據庫

數據模型segmentfault

優勢數組

缺點緩存

鍵值(key-value

Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB,memcache

內容緩存,主要用於處理大量數據的高訪問負載,也用於一些日誌系統等等。

Key 指向 Value 的鍵值對,一般用hash table來實現

查找速度快

數據無結構化,一般只被看成字符串或者二進制數據

列存儲數據庫

Cassandra, HBase, Riak

分佈式的文件系統

以列簇式存儲,將同一列數據存在一塊兒

查找速度快,可擴展性強,更容易進行分佈式擴展

功能相對侷限

文檔型數據庫

CouchDB, MongoDb

Web應用(與Key-Value相似,Value是結構化的,不一樣的是數據庫可以瞭解Value的內容)

Key-Value對應的鍵值對,Value爲結構化數據

數據結構要求不嚴格,表結構可變,不須要像關係型數據庫同樣須要預先定義表結構

查詢性能不高,並且缺少統一的查詢語法。

圖形數據庫

Neo4J, InfoGrid, Infinite Graph

社交網絡,推薦系統等。專一於構建關係圖譜

圖結構

利用圖結構相關算法。好比最短路徑尋址,N度關係查找等

不少時候須要對整個圖作計算才能得出須要的信息,並且這種結構不太好作分佈式的集羣方案。

1.2.1 共同特徵

  1. 不須要預約義模式:不須要事先定義數據模式,預約義表結構。數據中的每條記錄均可能有不一樣的屬性和格式。當插入數據時,並不須要預先定義它們的模式。

  2. 無共享架構:相對於將全部數據存儲的存儲區域網絡中的全共享架構。NoSQL每每將數據劃分後存儲在各個本地服務器上。由於從本地磁盤讀取數據的性能每每好於經過網絡傳輸讀取數據的性能,從而提升了系統的性能。

  3. 彈性可擴展:能夠在系統運行的時候,動態增長或者刪除結點。不須要停機維護,數據能夠自動遷移。

  4. 分區:相對於將數據存放於同一個節點,NoSQL數據庫須要將數據進行分區,將記錄分散在多個節點上面。而且一般分區的同時還要作複製。這樣既提升了並行性能,又能保證沒有單點失效的問題。

  5. 異步複製:和RAID存儲系統不一樣的是,NoSQL中的複製,每每是基於日誌的異步複製。這樣,數據就能夠儘快地寫入一個節點,而不會被網絡傳輸引發遲延。缺點是並不老是能保證一致性,這樣的方式在出現故障的時候,可能會丟失少許的數據。

  6. BASE:相對於事務嚴格的ACID(原子性,一致性,隔離性,持久性)特性,NoSQL數據庫保證的是BASE(BA——基本可用,S——軟狀態,柔性事務,E——最終一致性)特性。【注】CAP原理(C——一致性,A——可用性,P——分區容錯性,當前NoSQL大部分知足了AP原理)

1.2.2 適用場景 

NoSQL數據庫在如下的這幾種狀況下比較適用:一、數據模型比較簡單;二、須要靈活性更強的IT系統;三、對數據庫性能要求較高;四、不須要高度的數據一致性;五、對於給定key,比較容易映射覆雜值的環境。

2. 經常使用NoSQL介紹

2.1 Memcached

2.1.1 Memcached基礎

2.1.1.1 什麼是Memcached?

MemCache是一個開源的高性能的分佈式的內存對象緩存系統,用於各類動態應用以減輕數據庫負擔。它經過在內存中緩存數據和對象,來減小讀取數據庫的次數,從而提升動態、數據庫驅動應用速度。MemCache會在內存中開闢一塊空間,創建一個統一的巨大的hash表,hash表可以用來存儲各類格式的數據,包括圖像、視頻、文件以及數據庫檢索的結果等。

【注】MemCache 和 MemCached:MemCache是這個項目的名稱,而MemCached是服務器端的主程序名稱。

2.1.1.2 Memcached應用場景

一般在訪問量高的Web網站和應用中使用MemCache,用來緩解數據庫的壓力,而且提高網站和應用的響應速度

在應用程序中,咱們一般在如下節點來使用MemCached:

  1. 訪問頻繁的數據庫數據(身份token、首頁動態)
  2. 訪問頻繁的查詢條件和結果
  3. 做爲Session的存儲方式(提高Session存取性能)
  4. 頁面緩存
  5. 更新頻繁的非重要數據(訪客量、點擊次數)
  6. 大量的hot數據

經常使用工做流程(以下圖):


  1. 客戶端請求數據
  2. 檢查MemCached中是否有對應數據
  3. 有的話直接返回,結束
  4. 沒有的話,去數據庫裏請求數據
  5. 將數據寫入MemCached,供下次請求時使用
  6. 返回數據,結束

緩存到MemCached中的數據庫數據,在更新數據庫時要同時更新MemCached。

2.1.2 Memcached工做原理

MemCached採用了C/S架構,在Server端啓動後,以守護程序的方式,監聽客戶端的請求。啓動時能夠指定監聽的IP(服務器的內網ip/外網ip)、端口號(因此作分佈式測試時,一臺服務器上能夠啓動多個不一樣端口號的MemCached進程)、使用的內存大小等關鍵參數。一旦啓動,服務就會一直處於可用狀態。
爲了提升性能,MemCached緩存的數據所有存儲在MemCached管理的內存中,因此重啓服務器以後緩存數據會清空,不支持持久化

2.1.2.1 Memcached內存管理

1. 內存結構

  1. slab_class裏,存放的是一組組chunk大小相同的slab
  2. 每一個slab裏面包含若干個page,page的默認大小是1M,若是slab大小100M,就包含100個page
  3. 每一個page裏面包含若干個chunk,chunk是數據的實際存放單位,每一個slab裏面的chunk大小相同

2. 內存分配方式

  1. Memcached利用slab allocation機制來分配和管理內存,它按照預先規定的大小,將分配的內存分割成特定長度的內存塊,再把尺寸相同的內存塊分紅組,數據在存放時,根據鍵值大小去匹配slab大小,找就近的slab存放,因此存在空間浪費現象。而傳統的內存管理方式是,使用完經過malloc分配的內存後經過free來回收內存,這種方式容易產生內存碎片並下降操做系統對內存的管理效率。
  2. 存放數據時,首先slab要申請內存,申請內存是以page爲單位的。因此在放入第一個數據的時候,不管大小爲多少,都會有1M大小的page被分配給該slab。申請到page後,slab會將這個page的內存按chunk的大小進行切分,這樣就變成了一個chunk數組,最後從這個chunk數組中選擇一個用於存儲數據。

MemCache中的value存放位置是由value的大小決定,value會被存放到與chunk大小最接近的一個slab中,好比slab[1]的chunk大小爲88字節、slab[2]的chunk大小爲112字節、slab[3]的chunk大小爲144字節(默認相鄰slab內的chunk基本以1.25爲比例進行增加,MemCache啓動時能夠用-f指定這個比例),那麼一個100字節的value,將被放到2號slab中。

3. 內存回收方式

  1. 當數據容量用完MemCached分配的內存後,就會基於LRU(Least Recently Used,最近最少使用)算法清理失效的緩存數據(放入數據時可設置失效時間),或者清理最近最少使用的緩存數據,而後放入新的數據。
  2. 在LRU中,MemCached使用的是一種Lazy Expiration策略,本身不會監控存入的key/vlue對是否過時,而是在獲取key值時查看記錄的時間戳,檢查key/value對空間是否過時,這樣可減輕服務器的負載。
  3. 須要注意的是,若是若是MemCache啓動沒有追加-M,則表示禁止LRU,這種狀況下內存不夠會報Out Of Memory錯誤。

針對MemCache的內存分配及回收算法,總結三點:

  1. MemCache的內存分配chunk裏面會有內存浪費,88字節的value分配在128字節(緊接着大的用)的chunk中,就損失了30字節,可是這也避免了管理內存碎片的問題
  2. MemCache的LRU算法不是針對全局的,是針對slab的
  3. 應該能夠理解爲何MemCache存放的value大小是限制的,由於一個新數據過來,slab會先以page爲單位申請一塊內存,申請的內存最多就只有1M,因此value大小天然不能大於1M了

2.1.2.2 Memcached分佈式

爲了提高MemCached的存儲容量和性能,咱們應用的客戶端可能會對應多個MemCached服務器來提供服務,這就是MemCached的分佈式。

1. 分佈式實現原理

MemCached的目前版本是經過C實現,採用了單進程、多線程、異步I/O,基於事件(event_based)的服務方式.使用libevent做爲事件通知實現。多個Server能夠協同工做,但這些 Server 之間保存的數據各不相同,並且並不通訊(與之造成對比的,好比JBoss Cache,某臺服務器有緩存數據更新時,會通知集羣中其餘機器更新緩存或清除緩存數據),每一個Server只是對本身的數據進行管理

Client端經過IP地址和端口號指定Server端,將須要緩存的數據是以key->value對的形式保存在Server端。key的值經過hash進行轉換,根據hash值把value傳遞到對應的具體的某個Server上。當須要獲取對象數據時,也根據key進行。首先對key進行hash,經過得到的值能夠肯定它被保存在了哪臺Server上,而後再向該Server發出請求。Client端只須要知道保存hash(key)的值在哪臺服務器上就能夠了

2. 分佈式算法解析

  1. 餘數算法:
    先求得鍵的整數散列值(也是就鍵string的HashCode值 什麼是HashCode),再除以服務器臺數,根據餘數肯定存取服務器,這種方法計算簡單,高效,但在memcached服務器增長或減小時,幾乎全部的緩存都會失效
  2. 散列算法(一致性hash):
    先算出MemCached服務器的散列值,並將其分佈到0到2^32的圓上,而後用一樣的方法算出存儲數據的鍵的散列值並映射至圓上,最後從數據映射到的位置開始順時針查找,將數據保存到查找到的第一個服務器上,若是超過2的32次方,依然找不到服務器,就將數據保存到第一臺MemCached服務器上。若是添加了一臺MemCached服務器,只在圓上增長服務器的逆時針方向的第一臺服務器上的鍵會受到影響

2.1.2.3 Memcached線程管理

MemCached網絡模型是典型的單進程多線程模型,採用libevent處理網絡請求主進程負責將新來的鏈接分配給work線程,work線程負責處理鏈接,有點相似與負載均衡,經過主進程分發到對應的工做線程。

MemCached默認有7個線程,4個主要的工做線程,3個輔助線程,線程可劃分爲如下4種:

  1. 主線程,負責MemCached服務器初始化,監聽TCP、Unix Domain鏈接請求;
  2. 工做線程池,MemCached默認4個工做線程,可經過啓動參數修改,負責處理TCP、UDP,Unix域套接口鏈路上的請求;
  3. assoc維護線程,MemCached內存中維護一張巨大的hash表,該線程負責hash表動態增加
  4. slab維護線程,即內存管理模塊維護線程,負責class中slab的平衡,MemCached啓動選項中可關閉該線程。

2.1.3 MemCached特性與限制

  1. MemCache中能夠保存的item數據量是沒有限制的,只要內存足夠
  2. MemCache單進程在32位機中最大使用內存爲2G64位機則沒有限制
  3. Key最大爲250個字節,超過該長度沒法存儲
  4. 單個item最大數據是1MB,超過1MB的數據不予存儲
  5. MemCache服務端是不安全的,好比已知某個MemCache節點,能夠直接telnet過去,並經過flush_all讓已經存在的鍵值對當即失效,因此MemCache服務器最好配置到內網環境,經過防火牆制定可訪問客戶端
  6. 不可以遍歷MemCache中全部的item,由於這個操做的速度相對緩慢且會阻塞其餘的操做
  7. MemCached的高性能源自於兩階段哈希結構第一階段在客戶端,經過Hash算法根據Key值算出一個節點;第二階段在服務端,經過一個內部的Hash算法,查找真正的item並返回給客戶端。從實現的角度看,MemCache是一個非阻塞的、基於事件的服務器程序
  8. MemCache設置添加某一個Key值的時候,傳入expiry爲0表示這個Key值永久有效,這個Key值也會在30天以後失效。

2.2 Redis

Redis是一個key-value存儲系統。和Memcached相似,它支持存儲的value類型相對更多,包括string(字符串)、 list(鏈表)、set(集合)和zset(有序集合)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操做,並且這些操做都是原子性的。在此基礎上,redis支持各類不一樣方式的排序。與memcached同樣,爲了保證效率,數據都是緩存在內存中。區別的是redis會週期性的把更新的數據寫入磁盤或者把修改操做寫入追加的記錄文件,而且在此基礎上實現了master-slave(主從)同步,當前 Redis的應用已經很是普遍,國內像新浪、淘寶,國外像 Flickr、Github等均在使用Redis的緩存服務。

2.2.1 Redis數據結構

Redis支持豐富的數據類型,並提供了大量簡單高效的功能。Redis的底層數據結構總覽圖:

上圖列出了Redis內部底層的一些重要數據結構,包括List, Set, Hash, String等。

1. Redis Object 

redisObject定義了類型,編碼方式,LRU時間,引用計數,*ptr指向實際保存值指針

  • type: redisObject的類型,字符串,列表,集合,有序集,哈希表等

  • encoding: 底層實現結構,字符串,整數,跳躍表,壓縮列表等

  • ptr:實際指向保存值的數據結構

舉個具體例子,redisObject{type: REDIS_LIST, encoding:REDIS_ENCODING_LINKEDLIST}, 

這個對象是Redis列表,其值保存在一個鏈表中,ptr指針指向這個列表。Redis本身實現對象管理機制,並基於引用計數的垃圾回收。Redis提供了incrRefCount與decrRefCount來管理對象跟蹤對象的引用,  當減小引用時檢測計數器爲是否須要釋放內存對象。

2. RedisDB    

RedisDB內部數據結構,封裝了數據庫層面的信息:

從redisDB來看,幾個重要屬性:

  • id:數據庫內部編號,僅供內部操做使用,如AOF等

  • dict:存放整個數據庫的鍵值對,鍵爲字符串,值爲Redis的數據結構,如List, Set, Hash等。

  • expires:鍵的過時時間

3. RedisServer    

RedisServer代碼在Redis 3.0支持Cluster後變得較複雜,整體來講包含以下幾個大部分:

  • 通用部分:如pid進程id,數據庫指針,命令字典表,Sentinel模式標誌位等

  • 網絡信息:如port TCP監聽端口,Cluster Bus監聽socket, 已使用slot數量,Active的客戶端列表, 當前客戶端,Slaves列表等。

  • 其它信息:如AOF信息,統計信息, 配置信息(如已經配置總db數量dbnum等),日誌信息,Replication配置,Pub/Sub, Cluster信息,Lua腳本信息配置等等。

4. Redis Hash

Redis的哈希表/字典是其核心數據結構之一,值得深刻研究。Redis Hash數據結構, Hash新建立時,在不影響效率狀況下,Redis默認使用zipmap做爲底層實現以節省空間,只有當size超出必定限制後(hash-max-zipmap-entries ),Redis纔會自動把zipmap轉換爲下圖Hash Table。

上圖字典的底層實現爲哈希表,每一個字典包含2個哈希表,ht[0], ht[1], 1號哈希表是在rehash過程當中才使用的。而哈希表則由dictEntry構成。

每一個字典包含了3個內部數據結構:

  • Dict:字典的根結構,包含了2個dictht,其中2做爲rehashing之用

  • Dictht:包含了linkedlist dictEntry

  • DictEntry:包含了3個數據結構(double/uint64_6/int64t)的鏈表,相似Java HashMap中的Entry結構

5. Hash算法

目前Redis中引入了一些經典哈希算法,而HashTable則主要爲如下兩種:

  • MurmurHash2 32bit算法:著名的非加密型哈希函數,能產生32位或64位哈希值,最新版本爲MurmurHash3。該算法針對一個字符串進行哈希,可表現較強離散性。

  • 基於djb算法實現散列算法:該算法較爲簡單,一樣是將字符串轉換爲哈希值。主要利用字符串中的ASCII碼與一個隨機seed,進行變換獲得哈希值。

評估一個哈希算法的優劣,主要看其哈希值的離散均勻效果以及消除衝突程度

6. Rehash

相似Java中的HashMap, 當有新鍵值對添加到Redis字典時,有可能會觸發rehash。Redis中處理哈希碰撞的方法與Java同樣,都是採用鏈表法,整個哈希表的性能則依賴於它的大小size和它已經保存節點數量used的比率。比率在1:1時,哈希表的性能最好,若是節點數量比哈希表大小大不少的話,則整個哈希表就退化成多個鏈表,其性能優點全無。

上圖的哈希表,平均每次失敗查找須要訪問5個節點。爲了保持高效性能,在不修改鍵值對狀況下,須要進行rehash,目標是將ratio比率維持在1:1左右。Ratio = Used / Size

rehash觸發條件:

  • 天然rehash:ratio >= 1, 且變量dict_can_resize爲真

  • 強制rehash:ratio大於dict_force_resize_ratio(v 3.2.1版本爲5)

rehash執行過程:

  • 建立ht[1]並分配至少2倍於ht[0] table的空間

  • 將ht[0] table中的全部鍵值對遷移到ht[1] table

  • 將ht[0]數據清空,並將ht[1]替換爲新的ht[0]

Redis哈希爲了不整個rehash過程當中服務被阻塞,採用了漸進式的rehash,即rehash程序激活後,並非立刻執行直到完成,而是分屢次,漸進式(incremental)的完成。同時,爲了保證併發安全,在執行rehash中間執行添加時,新的節點會直接添加到ht[1]而不是ht[0], 這樣保證了數據的完整性與安全性。另外一方面,哈希的Rehash在還提供了創新的(相對於Java HashMap)收縮(shrink)字典,當可用節點遠遠大於已用節點的時候,rehash會自動進行收縮,具體過程與上面相似以保證比率始終高效使用。

2.2.2 Redis持久化

持久化簡單來說就是將數據放到斷電後數據不會丟失的設備中,也就是咱們一般理解的硬盤上。

首先咱們來看一下數據庫在進行寫操做時到底作了哪些事,主要有下面五個過程: 

  • 客戶端向服務端發送寫操做(數據在客戶端的內存中)。
  • 數據庫服務端接收到寫請求的數據(數據在服務端的內存中)。
  • 服務端調用write這個系統調用,將數據往磁盤上寫(數據在系統內存的緩衝區中)。
  • 操做系統將緩衝區中的數據轉移到磁盤控制器上(數據在磁盤緩存中)。
  • 磁盤控制器將數據寫到磁盤的物理介質中(數據真正落到磁盤上)。

經過上面5步的瞭解,可能咱們會但願搞清下面一些問題: 

  • 數據庫多長時間調用一次write,將數據寫到內核緩衝區?
  • 內核多長時間會將系統緩衝區中的數據寫到磁盤控制器?
  • 磁盤控制器又在何時把緩存中的數據寫到物理介質上?
  • 對於第一個問題,一般數據庫層面會進行全面控制。
  • 對第二個問題,操做系統有其默認的策略,可是咱們也能夠經過POSIX API提供的fsync系列命令強制操做系統將數據從內核區寫到磁盤控制器上。
  • 對於第三個問題,好像數據庫已經沒法觸及,但實際上,大多數狀況下磁盤緩存是被設置關閉的,或者是隻開啓爲讀緩存,也就是說寫操做不會進行緩存,直接寫到磁盤。建議的作法是僅僅當你的磁盤設備有備用電池時纔開啓寫緩存。 

數據損壞 

  所謂數據損壞,就是數據沒法恢復,上面咱們講的都是如何保證數據是確實寫到磁盤上去,可是寫到磁盤上可能並不意味着數據不會損壞。好比咱們可能一次寫請求會進行兩次不一樣的寫操做,當意外發生時,可能會致使一次寫操做安全完成,可是另外一次尚未進行。若是數據庫的數據文件結構組織不合理,可能就會致使數據徹底不能恢復的情況出現。 

這裏一般也有三種策略來組織數據,以防止數據文件損壞到沒法恢復的狀況: 

  • 第一種是最粗糙的處理,就是不經過數據的組織形式保證數據的可恢復性。而是經過配置數據同步備份的方式,在數據文件損壞後經過數據備份來進行恢復。實際上MongoDB在不開啓操做日誌,經過配置Replica Sets時就是這種狀況。
  • 另外一種是在上面基礎上添加一個操做日誌,每次操做時記一下操做的行爲,這樣咱們能夠經過操做日誌來進行數據恢復。由於操做日誌是順序追加的方式寫的,因此不會出現操做日誌也沒法恢復的狀況。這也相似於MongoDB開啓了操做日誌的狀況。
  • 更保險的作法是數據庫不進行舊數據的修改,只是以追加方式去完成寫操做,這樣數據自己就是一份日誌,這樣就永遠不會出現數據沒法恢復的狀況了。實際上CouchDB就是此作法的優秀範例。

2.2.2.1 Redis提供了RDB持久化和AOF持久化

RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤

也是默認的持久化方式,這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名爲dump.rdb

能夠經過配置設置自動作快照持久化的方式。咱們能夠配置redis在n秒內若是超過m個key被修改就自動作快照,下面是默認的快照保存配置

   save 900 1     #900秒內若是超過1個key被修改,則發起快照保存
   save 300 10    #300秒內容如超過10個key被修改,則發起快照保存
   save 60 10000

1. RDB文件保存過程

  • redis調用fork,如今有了子進程和父進程,此時服務進程將有短暫的停頓。
  • 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。因爲os的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會爲父進程要修改的頁面建立副本,而不是寫共享的頁面。因此子進程的地址空間內的數據是fork時刻整個數據庫的一個快照。
  • 當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,而後子進程退出。

client 也能夠使用save或者bgsave命令通知redis作一次快照持久化。save操做是在主線程中保存快照的,因爲redis是用一個主線程來處理全部 client的請求,這種方式會阻塞全部client請求。因此不推薦使用。

另外一點須要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,並非增量的只同步髒數據。若是數據量大的話,並且寫操做比較多,必然會引發大量的磁盤io操做,可能會嚴重影響性能

優點

  • 一旦採用該方式,那麼你的整個Redis數據庫將只包含一個文件,這樣很是方便進行備份。好比你可能打算沒1天歸檔一些數據。

  • 方便備份,咱們能夠很容易的將一個一個RDB文件移動到其餘的存儲介質上
  • RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
  • RDB 能夠最大化 Redis 的性能:父進程在保存 RDB 文件時惟一要作的就是 fork 出一個子進程,而後這個子進程就會處理接下來的全部保存工做,父進程無須執行任何磁盤 I/O 操做。

劣勢

  • 若是你須要儘可能避免在服務器故障時丟失數據,那麼 RDB 不適合你。 雖然 Redis 容許你設置不一樣的保存點(save point)來控制保存 RDB 文件的頻率, 可是, 由於RDB 文件須要保存整個數據集的狀態, 因此它並非一個輕鬆的操做。 所以你可能會至少 5 分鐘才保存一次 RDB 文件。 在這種狀況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的數據。
  • 每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,並由子進程來進行實際的持久化工做。 在數據集比較龐大時, fork() 可能會很是耗時,形成服務器在某某毫秒內中止處理客戶端; 若是數據集很是巨大,而且 CPU 時間很是緊張的話,那麼這種中止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也須要進行 fork() ,但不管 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。

2. AOF文件保存過程

redis會將每個收到的寫命令都經過write函數追加到文件中(默認是 appendonly.aof)。

當redis重啓時會經過從新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。固然因爲os會在內核中緩存 write作的修改,因此可能不是當即寫到磁盤上。這樣aof方式的持久化也仍是有可能會丟失部分修改。不過咱們能夠經過配置文件告訴redis咱們想要 經過fsync函數強制os寫入到磁盤的時機。有三種方式以下(默認是:每秒fsync一次)

appendonly yes //啓用aof持久化方式

# appendfsync always //每次收到寫命令就當即強制寫入磁盤,最慢的,可是保證徹底的持久化,不推薦使用

# appendfsync everysec //每秒鐘強制寫入磁盤一次,在性能和持久化方面作了很好的折中,推薦 

# appendfsync no //徹底依賴os,性能最好,持久化沒保證

aof 的方式也同時帶來了另外一個問題。持久化文件會變的愈來愈大。例如咱們調用incr test命令100次,文件中必須保存所有的100條命令,其實有99條都是多餘的。由於要恢復數據庫的狀態其實文件中保存一條set test 100就夠了。

爲了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照相似的方式將內存中的數據 以命令的方式保存到臨時文件中,最後替換原來的文件。具體過程以下

  • redis調用fork ,如今有父子兩個進程
  • 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令
  • 父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證若是子進程重寫失敗的話並不會出問題。
  • 當子進程把快照內容寫入已命令方式寫到臨時文件中後,子進程發信號通知父進程。而後父進程把緩存的寫命令也寫入到臨時文件。
  • 如今父進程可使用臨時文件替換老的aof文件,並重命名,後面收到的寫命令也開始往新的aof文件中追加。

須要注意到是重寫aof文件的操做,並無讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點相似。

優點

  • 使用 AOF 持久化會讓 Redis 變得很是耐久(much more durable):你能夠設置不一樣的 fsync 策略,好比無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。 AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然能夠保持良好的性能,而且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,因此主線程能夠繼續努力地處理命令請求)。

  • AOF 文件是一個只進行追加操做的日誌文件(append only log), 所以對 AOF 文件的寫入不須要進行 seek , 即便日誌由於某些緣由而包含了未寫入完整的命令(好比寫入時磁盤已滿,寫入中途停機,等等), redis-check-aof 工具也能夠輕易地修復這種問題
    Redis 能夠在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操做是絕對安全的,由於 Redis 在建立新 AOF 文件的過程當中,會繼續將命令追加到現有的 AOF 文件裏面,即便重寫過程當中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件建立完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操做。

  • AOF 文件有序地保存了對數據庫執行的全部寫入操做, 這些寫入操做以 Redis 協議的格式保存, 所以 AOF 文件的內容很是容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也很是簡單: 舉個例子, 若是你不當心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要中止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啓 Redis , 就能夠將數據集恢復到 FLUSHALL 執行以前的狀態。

劣勢

  • 對於相同的數據集來講,AOF 文件的體積一般要大於 RDB 文件的體積

  • 根據所使用的 fsync 策略AOF 的速度可能會慢於 RDB 。 在通常狀況下, 每秒 fsync 的性能依然很是高, 而關閉 fsync 可讓 AOF 的速度和 RDB 同樣快, 即便在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 能夠提供更有保證的最大延遲時間(latency)。

  • AOF 在過去曾經發生過這樣的 bug : 由於個別命令的緣由,致使 AOF 文件在從新載入時,沒法將數據集恢復成保存時的原樣。 (舉個例子,阻塞命令 BRPOPLPUSH 就曾經引發過這樣的 bug 。) 測試套件裏爲這種狀況添加了測試: 它們會自動生成隨機的、複雜的數據集, 並經過從新載入這些數據來確保一切正常。 雖然這種 bug 在 AOF 文件中並不常見, 可是對比來講, RDB 幾乎是不可能出現這種 bug 的。

Redis v.s. Memcached

  • 數據類型與操做:Redis擁有更多豐富的數據結構支持與操做,而Memcached則需客戶端本身處理並進行網絡交互

  • 內存使用率:簡單K/V存儲,Memcached內存利用率更高(使用了slab與大小不一樣的chunk來管理內存),而若是採用Redis Hash來存儲則其組合壓縮,內存利用率高於Memcached

  • 性能:整體來講,兩者性能接近;Redis使用了單核(單線程IO複用,封裝了AeEvent事件處理框架,實現了epoll,kqueue,select),Memcached採用了多核,各有利弊;當數據大於100K的時候,Memcached性能高於Redis

  • 數據持久化:Redis支持數據文件持久化,RDB與AOF兩種策略;Memcached則不支持

  • 分佈式:Memcached自己並不支持服務器端分佈式,客戶端只能藉助一致性哈希分佈式算法來實現Memcached分佈式存儲;固然Redis也是從3.0版本開始才支持服務器端cluster的,重要的是如今支持了。

  • 其餘方面:Redis提供其餘一些功能,如Pub/Sub, Queue, 簡單Transacation, Replication等。

Redis v.s. HashMap

HashMap單機受限於內存容量,而正是Redis分佈式之優點

  • HashMap當數據量超過必定限制後,須要妥善管理堆內存,否則會形成內存溢出或者Memory Leak;Redis則具有了文件持久性,以及Failover達到HA.

  • HashMap只能受限於本機,而Redis天生分佈式,可讓多個App Server訪問,負載均衡。

因此Redis適合所有數據都在內存的場景包括須要臨時持久化,尤爲做爲緩存來使用,並支持對緩存數據進行簡單處理計算;如涉及Redis與RDBMS雙向同步的話,則須要引入一些複雜度。

2.2.3  Redis系統架構  

Sentinel架構

Reids Sentinel誕生於2012年(Redis 2.4版本),建議在單機Redis或者客戶端模式Cluster的時候(非 3.0版本Redis Cluster)採用,做爲HA, Failover來使用。

Sentinel主要提供了集羣管理,包括監控,通知,自動故障恢復。如上圖,當其中一個master沒法正常工做時,Sentinel將把一個Slave提高爲Master, 從而自動恢復故障。而Sentinel自己也作到了分佈式,能夠部署多個Sentinel實例來監控Redis實例(建議基數,至少3個Sentinel實例來監控一組Redis Master/Slaves),多個Sentinel進程間經過Gossip協議來肯定Master是否宕機,經過Agreement協議來決定是否執行故障自動遷移以及從新選主,總體設計相似ZooKeeper)。

Cluster架構

所謂分佈式即支持數據分片,而且自動管理故障恢復(failover)與備份(replication)。

如上圖Redis Cluster採用了無中心結構,每一個節點都保存,共享數據和集羣狀態,每一個節點與其它全部節點通訊,使用Gossip協議來傳播及發現新節點,經過分區來提供必定程度可用性,當某個node的Master宕機時,Cluster會自動選舉一個Slave造成一個新的Master,這裏應該是

借鑑,重用了Sentinel的功能。另外,Redis Cluster並無使用一般的一致性哈希, 而引入哈希槽的概念,Cluster中固定有16384個slot, 每一個key經過CRC16校驗後對16384取模來決定其對應slot的位置,而每一個node負責一部分的slot管理,當node變化時,動態調整slot的分佈,而數據則無須挪動。對於客戶端來講,client能夠向任意一個實例請求,Cluster會自動定位須要訪問的slot

上圖查詢路由過程當中,咱們隨機發送到任意一個Redis實例,這個實例會按照上文提到的CRC16校驗後取模定位,並轉發至正確的Redis實例中。

參考

https://blog.csdn.net/cangchen/article/details/44830087

https://segmentfault.com/a/1190000012950110

https://www.cnblogs.com/work115/p/5584646.html

https://blog.csdn.net/erixhao/article/details/52223839

https://blog.csdn.net/lhx13636332274/article/details/73867165

相關文章
相關標籤/搜索