久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

關(guān)于Redis你可能不了解的一些事

共計(jì) 6304 個(gè)字符,預(yù)計(jì)需要花費(fèi) 16 分鐘才能閱讀完成。

Redis 是一個(gè)高性能分布式的 key-value 數(shù)據(jù)庫(kù)。它支持多種數(shù)據(jù)結(jié)構(gòu),并可應(yīng)用于緩存、隊(duì)列等多種場(chǎng)景下。使用過 Redis 的小伙伴們可能對(duì)這些已經(jīng)非常熟知了,下面我想來(lái)談?wù)?Redis 也許并不被每個(gè)人了解的那點(diǎn)事。

Redis 持久化機(jī)制

剛看到標(biāo)題你可能會(huì)說(shuō),我知道,不就是 RDB 和 AOF 嘛。這些已經(jīng)是老生常談了。那么我們今天就深入談?wù)勥@兩種持久化方式的邏輯和原理。

RDB 的原理

在 Redis 中 RDB 持久化的觸發(fā)分為兩種:自己手動(dòng)觸發(fā)與 Redis 定時(shí)觸發(fā)。

針對(duì) RDB 方式的持久化,手動(dòng)觸發(fā)可以使用:

(1)save:會(huì)阻塞當(dāng)前 Redis 服務(wù)器,直到持久化完成,線上應(yīng)該禁止使用。
(2)bgsave:該觸發(fā)方式會(huì) fork 一個(gè)子進(jìn)程,由子進(jìn)程負(fù)責(zé)持久化過程,因此阻塞只會(huì)發(fā)生在 fork 子進(jìn)程的時(shí)候。

而自動(dòng)觸發(fā)的場(chǎng)景如下:

根據(jù)我們的 save m n 配置規(guī)則自動(dòng)觸發(fā);
從節(jié)點(diǎn)全量復(fù)制時(shí),主節(jié)點(diǎn)發(fā)送 rdb 文件給從節(jié)點(diǎn)完成復(fù)制操作,主節(jié)點(diǎn)會(huì)觸發(fā) bgsave;
執(zhí)行 debug reload 時(shí)處罰;
執(zhí)行 shutdown 時(shí),如果沒有開啟 aof,也會(huì)觸發(fā)。

由于 save 基本不會(huì)被使用到,我們來(lái)看看 bgsave 這個(gè)命令是如何完成 RDB 的持久化的。

關(guān)于 Redis 你可能不了解的一些事

RDB 文件保存過程

(1)redis 調(diào)用 fork, 現(xiàn)在有了子進(jìn)程和父進(jìn)程。
(2)父進(jìn)程繼續(xù)處理 client 請(qǐng)求,子進(jìn)程負(fù)責(zé)將內(nèi)存內(nèi)容寫入到臨時(shí)文件。由于 os 的寫時(shí)復(fù)制機(jī)制(copy on write)父子進(jìn)程會(huì)共享相同的物理頁(yè)面,當(dāng)父進(jìn)程處理寫請(qǐng)求時(shí) os 會(huì)為父進(jìn)程要修改的頁(yè)面創(chuàng)建副本,而不是寫共享的頁(yè)面。所以子進(jìn)程的地址空間內(nèi)的數(shù)據(jù)是 fork 時(shí)刻整個(gè)數(shù)據(jù)庫(kù)的一個(gè)快照。
(3)當(dāng)子進(jìn)程將快照寫入臨時(shí)文件完畢后,用臨時(shí)文件替換原來(lái)的快照文件,然后子進(jìn)程退出。

PS:fork 操作會(huì)阻塞,導(dǎo)致 Redis 讀寫性能下降。我們可以控制單個(gè) Redis 實(shí)例的最大內(nèi)存,來(lái)盡可能降低 Redis 在 fork 時(shí)的時(shí)間消耗;或者控制自動(dòng)觸發(fā)的頻率減少 fork 次數(shù)。

AOF 的原理

AOF 的整個(gè)流程大體來(lái)看可以分為兩步,一步是命令的實(shí)時(shí)寫入(如果是 appendfsync everysec 配置,會(huì)有 1s 損耗),第二步是對(duì) aof 文件的重寫。

對(duì)于增量追加到文件這一步主要的流程是:

(1)命令寫入
(2)追加到 aof_buf
(3)同步到 aof 磁盤

那么這里為什么要先寫入 buf 再同步到磁盤呢?如果實(shí)時(shí)寫入磁盤會(huì)帶來(lái)非常高的磁盤 IO,影響整體性能。

AOF 重寫

你可以會(huì)想,每一條寫命令都生成一條日志,那么 AOF 文件是不是會(huì)很大?答案是肯定的,AOF 文件會(huì)越來(lái)越大,所以 Redis 又提供了一個(gè)功能,叫做 AOF rewrite。其功能就是重新生成一份 AOF 文件,新的 AOF 文件中一條記錄的操作只會(huì)有一次,而不像一份老文件那樣,可能記錄了對(duì)同一個(gè)值的多次操作。

手動(dòng)觸發(fā):bgrewriteaof

自動(dòng)觸發(fā)就是根據(jù)配置規(guī)則來(lái)觸發(fā),當(dāng)然自動(dòng)觸發(fā)的整體時(shí)間還跟 Redis 的定時(shí)任務(wù)頻率有關(guān)系。

下面來(lái)看看重寫的流程圖:

關(guān)于 Redis 你可能不了解的一些事

(1)redis 調(diào)用 fork,現(xiàn)在有父子兩個(gè)進(jìn)程
(2)子進(jìn)程根據(jù)內(nèi)存中的數(shù)據(jù)庫(kù)快照,往臨時(shí)文件中寫入重建數(shù)據(jù)庫(kù)狀態(tài)的命令
(3)父進(jìn)程繼續(xù)處理 client 請(qǐng)求,除了把寫命令寫入到原來(lái)的 aof 文件中。同時(shí)把收到的寫命令緩存起來(lái)。這樣就能保證如果子進(jìn)程重寫失敗的話并不會(huì)出問題。
(4)當(dāng)子進(jìn)程把快照內(nèi)容寫到臨時(shí)文件中后,子進(jìn)程發(fā)信號(hào)通知父進(jìn)程。然后父進(jìn)程把緩存的寫命令也寫入到臨時(shí)文件。
(5)現(xiàn)在父進(jìn)程可以使用臨時(shí)文件替換老的 aof 文件,并重命名,后面收到的寫命令也開始往新的 aof 文件中追加。

PS:需要注意到是重寫 aof 文件的操作,并沒有讀取舊的 aof 文件,而是將整個(gè)內(nèi)存中的數(shù)據(jù)庫(kù)內(nèi)容用命令的方式重寫了一個(gè)新的 aof 文件, 這點(diǎn)和快照有點(diǎn)類似。

Redis 為什么這么快?

Redis 采用的是基于內(nèi)存的單進(jìn)程單線程模型的 KV 數(shù)據(jù)庫(kù),由 C 語(yǔ)言編寫,官方提供的數(shù)據(jù)是可以達(dá)到 100000+ 的 QPS(每秒內(nèi)查詢次數(shù))。這個(gè)數(shù)據(jù)不比采用單進(jìn)程多線程的同樣基于內(nèi)存的 KV 數(shù)據(jù)庫(kù) Memcached 差!原因如下:

1、完全基于內(nèi)存,絕大部分請(qǐng)求是純粹的內(nèi)存操作,非常快速;
2、數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單,對(duì)數(shù)據(jù)操作也簡(jiǎn)單,Redis 中的數(shù)據(jù)結(jié)構(gòu)是專門進(jìn)行設(shè)計(jì)的;
3、采用單線程,避免了不必要的上下文切換和競(jìng)爭(zhēng)條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖的操作,也不可能出現(xiàn)死鎖而導(dǎo)致的性能消耗;
4、使用多路 I / O 復(fù)用模型,非阻塞 IO;
5、使用的底層模型不同,底層實(shí)現(xiàn)方式以及與客戶端之間通信的應(yīng)用協(xié)議不一樣,Redis 直接構(gòu)建了自己的 VM 機(jī)制。

多路 I / O 復(fù)用模型

多路 I / O 復(fù)用模型是利用 select、poll、epoll 可以同時(shí)監(jiān)察多個(gè)流的 I/O 事件的能力,在空閑的時(shí)候,會(huì)把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)流有 I/O 事件時(shí),就從阻塞態(tài)中喚醒,于是程序就會(huì)輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無(wú)用操作。這里“多路”指的是多個(gè)網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個(gè)線程。采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò) IO 的時(shí)間消耗)。

Redis 事務(wù)

Redis 中的事務(wù) (transaction) 是一組命令的集合。事務(wù)同命令一樣都是 Redis 最小的執(zhí)行單位,一個(gè)事務(wù)中的命令要么都執(zhí)行,要么都不執(zhí)行。Redis 事務(wù)的實(shí)現(xiàn)需要 MULTI 和 EXEC 兩個(gè)命令,事務(wù)開始的時(shí)候先向 Redis 服務(wù)器發(fā)送 MULTI 命令,然后依次發(fā)送需要在本次事務(wù)中處理的命令,最后再發(fā)送 EXEC 命令表示事務(wù)命令結(jié)束。

舉個(gè)例子,使用 redis-cli 連接 redis,然后在命令行工具中輸入如下命令:

關(guān)于 Redis 你可能不了解的一些事

從輸出中可以看到,當(dāng)輸入 MULTI 命令后,服務(wù)器返回 OK 表示事務(wù)開始成功,然后依次輸入需要在本次事務(wù)中執(zhí)行的所有命令,每次輸入一個(gè)命令服務(wù)器并不會(huì)馬上執(zhí)行,而是返回”QUEUED”,這表示命令已經(jīng)被服務(wù)器接受并且暫時(shí)保存起來(lái),最后輸入 EXEC 命令后,本次事務(wù)中的所有命令才會(huì)被依次執(zhí)行,可以看到最后服務(wù)器一次性返回了三個(gè) OK,這里返回的結(jié)果與發(fā)送的命令是按順序,這說(shuō)明這次事務(wù)中的命令全都執(zhí)行成功了。

再舉個(gè)例子,在命令行工具中輸入如下命令:

關(guān)于 Redis 你可能不了解的一些事

和前面的例子一樣,先輸入 MULTI 最后輸入 EXEC 表示中間的命令屬于一個(gè)事務(wù),不同的是中間輸入的命令有一個(gè)錯(cuò)誤(set 寫成了 sett),這樣因?yàn)橛幸粋€(gè)錯(cuò)誤的命令導(dǎo)致事務(wù)中的其他命令都不執(zhí)行了,可見事務(wù)中的所有命令是保持一致的。

如果客戶端在發(fā)送 EXEC 命令之前斷線了,則服務(wù)器會(huì)清空事務(wù)隊(duì)列,事務(wù)中的所有命令都不會(huì)被執(zhí)行。而一旦客戶端發(fā)送了 EXEC 命令之后,事務(wù)中的所有命令都會(huì)被執(zhí)行,即使此后客戶端斷線也沒關(guān)系,因?yàn)榉?wù)器已經(jīng)保存了事務(wù)中的所有命令。

除了保證事務(wù)中的所有命令要么全執(zhí)行要么全不執(zhí)行外,Redis 的事務(wù)還能保證一個(gè)事務(wù)中的命令依次執(zhí)行而不會(huì)被其他命令插入。試想一個(gè)客戶端 A 需要執(zhí)行幾條命令,同時(shí)客戶端 B 發(fā)送了幾條命令,如果不使用事務(wù),則客戶端 B 的命令有可能會(huì)插入到客戶端 A 的幾條命令中,如果想避免這種情況發(fā)生,也可以使用事務(wù)。

Redis 事務(wù)錯(cuò)誤處理

如果一個(gè)事務(wù)中的某個(gè)命令執(zhí)行出錯(cuò),Redis 會(huì)怎樣處理呢?要回答這個(gè)問題,首先要搞清楚是什么原因?qū)е旅顖?zhí)行出錯(cuò):

1. 語(yǔ)法錯(cuò)誤: 就像上面的例子一樣,語(yǔ)法錯(cuò)誤表示命令不存在或者參數(shù)錯(cuò)誤,這種情況需要區(qū)分 Redis 的版本,Redis 2.6.5 之前的版本會(huì)忽略錯(cuò)誤的命令,執(zhí)行其他正確的命令,2.6.5 之后的版本會(huì)忽略這個(gè)事務(wù)中的所有命令,都不執(zhí)行

2. 運(yùn)行錯(cuò)誤 運(yùn)行錯(cuò)誤表示命令在執(zhí)行過程中出現(xiàn)錯(cuò)誤,比如用 GET 命令獲取一個(gè)散列表類型的鍵值。這種錯(cuò)誤在命令執(zhí)行之前 Redis 是無(wú)法發(fā)現(xiàn)的,所以在事務(wù)里這樣的命令會(huì)被 Redis 接受并執(zhí)行。如果食物里有一條命令執(zhí)行錯(cuò)誤,其他命令依舊會(huì)執(zhí)行(包括出錯(cuò)之后的命令)。

關(guān)于 Redis 你可能不了解的一些事

Redis 中的事務(wù)并沒有關(guān)系型數(shù)據(jù)庫(kù)中的事務(wù)回滾 (rollback) 功能,因此使用者必須自己收拾剩下的爛攤子。不過由于 Redis 不支持事務(wù)回滾功能,這也使得 Redis 的事務(wù)簡(jiǎn)潔快速。

WATCH、UNWATCH、DISCARD 命令

從上面的例子我們可以看到,事務(wù)中的命令要全部執(zhí)行完之后才能獲取每個(gè)命令的結(jié)果,但是如果一個(gè)事務(wù)中的命令 B 依賴于他上一個(gè)命令 A 的結(jié)果的話該怎么辦呢?就比如說(shuō)實(shí)現(xiàn)類似 Java 中的 i ++ 的功能,先要獲取當(dāng)前值,才能在當(dāng)前值的基礎(chǔ)上做加一操作。這種場(chǎng)合僅僅使用上面介紹的 MULTI 和 EXEC 是不能實(shí)現(xiàn)的,因?yàn)?MULTI 和 EXEC 中的命令是一起執(zhí)行的,并不能將其中一條命令的執(zhí)行結(jié)果作為另一條命令的執(zhí)行參數(shù),所以這個(gè)時(shí)候就需要引進(jìn) Redis 事務(wù)家族中的另一成員:WATCH 命令

換個(gè)角度思考上面說(shuō)到的實(shí)現(xiàn) i ++ 的方法,可以這樣實(shí)現(xiàn):

監(jiān)控 i 的值,保證 i 的值不被修改
獲取 i 的原值
如果過程中 i 的值沒有被修改,則將當(dāng)前的 i 值 +1,否則不執(zhí)行

WATCH 命令可以監(jiān)控一個(gè)或多個(gè)鍵,一旦其中有一個(gè)鍵被修改(或刪除),之后的事務(wù)就不會(huì)執(zhí)行,監(jiān)控一直持續(xù)到 EXEC 命令(事務(wù)中的命令是在 EXEC 之后才執(zhí)行的,EXEC 命令執(zhí)行完之后被監(jiān)控的鍵會(huì)自動(dòng)被 UNWATCH)。舉個(gè)例子:

關(guān)于 Redis 你可能不了解的一些事

上面的例子中,首先設(shè)置 mykey 的鍵值為 1,然后使用 WATCH 命令監(jiān)控 mykey,隨后更改 mykey 的值為 2,然后進(jìn)入事務(wù),事務(wù)中設(shè)置 mykey 的值為 3,然后執(zhí)行 EXEC 運(yùn)行事務(wù)中的命令,最后使用 get 命令查看 mykey 的值,發(fā)現(xiàn) mykey 的值還是 2,也就是說(shuō)事務(wù)中的命令根本沒有執(zhí)行(因?yàn)?WATCH 監(jiān)控 mykey 的過程中,mykey 被修改了,所以隨后的事務(wù)便會(huì)被取消)。

UNWATCH 命令可以在 WATCH 命令執(zhí)行之后、MULTI 命令執(zhí)行之前取消對(duì)某個(gè)鍵的監(jiān)控。舉個(gè)例子:

關(guān)于 Redis 你可能不了解的一些事

上面的例子中,首先設(shè)置 mykey 的鍵值為 1,然后使用 WATCH 命令監(jiān)控 mykey,隨后更改 mykey 的值為 2,然后取消對(duì) mykey 的監(jiān)控,再進(jìn)入事務(wù),事務(wù)中設(shè)置 mykey 的值為 3,然后執(zhí)行 EXEC 運(yùn)行事務(wù)中的命令,最后使用 get 命令查看 mykey 的值,發(fā)現(xiàn) mykey 的值還是 3,也就是說(shuō)事務(wù)中的命令運(yùn)行成功。

DISCARD 命令則可以在 MULTI 命令執(zhí)行之后,EXEC 命令執(zhí)行之前取消 WATCH 命令并清空事務(wù)隊(duì)列,然后從事務(wù)狀態(tài)中退出。舉個(gè)例子:

關(guān)于 Redis 你可能不了解的一些事

上面的例子中,首先設(shè)置 mykey 的鍵值為 1,然后使用 WATCH 命令監(jiān)控 mykey,隨后更改 mykey 的值為 2,然后進(jìn)入事務(wù),事務(wù)中設(shè)置 mykey 的值為 3,然后執(zhí)行 DISCARD 命令,再執(zhí)行 EXEC 運(yùn)行事務(wù)中的命令,發(fā)現(xiàn)報(bào)錯(cuò)“ERR EXEC without MULTI”,說(shuō)明 DISCARD 命令成功執(zhí)行——取消 WATCH 命令并清空事務(wù)隊(duì)列,然后從事務(wù)狀態(tài)中退出。

Redis 分布式鎖

上面介紹的 Redis 的 WATCH、MULTI 和 EXEC 命令,只會(huì)在數(shù)據(jù)被其他客戶端搶先修改的情況下,通知執(zhí)行這些命令的客戶端,讓它撤銷對(duì)數(shù)據(jù)的修改操作,并不能阻止其他客戶端對(duì)數(shù)據(jù)進(jìn)行修改,所以只能稱之為樂觀鎖(optimistic locking)。

而這種樂觀鎖并不具備可擴(kuò)展性——當(dāng)客戶端嘗試完成一個(gè)事務(wù)的時(shí)候,可能會(huì)因?yàn)槭聞?wù)執(zhí)行失敗而進(jìn)行反復(fù)的重試。保證數(shù)據(jù)準(zhǔn)確性非常重要,但是當(dāng)負(fù)載變大的時(shí)候,使用樂觀鎖的做法并不完美。這時(shí)就需要使用 Redis 實(shí)現(xiàn)分布式鎖。

分布式鎖:是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動(dòng)作。如果不同的系統(tǒng)或是同一個(gè)系統(tǒng)的不同主機(jī)之間共享了一個(gè)或一組資源,那么訪問這些資源的時(shí)候,往往需要互斥來(lái)防止彼此干擾來(lái)保證一致性,在這種情況下,便需要使用到分布式鎖。

Redis 命令介紹:

Redis 實(shí)現(xiàn)分布式鎖主要用到命令是 SETNX 命令(SET if Not eXists)。

語(yǔ)法:SETNX key value

功能:當(dāng)且僅當(dāng) key 不存在,將 key 的值設(shè)為 value,并返回 1;若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作,并返回 0。

使用 Redis 構(gòu)建鎖:

思路:將“l(fā)ock:”+ 參數(shù)名設(shè)置為鎖的鍵,使用 SETNX 命令嘗試將一個(gè)隨機(jī)的 uuid 設(shè)置為鎖的值,并為鎖設(shè)置過期時(shí)間,使用 SETNX 設(shè)置鎖的值可以防止鎖被其他進(jìn)程獲取。如果嘗試獲取鎖的時(shí)候失敗,那么程序?qū)⒉粩嘀卦嚕钡匠晒Λ@取鎖或者超過給定是時(shí)限為止。

代碼:

public String acquireLockWithTimeout(Jedis conn, String lockName, long acquireTimeout, long lockTimeout)
 String identifier = UUID.randomUUID().toString(); // 鎖的值
 String lockKey = "lock:" + lockName; // 鎖的鍵
 int lockExpire = (int)(lockTimeout / 1000); // 鎖的過期時(shí)間
 long end = System.currentTimeMillis() + acquireTimeout; // 嘗試獲取鎖的時(shí)限
 while (System.currentTimeMillis() end) { // 判斷是否超過獲取鎖的時(shí)限
 if (conn.setnx(lockKey, identifier) == 1){ // 判斷設(shè)置鎖的值是否成功
 conn.expire(lockKey, lockExpire); // 設(shè)置鎖的過期時(shí)間
 return identifier; // 返回鎖的值
 if (conn.ttl(lockKey) == -1) { // 判斷鎖是否超時(shí)
 conn.expire(lockKey, lockExpire);
 try {Thread.sleep(1000); // 等待 1 秒后重新嘗試設(shè)置鎖的值
 }catch(InterruptedException ie){Thread.currentThread().interrupt();
 // 獲取鎖失敗時(shí)返回 null
 return null;
 }

鎖的釋放:

思路:使用 WATCH 命令監(jiān)視代表鎖的鍵,然后檢查鍵的值是否和加鎖時(shí)設(shè)置的值相同,并在確認(rèn)值沒有變化后刪除該鍵。

代碼:

public boolean releaseLock(Jedis conn, String lockName, String identifier) {
 String lockKey = "lock:" + lockName; // 鎖的鍵
 while (true){conn.watch(lockKey); // 監(jiān)視鎖的鍵
 if (identifier.equals(conn.get(lockKey))){ // 判斷鎖的值是否和加鎖時(shí)設(shè)置的一致,即檢查進(jìn)程是否仍然持有鎖
 Transaction trans = conn.multi();
 trans.del(lockKey); // 在 Redis 事務(wù)中釋放鎖
 List Object results = trans.exec();
 if (results == null){ 
 continue; // 事務(wù)執(zhí)行失敗后重試(監(jiān)視的鍵被修改導(dǎo)致事務(wù)失敗,重新監(jiān)視并釋放鎖)return true;
 conn.unwatch(); // 解除監(jiān)視
 break;
 return false;
 }

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)丸趣 TV 的支持。

向 AI 問一下細(xì)節(jié)

丸趣 TV 網(wǎng) – 提供最優(yōu)質(zhì)的資源集合!

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-12-18發(fā)表,共計(jì)6304字。
轉(zhuǎn)載說(shuō)明:除特殊說(shuō)明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請(qǐng)注明出處。
評(píng)論(沒有評(píng)論)
主站蜘蛛池模板: 昭平县| 南充市| 尼勒克县| 开原市| 沁源县| 崇州市| 洪湖市| 清徐县| 涟水县| 芦溪县| 绥阳县| 楚雄市| 潮州市| 岑溪市| 宜昌市| 都昌县| 北京市| 玛多县| 郁南县| 晋江市| 汝阳县| 榆树市| 区。| 都江堰市| 政和县| 临夏市| 汤原县| 兰州市| 富锦市| 耒阳市| 天津市| 密山市| 菏泽市| 德阳市| 福泉市| 旬阳县| 黄龙县| 丁青县| 汽车| 保亭| 平利县|