. 簡(jiǎn)述:
Redis由Salvatore Sanfilippo使用C語(yǔ)言編寫(xiě)的一種支持網(wǎng)絡(luò)课锌、可基于內(nèi)存亦可持久化的日志型挣柬、Key-Value數(shù)據(jù)庫(kù)萌业。
2. 特點(diǎn):
2.1 優(yōu)點(diǎn):
2.1.1 redis的支持?jǐn)?shù)據(jù)持久化
可以將內(nèi)存中的數(shù)據(jù)保存在磁盤(pán)中愿汰,重啟時(shí)通過(guò)再次加載彬碱,這樣數(shù)據(jù)不會(huì)丟失窃诉;
2.1.2 Redis支持?jǐn)?shù)據(jù)類(lèi)型豐富
支持String杨耙,List,Set飘痛,Zset(sorted sets)珊膜,Hash數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ);
2.1.3 Redis支持master-slave模式的數(shù)據(jù)備份宣脉;
2.1.4 Redis的操作支持原子性
Redis提供了簡(jiǎn)單的事務(wù)功能车柠, 將一組需要一起執(zhí)行的命令放到multi和exec兩個(gè)命令之間(其中 multi命令代表事務(wù)開(kāi)始, exec命令代表事務(wù)結(jié)束)塑猖,它們之間的命令是原子順序執(zhí)行的竹祷。
2.1.5 性能極高
讀寫(xiě)數(shù)據(jù)每秒可達(dá)10萬(wàn)次左右。
2.1.6 字符串能夠存儲(chǔ)的最大值是512M羊苟,幾乎能夠滿(mǎn)足所有常見(jiàn)業(yè)務(wù)場(chǎng)景溶褪。
缺點(diǎn):
2.2.1 容量收到物理內(nèi)存的限制,適合小數(shù)據(jù)量級(jí)別的高性能讀寫(xiě)操作践险,大數(shù)據(jù)量可以選擇淘寶的Tair
2.2.2 不具有自動(dòng)容錯(cuò)和恢復(fù)能力猿妈;
2.2.3 主機(jī)宕機(jī)之后,如果有部分沒(méi)有同步到從機(jī)的數(shù)據(jù)巍虫,那么切到從機(jī)之后彭则,會(huì)出現(xiàn)數(shù)據(jù)不一致的情況;
2.2.3 redis事務(wù)不支持回滾占遥。
3.Redis的數(shù)據(jù)持久化
Redis提供了RDB(Redis DataBase)和AOF(Append Only File)兩種持久化方式俯抖。
3.1 RDB是指在一定的時(shí)間間隔將數(shù)據(jù)快照存儲(chǔ)到磁盤(pán)。
當(dāng)?shù)竭_(dá)數(shù)據(jù)備份時(shí)間時(shí)瓦胎,Redis會(huì)啟動(dòng)一個(gè)線(xiàn)程芬萍,該線(xiàn)程將數(shù)據(jù)保存到一個(gè)臨時(shí)文件,以替換上個(gè)時(shí)間周期備份的臨時(shí)文件搔啊。優(yōu)點(diǎn):數(shù)據(jù)恢復(fù)快柬祠,缺點(diǎn):最后一次持久化后的數(shù)據(jù)有可能會(huì)丟失。
3.2 AOF是以日志的形式記錄每次對(duì)Redis的寫(xiě)操作
以redis協(xié)議追加保存每次寫(xiě)的操作到文件末尾负芋,當(dāng)Redis重啟的時(shí)漫蛔,重新執(zhí)行這些命令來(lái)恢復(fù)原始的數(shù)據(jù)。優(yōu)點(diǎn):數(shù)據(jù)實(shí)時(shí)同步,缺點(diǎn):AOF文件持續(xù)增長(zhǎng)而過(guò)大(Redis通過(guò)設(shè)置文件大小的閥值來(lái)觸發(fā)AOF文件的壓縮)莽龟。
注:當(dāng)同時(shí)開(kāi)啟RDB和AOF時(shí)蠕嫁,系統(tǒng)優(yōu)先使用AOF。
4.主從復(fù)制
4.1 主從復(fù)制的延遲
主從復(fù)制可以通過(guò)slaveof命令配置實(shí)現(xiàn)毯盈,默認(rèn)情況下Redis都是主節(jié)點(diǎn)剃毒,每個(gè)從節(jié)點(diǎn)只能有一個(gè)主節(jié)點(diǎn),而主節(jié)點(diǎn)可以有多個(gè)從節(jié)點(diǎn)搂赋,故復(fù)制的數(shù)據(jù)流是單向的迟赃,即只能從主節(jié)點(diǎn)復(fù)制到從節(jié)點(diǎn),因此主節(jié)點(diǎn)無(wú)法感知從節(jié)點(diǎn)的修改厂镇,所以一般從節(jié)點(diǎn)提供只讀模式纤壁。
由于主從部署在不同的機(jī)器上,Redis提供了repl-disable-tcp-nodelay參數(shù)用于控制是否關(guān)閉
TCP_NODELAY捺信, 默認(rèn)關(guān)閉酌媒。
a.當(dāng)關(guān)閉時(shí), 主節(jié)點(diǎn)產(chǎn)生的命令數(shù)據(jù)大小都會(huì)及時(shí)地發(fā)送給從節(jié)點(diǎn)迄靠, 優(yōu)點(diǎn):延遲會(huì)變小秒咨, 缺點(diǎn):網(wǎng)絡(luò)帶寬的消耗增加。 適用于同機(jī)架或同機(jī)房部署等網(wǎng)絡(luò)環(huán)境好的場(chǎng)景掌挚。
b.當(dāng)開(kāi)啟時(shí)雨席, 主節(jié)點(diǎn)會(huì)合并較小的數(shù)據(jù)包發(fā)送到從節(jié)點(diǎn),一般時(shí)間間隔設(shè)置為30-50毫秒吠式。優(yōu)點(diǎn):節(jié)省帶寬陡厘,缺點(diǎn):增大主從之間的延遲。 適用于主從網(wǎng)絡(luò)環(huán)境較差的環(huán)境特占, 如跨機(jī)房部署等糙置。
注意:從節(jié)點(diǎn)更換主節(jié)點(diǎn)后從節(jié)點(diǎn)會(huì)清空之前所有的數(shù)據(jù), 所以執(zhí)行該操作時(shí)一定要在正確的主節(jié)點(diǎn)和從節(jié)點(diǎn)上是目。
4.2 Redis拓?fù)浣Y(jié)構(gòu)
4.2.1 一主一從
4.2.2 一主多從
4.2.3 樹(shù)狀主從
4.3 主從復(fù)制過(guò)程
1.從節(jié)點(diǎn)保存主節(jié)點(diǎn)信息
即執(zhí)行slaveof后谤饭,從節(jié)點(diǎn)保存主節(jié)點(diǎn)的地址信息。
2.主從建立Socket連接
從節(jié)點(diǎn)通過(guò)執(zhí)行定時(shí)任務(wù)(即每秒執(zhí)行一次)發(fā)現(xiàn)slaveof新的主節(jié)點(diǎn)后懊纳,會(huì)嘗試建立Socket網(wǎng)絡(luò)連接揉抵。如果連接失敗,從節(jié)點(diǎn)會(huì)通過(guò)定時(shí)任務(wù)來(lái)無(wú)限重試嗤疯。
3.發(fā)送ping命令
Socket連接成功后冤今,從節(jié)點(diǎn)發(fā)送ping請(qǐng)求進(jìn)行首次通信,主要目的是:檢測(cè)主從之間網(wǎng)絡(luò)套接字是否可用身弊,并檢測(cè)主節(jié)點(diǎn)當(dāng)前是否可接受處理命令辟汰。
4.權(quán)限驗(yàn)證
如果主節(jié)點(diǎn)設(shè)置了requirepass參數(shù)列敲, 則需要密碼驗(yàn)證阱佛,從節(jié)點(diǎn)必須配置masterauth參數(shù)保證與主節(jié)點(diǎn)相同的密碼才能通過(guò)驗(yàn)證帖汞。
5.同步數(shù)據(jù)集
主從復(fù)制連接正常后, 對(duì)于首次建立復(fù)制的場(chǎng)景凑术, 主節(jié)點(diǎn)會(huì)把持有的數(shù)據(jù)全部發(fā)送給從節(jié)點(diǎn)翩蘸,
6.命令持續(xù)復(fù)制
主節(jié)點(diǎn)會(huì)持續(xù)地把寫(xiě)命令發(fā)送給從節(jié)點(diǎn), 從而保證主從數(shù)據(jù)一致淮逊。
4.4 數(shù)據(jù)延遲
Redis主從數(shù)據(jù)的延遲由于異步復(fù)制特性導(dǎo)致的催首,是無(wú)法避免的, 延遲受到網(wǎng)絡(luò)帶寬和命令阻塞情況的影響泄鹏, 如果業(yè)務(wù)不允許短時(shí)間內(nèi)的數(shù)據(jù)延遲郎任, 可以編碼實(shí)現(xiàn)監(jiān)控程序,監(jiān)聽(tīng)主從節(jié)點(diǎn)的復(fù)制偏移量备籽, 當(dāng)延遲較大時(shí)觸發(fā)報(bào)警或通知客戶(hù)端避免讀取延遲過(guò)大的從節(jié)點(diǎn)舶治,從而避免讀不到數(shù)據(jù)的情況發(fā)生。
4.5 過(guò)期數(shù)據(jù)的處理
4.5.1 惰性刪除
主節(jié)點(diǎn)每次處理讀取命令時(shí)车猬,除了讀取key-value表霉猛,還會(huì)讀取Redis超時(shí)時(shí)間表,以檢查key是否超時(shí)珠闰, 如果超時(shí)則刪除key對(duì)象惜浅, 之后del命令也會(huì)異步發(fā)送給從節(jié)點(diǎn),從節(jié)點(diǎn)再執(zhí)行del伏嗜。
4.5.2 定時(shí)刪除
Redis主節(jié)點(diǎn)在內(nèi)部定時(shí)任務(wù)會(huì)循環(huán)讀取一定數(shù)量的Key坛悉, 當(dāng)發(fā)現(xiàn)讀取的Key過(guò)期時(shí)執(zhí)行del命令,之后del命令也會(huì)異步發(fā)送給從節(jié)點(diǎn)承绸,從節(jié)點(diǎn)再執(zhí)行del吹散。
注意:為了保證復(fù)制數(shù)據(jù)的一致性, 從節(jié)點(diǎn)自身不會(huì)主動(dòng)刪除超時(shí)數(shù)據(jù)八酒。
4.6 Redis讀寫(xiě)分離的考量
考慮到數(shù)據(jù)延遲空民,過(guò)期數(shù)據(jù)等,以及Redis的高性能羞迷,因此在考慮讀寫(xiě)分離之前界轩,一定要在主節(jié)點(diǎn)上做好充分優(yōu)化,如解決慢查詢(xún)衔瓮,持久化阻塞和合理的數(shù)據(jù)結(jié)構(gòu)等浊猾,當(dāng)仍然不滿(mǎn)足時(shí),可以先考慮Redis Cluster等分布式方案热鞍,再考慮讀寫(xiě)分離方案葫慎。
5.Redis的阻塞問(wèn)題
由于Redis是典型的單線(xiàn)程架構(gòu)衔彻,所有的讀寫(xiě)操作都在一條主線(xiàn)程中完成,因此如果主線(xiàn)程擁塞了偷办,那將是業(yè)務(wù)系統(tǒng)的噩夢(mèng)艰额。
造成阻塞的原因如下:
5.1 API或數(shù)據(jù)結(jié)構(gòu)使用不合理
問(wèn)題定位:1.通過(guò)slowlog get { n } 獲取最近n條慢查詢(xún)(Redis默認(rèn)對(duì)于執(zhí)行超過(guò)10毫秒的命令都會(huì)記錄到一個(gè)定長(zhǎng)隊(duì)列(隊(duì)列默認(rèn)長(zhǎng)度為128)中),發(fā)現(xiàn)慢查詢(xún)后椒涯,可以通過(guò)使用低算法復(fù)雜度的命令或避免大對(duì)象數(shù)據(jù)來(lái)優(yōu)化柄沮。
5.2 CPU飽和的問(wèn)題
CPU飽和是指Redis把單核CPU(由于Redis是單線(xiàn)程,只能使用一個(gè)CPU)使用率跑到接近100%废岂∽娲辏可以使用top命令查看Redis線(xiàn)程的CPU使用率,可參考:http://www.reibang.com/p/6d7571d82304湖苞。對(duì)于Redis的使用情況拯欧,可通過(guò)redis-cli-h{ip}-p{port}--stat命令每秒輸出Redis的統(tǒng)計(jì)信息。如果此時(shí)發(fā)現(xiàn)Redis的請(qǐng)求量比較低财骨,那說(shuō)明使用了高復(fù)雜度算法的命令镐作;如果此時(shí)發(fā)現(xiàn)Redis的請(qǐng)求量已經(jīng)很高了,而且應(yīng)用的優(yōu)化有限蚓再,那就需要使用新的Redis來(lái)分?jǐn)傉?qǐng)求和CPU的壓力滑肉。
5.3 持久化相關(guān)的阻塞
對(duì)于開(kāi)啟了持久化功能的Redis節(jié)點(diǎn),要檢查是否是fork阻塞摘仅,AOF刷盤(pán)阻塞和HugePage寫(xiě)操作阻塞導(dǎo)致的CPU使用率過(guò)高靶庙。具體可參考官網(wǎng):http://www.redis.io/topics/latency
5.4 CPU競(jìng)爭(zhēng)
當(dāng)Redis和其他多核CPU密集型服務(wù)部署在一起時(shí),其他進(jìn)程過(guò)度消耗CPU時(shí)娃属, 將嚴(yán)重影響Redis吞吐量六荒。可以將Redis綁定到一個(gè)CPU上矾端,避免CPU切換的開(kāi)銷(xiāo)掏击。
5.5 內(nèi)存交換
如果系統(tǒng)把Redis的部分內(nèi)存中的數(shù)據(jù)換出并序列化到磁盤(pán)后,如果請(qǐng)求的數(shù)據(jù)不在內(nèi)存中秩铆,需要內(nèi)存交換砚亭,那么Redis性能?chē)?yán)重下降。解決方式:擴(kuò)大內(nèi)存殴玛,設(shè)置Redis的最大可用內(nèi)存捅膘,降低系統(tǒng)內(nèi)存交換執(zhí)行的優(yōu)先級(jí)。
5.6 網(wǎng)絡(luò)問(wèn)題
解決方式:增加帶寬滚粟,同機(jī)房部署寻仗,機(jī)房部署使用專(zhuān)線(xiàn),改善機(jī)器的物理拓?fù)洌ㄍ锢頇C(jī)>同機(jī)架>跨機(jī)架>同機(jī)房>同城機(jī)房>異地機(jī)房凡壤,注意:容災(zāi)性相反)
6. Redis高性能的原因:
6.1.純內(nèi)存
Redis將所有數(shù)據(jù)放在內(nèi)存中署尤,而內(nèi)存的相應(yīng)時(shí)長(zhǎng)在100納秒左右耙替。
6.2.非阻塞I/O
Redis使用了epoll多路復(fù)用技術(shù),另外Redis自身的事件處理模型將epoll中的連接曹体、 讀寫(xiě)俗扇、關(guān)閉都轉(zhuǎn)換為事件。
6.3.沒(méi)有線(xiàn)程切換(Redis 6已經(jīng)支持了多線(xiàn)程)
Redis采用了單線(xiàn)程架構(gòu)混坞,避免了線(xiàn)程切換狐援、競(jìng)態(tài)和鎖等產(chǎn)生的自我消耗钢坦。
7.Redis的Pipeline流水線(xiàn)
Redis提供了批量操作命令(如mget究孕、 mset、hmset等)爹凹,有效的節(jié)約了多次訪(fǎng)問(wèn)的往返時(shí)間厨诸,但是有些命令沒(méi)有對(duì)應(yīng)的批量命令(如hgetall沒(méi)有對(duì)應(yīng)的mhgetall),如果n次調(diào)用禾酱,就會(huì)消耗n次往返時(shí)間微酬。Redis針對(duì)這一問(wèn)題提供了Pipeline流水線(xiàn)機(jī)制,Redis將一組Redis命令組裝到一起颤陶,通過(guò)一次調(diào)用Redis颗管,再將執(zhí)行結(jié)果按照請(qǐng)求順序返回客戶(hù)端。
8 Redis內(nèi)存優(yōu)化:
info memory命令可以顯示內(nèi)存的相關(guān)指標(biāo)滓走,主要有used_memory(Redis內(nèi)部存儲(chǔ)所有數(shù)據(jù)內(nèi)存占用量)垦江,used_memory_ssr(操作系統(tǒng)顯示Redis進(jìn)程占用的物理內(nèi)存)以及他們的比值mem_fragmentation_ratio。
當(dāng)mem_fragmentation_ratio>1時(shí)搅方,說(shuō)明有部分內(nèi)存沒(méi)有用于數(shù)據(jù)存儲(chǔ)比吭,而是被內(nèi)存碎片消耗,因此要降低mem_fragmentation_ratio的值姨涡,即降低碎片率衩藤。
當(dāng)mem_fragmentation_ratio<1時(shí),是因?yàn)椴僮飨到y(tǒng)把Redis的內(nèi)存數(shù)據(jù)交換到磁盤(pán)涛漂,因此used_memory_ssr才會(huì)大于used_memory赏表,由于硬盤(pán)的讀寫(xiě)速度和內(nèi)存差距很大,此時(shí)Redis的性能會(huì)很差匈仗。
8.2.內(nèi)存的使用劃分
8.2.1 Redis的進(jìn)程自身的內(nèi)存
這部分?jǐn)?shù)據(jù)在幾MB級(jí)別瓢剿,很小可以忽略不計(jì)。
8.2.2 對(duì)象內(nèi)存
即所有key-value數(shù)據(jù)使用的內(nèi)存,包括所有key的消耗和所有value的消耗锚沸。
8.2.3 緩存內(nèi)存
主要包括客戶(hù)端緩存跋选,復(fù)制積壓區(qū)緩存,AOF緩存哗蜈。
8.2.3.1 客戶(hù)端緩存:
指的是所有接入到Redis服務(wù)器TCP連接的輸入輸出緩沖前标。輸入緩沖無(wú)法控制(最大空間為1G)坠韩, 如果超過(guò)將斷開(kāi)連接。 輸出緩沖通過(guò)參數(shù)控制(默認(rèn)為1G)
a.客戶(hù)端:可以通過(guò)設(shè)置最大客戶(hù)端連接數(shù)(特別是有慢連接時(shí))來(lái)限制大量客戶(hù)端接入造成的內(nèi)存消耗
b.主從復(fù)制客戶(hù)端:當(dāng)主從復(fù)制延遲較高或從節(jié)點(diǎn)掛載從節(jié)點(diǎn)數(shù)量過(guò)多時(shí)炼列,內(nèi)存消耗將增加只搁。可通過(guò)改善網(wǎng)絡(luò)環(huán)境和減少主節(jié)點(diǎn)掛載的從節(jié)點(diǎn)數(shù)來(lái)解決俭尖。
c.訂閱客戶(hù)端:使用發(fā)布/訂閱時(shí)氢惋, 連接客戶(hù)端使用獨(dú)立的輸出緩沖區(qū),對(duì)于訂閱消息短時(shí)間內(nèi)量比較大時(shí)稽犁,輸出緩沖區(qū)會(huì)產(chǎn)生積壓溢出焰望。
d 復(fù)制積壓緩存區(qū)
復(fù)制積壓緩存區(qū)主要用來(lái)實(shí)現(xiàn)主從復(fù)制功能,一個(gè)主節(jié)點(diǎn)只有一個(gè)復(fù)制積壓緩存區(qū)已亥,所有從節(jié)點(diǎn)共享該緩存區(qū)熊赖。
e AOF緩沖區(qū)
AOF緩沖區(qū)用于在Redis重寫(xiě)期間保存最近的寫(xiě)入命令,AOF緩存區(qū)的消耗大小由AOF重寫(xiě)時(shí)間和寫(xiě)入命令量決定虑椎,這部分空間占用量比較小震鹉。
8.2.3.2 內(nèi)存碎片
Redis提供了jemalloc(默認(rèn)),glibc和tcmalloc三種內(nèi)存分配器捆姜。當(dāng)存儲(chǔ)的數(shù)據(jù)長(zhǎng)短差異較大時(shí)传趾,做頻繁的更新,刪除大量key泥技。主要解決碎片的方法有:
a.數(shù)據(jù)對(duì)齊浆兰,盡量采用數(shù)字類(lèi)型或者固定長(zhǎng)度字符串等;
b.Redis重啟,內(nèi)存會(huì)重新整理零抬。
8.2.3.3 子進(jìn)程內(nèi)存消耗
子進(jìn)程內(nèi)存消耗主要指執(zhí)行AOF/RDB重寫(xiě)時(shí)Redis創(chuàng)建的子進(jìn)程內(nèi)存消耗镊讼。
8.3 內(nèi)存管理
8.3.1 控制內(nèi)存上限
maxmemory參數(shù)設(shè)置最大可用內(nèi)存,一方面方式所用內(nèi)存超過(guò)物理內(nèi)存平夜,另一方面用于緩存蝶棋,超過(guò)上限時(shí),觸發(fā)內(nèi)存回收忽妒,以便釋放空間玩裙。
8.3.2 內(nèi)存回收
a.刪除過(guò)期key對(duì)象
參考上文
b.內(nèi)存溢出控制策略
內(nèi)存達(dá)到maxmemory上限時(shí)會(huì)觸發(fā)溢出控制策略,Redis提供了6種溢出控制策略:
1)noeviction:即拒絕寫(xiě)只響應(yīng)讀(默認(rèn))段直;
2) volatile-lru:根據(jù)LRU(Least Recently Used吃溅,即最近最少使用),刪除過(guò)期的key數(shù)據(jù)鸯檬,以騰出空間决侈。如果沒(méi)有可刪除的過(guò)期key數(shù)據(jù),回退到noeviction喧务;
3)allkeys-lru:不考慮過(guò)期key數(shù)據(jù)的LRU算法赖歌;
4)allkeys-random:隨機(jī)刪除所有鍵枉圃;
5)volatile-random:隨機(jī)刪除過(guò)期鍵;
6)volatile-ttl:根據(jù)鍵值對(duì)象的ttl屬性庐冯, 刪除將要過(guò)期的key數(shù)據(jù)孽亲。 如果沒(méi)有可刪除的數(shù)據(jù), 回退到noeviction策略展父。
8.3.3 手動(dòng)內(nèi)存回收
可以使用scan+object idletime命令批量查詢(xún)哪些key長(zhǎng)時(shí)間未被訪(fǎng)問(wèn)返劲, 然后對(duì)key進(jìn)行清理, 可降低內(nèi)存占用栖茉。
8.4 內(nèi)存優(yōu)化
8.4.1 縮減key(key值越短越好)和value的值的大小
主要方法有把key和value序列化成二進(jìn)制數(shù)組數(shù)據(jù)(可以通過(guò)protostuff,kryo等高效序列化工具)或者序列化成json篮绿,xml壓縮(如GZIP,Snappy壓縮工具)再放入Redis衡载,去掉value中不必要的字段搔耕。
8.4.2 共享對(duì)象池
共享對(duì)象池是指Redis內(nèi)部維護(hù)[0-9999]的整數(shù)對(duì)象池隙袁,避免Redis封裝RedisObject(Redis存儲(chǔ)的數(shù)據(jù)都使用redisObject來(lái)封裝)內(nèi)存消耗痰娱。
8.4.3 字符串優(yōu)化
a.Redis針對(duì)字符串采用了預(yù)分配機(jī)制,防止更新操作需要不斷重分配內(nèi)存和字節(jié)數(shù)據(jù)拷貝嘱吗,但是同事造成了內(nèi)存浪費(fèi)苗踪。因此盡量減少字符串頻繁修改操作(如append盛嘿、setrange),直接使用set修改字符串坡贺。
8.4.4 字符串重構(gòu)
避免每份數(shù)據(jù)作為字符串整體存儲(chǔ), 像json這樣的數(shù)據(jù)可以使用hash結(jié)構(gòu)箱舞, 使用二級(jí)結(jié)構(gòu)存儲(chǔ)能夠節(jié)省內(nèi)存遍坟。 可以使用hmget、 hmset命令支持字段的部分讀取修改晴股, 而不用每次整體存取愿伴。
8.4.5 編碼優(yōu)化
Redis通過(guò)不同編碼實(shí)現(xiàn)效率和空間的平衡,編碼類(lèi)型轉(zhuǎn)換在Redis寫(xiě)入數(shù)據(jù)時(shí)自動(dòng)完成电湘, 這個(gè)轉(zhuǎn)換過(guò)程是不可逆的隔节, 轉(zhuǎn)換規(guī)則只能從小內(nèi)存編碼向大內(nèi)存編碼轉(zhuǎn)換。針對(duì)性能要求較高的場(chǎng)景使用ziplist
8.4.6 控制key的數(shù)量
利用Redis的數(shù)據(jù)結(jié)構(gòu)降低外層鍵的數(shù)量寂呛,比如把大量鍵分組映射到多個(gè)hash(用field記錄副key)結(jié)構(gòu)中降低鍵的數(shù)量怎诫。
9.Redis的經(jīng)典問(wèn)題
9.1 緩存穿透
緩存穿透:指查詢(xún)的數(shù)據(jù),在數(shù)據(jù)庫(kù)不存在的情況贷痪。這樣程序會(huì)先訪(fǎng)問(wèn)Redis幻妓,沒(méi)有找到數(shù)據(jù),再查詢(xún)數(shù)據(jù)庫(kù)劫拢,也沒(méi)有找到肉津,然后返回空胖缤。如果惡意大量的訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)中不存在的數(shù)據(jù),造成資源浪費(fèi)阀圾,對(duì)數(shù)據(jù)庫(kù)造成壓力哪廓,甚至?xí)?dǎo)致業(yè)務(wù)不可用,甚至壓垮數(shù)據(jù)庫(kù)初烘。
解決方法:空值緩存涡真,即如果數(shù)據(jù)庫(kù)查詢(xún)的時(shí)候,沒(méi)有查詢(xún)到數(shù)據(jù)肾筐,也將空值緩存到Redis中哆料,并設(shè)置過(guò)期時(shí)間(相對(duì)普通的數(shù)據(jù),過(guò)期時(shí)間設(shè)置較短吗铐,避免占用過(guò)大的存儲(chǔ)空間)东亦,這樣就避免造成對(duì)數(shù)據(jù)庫(kù)的壓力。
9.2 緩存雪崩
緩存雪崩: 指在某一時(shí)間段內(nèi)唬渗,緩存數(shù)據(jù)集中過(guò)期失效典阵。比如CRM系統(tǒng),工作日早上9點(diǎn)左右镊逝,會(huì)迎來(lái)大量的數(shù)據(jù)訪(fǎng)問(wèn)壮啊,加入緩存是2個(gè)小時(shí),那么到了11點(diǎn)撑蒜,很多緩存數(shù)據(jù)就會(huì)過(guò)期歹啼,很多數(shù)據(jù)的訪(fǎng)問(wèn)都會(huì)落到數(shù)據(jù)庫(kù)上,導(dǎo)致此時(shí)的數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)量大增座菠,會(huì)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生周期性的壓力狸眼。另外,Redis集群的某些節(jié)點(diǎn)宕機(jī)浴滴,也會(huì)導(dǎo)致緩存雪崩拓萌,而且是不可預(yù)知的,此時(shí)DB的訪(fǎng)問(wèn)量大增巡莹,甚至壓垮DB司志。
解決方法: 針對(duì)不同的類(lèi)型數(shù)據(jù),如客戶(hù)降宅,銷(xiāo)售訂單等骂远,采用不同的緩存過(guò)期時(shí)間;在同一個(gè)對(duì)象類(lèi)中腰根,采取隨機(jī)因子的方式激才,設(shè)置不同的過(guò)期時(shí)間。
9.3 緩存擊穿
緩存擊穿: 指一個(gè)cache中的key并發(fā)訪(fǎng)問(wèn)量非常大,形成了熱點(diǎn)瘸恼。當(dāng)這個(gè)key過(guò)期后劣挫,持續(xù)的大并發(fā)就會(huì)跳過(guò)緩存,直接訪(fǎng)問(wèn)DB东帅,造成DB的壓力压固。
解決方法: 對(duì)熱點(diǎn)的key程序?qū)崿F(xiàn)自動(dòng)轉(zhuǎn)為永不過(guò)期緩存,可以在value中設(shè)置超時(shí)時(shí)間靠闭,程序內(nèi)部校驗(yàn)是否過(guò)期帐我。如果過(guò)期,異步發(fā)起一個(gè)線(xiàn)程更新緩存(可以使用鎖控制并發(fā))愧膀。
10.哨兵(Sentinel)
哨兵是Redis的高可用解決方案拦键,它由一個(gè)或者多個(gè)Sentinel實(shí)例組成Sentinel系統(tǒng),Sentinel系統(tǒng)監(jiān)視n個(gè)服務(wù)器以及對(duì)應(yīng)的從服務(wù)器檩淋,當(dāng)監(jiān)視到主服務(wù)器線(xiàn)下時(shí)芬为,自動(dòng)將主服務(wù)器對(duì)應(yīng)的某個(gè)從服務(wù)器升級(jí)為新的主服務(wù)器,代替主服務(wù)器繼續(xù)處理請(qǐng)求蟀悦。
哨兵系統(tǒng)架構(gòu):
10.1 定時(shí)監(jiān)控
a.每個(gè)哨兵實(shí)例會(huì)間隔10秒向主節(jié)點(diǎn)和從節(jié)點(diǎn)發(fā)送info命令媚朦,獲取最新的Redis的拓?fù)浣Y(jié)構(gòu);
b.每隔2秒每個(gè)哨兵節(jié)點(diǎn)會(huì)向Redis數(shù)據(jù)節(jié)點(diǎn)的指定頻道上發(fā)送 該節(jié)點(diǎn)對(duì)主節(jié)點(diǎn)的判斷以及自身的信息熬芜,同時(shí)也從該頻道上獲取其他哨兵節(jié)點(diǎn)的發(fā)送的信息莲镣;
c.每隔1秒,每個(gè)哨兵節(jié)點(diǎn)會(huì)向主節(jié)點(diǎn)涎拉、從節(jié)點(diǎn)、其他哨兵節(jié)點(diǎn)發(fā)送ping命令的圆,做心跳檢測(cè)鼓拧,來(lái)判斷節(jié)點(diǎn)的可達(dá)性。
10.2 節(jié)點(diǎn)下線(xiàn)
主觀(guān)下線(xiàn)
如果哨兵節(jié)點(diǎn)發(fā)送心跳檢測(cè)越妈,超過(guò)down_after_milliseconds后季俩,仍然沒(méi)有得到回復(fù),哨兵節(jié)點(diǎn)會(huì)認(rèn)為該節(jié)點(diǎn)為失效節(jié)點(diǎn)梅掠,稱(chēng)為主觀(guān)下線(xiàn)酌住。
客觀(guān)下線(xiàn)
當(dāng)一個(gè)哨兵節(jié)點(diǎn)主觀(guān)下線(xiàn)的節(jié)點(diǎn)是Master時(shí),該哨兵節(jié)點(diǎn)會(huì)向其他哨兵節(jié)點(diǎn)詢(xún)問(wèn)當(dāng)前Master節(jié)點(diǎn)的狀態(tài)阎抒,當(dāng)超過(guò)quorum指定個(gè)數(shù)時(shí)酪我,該哨兵節(jié)點(diǎn)認(rèn)為Master節(jié)點(diǎn)確實(shí)失效,然后做出客觀(guān)下線(xiàn)且叁。
10.3 選舉哨兵主節(jié)點(diǎn)
故障轉(zhuǎn)移有一個(gè)哨兵節(jié)點(diǎn)負(fù)責(zé)都哭,當(dāng)哨兵系統(tǒng)有多個(gè)哨兵節(jié)點(diǎn)時(shí),需要進(jìn)行哨兵領(lǐng)導(dǎo)者選舉:
當(dāng)一個(gè)哨兵節(jié)點(diǎn)最先完成客觀(guān)下線(xiàn)后,會(huì)向其他哨兵節(jié)點(diǎn)發(fā)送"我要成為領(lǐng)導(dǎo)者"欺矫,其他哨兵節(jié)點(diǎn)如果沒(méi)有同意過(guò)其他哨兵節(jié)點(diǎn)的請(qǐng)求纱新,則返回同意該請(qǐng)求,否則拒絕穆趴。當(dāng)該節(jié)點(diǎn)獲得的同意票大于等于max(quorum脸爱,num(sentinels)/2 +1)時(shí),該哨兵節(jié)點(diǎn)成為領(lǐng)導(dǎo)者未妹;否則進(jìn)入下一輪選舉阅羹。實(shí)際選舉過(guò)程非常快教寂,基本誰(shuí)先完成客觀(guān)下線(xiàn)捏鱼,誰(shuí)就是領(lǐng)導(dǎo)者。
10.4 故障轉(zhuǎn)移
哨兵領(lǐng)導(dǎo)者負(fù)責(zé)故障轉(zhuǎn)移酪耕,具體過(guò)程如下:
1.選取slave節(jié)點(diǎn)作為Master節(jié)點(diǎn)导梆;
?1.1 過(guò)濾失效的slave節(jié)點(diǎn);
?1.2 如果有slave優(yōu)先級(jí)迂烁,則根據(jù)slave節(jié)點(diǎn)的優(yōu)先級(jí)返回slave節(jié)點(diǎn)看尼,否則繼續(xù);
?1.3 選擇復(fù)制偏移量最大(這樣節(jié)省復(fù)制時(shí)間)的slave節(jié)點(diǎn)盟步,如果沒(méi)有則繼續(xù)藏斩;
?1.4 選擇runid最小的slave節(jié)點(diǎn);
2.哨兵領(lǐng)導(dǎo)者根據(jù)第一步選舉出的slave節(jié)點(diǎn)執(zhí)行slaveof on one命令却盘,使其成為Master;
3.哨兵領(lǐng)導(dǎo)者向剩余的slave節(jié)點(diǎn)發(fā)送命令狰域,讓他們成為新Master節(jié)點(diǎn)的從節(jié)點(diǎn);
4.哨兵節(jié)點(diǎn)集合將舊的Master節(jié)點(diǎn)更改為Slave節(jié)點(diǎn)黄橘,并持續(xù)監(jiān)控兆览,當(dāng)其恢復(fù)可用后,讓它c(diǎn)opy新的Master節(jié)點(diǎn)塞关。
當(dāng)前很少用哨兵抬探,目前推薦使用Redis集群,這個(gè)的話(huà)就避免了單個(gè)主節(jié)點(diǎn)的問(wèn)題帆赢。
11.Redis跳表
可參考文章小压,寫(xiě)的很詳細(xì)。
(https://www.cnblogs.com/Elliott-Su-Faith-change-our-life/p/7545940.html)
跳表的優(yōu)點(diǎn):
1. 存儲(chǔ)空間小
改變關(guān)于節(jié)點(diǎn)具有給定級(jí)別的概率的參數(shù)將使其比b樹(shù)更少的內(nèi)存占用
2. 范圍查找
紅黑樹(shù)結(jié)構(gòu)在范圍查找時(shí)椰于,要進(jìn)行中序遍歷怠益;
跳表查詢(xún)范圍非常簡(jiǎn)單,只需要在找到小值之后廉羔,對(duì)第1層鏈表進(jìn)行若干步的遍歷就可以實(shí)現(xiàn)溉痢。
3. 插入刪除效率
紅黑樹(shù)的插入和刪除會(huì)導(dǎo)致樹(shù)的旋轉(zhuǎn)僻造,跳表的插入和刪除只需要修改相鄰節(jié)點(diǎn)的指針,簡(jiǎn)單又快速孩饼。
12.Redis的字典
Redis數(shù)據(jù)庫(kù)使用字典作為底層的實(shí)現(xiàn)髓削,利用字典提供CRUD操作。
12.1 Redis的Hash表
Redis使用的Hash表dict.h/dictht镀娶,具體格式如下:
typedef struct dictht {
dictEntry **table; // Hash表數(shù)組
unsigned long size;// Hash表的大小
unsigned long sizemasky;// Hash表的大小的掩碼立膛,大小總是等于 size-1,用于計(jì)算索引值
unsigned long size; // table中已有節(jié)點(diǎn)的數(shù)量
} dictht梯码;
typedef struct dictEntry {
// key
void *key;
// value宝泵,可以是指針*val、整數(shù)u64和整數(shù)s64的其中之一
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; // 指向下一個(gè)Hash節(jié)點(diǎn)轩娶,形成鏈表儿奶,解決Hash沖突
} dictEntry;
數(shù)據(jù)保存格式
12.2 Redis字典
Redis的字典由dict.h/dict 結(jié)構(gòu):
typedef struct dict {
// 類(lèi)型特定函數(shù),指向一簇用于操作特定類(lèi)型key-value的函數(shù)鳄抒,主要函數(shù)有hash值計(jì)算(HashFunction)闯捎、復(fù)制key(keyDup)、復(fù)制value(valDup)许溅、對(duì)比key大小(keyCompare)瓤鼻、刪除key(keyDestructor)和刪除value(valDestructor)的函數(shù)等。
dictType *type;
// 私有數(shù)據(jù)贤重,保存了傳給dictType的參數(shù)
void *private;
// Hash表茬祷,字典只使用ht[0],ht[1] 只會(huì)在對(duì)ht[0]進(jìn)行rehash時(shí)使用
dictht ht[2];
// rehash索引并蝗,記錄當(dāng)前rehash的進(jìn)度祭犯,當(dāng)rehash不在進(jìn)行中,值為-1
in trehashidx;
} dict;
沒(méi)有進(jìn)行rehash的字典數(shù)據(jù)結(jié)構(gòu):
Hash值的計(jì)算:
hash = dict - > type -> hashFunction(key);
Redis使用MurmurHash2作為其Hash函數(shù)借卧。
index的計(jì)算:
index = hash & dict - > ht[i].sizemask;
//字典一般只使用ht[0]盹憎,ht[1] 只會(huì)在對(duì)ht[0]進(jìn)行rehash時(shí)使用
沖突解決:
Redis使用鏈表方法來(lái)解決hash值的沖突,很類(lèi)似JDK1.7的HashMap實(shí)現(xiàn)铐刘。
12.3 Redis的ReHash
針對(duì)Hash表保存的key-value的增加和減少,Redis提供了對(duì)hash表進(jìn)行擴(kuò)展或者收縮的功能影晓,以讓hash表的負(fù)載因子維持在一個(gè)合理的范圍之內(nèi)镰吵。
具體步驟如下:
1.為ht[1] Hash表分配空間:
如果是擴(kuò)展(負(fù)載因子大于1或者負(fù)載因子大于5(如果正在執(zhí)行BGSAVE或者BGREWRIGEAOF時(shí))),ht[1]的大小為第一個(gè)大于等于ht[0].used * 2的;
如果是收縮(負(fù)載因子小于0.1時(shí))挂签,ht[1]的大小為第一個(gè)大于等于ht[0].used 的 2的.
2.將ht[0]中的所有key-value 重新散列到ht[1] 上疤祭,需要重新計(jì)算hash值和索引值,因?yàn)閔t[1]的長(zhǎng)度和ht[0]不一樣饵婆;
3.遷移完成后勺馆,釋放ht[0]的空間,交換ht[0]和ht[1],為下一次rehash準(zhǔn)備草穆。
負(fù)載因子load_facotr = ht[0].used / ht[0].size;
13.Redis Cluster
Redis 3.0之后灌灾,Redis推出了Redis集群,解決了單點(diǎn)的問(wèn)題悲柱,每個(gè)節(jié)點(diǎn)之間是去中心化的锋喜,并且提供了sharding(數(shù)據(jù)分片)、replication(主從復(fù)制)豌鸡、failover(故障轉(zhuǎn)移)等解決方案嘿般。Redis Cluster的最小配置節(jié)點(diǎn)個(gè)數(shù)為6個(gè)(即3主3從,主節(jié)點(diǎn)提供讀寫(xiě)涯冠,從節(jié)點(diǎn)不提供請(qǐng)求服務(wù))炉奴,最大16384個(gè)節(jié)點(diǎn),采用虛擬槽分區(qū)的原理蛇更,即根據(jù)key的Hash值映射到0~16383個(gè)整數(shù)槽內(nèi)瞻赶。另外,Redis集群具有感知主備的能力械荷。
注意Redis Cluster和哨兵的卻別共耍,而且并不是功能重疊,也不是相互替換的吨瞎。