支持持久化的內存數據庫-----Redis

1、Redis概述

1.一、什麼是Redis

Redis是一種高級key-value數據庫。它跟memcached相似,不過數據 能夠持久化,並且支持的數據類型很豐富。有字符串,鏈表,集 合和有序集合。支持在服務器端計算集合的並,交和補集(difference)等,還支持多種排序功能。因此Redis也能夠被當作是一個數據結構服務 器。
Redis的全部數據都是保存在內存中,而後不按期的經過異步方式保存到磁盤上(這稱爲「半持久化模式」);也能夠把每一次數據變化都寫入到一個append only file(aof)裏面(這稱爲「全持久化模式」)。html

1.二、Redis數據持久化(俗稱「數據落地」)

redis是一個支持持久化的內存數據庫,也就是說redis須要常常將內存中的數據同步到磁盤來保證持久化。redis支持四種持久化方式,一是 Snapshotting(快照)也是默認方式;二是Append-only file(縮寫aof)的方式;三是虛擬內存方式;四是diskstore方式。下面分別介紹之。
(一)Snapshotting
快照是默認的持久化方式。這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名爲dump.rdb。能夠經過配置設置自動作快照持久化的方式。咱們能夠配置redis在n秒內若是超過m個key被修改就自動作快照,下面是默認的快照保存配置:redis

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


快照保存過程:

1. redis調用fork函數,有了子進程和父進程。
fork() 建立一個新進程,併爲它建立新的地址空間
2. 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。因爲os的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會爲父進程要修改的頁面建立副本,而不是寫共享的頁面。因此子進程的地址空間內的數據是fork時刻整個數據庫的一個快照。
3. 當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,而後子進程退出(fork一個進程入內在也被複制了,即內存會是原來的兩倍)。

client 也可使用save或者bgsave命令通知redis作一次快照持久化。save操做是在主線程中保存快照的,因爲redis是用一個主線程來處理全部 client的請求,這種方式會阻塞全部client請求。因此不推薦使用。另外一點須要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,並非增量的只同步髒數據。若是數據量大或者頻繁修改的話,並且寫操做比較多,必然會引發大量的磁盤io操做,可能會嚴重影響性能。
另外因爲快照方式是在必定間隔時間作一次的,因此若是redis意外down掉的話,就會丟失最後一次快照後的全部修改。若是應用要求不能丟失任何修改的話,能夠採用aof持久化方式。下面介紹:
(二)Append-only file
aof 比快照方式有更好的持久化性,是因爲在使用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將使用與快照相似的方式將內存中的數據以命令的方式保存到臨時文件中,最後替換原來的文件。具體過程以下:

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

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

(三)虛擬內存方式(desprecated)
首先說明:在Redis-2.4後虛擬內存功能已經被deprecated了,緣由以下:
1)slow restart重啓太慢
2)slow saving保存數據太慢
3)slow replication上面兩條致使 replication 太慢
4)complex code代碼過於複雜
下面仍是介紹一下redis的虛擬內存。
redis的虛擬內存與os的虛擬內存不是一碼事,可是思路和目的都是相同的。就是暫時把不常常訪問的數據從內存交換到磁盤中,從而騰出寶貴的內存空間用於其餘須要訪問的數據。尤爲是對於redis這樣的內存數據庫,內存老是不夠用的。除了能夠將數據分割到多個redis server外。另外的可以提升數據庫容量的辦法就是使用vm把那些不常常訪問的數據交換的磁盤上。若是咱們的存儲的數據老是有少部分數據被常常訪問,大部分數據不多被訪問,對於網站來講確實老是隻有少許用戶常常活躍。當少許數據被常常訪問時,使用vm不但能提升單臺redis server數據庫的容量,並且也不會對性能形成太多影響。

redis沒有使用os提供的虛擬內存機制而是本身在用戶態實現了本身的虛擬內存機制,做者在本身的blog專門解釋了其中緣由。
http://antirez.com/post/redis-virtual-memory-story.html
主要的理由有兩點:
1. os 的虛擬內存是已4k頁面爲最小單位進行交換的。而redis的大多數對象都遠小於4k,因此一個os頁面上可能有多個redis對象。另外redis的集合對象類型如list,set可能存在與多個os頁面上。最終可能形成只有10%key被常常訪問,可是全部os頁面都會被os認爲是活躍的,這樣只有內存真正耗盡時os纔會交換頁面。
2.相比於os的交換方式。redis能夠將被交換到磁盤的對象進行壓縮,保存到磁盤的對象能夠去除指針和對象元數據信息。通常壓縮後的對象會比內存中的對象小10倍。這樣redis的vm會比os vm能少作不少io操做。

下面是vm相關配置:數據庫

slaveof 192.168.1.1 6379  #指定master的ip和端口
  vm-enabled   yes          #開啓vm功能
   vm-swap-file /tmp/redis.swap   #交換出來的value保存的文件路徑/tmp/redis.swap
   vm-max-memory 1000000  #redis使用的最大內存上限,超過上限後redis開始交換value到磁盤文件中
   vm-page-size 32        #每一個頁面的大小32個字節
   vm-pages 134217728     #最多使用在文件中使用多少頁面,交換文件的大小 = vm-page-size * vm-pages
   vm-max-threads 4       #用於執行value對象換入換出的工做線程數量,0表示不使用工做線程(後面介紹)



redis的vm在設計上爲了保證key的查找速度,只會將value交換到swap文件中。因此若是是內存問題是因爲太多value很小的key形成的,那麼vm並不能解決。和os同樣redis也是按頁面來交換對象的。redis規定同一個頁面只能保存一個對象。可是一個對象能夠保存在多個頁面中。
在redis使用的內存沒超過vm-max-memory以前是不會交換任何value的。當超過最大內存限制後,redis會選擇較老的對象。若是兩個對象同樣老會優先交換比較大的對象,精確的公式swappability = age*log(size_in_memory)。對於vm-page-size的設置應該根據本身的應用將頁面的大小設置爲能夠容納大多數對象的大小。太大了會浪費磁盤空間,過小了會形成交換文件出現碎片。對於交換文件中的每一個頁面,redis會在內存中對應一個1bit值來記錄頁面的空閒狀態。因此像上面配置中頁面數量(vm-pages 134217728 )會佔用16M內存用來記錄頁面空閒狀態。vm-max-threads表示用作交換任務的線程數量。若是大於0推薦設爲服務器的cpu core的數量。若是是0則交換過程在主線程進行。

參數配置討論完後,在來簡單介紹下vm是如何工做的:
當vm-max-threads設爲0時(Blocking VM)
換出:
主線程按期檢查發現內存超出最大上限後,會直接已阻塞的方式,將選中的對象保存到swap文件中,並釋放對象佔用的內存,此過程會一直重複直到下面條件知足
1.內存使用降到最大限制如下
2.swap文件滿了
3.幾乎所有的對象都被交換到磁盤了
換入:
當有client請求value被換出的key時。主線程會以阻塞的方式從文件中加載對應的value對象,加載時此時會阻塞全部client。而後處理client的請求

當vm-max-threads大於0(Threaded VM)
換出:
當主線程檢測到使用內存超過最大上限,會將選中的要交換的對象信息放到一個隊列中交由工做線程後臺處理,主線程會繼續處理client請求。
換入:
若是有client請求的key被換出了,主線程先阻塞發出命令的client,而後將加載對象的信息放到一個隊列中,讓工做線程去加載。加載完畢後工做線程通知主線程。主線程再執行client的命令。這種方式只阻塞請求value被換出key的client

總的來講blocking vm的方式總的性能會好一些,由於不須要線程同步,建立線程和恢復被阻塞的client等開銷。可是也相應的犧牲了響應性。threaded vm的方式主線程不會阻塞在磁盤io上,因此響應性更好。若是咱們的應用不太常常發生換入換出,並且也不太在乎有點延遲的話則推薦使用blocking vm的方式。
關於redis vm的更詳細介紹能夠參考下面連接:
http://antirez.com/post/redis-virtual-memory-story.html
http://redis.io/topics/internals-vm

(四)diskstore方式
diskstore方式是做者放棄了虛擬內存方式後選擇的一種新的實現方式,也就是傳統的B-tree的方式。具體細節是:
1) 讀操做,使用read through以及LRU方式。內存中不存在的數據從磁盤拉取並放入內存,內存中放不下的數據採用LRU淘汰。
2) 寫操做,採用另外spawn一個線程單獨處理,寫線程一般是異步的,固然也能夠 把cache-flush-delay配置設成0,Redis儘可能保證即時寫入。可是在不少場合延遲寫會有更好的性能,好比一些計數器用Redis存儲, 在短期若是某個計數反覆被修改,Redis只須要將最終的結果寫入磁盤。這種作法做者叫per key persistence。因爲寫入會按key合併,所以和snapshot仍是有差別,disk store並不能保證時間一致性。
因爲寫操做是單線程,即便cache-flush-delay設成0,多個client同時寫則須要排隊等待,若是隊列容量超過cache-max-memory Redis設計會進入等待狀態,形成調用方卡住。
Google Group上有熱心網友迅速完成了壓力測試,當內存用完以後,set每秒處理速度從25k降低到10k再到後來幾乎卡住。 雖然經過增長cache-flush-delay能夠提升相同key重複寫入性能;經過增長cache-max-memory能夠應對臨時峯值寫入。可是diskstore寫入瓶頸最終仍是在IO。
3) rdb 和新 diskstore 格式關係
rdb是傳統Redis內存方式的存儲格式,diskstore是另一種格式,那二者關係如何?
·經過BGSAVE能夠隨時將diskstore格式另存爲rdb格式,並且rdb格式還用於Redis複製以及不一樣存儲方式之間的中間格式。
· 經過工具能夠將rdb格式轉換成diskstore格式。
固然,diskstore原 理很美好,可是目前還處於alpha版本,也只是一個簡單demo,diskstore.c加上註釋只有300行,實現的方法就是將每一個value做爲一 個獨立文件保存,文件名是key的hash值。所以diskstore須要未來有一個更高效穩定的實現才能用於生產環境。但因爲有清晰的接口設 計,diskstore.c也很容易換成一種B-Tree的實現。不少開發者也在積極探討使用bdb或者innodb來替換默認diskstore.c的 可行性。

下面介紹一下Diskstore的算法。
其實DiskStore類 似於Hash算法,首先經過SHA1算法把Key轉化成一個40個字符的Hash值,而後把Hash值的前兩位做爲一級目錄,而後把Hash值的三四位做 爲二級目錄,最後把Hash值做爲文件名,相似於「/0b/ee/0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33」 形式。算法以下:
dsKeyToPath(key):
char path[1024];
char *hashKey = sha1(key);
path[0] = hashKey[0];
path[1] = hashKey[1];
path[2] = '/';
path[3] = hashKey[2];
path[4] = hashKey[3];
path[5] = '/';
memcpy(path + 6, hashKey, 40);
return path;

存儲算法(如key == apple):
dsSet(key, value, expireTime):
// d0be2dc421be4fcd0172e5afceea3970e2f3d940
char *hashKey = sha1(key);

// d0/be/d0be2dc421be4fcd0172e5afceea3970e2f3d940
char *path = dsKeyToPath(hashKey);
FILE *fp = fopen(path, "w");
rdbSaveKeyValuePair(fp, key, value, expireTime);
fclose(fp)

獲取算法:
dsGet(key):
char *hashKey = sha1(key);
char *path = dsKeyToPath(hashKey);
FILE *fp = fopen(path, "r");
robj *val = rdbLoadObject(fp);
return val;



緩存

2、Redis安裝



2.一、redis安裝


# cd /data/soft/
# tar xf redis-2.4.15.tar.gz -C tmp/
# cd tmp/redis-2.4.15/
#make PREFIX=/usr/local/services/redis-2.4.15 >/dev/null
#make PREFIX=/usr/local/services/redis-2.4.15 install >/dev/null
#ln -s /usr/local/services/redis-2.4.15/bin/ /usr/local/services/redis/
會在當前目錄下生成本個可執行文件,分別是redis-server、redis-cli、redis-benchmark、redis-stat,它們的做用以下:
· redis-server:Redis服務器的daemon啓動程序
· redis-cli:Redis命令行操做工具。固然,你也能夠用telnet根據其純文本協議來操做
· redis-benchmark:Redis性能測試工具,測試Redis在你的系統及你的配置下的讀寫性能
· redis-stat:Redis狀態檢測工具,能夠檢測Redis當前狀態參數及延遲情況


安全

2.二、調整overcommit_memory參數


若是內存狀況比較緊張的話,須要設定內核參數overcommit_memory,指定內核針對內存分配的策略,其值能夠是0、一、2。
0,表示內核將檢查是否有足夠的可用內存供應用進程使用;若是有足夠的可用內存,內存申請容許;不然,內存申請失敗,並把錯誤返回給應用進程。
1,表示內核容許分配全部的物理內存,而無論當前的內存狀態如何。
2,表示內核容許分配超過全部物理內存和交換空間總和的內存
Redis在dump數據的時候,會fork出一個子進程,理論上child進程所佔用的內存和parent是同樣的,好比parent佔用的內存爲 8G,這個時候也要一樣分配8G的內存給child, 若是內存沒法負擔,每每會形成redis服務器的down機或者IO負載太高,效率降低。因此這裏比較優化的內存分配策略應該設置爲 1(表示內核容許分配全部的物理內存,而無論當前的內存狀態如何)。
設置方式有兩種,需肯定當前用戶的權限活使用root用戶修改:
1:重設文件 # echo 1 > /proc/sys/vm/overcommit_memory(默認爲0)
2: # echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
# /sbin/sysctl -p

服務器

2.三、拷貝配置文件


#mkdir /usr/local/services/redis-2.4.15/etc
# cd /soft/redis/redis-2.4.15
# cp redis.conf /usr/local/services/redis-2.4.15/etc/



數據結構

2.四、redis配置文件


# mkdir –p /data/redis/redis_db
#mkdir –p /data/redis/redis_dump
daemonize yes
pidfile /data/redis/redis_db/redis.pid
port 6379
timeout 300
loglevel debug
logfile stdout
databases 16
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data/redis/redis_dump
slave-serve-stale-data yes
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
slowlog-log-slower-than 10000
slowlog-max-len 128
vm-enabled no
vm-swap-file /data/redis/redis_db/redis.swap
vm-max-memory 0
vm-page-size 32
vm-pages 134217728
vm-max-threads 4
hash-max-zipmap-entries 512
hash-max-zipmap-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
bind 127.0.0.1


配置文件說明
daemonize yes # Redis默認不是以守護進程的方式運行,能夠經過該配置項修改,使用yes啓用守護進程
pidfile /data/redis/redis_db/redis.pid #當Redis以守護進程方式運行時,Redis默認會把pid寫入/var/run/redis.pid文件,能夠經過pidfile指定

port 6379 #指定Redis監聽端口,默認端口爲6379,做者在本身的一篇博文中解釋了爲何選用6379做爲默認端口,由於6379在手機按鍵上MERZ對應的號碼,而MERZ取自意大利歌女Alessia Merz的名字

timeout 300  #當客戶端閒置多長時間後關閉鏈接,若是指定爲0,表示關閉該功能

loglevel debug #指定日誌記錄級別,Redis總共支持四個級別:debug、verbose、notice、warning,默認爲verbose

logfile stdout  #日誌記錄方式,默認爲標準輸出,若是配置Redis爲守護進程方式運行,而這裏又配置爲日誌記錄方式爲標準輸出,則日誌將會發送給/dev/null

databases 16 #設置數據庫的數量,默認數據庫爲0,可使用SELECT <dbid>命令在鏈接上指定數據庫id

#指定在多長時間內,有多少次更新操做,就將數據同步到數據文件,能夠多個條件配合
Redis默認配置文件中提供了三個條件
save 900 1
save 300 10
save 60 10000

#指定存儲至本地數據庫時是否壓縮數據,默認爲yes,Redis採用LZF壓縮,若是爲了節省CPU時間,能夠關閉該選項,但會致使數據庫文件變的巨大
rdbcompression yes

#指定本地數據庫文件名,默認值爲dump.rdb
dbfilename dump.rdb
#指定本地數據庫存放目錄
dir /data/redis/redis_dump

設置當本機爲slav服務時,設置master服務的IP地址及端口,在Redis啓動時,它會自動從master進行數據同步
slave-serve-stale-data yes

. 指定是否在每次更新操做後進行日誌記錄,Redis在默認狀況下是異步的把數據寫入磁盤,若是不開啓,可能會在斷電時致使一段時間內的數據丟失。由於 redis自己同步數據文件是按上面save條件來同步的,因此有的數據會在一段時間內只存在於內存中。默認爲no
appendonly no

指定更新日誌條件,共有3個可選值:
no:表示等操做系統進行數據緩存同步到磁盤(快)
always:表示每次更新操做後手動調用fsync()將數據寫到磁盤(慢,安全)
everysec:表示每秒同步一次(折衷,默認值)
appendfsync everysec

當AOF文件增加到必定大小的時候Redis可以調用 BGREWRITEAOF 對日誌文件進行重寫
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

注意制定一個負數將關閉慢日誌,而設置爲0將強制每一個命令都會記錄
slowlog-log-slower-than 10000
slowlog-max-len 128

指定是否啓用虛擬內存機制,默認值爲no,簡單的介紹一下,VM機制將數據分頁存放,由Redis將訪問量較少的頁即冷數據swap到磁盤上,訪問多的頁面由磁盤自動換出到內存中(在後面的文章我會仔細分析Redis的VM機制)
vm-enabled no

虛擬內存文件路徑,默認值爲/tmp/redis.swap,不可多個Redis實例共享
vm-swap-file /data/redis/redis_db/redis.swap #交換文件

將全部大於vm-max-memory的數據存入虛擬內存,不管vm-max-memory設置多小,全部索引數據都是內存存儲的(Redis的索引數據 就是keys),也就是說,當vm-max-memory設置爲0的時候,實際上是全部value都存在於磁盤。默認值爲0
vm-max-memory 0

Redis swap文件分紅了不少的page,一個對象能夠保存在多個page上面,但一個page上不能被多個對象共享,vm-page-size是要根據存儲的 數據大小來設定的,做者建議若是存儲不少小對象,page大小最好設置爲32或者64bytes;若是存儲很大大對象,則可使用更大的page,若是不 肯定,就使用默認值
vm-page-size 32

設置swap文件中的page數量,因爲頁表(一種表示頁面空閒或使用的bitmap)是在放在內存中的,,在磁盤上每8個pages將消耗1byte的內存
vm-pages 134217728


設置訪問swap文件的線程數,最好不要超過機器的核數,若是設置爲0,那麼全部對swap文件的操做都是串行的,可能會形成比較長時間的延遲。默認值爲4
vm-max-threads 4

指定在超過必定的數量或者最大的元素超過某一臨界值時,採用一種特殊的哈希算法
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

list數據類型節點值大小小於多少字節會採用緊湊存儲格式。
list-max-ziplist-entries 512
list-max-ziplist-value 64

set數據類型內部數據若是所有是數值型,且包含多少節點如下會採用緊湊格式存儲。
set-max-intset-entries 512

zsort數據類型節點值大小小於多少字節會採用緊湊存儲格式。
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

指定是否激活重置哈希,默認爲開啓
activerehashing yes

綁定的主機地址
bind 127.0.0.1




app

2.五、啓動Redis服務


# redis-server conf/redis.conf
# redis-cli shutdown  中止Redis  關閉服務
# redis-cli -p 6380 shutdown  若是非默認端口,可指定端口:







less

2.六、測試Redis


# ls /data/redis/redis_dump/  看看是否有文件。沒有?正常。咱們寫入數據進去
# telnet localhost  6379   
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
get mykey
$-1
set foo 3
+OK
get foo
$1
3
quit+OKConnection closed by foreign host. # ls /data/redis/redis_dump/  在此嘗試看看。。

相關文章
相關標籤/搜索