讀完了Redis實(shí)戰(zhàn),感覺(jué)收獲還是蠻多的葛圃。像往常那樣,讀完就想將書(shū)束之高閣憎妙。這幾天總感覺(jué)差點(diǎn)什么库正,于是又翻了一下這本書(shū),打算記錄書(shū)上和自己知道的關(guān)于Redis優(yōu)化的小知識(shí)點(diǎn)厘唾。
數(shù)據(jù)持久化
選擇恰當(dāng)?shù)某志没绞饺旆edis提供
RDB
和AOF
兩種持久化方式。用戶(hù)需要根據(jù)實(shí)際場(chǎng)景對(duì)兩種持久化方式進(jìn)行考量和選擇抚垃。
RDB
會(huì)在一定時(shí)間間隔內(nèi)一次性將內(nèi)存中的所有數(shù)據(jù)刷到磁盤(pán)上喷楣,非增量趟大。它的一個(gè)主要缺點(diǎn)是如果Redis宕機(jī)了,那么可能會(huì)造成部分?jǐn)?shù)據(jù)丟失铣焊,丟失的數(shù)據(jù)兩和RDB
持久化的時(shí)間間隔有關(guān)逊朽。此外,如果數(shù)據(jù)集非常大曲伊,Redis在創(chuàng)建子進(jìn)程時(shí)可能會(huì)消耗相對(duì)較長(zhǎng)的時(shí)間叽讳,這期間客戶(hù)端請(qǐng)求都會(huì)被阻塞。這種情況下我們可以關(guān)閉自動(dòng)保存坟募,通過(guò)手動(dòng)發(fā)送SAVE
或者BGSAVE
命令來(lái)控制停頓出現(xiàn)的時(shí)間岛蚤。由于SAVE
命令不需要?jiǎng)?chuàng)建子進(jìn)程,從而避免了與子進(jìn)程的資源競(jìng)爭(zhēng)懈糯,因此它比BGSAVE
速度更快涤妒。手動(dòng)生成快照可以選擇在線(xiàn)用戶(hù)很少的情況下執(zhí)行,比如使用腳本在凌晨三點(diǎn)執(zhí)行SAVE
命令赚哗。
從上述內(nèi)容可以看出她紫,RDB
適用于即使丟失部分?jǐn)?shù)據(jù)也不會(huì)造成問(wèn)題的場(chǎng)景。同時(shí)我們需要注意快照是否生成得過(guò)于頻繁或者稀少蜂奸。
AOF
持久化會(huì)將被執(zhí)行的命令追加到AOF
文件末尾犁苏。在redis.conf
中該功能默認(rèn)是關(guān)閉的,設(shè)置appendonly yes
以開(kāi)啟該功能扩所。這種方式會(huì)對(duì)磁盤(pán)進(jìn)行大量寫(xiě)入围详,因此Redis
處理命令的速度會(huì)受到硬盤(pán)性能的限制。并且AOF
文件通常比RDB
文件更大祖屏,數(shù)據(jù)恢復(fù)速度比RDB慢助赞,性能消耗也比RDB
高。由于它記錄的是實(shí)際執(zhí)行的命令袁勺,所以也易讀雹食。為了兼顧寫(xiě)入性能和數(shù)據(jù)安全,可以在配置文件設(shè)置appendfsysnc everysec
期丰。并不推薦appendfsync no
選項(xiàng)群叶,因?yàn)檫@種方式是由操作系統(tǒng)決定何時(shí)對(duì)AOF文件進(jìn)行寫(xiě)入。在緩沖區(qū)被待寫(xiě)入硬盤(pán)的數(shù)據(jù)填滿(mǎn)時(shí)钝荡,可能造成Redis的寫(xiě)入操作被阻塞街立,嚴(yán)重影響性能。-
重寫(xiě)
AOF
文件埠通。如果用戶(hù)開(kāi)啟了AOF
功能赎离,Redis運(yùn)行時(shí)間越長(zhǎng),`AOF文件也會(huì)越來(lái)越大端辱。用戶(hù)可以發(fā)送
BGREWRITEAOF重寫(xiě)AOF文件梁剔,它會(huì)移除AOF文件中的冗余命令以此來(lái)減小
AOF文件的體積虽画。由于AOF文件重寫(xiě)會(huì)用到子進(jìn)程,因此也存在
BGSAVE`命令持久化快照時(shí)因?yàn)閯?chuàng)建子進(jìn)程而導(dǎo)致的性能問(wèn)題和內(nèi)存占用問(wèn)題荣病。除了使用命令重寫(xiě)AOF文件码撰,也可以在配置文件中配置,以讓Redis自動(dòng)執(zhí)行重寫(xiě)命令众雷。#當(dāng)aof文件體積大于64mb且比上次重寫(xiě)之后的體積增大了至少一倍 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
內(nèi)存優(yōu)化
設(shè)置maxmemory灸拍。設(shè)置Redis使用的最大物理內(nèi)存,即Redis在占用maxmemory大小的內(nèi)存之后就開(kāi)始拒絕后續(xù)的寫(xiě)入請(qǐng)求砾省,該參數(shù)可以確保Redis因?yàn)槭褂昧舜罅績(jī)?nèi)存嚴(yán)重影響速度或者發(fā)生OOM鸡岗。此外,可以使用
info
命令查看Redis占用的內(nèi)存及其它信息编兄。讓鍵名保持簡(jiǎn)短轩性。鍵的長(zhǎng)度越長(zhǎng),
Redis
需要存儲(chǔ)的數(shù)據(jù)也就越多-
使用短結(jié)構(gòu)狠鸳。這節(jié)主要談?wù)凴edis的list揣苏、hash、set件舵、zset這四種數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ)優(yōu)化卸察。
在Redis3.2之前,如果列表铅祸、散列或者有序集合的長(zhǎng)度或者體積較小坑质,Redis會(huì)選擇一種名為ziplist的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)它們。該結(jié)構(gòu)是列表临梗、散列和有序集合三種不同類(lèi)型的對(duì)象的一種非結(jié)構(gòu)化表示涡扼,與Redis在通常情況下使用雙向鏈表來(lái)表示列表、使用散列表示散列盟庞、使用散列加跳躍表表示有序集合相比吃沪,它更加緊湊,避免了存儲(chǔ)額外的指針和元數(shù)據(jù)(比如字符串值的剩余可用空間和結(jié)束符"\0")什猖。但是壓縮列表需要在存儲(chǔ)的時(shí)候進(jìn)行序列化票彪,讀取的時(shí)候進(jìn)行反序列化。以散列為例不狮,在redis.conf
中降铸,可以進(jìn)行如下設(shè)置hash-max-ziplist-entries 512
hash-max-ziplist-value 64entries
選項(xiàng)說(shuō)明允許被編碼為ziplist的最大元素?cái)?shù)量,value
表示壓縮列表每個(gè)節(jié)點(diǎn)的最大體積是多少個(gè)字節(jié)荤傲。如果任意一個(gè)條件不滿(mǎn)足,則壓縮列表會(huì)退化成相應(yīng)的常規(guī)結(jié)構(gòu)颈渊。這樣做的原因是遂黍,當(dāng)壓縮列表的體積越來(lái)越大時(shí)终佛,操作這些數(shù)據(jù)結(jié)構(gòu)的速度也會(huì)越來(lái)越慢,特別是當(dāng)需要掃描整個(gè)列表的時(shí)候雾家,因?yàn)镽edis需要解碼很多單獨(dú)的節(jié)點(diǎn)铃彰。那么上述值各取多少合適呢?合理的做法是將壓縮列表長(zhǎng)度限制在500~2000個(gè)元素之內(nèi)芯咧,并且每個(gè)元素體積在128字節(jié)之內(nèi)牙捉。Redis實(shí)戰(zhàn)推薦的做法是將壓縮列表長(zhǎng)度限制在1024個(gè)元素之內(nèi),并且每個(gè)元素體積不超過(guò)64字節(jié)敬飒。這類(lèi)參數(shù)可能還得由應(yīng)用的實(shí)際場(chǎng)景來(lái)定邪铲。此外,我們可以使用DEBUG OBJECT
命令來(lái)查看某個(gè)存儲(chǔ)的數(shù)據(jù)使用了何種數(shù)據(jù)結(jié)構(gòu)及其它一些重要信息无拗。
在Redis3.2及以后带到,列表的內(nèi)部實(shí)現(xiàn)變成了quicklist
而非ziplist或者傳統(tǒng)的雙端鏈表。官方定義是A doubly linked list of ziplists
英染,即由ziplist組成的雙向鏈表揽惹。quicklist這樣設(shè)計(jì)的原因大概是一個(gè)空間和時(shí)間的折中:(1)雙向鏈表便于在表的兩端進(jìn)行push和pop操作,但是它的內(nèi)存開(kāi)銷(xiāo)比較大四康。首先搪搏,它在每個(gè)節(jié)點(diǎn)上除了要保存數(shù)據(jù)之外,還要額外保存兩個(gè)指針闪金;其次疯溺,雙向鏈表的各個(gè)節(jié)點(diǎn)是單獨(dú)的內(nèi)存塊,地址不連續(xù)毕泌,節(jié)點(diǎn)多了容易產(chǎn)生內(nèi)存碎片喝检。(2)ziplist由于是一整塊連續(xù)內(nèi)存,所以存儲(chǔ)效率很高撼泛。但是挠说,它不利于修改操作,每次數(shù)據(jù)變動(dòng)都會(huì)引發(fā)一次內(nèi)存的realloc愿题。特別是當(dāng)ziplist長(zhǎng)度很長(zhǎng)的時(shí)候损俭,一次realloc可能會(huì)導(dǎo)致大批量的數(shù)據(jù)拷貝,進(jìn)一步降低性能潘酗。那么到底一個(gè)quicklist節(jié)點(diǎn)包含多長(zhǎng)的ziplist合適呢杆兵?我們從存儲(chǔ)效率來(lái)分析:(1)每個(gè)quicklist節(jié)點(diǎn)上的ziplist越短,則內(nèi)存碎片越多仔夺。內(nèi)存碎片多了琐脏,有可能在內(nèi)存中產(chǎn)生很多無(wú)法被利用的小碎片,從而降低存儲(chǔ)效率。這種情況的極端是每個(gè)quicklist節(jié)點(diǎn)上的ziplist只包含一個(gè)數(shù)據(jù)項(xiàng)日裙,這就退化成一個(gè)普通的雙向鏈表了吹艇。(2)每個(gè)quicklist節(jié)點(diǎn)上的ziplist越長(zhǎng),則為ziplist分配大塊連續(xù)內(nèi)存空間的難度就越大。有可能出現(xiàn)內(nèi)存里有很多小塊的空閑空間(它們加起來(lái)很多),但卻找不到一塊足夠大的空閑空間分配給ziplist的情況截歉。這同樣會(huì)降低存儲(chǔ)效率五嫂。這種情況的極端是整個(gè)quicklist只有一個(gè)節(jié)點(diǎn),所有的數(shù)據(jù)項(xiàng)都分配在這僅有的一個(gè)節(jié)點(diǎn)的ziplist里面。這其實(shí)退化成一個(gè)ziplist了。
redis.conf
提供了以下參數(shù)來(lái)設(shè)置quicklist
的相關(guān)屬性list-max-ziplist-size -2 list-compress-depth 0
size
參數(shù)可取正值和負(fù)值,取正的時(shí)候表示按照數(shù)據(jù)項(xiàng)個(gè)數(shù)來(lái)限定每個(gè)quicklist節(jié)點(diǎn)上的ziplist長(zhǎng)度撑碴。取負(fù)的時(shí)候表示按照數(shù)據(jù)項(xiàng)大小來(lái)限制每個(gè)quicklist上的ziplist長(zhǎng)度。計(jì)算方式是2^(abs(n)+1)
,比如這里-2
表示每個(gè)quicklist節(jié)點(diǎn)上的ziplist大小不能超過(guò)2^(2+1)即8kb碎连。
當(dāng)列表很長(zhǎng)的時(shí)候灰羽,最容易被訪(fǎng)問(wèn)的很可能是兩端的數(shù)據(jù),中間的數(shù)據(jù)被訪(fǎng)問(wèn)的頻率比較低(訪(fǎng)問(wèn)起來(lái)性能也很低)鱼辙。如果應(yīng)用場(chǎng)景符合這個(gè)特點(diǎn)廉嚼,那么list還提供了一個(gè)選項(xiàng),能夠把中間的數(shù)據(jù)節(jié)點(diǎn)進(jìn)行壓縮倒戏,從而進(jìn)一步節(jié)省內(nèi)存空間怠噪。Redis的配置參數(shù)list-compress-depth
就是用來(lái)完成這個(gè)設(shè)置的。它表示兩端不被壓縮的元素個(gè)數(shù)杜跷。這里節(jié)點(diǎn)個(gè)數(shù)指quicklist雙向鏈表的節(jié)點(diǎn)個(gè)數(shù)傍念,如果一個(gè)quicklist節(jié)點(diǎn)上的ziplist被壓縮,就是整體被壓縮葛闷。如果值為0憋槐,則表示兩端數(shù)據(jù)都不被壓縮,為n淑趾,則表示兩端各n個(gè)數(shù)據(jù)不被壓縮阳仔。
關(guān)于ziplist
和quicklist
細(xì)節(jié)可以閱讀參考鏈接中的相關(guān)文章。集合(set)也有自己的緊湊表示形式扣泊。如果集合元素全是整數(shù)近范,而這些整數(shù)處于平臺(tái)的有符號(hào)范圍之內(nèi),并且它們的數(shù)量又在一定范圍內(nèi)延蟹,那么Redis會(huì)以有序整數(shù)數(shù)組的方式存儲(chǔ)集合评矩,這種方式被成為整數(shù)集合(intset)。
redis.conf
中可以通過(guò)set-max-intset-entries 512
設(shè)置該范圍阱飘。當(dāng)存儲(chǔ)數(shù)據(jù)個(gè)數(shù)大于
512
的時(shí)候或者存儲(chǔ)了其它類(lèi)型的數(shù)據(jù)時(shí)斥杜,它會(huì)退化為hashtable
虱颗。在數(shù)據(jù)量較大的時(shí)候,與ziplist
由于編碼解碼數(shù)據(jù)(如果有對(duì)數(shù)據(jù)移動(dòng)的操作也會(huì)有影響)主要造成性能瓶頸的原因不同蔗喂,主要影響intset
性能的原因是它在執(zhí)行插入或者刪除操作的時(shí)候都需要對(duì)數(shù)據(jù)進(jìn)行移動(dòng)上枕。因此,需要根據(jù)實(shí)際情況設(shè)置intset
最大的元素個(gè)數(shù)弱恒。 對(duì)數(shù)據(jù)進(jìn)行分片。比如當(dāng)單個(gè)散列比較大的時(shí)候棋恼,可以按一定規(guī)則(key+id%shard_num)對(duì)數(shù)據(jù)進(jìn)行分片返弹,然后ziplist便更不容易退化為
hashtable
,且不會(huì)出現(xiàn)編碼解碼引起的性能問(wèn)題爪飘。
擴(kuò)展讀寫(xiě)能力
擴(kuò)展讀性能义起。在
redis.conf
中添加slaveof host port
即可將其配置為另一臺(tái)Redis服務(wù)器的從服務(wù)器。注意师崎,在從服務(wù)器連接主服務(wù)器的時(shí)候默终,從服務(wù)器之前的數(shù)據(jù)會(huì)被清空±缯郑可以用這種方式建立從服務(wù)器樹(shù)齐蔽,擴(kuò)展其讀能力。但這種方式并未做故障轉(zhuǎn)移床估,高可用Redis部署方案可以參考Redis Sentinel,Redis Cluster和Codis含滴。擴(kuò)展寫(xiě)性能。(1)使用集群分片技術(shù)丐巫,比如Redis Cluster;(2)單機(jī)上運(yùn)行多個(gè)Redis實(shí)例谈况。由于Redis是單線(xiàn)程設(shè)計(jì),在涉及到cpu bound的操作的時(shí)候递胧,可能速度會(huì)大大降低碑韵。如果服務(wù)器的cpu、io資源充足缎脾,可以在同一臺(tái)機(jī)器上運(yùn)行多個(gè)Redis服務(wù)器祝闻。
應(yīng)用程序優(yōu)化
應(yīng)用程序優(yōu)化部分主要是客戶(hù)端和Redis交互的一些建議。主要思想是盡可能減少操作Redis往返的通信次數(shù)赊锚。
使用流水線(xiàn)操作治筒。Redis支持流水線(xiàn)(
pipeline
)操作,其中包括了事務(wù)流水線(xiàn)和非事務(wù)流水線(xiàn)舷蒲。Redis提供了WATCH
命令與事務(wù)搭配使用耸袜,實(shí)現(xiàn)CAS樂(lè)觀鎖的機(jī)制。WATCH的機(jī)制是:在事務(wù)EXEC命令執(zhí)行時(shí)牲平,Redis會(huì)檢查被WATCH的key堤框,只有被WATCH的key從WATCH起始時(shí)至今沒(méi)有發(fā)生過(guò)變更,EXEC才會(huì)被執(zhí)行。如果WATCH的key在WATCH命令到EXEC命令之間發(fā)生過(guò)變化蜈抓,則EXEC命令會(huì)返回失敗启绰。使用事務(wù)的一個(gè)好處是被MULTI
和EXEC
包裹的命令在執(zhí)行時(shí)不會(huì)被其它客戶(hù)端打斷。但是事務(wù)會(huì)消耗資源沟使,隨著負(fù)載不斷增加委可,由WATCH
、MULTI
腊嗡、EXEC
組成的事務(wù)(CAS)可能會(huì)進(jìn)行大量重試着倾,嚴(yán)重影響程序性能。
如果用戶(hù)需要向Redis發(fā)送多個(gè)命令燕少,且一個(gè)命令的執(zhí)行結(jié)果不會(huì)影響另一個(gè)命令的輸入卡者,那么我們可以使用非事務(wù)流水線(xiàn)來(lái)代替事務(wù)性流水線(xiàn)。非事務(wù)流水線(xiàn)主要作用是將待執(zhí)行的命令一次性全部發(fā)送給Redis客们,減少來(lái)回通信的次數(shù)崇决,以此來(lái)提升性能。使用mset底挫、lpush恒傻、zadd等批量操作數(shù)據(jù)。它的原理同非事務(wù)性流行線(xiàn)操作建邓。
使用
lua
腳本碌冶。Lua腳本跟單個(gè)Redis命令及MULTI/EXEC
組成的事務(wù)一樣,都是原子操作涝缝。Redis采用單線(xiàn)程設(shè)計(jì)扑庞,每次只能執(zhí)行一個(gè)命令,每個(gè)單獨(dú)的命令都是原子的拒逮。Lua腳本有兩個(gè)好處:(1)減少多個(gè)操作通信往返帶來(lái)的開(kāi)銷(xiāo)(2)無(wú)需擔(dān)心由于事務(wù)競(jìng)爭(zhēng)導(dǎo)致的性能開(kāi)銷(xiāo)罐氨。-
盡可能使用時(shí)間復(fù)雜度為
O(1)
的操作,避免使用復(fù)雜度為O(N)
的操作滩援。避免使用這些O(N)命令主要有幾個(gè)辦法:(1)不要把List當(dāng)做列表使用栅隐,僅當(dāng)做隊(duì)列來(lái)使用;(2)通過(guò)機(jī)制嚴(yán)格控制Hash、Set玩徊、Sorted Set的大小;(3)可能的話(huà)租悄,將排序、并集恩袱、交集等操作放在客戶(hù)端執(zhí)行;(4)絕對(duì)禁止使用KEYS命令;(5)避免一次性遍歷集合類(lèi)型的所有成員泣棋,而應(yīng)使用SCAN
類(lèi)的命令進(jìn)行分批的,游標(biāo)式的遍歷Redis提供了Slow Log功能畔塔,可以自動(dòng)記錄耗時(shí)較長(zhǎng)的命令潭辈,
redis.conf
中的配置如下#執(zhí)行時(shí)間慢于10000毫秒的命令計(jì)入Slow Log slowlog-log-slower-than 10000 #最大紀(jì)錄多少條Slow Log slowlog-max-len 128
使用
SLOWLOG GET n
命令鸯屿,可以輸出最近n條慢查詢(xún)?nèi)罩尽J褂?code>SLOWLOG RESET命令把敢,可以重置Slow Log寄摆。
參考