之前只是在項(xiàng)目簡(jiǎn)單使用了Redis(只是充當(dāng)緩存層實(shí)現(xiàn))曹洽,對(duì)Redis的體系技術(shù)沒(méi)深入了解尔当,最近時(shí)間比較充裕,所以再次復(fù)習(xí)鞏固Redis酪穿,然后打算寫(xiě)幾篇博客記錄以及分享所復(fù)習(xí)的Redis知識(shí)。
- Redis從入門(mén)到實(shí)戰(zhàn):入門(mén)篇
- Redis從入門(mén)到實(shí)戰(zhàn):實(shí)戰(zhàn)篇
- Redis從入門(mén)到實(shí)戰(zhàn):進(jìn)階篇
- Redis從入門(mén)到實(shí)戰(zhàn):完結(jié)篇
Redis從入門(mén)到實(shí)戰(zhàn):進(jìn)階篇
- Redis事務(wù)
- Redis的發(fā)布與訂閱
- Redis持久化機(jī)制(RDB+AOF)
- Redis明明是單線程晴裹,為什么性能還那么高(每秒讀寫(xiě)10W次等特性)
- Redis流水線技術(shù)(管道)
Redis事務(wù)
事務(wù)這個(gè)詞我們應(yīng)該不陌生了被济,我們?cè)谑褂肕ySQL或者Oracle過(guò)程中,都會(huì)開(kāi)啟事務(wù)涧团,直到SQL執(zhí)行完畢才會(huì)關(guān)掉事務(wù)只磷。
可以說(shuō),事務(wù)是一種保證數(shù)據(jù)安全的機(jī)制泌绣。在關(guān)系型數(shù)據(jù)庫(kù)中钮追,事務(wù)是一組SQL的集合,要么全部執(zhí)行成功阿迈,要么全部執(zhí)行失敗元媚。
而在Redis中,事務(wù)是一組命令的集合苗沧,這一組命令會(huì)按照順序在隔離的環(huán)境中執(zhí)行刊棕,不受其他客戶(hù)端發(fā)送來(lái)的命令打斷。
下面給出官方解說(shuō)
- 事務(wù)是一個(gè)單獨(dú)的隔離操作崎页,事務(wù)中的所有命令都會(huì)序列化鞠绰、按順序地執(zhí)行。事務(wù)在執(zhí)行的過(guò)程中飒焦,不會(huì)被其他客戶(hù)端發(fā)送來(lái)的命令請(qǐng)求所打斷蜈膨。
- 事務(wù)是一個(gè)原子操作,事務(wù)中的命令要么全部被執(zhí)行牺荠,要么全部都不執(zhí)行翁巍。
- 事務(wù)可以一次執(zhí)行多個(gè)命令。MULTI休雌、EXEC灶壶、DISCARD和WATCH是Redis事務(wù)相關(guān)的命令。
- 可以把Redis事務(wù)看成四種階段:開(kāi)啟事務(wù)杈曲,命令入隊(duì)驰凛,執(zhí)行事務(wù)胸懈,關(guān)閉事務(wù)。
開(kāi)啟事務(wù)
- multi命令的執(zhí)行標(biāo)志著事務(wù)開(kāi)啟恰响。
127.0.0.1:6379> multi OK
- multi命令可以將執(zhí)行該命令的客戶(hù)端從非事務(wù)狀態(tài)切換至事務(wù)狀態(tài)趣钱。
命令入隊(duì)
- 當(dāng)Redis客戶(hù)端處于非事務(wù)狀態(tài)時(shí),這個(gè)客戶(hù)端的命令會(huì)立即被服務(wù)器執(zhí)行胚宦。
- 當(dāng)Redis客戶(hù)端切換到事務(wù)狀態(tài)之后首有,服務(wù)器會(huì)根據(jù)這個(gè)客戶(hù)端發(fā)來(lái)的命令做出不同的響應(yīng)。
如果客戶(hù)端發(fā)送的命令是MULTI枢劝、EXEC井联、DISCARD和WATCH這四個(gè)命令中的一個(gè),那么服務(wù)器會(huì)立即執(zhí)行這個(gè)命令您旁。
如果客戶(hù)端發(fā)來(lái)的命令不是這四個(gè)命令烙常,那么服務(wù)器不會(huì)立即執(zhí)行這個(gè)命令,而是將這個(gè)命令放在一個(gè)事務(wù)隊(duì)列中被冒,然后向客戶(hù)端返回QUEUED
军掂,表明這個(gè)命令被放入事務(wù)隊(duì)列中了。127.0.0.1:6379> multi OK 127.0.0.1:6379> set name xxxq QUEUED
事務(wù)隊(duì)列
- 事務(wù)隊(duì)列是一個(gè)以先進(jìn)先出(FIFO)的方式保存入隊(duì)的命令昨悼,較先入隊(duì)的命令放在隊(duì)列的前面,較后入隊(duì)的命令放在隊(duì)列的后面跃洛。
127.0.0.1:6379> multi OK 127.0.0.1:6379> set name xxxq QUEUED 127.0.0.1:6379> get name QUEUED 127.0.0.1:6379> set author zwq QUEUED 127.0.0.1:6379> get author QUEUED
-
服務(wù)器將會(huì)為上面所執(zhí)行的命令創(chuàng)建一個(gè)隊(duì)列率触,如下圖所示:
執(zhí)行事務(wù)
- 當(dāng)一個(gè)處于事務(wù)狀態(tài)的客戶(hù)端向服務(wù)器發(fā)送EXEC命令時(shí),服務(wù)器會(huì)立即執(zhí)行這個(gè)命令汇竭,然后執(zhí)行事務(wù)隊(duì)列中保存的所以命令葱蝗,最后將執(zhí)行命令的結(jié)果返回給客戶(hù)端。當(dāng)返回結(jié)果給客戶(hù)端的時(shí)候细燎,這個(gè)事務(wù)也就被關(guān)閉了两曼。
127.0.0.1:6379> multi OK 127.0.0.1:6379> set name xxxq QUEUED 127.0.0.1:6379> get name QUEUED 127.0.0.1:6379> set author zwq QUEUED 127.0.0.1:6379> get author QUEUED 127.0.0.1:6379> exec 1) OK 2) "xxxq" 3) OK 4) "zwq" 127.0.0.1:6379> get name "xxxq"
watch命令
- watch命令是一個(gè)樂(lè)觀鎖。
- 它可以在EXEC命令執(zhí)行前玻驻,監(jiān)視任意數(shù)量的數(shù)據(jù)庫(kù)鍵悼凑,并在EXEC命令執(zhí)行時(shí),檢查監(jiān)視的鍵是否至少有一個(gè)已經(jīng)被修改過(guò)了璧瞬,如果修改過(guò)了户辫,服務(wù)器將拒絕執(zhí)行事務(wù),并向客戶(hù)端返回代表事務(wù)執(zhí)行失敗的空回復(fù)嗤锉。
- 下面舉一個(gè)例子渔欢,我開(kāi)啟兩個(gè)Redis客戶(hù)端,第一個(gè)客戶(hù)端使用Watch命令監(jiān)聽(tīng)
name
和author
兩個(gè)Key瘟忱,隨后使用multi命令開(kāi)啟事務(wù)奥额,之后修改name的值苫幢。
第二個(gè)客戶(hù)端也執(zhí)行修改name的值。127.0.0.1:6379> watch name author OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set name xq QUEUED
127.0.0.1:6379> set name xxxxxq OK
然后在第一個(gè)客戶(hù)端執(zhí)行EXEC命令垫挨,發(fā)現(xiàn)此次事務(wù)執(zhí)行失敗态坦。因?yàn)樵趫?zhí)行EXEC命令的時(shí)候,Watch命令監(jiān)聽(tīng)到name已經(jīng)被修改了棒拂,Redis認(rèn)為此次事務(wù)已經(jīng)不安全了伞梯,所以就無(wú)效化事務(wù)隊(duì)列中的所有命令。最后帚屉,name的值為第二個(gè)客戶(hù)端修改的結(jié)果谜诫。
Redis的發(fā)布與訂閱
- Redis的發(fā)布訂閱主要有三個(gè)角色:發(fā)布者,訂閱者攻旦,頻道(消息的載體)喻旷。
- 頻道是消息的載體,發(fā)布者可以往特定頻道發(fā)送消息牢屋,訂閱者可以訂閱(也可以說(shuō)是監(jiān)聽(tīng))特定頻道的消息且预,一旦頻道有了新的消息,就會(huì)推送給訂閱者烙无。
- 最經(jīng)典的應(yīng)用場(chǎng)景就是微博和公眾號(hào)锋谐,任何粉絲只要關(guān)注(訂閱)了某一個(gè)人的微博或者公眾號(hào),該微博或者公眾號(hào)只有有狀態(tài)更新截酷,都會(huì)將消息推送(發(fā)布)到粉絲....
- 命令:
subscribe channel [channel..]
:如subscribe “chat”
表示訂閱了"chat"頻道涮拗。 -
這里還是打開(kāi)兩個(gè)客戶(hù)端,第一個(gè)客戶(hù)端(訂閱者)訂閱chat頻道(當(dāng)執(zhí)行完上面命令的時(shí)候迂苛,第一個(gè)客戶(hù)端處于監(jiān)聽(tīng)狀態(tài))三热,第二個(gè)客戶(hù)端(發(fā)布者)往chat頻道發(fā)送消息,然后第一個(gè)客戶(hù)端就會(huì)立即收到消息三幻。
Redis持久化機(jī)制(RDB+AOF)
為什么要將數(shù)據(jù)持久化就漾?假如Redis服務(wù)器重啟了一下,這時(shí)Redis之前緩存的數(shù)據(jù)都會(huì)消失念搬,就得去MySQL等數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù)抑堡,再備份到Redis中,這樣會(huì)導(dǎo)致程序效率低下锁蠕。而將Redis數(shù)據(jù)持久化之后夷野,在重啟Redis服務(wù)器的時(shí)候,只需要將持久化的數(shù)據(jù)拉取到內(nèi)存中即可荣倾,這對(duì)于Redis每秒讀寫(xiě)10次不算什么悯搔。Redis支持兩種持久化方式:RDB,AOF。
RDB
- RDB(Redis Database)持久化是把當(dāng)前進(jìn)程數(shù)據(jù)生成快照保存在RDB文件的過(guò)程妒貌。
- RDB持久化生成的RDB文件是一個(gè)經(jīng)過(guò)壓縮的二進(jìn)制文件通危。
- 觸發(fā)RDB持久化過(guò)程分為手動(dòng)觸發(fā)和自動(dòng)觸發(fā)。
手動(dòng)觸發(fā)
SAVE命令和BGSAVE命令都可以實(shí)現(xiàn)手動(dòng)觸發(fā)灌曙。
save命令
-
在客戶(hù)端中執(zhí)行save命令菊碟,就會(huì)觸發(fā)Redis的持久化,但同時(shí)也是使Redis處于阻塞狀態(tài)在刺,直到RDB持久化完成逆害,才會(huì)響應(yīng)其他客戶(hù)端發(fā)來(lái)的命令,所以在生產(chǎn)環(huán)境一定要慎用蚣驼。
bgsave命令
- 執(zhí)行bgsave命令魄幕,Redis父進(jìn)程判斷當(dāng)前是否有正在執(zhí)行的子進(jìn)程,如RDB/AOF子進(jìn)程颖杏,如果存在直接返回纯陨。
- 如果沒(méi)有,父進(jìn)程執(zhí)行fork操作創(chuàng)建子進(jìn)程留储,fork操作會(huì)阻塞父進(jìn)程翼抠。父進(jìn)程fork操作執(zhí)行完成后,便不再阻塞父進(jìn)程获讳,可以繼續(xù)響應(yīng)其他命令阴颖。
- 子進(jìn)程創(chuàng)建RDB文件,根據(jù)父進(jìn)程內(nèi)存生成臨時(shí)快照文件赔嚎,完成后對(duì)原有文件進(jìn)行原子替換膘盖。
-
子進(jìn)程發(fā)送信號(hào)給父進(jìn)程表示完成,父進(jìn)程更新統(tǒng)計(jì)信息尤误。
自動(dòng)觸發(fā)
- 自動(dòng)觸發(fā)的意思是Redis會(huì)判斷是否滿(mǎn)足持久化的條件,滿(mǎn)足則自動(dòng)執(zhí)行bgsave命令進(jìn)行持久化结缚。是否滿(mǎn)足則是通過(guò)Redis核心配置文件redis.conf來(lái)配置的损晤。
- 接下來(lái)介紹有關(guān)RDB配置的內(nèi)容:
# RDB 保存的條件 save 900 1 save 300 10 save 60 10000 # bgsave 失敗之后,是否停止持久化數(shù)據(jù)到磁盤(pán)红竭,yes 表示停止持久化尤勋,no 表示忽略錯(cuò)誤繼續(xù)寫(xiě)文件。 stop-writes-on-bgsave-error yes # RDB 文件壓縮 rdbcompression yes # 寫(xiě)入文件和讀取文件時(shí)是否開(kāi)啟 RDB 文件檢查茵宪,檢查是否有無(wú)損壞最冰,如果在啟動(dòng)是檢查發(fā)現(xiàn)損壞,則停止啟動(dòng)稀火。 rdbchecksum yes # RDB 文件名 dbfilename dump.rdb # RDB 文件目錄 dir ./
-
save 參數(shù):它是用來(lái)配置觸發(fā)RDB持久化條件的參數(shù)暖哨,滿(mǎn)足保存條件時(shí)將會(huì)把數(shù)據(jù)持久化到硬盤(pán),使用的是bgsave命令凰狞。
save m n
:是指在 m 秒內(nèi)篇裁,如果有 n 個(gè)鍵發(fā)生改變沛慢,則自動(dòng)觸發(fā)持久化——bgsave命令。
save 900 1:表示 900 秒內(nèi)如果至少有 1 個(gè) key 值變化达布,則把數(shù)據(jù)持久化到硬盤(pán)团甲。
save 300 10:表示 300 秒內(nèi)如果至少有 10 個(gè) key 值變化,則把數(shù)據(jù)持久化到硬盤(pán)黍聂。
save 60 10000:表示 60 秒內(nèi)如果至少有 10000 個(gè) key 值變化躺苦,則把數(shù)據(jù)持久化到硬盤(pán)。 -
rdbcompression 參數(shù)
它的默認(rèn)值是yes表示開(kāi)啟RDB文件壓縮产还,Redis會(huì)采用LZF算法進(jìn)行壓縮匹厘。如果不想消耗CPU性能來(lái)進(jìn)行文件壓縮的話(huà),可以設(shè)置為關(guān)閉此功能雕沉,這樣的缺點(diǎn)是需要更多的磁盤(pán)空間來(lái)保存文件集乔。 -
rdbchecksum 參數(shù)
它的默認(rèn)值為yes表示寫(xiě)入文件和讀取文件時(shí)是否開(kāi)啟RDB文件檢查,檢查是否有無(wú)損壞坡椒,如果在啟動(dòng)是檢查發(fā)現(xiàn)損壞扰路,則停止啟動(dòng)。
RDB文件的載入
- 如果Redis開(kāi)啟了AOF持久化功能倔叼,就會(huì)優(yōu)先使用AOF文件來(lái)還原數(shù)據(jù)庫(kù)狀態(tài)汗唱,因?yàn)锳OF文件的更新頻率比RDB文件的更新頻率高。
- 只有在AOF持久化關(guān)閉的狀態(tài)時(shí)丈攒,服務(wù)器才會(huì)使用RDB文件來(lái)還原數(shù)據(jù)庫(kù)狀態(tài)哩罪。
- 服務(wù)器在載入RDB文件期間,會(huì)一直處于阻塞狀態(tài)巡验,直到載入工作完成為止际插。
RDB的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):RDB文件時(shí)一個(gè)緊湊的二進(jìn)制文件,適用備份显设、全量復(fù)制的場(chǎng)景框弛。加載RDB文件恢復(fù)數(shù)據(jù)遠(yuǎn)遠(yuǎn)快于AOF文件。
- 缺點(diǎn):不能實(shí)時(shí)持久化捕捂,數(shù)據(jù)丟失的風(fēng)險(xiǎn)高瑟枫。每次BGSAVE創(chuàng)建子進(jìn)程屬于重量級(jí)操作,頻繁執(zhí)行成本高指攒。
AOF
- AOF(Append Only File)持久化以獨(dú)立日志方式記錄每次寫(xiě)命令慷妙,重啟時(shí)在重新執(zhí)行AOF文件中的命令達(dá)到恢復(fù)數(shù)據(jù)的目的。
- AOF的主要作用就是解決了數(shù)據(jù)持久化的實(shí)時(shí)性允悦。
- 被寫(xiě)入AOF文件的所有命令都是以Redis的命令請(qǐng)求協(xié)議格式保存的膝擂,因?yàn)镽edis命令請(qǐng)求協(xié)議格式是純文本格式,所以我們可以直接打開(kāi)一個(gè)AOF文件觀察里面的內(nèi)容。
- 默認(rèn)情況下AOF功能是關(guān)閉的猿挚。我們需要修改配置文件:
appendonly no
改為appendonly yes
咐旧。
AOF工作流程
- 命令追加:服務(wù)器執(zhí)行完一個(gè)寫(xiě)命令后,會(huì)以協(xié)議格式將被執(zhí)行的寫(xiě)命令追加到服務(wù)器狀態(tài)的aof_buf緩沖區(qū)末尾绩蜻。
- 文件同步:AOF緩沖區(qū)根據(jù)對(duì)應(yīng)的策略向硬盤(pán)做同步操作铣墨。
- 文件重寫(xiě):隨著AOF越來(lái)越大,需要定期對(duì)AOF文件進(jìn)行重寫(xiě)办绝,達(dá)到壓縮的目的伊约。
- 重啟加載:當(dāng)Redis重啟時(shí),可以加載AOF文件進(jìn)行數(shù)據(jù)恢復(fù)孕蝉。
文件同步策略
- appendfsync always:每次寫(xiě)入都要同步AOF文件屡律,從效率上看,是最慢的降淮,從安全性上看超埋,是最安全的。
- appendfsync everysec:每1秒鐘同步一次佳鳖,該策略為AOF的缺省策略霍殴。
- appendfsync no:從不同步。高效但是數(shù)據(jù)不會(huì)被持久化系吩。
Redis明明是單線程来庭,為什么性能還那么高?
- 純內(nèi)存訪問(wèn),Redis將所有數(shù)據(jù)放在內(nèi)存中穿挨,內(nèi)存響應(yīng)的速度非吃鲁冢快。
- 非阻塞I/O科盛,Redis使用epoll作為I/O多路復(fù)用技術(shù)的實(shí)現(xiàn)的帽衙,再加上Redis自身的事件處理模型將epoll中的連接、讀寫(xiě)贞绵、關(guān)閉都轉(zhuǎn)換為事件佛寿,不在網(wǎng)絡(luò)I/O上浪費(fèi)過(guò)多事件。
- 單線程避免了線程切換和競(jìng)態(tài)產(chǎn)生的消耗但壮。
單線程優(yōu)缺點(diǎn)
- 單線程可以簡(jiǎn)化數(shù)據(jù)結(jié)構(gòu)和算法的實(shí)現(xiàn)。
- 單線程可以避免競(jìng)態(tài)產(chǎn)生的消耗常侣,鎖和線程切換通常是性能殺手蜡饵。
- 對(duì)于每個(gè)命令的執(zhí)行時(shí)間有要求,如果某個(gè)命令執(zhí)行過(guò)長(zhǎng)胳施,會(huì)造成其他命令阻塞溯祸,對(duì)于Redis這種高性能的服務(wù)來(lái)說(shuō)是致命的。
Redis流水線(管道)
- 以前我們每向Redis服務(wù)器發(fā)送一個(gè)命令的時(shí)候,Redis服務(wù)器就會(huì)執(zhí)行該命令焦辅,然后返回結(jié)果給客戶(hù)端博杖。每一次請(qǐng)求與響應(yīng)都會(huì)消耗一些時(shí)間,由于Redis讀寫(xiě)速度很快筷登,所有一旦接收到客戶(hù)端發(fā)來(lái)的命令剃根,就會(huì)立即執(zhí)行并返回結(jié)果。
-
如果在網(wǎng)絡(luò)差的情況下前方,客戶(hù)端發(fā)送命令給服務(wù)器需要一些時(shí)間狈醉,此時(shí)Redis服務(wù)器一直等待命令的到來(lái),這個(gè)原因不是Redis的問(wèn)題惠险,而是網(wǎng)絡(luò)差的問(wèn)題苗傅,如果一次性需要發(fā)送上百條命令,都會(huì)出現(xiàn)上述原因班巩,那么效率是非常差的渣慕,所有Redis提供了一個(gè)流水線技術(shù),也叫管道抱慌,就是能在一次連接上逊桦,執(zhí)行大量的命令,減少客戶(hù)端與服務(wù)器的來(lái)回交互次數(shù)遥缕,大大提升效率卫袒。
- 使用Java Jedis模擬批量插入100W條數(shù)據(jù)時(shí),不使用與使用流水線技術(shù)的差距单匣。
@Test public void pipeline(){ Jedis jedis = new Jedis("localhost",6379); //獲取流水線 Pipeline pipelined = jedis.pipelined(); //獲取開(kāi)始執(zhí)行時(shí)間 long startTime = System.currentTimeMillis(); IntStream.range(0,1000000).forEach(it -> pipelined.set("batch"+it,it+"")); List<Object> list = pipelined.syncAndReturnAll(); //獲取執(zhí)行完畢時(shí)間 long endTime = System.currentTimeMillis(); System.out.println(endTime - startTime); } 耗時(shí):3551毫秒夕凝,3秒多
@Test public void set(){ Jedis jedis = new Jedis("localhost",6379); long startTime = System.currentTimeMillis(); IntStream.range(0,1000000).forEach(it -> jedis.set("setBatch"+it,it+"")); long endTime = System.currentTimeMillis(); System.out.println(endTime - startTime); } 耗時(shí):71948毫秒,71秒多
流水線總結(jié)
從上面兩個(gè)程序來(lái)看户秤。使用流水線與不使用流水線的差距不是一般的大码秉。
- 在一般情況下,用戶(hù)每執(zhí)行一個(gè)Redis命令鸡号,客戶(hù)端和服務(wù)器都需要進(jìn)行一次通信:客戶(hù)端向服務(wù)器發(fā)送命令請(qǐng)求转砖,而服務(wù)器則會(huì)將執(zhí)行命令所得的結(jié)果返回給客戶(hù)端。
- 在大多數(shù)情況下鲸伴,執(zhí)行命令時(shí)的絕大部分時(shí)間都耗費(fèi)在發(fā)送命令和接收命令回復(fù)的過(guò)程上府蔗,因此減少客戶(hù)端和服務(wù)器之間的通信次數(shù),可以有效提高程序的執(zhí)行效率汞窗。3. 流水線功能可以將多條命令打包一起發(fā)送姓赤,并且在一次命令恢復(fù)中包含所有被執(zhí)行命令的結(jié)果,使用這個(gè)功能可以有效提升程序在執(zhí)行多條命令時(shí)的效率仲吏。
總結(jié)
- Redis進(jìn)階篇就介紹完了不铆,大家覺(jué)得OK的話(huà)蝌焚,不妨來(lái)個(gè)??或者關(guān)注也行。