網(wǎng)站建設(shè) 開發(fā)的團(tuán)隊(duì)需要幾個(gè)人網(wǎng)絡(luò)營(yíng)銷的方式有幾種
目錄
緩存擊穿
什么是緩存擊穿?
有哪些解決辦法?
緩存穿透和緩存擊穿有什么區(qū)別?
緩存雪崩
什么是緩存雪崩?
有哪些解決辦法?
緩存預(yù)熱如何實(shí)現(xiàn)?
緩存雪崩和緩存擊穿有什么區(qū)別?
如何保證緩存和數(shù)據(jù)庫數(shù)據(jù)的一致性?
哪些情況可能會(huì)導(dǎo)致 Redis 阻塞?
O(n) 命令
SAVE 創(chuàng)建 RDB 快照
AOF
AOF 日志記錄阻塞
AOF 刷盤阻塞
AOF 重寫阻塞
大 Key
查找大 key
刪除大 key
清空數(shù)據(jù)庫
集群擴(kuò)容
Swap(內(nèi)存交換)
CPU 競(jìng)爭(zhēng)
網(wǎng)絡(luò)問題
緩存擊穿
什么是緩存擊穿?
緩存擊穿中,請(qǐng)求的 key 對(duì)應(yīng)的是 熱點(diǎn)數(shù)據(jù) ,該數(shù)據(jù) 存在于數(shù)據(jù)庫中,但不存在于緩存中(通常是因?yàn)榫彺嬷械哪欠輸?shù)據(jù)已經(jīng)過期) 。這就可能會(huì)導(dǎo)致瞬時(shí)大量的請(qǐng)求直接打到了數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力,可能直接就被這么多請(qǐng)求弄宕機(jī)了。
????????????????????????????????
舉個(gè)例子:秒殺進(jìn)行過程中,緩存中的某個(gè)秒殺商品的數(shù)據(jù)突然過期,這就導(dǎo)致瞬時(shí)大量對(duì)該商品的請(qǐng)求直接落到數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力。
有哪些解決辦法?
- 永不過期(不推薦):設(shè)置熱點(diǎn)數(shù)據(jù)永不過期或者過期時(shí)間比較長(zhǎng)。
- 提前預(yù)熱(推薦):針對(duì)熱點(diǎn)數(shù)據(jù)提前預(yù)熱,將其存入緩存中并設(shè)置合理的過期時(shí)間比如秒殺場(chǎng)景下的數(shù)據(jù)在秒殺結(jié)束之前不過期。
- 加鎖(看情況):在緩存失效后,通過設(shè)置互斥鎖確保只有一個(gè)請(qǐng)求去查詢數(shù)據(jù)庫并更新緩存。
緩存穿透和緩存擊穿有什么區(qū)別?
緩存穿透中,請(qǐng)求的 key 既不存在于緩存中,也不存在于數(shù)據(jù)庫中。
緩存擊穿中,請(qǐng)求的 key 對(duì)應(yīng)的是 熱點(diǎn)數(shù)據(jù) ,該數(shù)據(jù) 存在于數(shù)據(jù)庫中,但不存在于緩存中(通常是因?yàn)榫彺嬷械哪欠輸?shù)據(jù)已經(jīng)過期) 。
緩存雪崩
什么是緩存雪崩?
我發(fā)現(xiàn)緩存雪崩這名字起的有點(diǎn)意思,哈哈。
實(shí)際上,緩存雪崩描述的就是這樣一個(gè)簡(jiǎn)單的場(chǎng)景:緩存在同一時(shí)間大面積的失效,導(dǎo)致大量的請(qǐng)求都直接落到了數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力。 這就好比雪崩一樣,摧枯拉朽之勢(shì),數(shù)據(jù)庫的壓力可想而知,可能直接就被這么多請(qǐng)求弄宕機(jī)了。
另外,緩存服務(wù)宕機(jī)也會(huì)導(dǎo)致緩存雪崩現(xiàn)象,導(dǎo)致所有的請(qǐng)求都落到了數(shù)據(jù)庫上。
???????????????????????????????????????
舉個(gè)例子:數(shù)據(jù)庫中的大量數(shù)據(jù)在同一時(shí)間過期,這個(gè)時(shí)候突然有大量的請(qǐng)求需要訪問這些過期的數(shù)據(jù)。這就導(dǎo)致大量的請(qǐng)求直接落到數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力。
有哪些解決辦法?
針對(duì) Redis 服務(wù)不可用的情況:
- Redis 集群:采用 Redis 集群,避免單機(jī)出現(xiàn)問題整個(gè)緩存服務(wù)都沒辦法使用。Redis Cluster 和 Redis Sentinel 是兩種最常用的 Redis 集群實(shí)現(xiàn)方案,詳細(xì)介紹可以參考:Redis 集群詳解(付費(fèi))。
- 多級(jí)緩存:設(shè)置多級(jí)緩存,例如本地緩存+Redis 緩存的二級(jí)緩存組合,當(dāng) Redis 緩存出現(xiàn)問題時(shí),還可以從本地緩存中獲取到部分?jǐn)?shù)據(jù)。
針對(duì)大量緩存同時(shí)失效的情況:
- 設(shè)置隨機(jī)失效時(shí)間(可選):為緩存設(shè)置隨機(jī)的失效時(shí)間,例如在固定過期時(shí)間的基礎(chǔ)上加上一個(gè)隨機(jī)值,這樣可以避免大量緩存同時(shí)到期,從而減少緩存雪崩的風(fēng)險(xiǎn)。
- 提前預(yù)熱(推薦):針對(duì)熱點(diǎn)數(shù)據(jù)提前預(yù)熱,將其存入緩存中并設(shè)置合理的過期時(shí)間比如秒殺場(chǎng)景下的數(shù)據(jù)在秒殺結(jié)束之前不過期。
- 持久緩存策略(看情況):雖然一般不推薦設(shè)置緩存永不過期,但對(duì)于某些關(guān)鍵性和變化不頻繁的數(shù)據(jù),可以考慮這種策略。
?
緩存預(yù)熱如何實(shí)現(xiàn)?
常見的緩存預(yù)熱方式有兩種:
- 使用定時(shí)任務(wù),比如 xxl-job,來定時(shí)觸發(fā)緩存預(yù)熱的邏輯,將數(shù)據(jù)庫中的熱點(diǎn)數(shù)據(jù)查詢出來并存入緩存中。
- 使用消息隊(duì)列,比如 Kafka,來異步地進(jìn)行緩存預(yù)熱,將數(shù)據(jù)庫中的熱點(diǎn)數(shù)據(jù)的主鍵或者 ID 發(fā)送到消息隊(duì)列中,然后由緩存服務(wù)消費(fèi)消息隊(duì)列中的數(shù)據(jù),根據(jù)主鍵或者 ID 查詢數(shù)據(jù)庫并更新緩存。
?
緩存雪崩和緩存擊穿有什么區(qū)別?
緩存雪崩和緩存擊穿比較像,但緩存雪崩導(dǎo)致的原因是緩存中的大量或者所有數(shù)據(jù)失效,緩存擊穿導(dǎo)致的原因主要是某個(gè)熱點(diǎn)數(shù)據(jù)不存在與緩存中(通常是因?yàn)榫彺嬷械哪欠輸?shù)據(jù)已經(jīng)過期)。
如何保證緩存和數(shù)據(jù)庫數(shù)據(jù)的一致性?
細(xì)說的話可以扯很多,但是我覺得其實(shí)沒太大必要(小聲 BB:很多解決方案我也沒太弄明白)。我個(gè)人覺得引入緩存之后,如果為了短時(shí)間的不一致性問題,選擇讓系統(tǒng)設(shè)計(jì)變得更加復(fù)雜的話,完全沒必要。
下面單獨(dú)對(duì) Cache Aside Pattern(旁路緩存模式) 來聊聊。
Cache Aside Pattern 中遇到寫請(qǐng)求是這樣的:更新數(shù)據(jù)庫,然后直接刪除緩存 。
如果更新數(shù)據(jù)庫成功,而刪除緩存這一步失敗的情況的話,簡(jiǎn)單說有兩個(gè)解決方案:
- 緩存失效時(shí)間變短(不推薦,治標(biāo)不治本):我們讓緩存數(shù)據(jù)的過期時(shí)間變短,這樣的話緩存就會(huì)從數(shù)據(jù)庫中加載數(shù)據(jù)。另外,這種解決辦法對(duì)于先操作緩存后操作數(shù)據(jù)庫的場(chǎng)景不適用。
- 增加緩存更新重試機(jī)制(常用):如果緩存服務(wù)當(dāng)前不可用導(dǎo)致緩存刪除失敗的話,我們就隔一段時(shí)間進(jìn)行重試,重試次數(shù)可以自己定。不過,這里更適合引入消息隊(duì)列實(shí)現(xiàn)異步重試,將刪除緩存重試的消息投遞到消息隊(duì)列,然后由專門的消費(fèi)者來重試,直到成功。雖然說多引入了一個(gè)消息隊(duì)列,但其整體帶來的收益還是要更高一些。
相關(guān)文章推薦:緩存和數(shù)據(jù)庫一致性問題,看這篇就夠了 - 水滴與銀彈。
哪些情況可能會(huì)導(dǎo)致 Redis 阻塞?
???????O(n) 命令
Redis 中的大部分命令都是 O(1)時(shí)間復(fù)雜度,但也有少部分 O(n) 時(shí)間復(fù)雜度的命令,例如:
KEYS *
:會(huì)返回所有符合規(guī)則的 key。HGETALL
:會(huì)返回一個(gè) Hash 中所有的鍵值對(duì)。LRANGE
:會(huì)返回 List 中指定范圍內(nèi)的元素。SMEMBERS
:返回 Set 中的所有元素。SINTER
/SUNION
/SDIFF
:計(jì)算多個(gè) Set 的交集/并集/差集。- ……
由于這些命令時(shí)間復(fù)雜度是 O(n),有時(shí)候也會(huì)全表掃描,隨著 n 的增大,執(zhí)行耗時(shí)也會(huì)越長(zhǎng),從而導(dǎo)致客戶端阻塞。不過, 這些命令并不是一定不能使用,但是需要明確 N 的值。另外,有遍歷的需求可以使用 HSCAN
、SSCAN
、ZSCAN
代替。
除了這些 O(n)時(shí)間復(fù)雜度的命令可能會(huì)導(dǎo)致阻塞之外, 還有一些時(shí)間復(fù)雜度可能在 O(N) 以上的命令,例如:
ZRANGE
/ZREVRANGE
:返回指定 Sorted Set 中指定排名范圍內(nèi)的所有元素。時(shí)間復(fù)雜度為 O(log(n)+m),n 為所有元素的數(shù)量, m 為返回的元素?cái)?shù)量,當(dāng) m 和 n 相當(dāng)大時(shí),O(n) 的時(shí)間復(fù)雜度更小。ZREMRANGEBYRANK
/ZREMRANGEBYSCORE
:移除 Sorted Set 中指定排名范圍/指定 score 范圍內(nèi)的所有元素。時(shí)間復(fù)雜度為 O(log(n)+m),n 為所有元素的數(shù)量, m 被刪除元素的數(shù)量,當(dāng) m 和 n 相當(dāng)大時(shí),O(n) 的時(shí)間復(fù)雜度更小。
SAVE 創(chuàng)建 RDB 快照
Redis 提供了兩個(gè)命令來生成 RDB 快照文件:
save
: 同步保存操作,會(huì)阻塞 Redis 主線程;bgsave
: fork 出一個(gè)子進(jìn)程,子進(jìn)程執(zhí)行,不會(huì)阻塞 Redis 主線程,默認(rèn)選項(xiàng)。
默認(rèn)情況下,Redis 默認(rèn)配置會(huì)使用 bgsave
命令。如果手動(dòng)使用 save
命令生成 RDB 快照文件的話,就會(huì)阻塞主線程。
AOF
AOF 日志記錄阻塞
Redis AOF 持久化機(jī)制是在執(zhí)行完命令之后再記錄日志,這和關(guān)系型數(shù)據(jù)庫(如 MySQL)通常都是執(zhí)行命令之前記錄日志(方便故障恢復(fù))不同。
?????????????????????????
????????????????????????????????????????AOF 記錄日志過程
為什么是在執(zhí)行完命令之后記錄日志呢?
- 避免額外的檢查開銷,AOF 記錄日志不會(huì)對(duì)命令進(jìn)行語法檢查;
- 在命令執(zhí)行完之后再記錄,不會(huì)阻塞當(dāng)前的命令執(zhí)行。
這樣也帶來了風(fēng)險(xiǎn)(我在前面介紹 AOF 持久化的時(shí)候也提到過):
- 如果剛執(zhí)行完命令 Redis 就宕機(jī)會(huì)導(dǎo)致對(duì)應(yīng)的修改丟失;
- 可能會(huì)阻塞后續(xù)其他命令的執(zhí)行(AOF 記錄日志是在 Redis 主線程中進(jìn)行的)。
AOF 刷盤阻塞
開啟 AOF 持久化后每執(zhí)行一條會(huì)更改 Redis 中的數(shù)據(jù)的命令,Redis 就會(huì)將該命令寫入到 AOF 緩沖區(qū) server.aof_buf
中,然后再根據(jù) appendfsync
配置來決定何時(shí)將其同步到硬盤中的 AOF 文件。
在 Redis 的配置文件中存在三種不同的 AOF 持久化方式( fsync
策略),它們分別是:
appendfsync always
:主線程調(diào)用write
執(zhí)行寫操作后,后臺(tái)線程(aof_fsync
線程)立即會(huì)調(diào)用fsync
函數(shù)同步 AOF 文件(刷盤),fsync
完成后線程返回,這樣會(huì)嚴(yán)重降低 Redis 的性能(write
+fsync
)。appendfsync everysec
:主線程調(diào)用write
執(zhí)行寫操作后立即返回,由后臺(tái)線程(aof_fsync
線程)每秒鐘調(diào)用fsync
函數(shù)(系統(tǒng)調(diào)用)同步一次 AOF 文件(write
+fsync
,fsync
間隔為 1 秒)appendfsync no
:主線程調(diào)用write
執(zhí)行寫操作后立即返回,讓操作系統(tǒng)決定何時(shí)進(jìn)行同步,Linux 下一般為 30 秒一次(write
但不fsync
,fsync
的時(shí)機(jī)由操作系統(tǒng)決定)。
當(dāng)后臺(tái)線程( aof_fsync
線程)調(diào)用 fsync
函數(shù)同步 AOF 文件時(shí),需要等待,直到寫入完成。當(dāng)磁盤壓力太大的時(shí)候,會(huì)導(dǎo)致 fsync
操作發(fā)生阻塞,主線程調(diào)用 write
函數(shù)時(shí)也會(huì)被阻塞。fsync
完成后,主線程執(zhí)行 write
才能成功返回。
關(guān)于 AOF 工作流程的詳細(xì)介紹可以查看:Redis 持久化機(jī)制詳解,有助于理解 AOF 刷盤阻塞
?
AOF 重寫阻塞
- fork 出一條子線程來將文件重寫,在執(zhí)行
BGREWRITEAOF
命令時(shí),Redis 服務(wù)器會(huì)維護(hù)一個(gè) AOF 重寫緩沖區(qū),該緩沖區(qū)會(huì)在子線程創(chuàng)建新 AOF 文件期間,記錄服務(wù)器執(zhí)行的所有寫命令。 - 當(dāng)子線程完成創(chuàng)建新 AOF 文件的工作之后,服務(wù)器會(huì)將重寫緩沖區(qū)中的所有內(nèi)容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的數(shù)據(jù)庫狀態(tài)與現(xiàn)有的數(shù)據(jù)庫狀態(tài)一致。
- 最后,服務(wù)器用新的 AOF 文件替換舊的 AOF 文件,以此來完成 AOF 文件重寫操作。
阻塞就是出現(xiàn)在第 2 步的過程中,將緩沖區(qū)中新數(shù)據(jù)寫到新文件的過程中會(huì)產(chǎn)生阻塞。
大 Key
如果一個(gè) key 對(duì)應(yīng)的 value 所占用的內(nèi)存比較大,那這個(gè) key 就可以看作是 bigkey。具體多大才算大呢?有一個(gè)不是特別精確的參考標(biāo)準(zhǔn):
- string 類型的 value 超過 1MB
- 復(fù)合類型(列表、哈希、集合、有序集合等)的 value 包含的元素超過 5000 個(gè)(對(duì)于復(fù)合類型的 value 來說,不一定包含的元素越多,占用的內(nèi)存就越多)。
大 key 造成的阻塞問題如下:
- 客戶端超時(shí)阻塞:由于 Redis 執(zhí)行命令是單線程處理,然后在操作大 key 時(shí)會(huì)比較耗時(shí),那么就會(huì)阻塞 Redis,從客戶端這一視角看,就是很久很久都沒有響應(yīng)。
- 引發(fā)網(wǎng)絡(luò)阻塞:每次獲取大 key 產(chǎn)生的網(wǎng)絡(luò)流量較大,如果一個(gè) key 的大小是 1 MB,每秒訪問量為 1000,那么每秒會(huì)產(chǎn)生 1000MB 的流量,這對(duì)于普通千兆網(wǎng)卡的服務(wù)器來說是災(zāi)難性的。
- 阻塞工作線程:如果使用 del 刪除大 key 時(shí),會(huì)阻塞工作線程,這樣就沒辦法處理后續(xù)的命令
?
查找大 key
當(dāng)我們?cè)谑褂?Redis 自帶的 --bigkeys
參數(shù)查找大 key 時(shí),最好選擇在從節(jié)點(diǎn)上執(zhí)行該命令,因?yàn)橹鞴?jié)點(diǎn)上執(zhí)行時(shí),會(huì)阻塞主節(jié)點(diǎn)。
-
我們還可以使用 SCAN 命令來查找大 key;
-
通過分析 RDB 文件來找出 big key,這種方案的前提是 Redis 采用的是 RDB 持久化。網(wǎng)上有現(xiàn)成的工具:
-
- redis-rdb-tools:Python 語言寫的用來分析 Redis 的 RDB 快照文件用的工具
- rdb_bigkeys:Go 語言寫的用來分析 Redis 的 RDB 快照文件用的工具,性能更好。
刪除大 key
刪除操作的本質(zhì)是要釋放鍵值對(duì)占用的內(nèi)存空間。
釋放內(nèi)存只是第一步,為了更加高效地管理內(nèi)存空間,在應(yīng)用程序釋放內(nèi)存時(shí),操作系統(tǒng)需要把釋放掉的內(nèi)存塊插入一個(gè)空閑內(nèi)存塊的鏈表,以便后續(xù)進(jìn)行管理和再分配。這個(gè)過程本身需要一定時(shí)間,而且會(huì)阻塞當(dāng)前釋放內(nèi)存的應(yīng)用程序。
所以,如果一下子釋放了大量?jī)?nèi)存,空閑內(nèi)存塊鏈表操作時(shí)間就會(huì)增加,相應(yīng)地就會(huì)造成 Redis 主線程的阻塞,如果主線程發(fā)生了阻塞,其他所有請(qǐng)求可能都會(huì)超時(shí),超時(shí)越來越多,會(huì)造成 Redis 連接耗盡,產(chǎn)生各種異常。
刪除大 key 時(shí)建議采用分批次刪除和異步刪除的方式進(jìn)行。
清空數(shù)據(jù)庫
清空數(shù)據(jù)庫和上面 bigkey 刪除也是同樣道理,flushdb
、flushall
也涉及到刪除和釋放所有的鍵值對(duì),也是 Redis 的阻塞點(diǎn)。
集群擴(kuò)容
Redis 集群可以進(jìn)行節(jié)點(diǎn)的動(dòng)態(tài)擴(kuò)容縮容,這一過程目前還處于半自動(dòng)狀態(tài),需要人工介入。
在擴(kuò)縮容的時(shí)候,需要進(jìn)行數(shù)據(jù)遷移。而 Redis 為了保證遷移的一致性,遷移所有操作都是同步操作。
執(zhí)行遷移時(shí),兩端的 Redis 均會(huì)進(jìn)入時(shí)長(zhǎng)不等的阻塞狀態(tài),對(duì)于小 Key,該時(shí)間可以忽略不計(jì),但如果一旦 Key 的內(nèi)存使用過大,嚴(yán)重的時(shí)候會(huì)觸發(fā)集群內(nèi)的故障轉(zhuǎn)移,造成不必要的切換。
Swap(內(nèi)存交換)
什么是 Swap? Swap 直譯過來是交換的意思,Linux 中的 Swap 常被稱為內(nèi)存交換或者交換分區(qū)。類似于 Windows 中的虛擬內(nèi)存,就是當(dāng)內(nèi)存不足的時(shí)候,把一部分硬盤空間虛擬成內(nèi)存使用,從而解決內(nèi)存容量不足的情況。因此,Swap 分區(qū)的作用就是犧牲硬盤,增加內(nèi)存,解決 VPS 內(nèi)存不夠用或者爆滿的問題。
Swap 對(duì)于 Redis 來說是非常致命的,Redis 保證高性能的一個(gè)重要前提是所有的數(shù)據(jù)在內(nèi)存中。如果操作系統(tǒng)把 Redis 使用的部分內(nèi)存換出硬盤,由于內(nèi)存與硬盤的讀寫速度差幾個(gè)數(shù)量級(jí),會(huì)導(dǎo)致發(fā)生交換后的 Redis 性能急劇下降。
?
1、查詢 Redis 進(jìn)程號(hào)
redis-cli -p 6383 info server | grep process_id
process_id: 4476
2、根據(jù)進(jìn)程號(hào)查詢內(nèi)存交換信息
cat /proc/4476/smaps | grep Swap
Swap: 0kB
Swap: 0kB
Swap: 4kB
Swap: 0kB
Swap: 0kB
.....
如果交換量都是 0KB 或者個(gè)別的是 4KB,則正常。
預(yù)防內(nèi)存交換的方法:
?
- 保證機(jī)器充足的可用內(nèi)存
- 確保所有 Redis 實(shí)例設(shè)置最大可用內(nèi)存(maxmemory),防止極端情況 Redis 內(nèi)存不可控的增長(zhǎng)
- 降低系統(tǒng)使用 swap 優(yōu)先級(jí),如
echo 10 > /proc/sys/vm/swappiness
CPU 競(jìng)爭(zhēng)
Redis 是典型的 CPU 密集型應(yīng)用,不建議和其他多核 CPU 密集型服務(wù)部署在一起。當(dāng)其他進(jìn)程過度消耗 CPU 時(shí),將嚴(yán)重影響 Redis 的吞吐量。
可以通過redis-cli --stat
獲取當(dāng)前 Redis 使用情況。通過top
命令獲取進(jìn)程對(duì) CPU 的利用率等信息 通過info commandstats
統(tǒng)計(jì)信息分析出命令不合理開銷時(shí)間,查看是否是因?yàn)楦咚惴◤?fù)雜度或者過度的內(nèi)存優(yōu)化問題。
網(wǎng)絡(luò)問題
連接拒絕、網(wǎng)絡(luò)延遲,網(wǎng)卡軟中斷等網(wǎng)絡(luò)問題也可能會(huì)導(dǎo)致 Redis 阻塞