redis高級用法包括:
排序
事務
管道
發(fā)布/訂閱
持久化
主從復制
虛擬內(nèi)存
排序
可以對list和set和zset進行排序傲诵,以list為例子來講:
既可以用list本身的按數(shù)字或者字母的升序律杠、降序來排序,例如:
127.0.0.1:6379> rpush l 3 4 1 5 2
(integer) 5
127.0.0.1:6379> lrange l 0 5
1) "3"
2) "4"
3) "1"
4) "5"
5) "2"
127.0.0.1:6379> sort l
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379>
---------------------------------------------------------------------------
127.0.0.1:6379> lpush l xiaoh huo xingming beijing changping
(integer) 5
127.0.0.1:6379> lrange l 0 5
1) "changping"
2) "beijing"
3) "xingming"
4) "huo"
5) "xiaoh"
127.0.0.1:6379> sort l alpha
1) "beijing"
2) "changping"
3) "huo"
4) "xiaoh"
5) "xingming"
127.0.0.1:6379> sort l alpha desc
1) "xingming"
2) "xiaoh"
3) "huo"
4) "changping"
5) "beijing"
127.0.0.1:6379>
又可以自己自定義規(guī)則瞬哼,根據(jù)自定義規(guī)則進行排序。但是redis里面的這個自定義規(guī)則看起來很奇怪奏属,這個規(guī)則和list里面的元素是怎么建立聯(lián)系的,好像并不是那么直觀:
127.0.0.1:6379> lpush l e s g f a
(integer) 5
127.0.0.1:6379> lrange l 0 9
1) "a"
2) "f"
3) "g"
4) "s"
5) "e"
127.0.0.1:6379> sort l alpha
1) "a"
2) "e"
3) "f"
4) "g"
5) "s"
127.0.0.1:6379> set namea 4
OK
127.0.0.1:6379> set namee 5
OK
127.0.0.1:6379> set namef 3
OK
127.0.0.1:6379> set nameg 1
OK
127.0.0.1:6379> set names 2
OK
127.0.0.1:6379> sort l by name*
1) "g"
2) "s"
3) "f"
4) "a"
5) "e"
-----------------------------------------------------------
127.0.0.1:6379> set namea xiaoh
OK
127.0.0.1:6379> set namee me
OK
127.0.0.1:6379> set namef huo
OK
127.0.0.1:6379> set nameg blog
OK
127.0.0.1:6379> set names xingming
OK
127.0.0.1:6379> sort l by name* alpha
1) "g"
2) "f"
3) "e"
4) "a"
5) "s"
上面的方法返回的是原始的數(shù)據(jù)內(nèi)容掏秩,如果想獲得新的KEY對應的值(也就是進行排序的KEY)可以使用 GET PATTERN
127.0.0.1:6379> sort l by name* alpha get name*
1) "blog"
2) "huo"
3) "me"
4) "xiaoh"
5) "xingming"
127.0.0.1:6379> sort l by name* alpha get name* get #
1) "blog"
2) "g"
3) "huo"
4) "f"
5) "me"
6) "e"
7) "xiaoh"
8) "a"
9) "xingming"
10) "s"
127.0.0.1:6379>
對redis里面排序的思考:如果我們有多個redis server的話,不同的key可能存在于不同的server中荆姆,比如name1哗讥,name2,name3可能就分別存儲在不同的server中胞枕。這種情況對排序性能就會造成很大的影響。
redis作者在他的blog上提到了這個問題的解決辦法魏宽,就是通過key tag將需要排序的key都放到同一個server上 腐泻。
再回來,具體決定哪個key存儲在哪個server上队询,是由客戶端的hash算法決定的派桩。我們可以通過對key的一部分內(nèi)容進行hash來確保要排序的key在同一臺server上,比如name1蚌斩,name2铆惑,name3這三個key,如果只對相同的部分"name"進行hash送膳,則name1员魏,name2和name3這三個key肯定就hash到同一臺server上了,這樣就可以在一臺server上排序了叠聋。
舉個例子假如我們的client如果發(fā)現(xiàn)key中包含[]撕阎。那么只對key中[]包含的內(nèi)容進行hash。我們將四個name相關(guān)的key碌补,都這樣命名[name]1 [name]2 [name]3 [name]4 [name]5虏束,于是client程序就會把他們都放到同一server上。
還有一個問題厦章,如果要排序的set或者list很大镇匀,排序就需要消耗很長時間。由于redis是單線程的袜啃,所以長時間的排序操作會阻塞其他client的請求汗侵。解決辦法是通過主從復制,把數(shù)據(jù)從主server復制到slave server上群发,只在slave server上做排序晃择,主server就可以無阻的接收其他client的請求了。slave server上做的排序操作還可以緩存起來也物。另一個辦法就是用sorted set來對需要按某個順序排序的集合建立索引宫屠。
事務
redis對事務的支持比較簡單,redis只能保證一個client發(fā)起的事務執(zhí)行過程中滑蚯,不會插入其他client的命令浪蹂。
因為是單線程的抵栈,所以實現(xiàn)事務是很容易的。但是redis里面的事務有缺點坤次。
事務是從multi開始古劲,以exec結(jié)束。一般client提交請求缰猴,server會馬上執(zhí)行并返回結(jié)果給client产艾。但是當client在一個連接中發(fā)出multi命令,這個連接會進入一個事務上下文環(huán)境滑绒,該連接后續(xù)的命令并不是立刻執(zhí)行闷堡,而是放到一個隊列中,當此連接接收到一個exec命令后疑故,redis會順序的執(zhí)行隊列中的那些命令杠览,并將執(zhí)行結(jié)果一起返回給client,然后此連接結(jié)束事務上下文纵势。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> incr b
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 2
3) (integer) 1
127.0.0.1:6379>
redis事務的缺點:
第一個問題是redis只能保證事務的每個命令連續(xù)執(zhí)行踱阿,但是如果事務中的一個命令失敗了,并不回滾其他命令钦铁,比如使用的命令類型不匹配软舌。
127.0.0.1:6379> set b 1
OK
127.0.0.1:6379> set a e
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> incr b
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) (integer) 2
127.0.0.1:6379>
可以發(fā)現(xiàn),雖然執(zhí)行中間有問題牛曹,但并沒有回滾葫隙,其他命令還是執(zhí)行了,而且第一條命令執(zhí)行失敗,并不影響后面命令的執(zhí)行躏仇。
最后一個十分罕見的問題是恋脚,當事務的執(zhí)行過程中,如果redis意外的掛了焰手。很遺憾只有部分命令執(zhí)行了糟描,后面的也就被丟棄了。當然如果我們使用的append-only file方式持久化书妻,redis會用單個write操作寫入整個事務內(nèi)容船响。即是是這種方式還是有可能只部分寫入了事務到磁盤。發(fā)生部分寫入事務的情況下躲履,redis重啟時會檢測到這種情況见间,然后失敗退出」げ拢可以使用redis-check-aof工具進行修復米诉,修復會刪除部分寫入的事務內(nèi)容。修復完后就能夠重新啟動了篷帅。
管道pipeline
redis是一個cs模式的tcp server史侣,使用和http類似的請求響應協(xié)議拴泌。
一個client可以通過一個socket連接發(fā)起多個請求命令。每個請求命令發(fā)出后client通常會阻塞并等待redis服務處理惊橱,redis處理完后請求命令后會將結(jié)果通過響應報文返回給client蚪腐。基本的通信過程如下
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4
基本上四個命令需要8個tcp報文才能完成税朴。由于通信會有網(wǎng)絡延遲,假如從client和server之間的包傳輸時間需要0.125秒回季。那么上面的四個命令8個報文至少會需要1秒才能完成。這樣即使redis每秒能處理100個命令正林,而我們的client也只能一秒鐘發(fā)出四個命令泡一。這顯示沒有充分利用redis的處理能力。除了可以利用mget,mset之類的單條命令處理多個key的命令外我們還可以利用pipeline的方式從client打包多條命令一起發(fā)出卓囚,不需要等待單條命令的響應返回,而redis服務端會處理完多條命令后會將多條命令的處理結(jié)果打包到一起返回給客戶端诅病。通信過程如下
Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4
假設(shè)不會因為tcp報文過長而被拆分哪亿。可能兩個tcp報文就能完成四條命令,client可以將四個incr命令放到一個tcp報文一起發(fā)送,server則可以將四條命令的處理結(jié)果放到一個tcp報文返回。
通過pipeline方式當有大批量的操作時候聘裁。我們可以節(jié)省很多原來浪費在網(wǎng)絡延遲的時間艳汽。需要注意到是用pipeline方式打包命令發(fā)送,redis必須在處理完所有命令前先緩存起所有命令的處理結(jié)果嫩絮。打包的命令越多,緩存消耗內(nèi)存也越多。所以并是不是打包的命令越多越好板辽。具體多少合適需要根據(jù)具體情況測試。
下面是我測試是否使用pipeline的代碼:
from redis import Redis
import time
r = Redis()
def without_pipeline(times=100000):
r.set('a', 0)
for i in range(times):
r.incr('a')
def use_pipeline(times=100000):
r.set('a', 0)
r.set('a', 0)
pip = r.pipeline()
for i in range(times):
pip.incr('a')
pip.execute()
start = time.time()
without_pipeline()
end = time.time()
print 'without pipeline spendtime:%f' % (end-start)
start = time.time()
use_pipeline()
end = time.time()
print 'use pipeline spendtime:%f' % (end-start)
結(jié)果為:
without pipeline spendtime:4.221240
use pipeline spendtime:1.572646
看起來對效果提升還是很明顯的棘催。
持久化
有快照和appendof兩種方式劲弦,
快照是將數(shù)據(jù)全部寫入到rdb文件中,appendof是將修改命令寫入到aof文件中醇坝,所以aof文件會越來越大邑跪,redis提供了bgrewriteaof命令來整理aof文件。
主從復制(自己沒用過主從復制呼猪,都是別人的經(jīng)驗)
Redis主從復制配置和使用都非常簡單画畅。通過主從復制可以允許多個slave server擁有和master server相同的數(shù)據(jù)庫副本。
關(guān)于redis主從復制的一些特點
master可以有多個slave
除了多個slave連到相同的master外宋距,slave也可以連接其他slave形成圖狀結(jié)構(gòu)
主從復制不會阻塞master轴踱。也就是說當一個或多個slave與master進行初次同步數(shù)據(jù)時,master可以繼續(xù)處理client發(fā)來的請求谚赎。相反slave在初次同步數(shù)據(jù)時則會阻塞不能處理client的請求寇僧。
主從復制可以用來提高系統(tǒng)的可伸縮性,我們可以用多個slave專門用于client的讀請求摊腋,比如sort操作可以使用slave來處理。也可以用來做簡單的數(shù)據(jù)冗余
可以在master禁用數(shù)據(jù)持久化嘁傀,只需要注釋掉master配置文件中的所有save配置兴蒸,然后只在slave上配置數(shù)據(jù)持久化。
下面介紹下主從復制的過程
當設(shè)置好slave服務器后细办,slave會建立和master的連接橙凳,然后發(fā)送sync命令。
無論是第一次同步建立的連接還是連接斷開后的重新連接笑撞,master都會啟動一個后臺進程岛啸,將數(shù)據(jù)庫快照保存到文件中,同時master主進程會開始收集新的寫命令并緩存起來茴肥。后臺進程完成寫文件后坚踩,master就發(fā)送文件給slave,slave將文件保存到磁盤上瓤狐,然后加載到內(nèi)存恢復數(shù)據(jù)庫快照到slave上瞬铸。
接著master就會把緩存的命令轉(zhuǎn)發(fā)給slave。而且后續(xù)master收到的寫命令都會通過開始建立的連接發(fā)送給slave础锐。
從master到slave的同步數(shù)據(jù)的命令和從client發(fā)送的命令使用相同的協(xié)議格式嗓节。當master和slave的連接斷開時slave可以自動重新建立連接。
如果master同時收到多個slave發(fā)來的同步連接命令皆警,只會使用啟動一個進程來寫數(shù)據(jù)庫鏡像拦宣,然后發(fā)送給所有slave。
配置slave服務器很簡單信姓,只需要在配置文件中加入如下配置
slaveof 192.168.1.1 6379 #指定master的ip和端口
虛擬內(nèi)存
首先說明下redis
的虛擬內(nèi)存與os
的虛擬內(nèi)存不是一碼事鸵隧,但是思路和目的都是相同的。就是暫時把不經(jīng)常訪問的數(shù)據(jù)從內(nèi)存交換到磁盤中意推,從而騰出寶貴的內(nèi)存空間用于其他需要訪問的數(shù)據(jù)掰派。尤其是對于redis
這樣的內(nèi)存數(shù)據(jù)庫,內(nèi)存總是不夠用的左痢。除了可以將數(shù)據(jù)分割到多個redis server
外靡羡。另外的能夠提高數(shù)據(jù)庫容量的辦法就是使用vm
把那些不經(jīng)常訪問的數(shù)據(jù)交換的磁盤上。
如果我們的存儲的數(shù)據(jù)總是有少部分數(shù)據(jù)被經(jīng)常訪問俊性,大部分數(shù)據(jù)很少被訪問略步,對于網(wǎng)站來說確實總是只有少量用戶經(jīng)常活躍定页。當少量數(shù)據(jù)被經(jīng)常訪問時趟薄,使用vm不但能提高單臺redis server
數(shù)據(jù)庫的容量,而且也不會對性能造成太多影響典徊。
redis
沒有使用os
提供的虛擬內(nèi)存機制而是自己在用戶態(tài)實現(xiàn)了自己的虛擬內(nèi)存機制,作者在自己的blog
專門解釋了其中原因杭煎。http://antirez.com/post/redis-virtual-memory-story.html
os
的虛擬內(nèi)存是已4k頁面為最小單位進行交換的恩够。而redis
的大多數(shù)對象都遠小于4k,所以一個os頁面上可能有多個redis
對象羡铲。另外redis
的集合對象類型如list
,set
可能存在與多個os頁面上蜂桶。最終可能造成只有10%key被經(jīng)常訪問,但是所有os
頁面都會被os
認為是活躍的也切,這樣只有內(nèi)存真正耗盡時os才會交換頁面扑媚。
相比于os的交換方式。redis
可以將被交換到磁盤的對象進行壓縮,保存到磁盤的對象可以去除指針和對象元數(shù)據(jù)信息雷恃。一般壓縮后的對象會比內(nèi)存中的對象小10倍疆股。這樣redis
的vm會比os vm能少做很多io操作。
vm-enabled yes #開啟vm功能
vm-swap-file /tmp/redis.swap #交換出來的value保存的文件路徑/tmp/redis.swap
vm-max-memory 1000000 #redis使用的最大內(nèi)存上限倒槐,超過上限后redis開始交換value到磁盤文件中旬痹。
vm-page-size 32 #每個頁面的大小32個字節(jié)
vm-pages 134217728 #最多使用在文件中使用多少頁面,交換文件的大小 = vm-page-size * vm-pages
vm-max-threads 4 #用于執(zhí)行value對象換入換出的工作線程數(shù)量。0表示不使用工作線程(后面介紹)
redis
的vm
在設(shè)計上為了保證key
的查找速度讨越,只會將value
交換到swap
文件中两残。所以如果是內(nèi)存問題是由于太多value
很小的key
造成的,那么vm
并不能解決谎痢。
和os一樣redis
也是按頁面來交換對象的磕昼。redis
規(guī)定同一個頁面只能保存一個對象卷雕。但是一個對象可以保存在多個頁面中节猿。
在redis
使用的內(nèi)存沒超過vm-max-memory
之前是不會交換任何value
的。當超過最大內(nèi)存限制后漫雕,redis
會選擇較老的對象滨嘱。如果兩個對象一樣老會優(yōu)先交換比較大的對象,精確的公式swappability = age*log(size_in_memory)
浸间。
對于vm-page-size
的設(shè)置應該根據(jù)自己的應用將頁面的大小設(shè)置為可以容納大多數(shù)對象的大小太雨。太大了會浪費磁盤空間,太小了會造成交換文件出現(xiàn)碎片魁蒜。
對于交換文件中的每個頁面囊扳,redis
會在內(nèi)存中對應一個1bit
值來記錄頁面的空閑狀態(tài)。所以像上面配置中頁面數(shù)量(vm-pages 134217728
)會占用16M內(nèi)存用來記錄頁面空閑狀態(tài)兜看。`
vm-max-threads表示用做交換任務的線程數(shù)量锥咸。如果大于0推薦設(shè)為服務器的
cpu core`的數(shù)量。如果是0則交換過程在主線程進行细移。