搞懂Redis,這一篇就夠了

1、緩存帶來的好處:

  • 高性能
    大量相同的請求過來,每次查詢mysql耗時600ms,如果用緩存耗時20ms,性能提高30倍.
  • 高并發(fā)
    單機mysql一般的最大QPS 2000,超過2000mysql直接死掉;單機redis QPS輕松10萬,并發(fā)提高50倍.

2、緩存帶來的壞處:

  • 緩存與數(shù)據(jù)庫的雙寫不一致
  • 緩存雪崩、緩存穿透、緩存擊穿
  • 緩存并發(fā)競爭問題

3、redis高性能的三大核心(為什么單線程還能這么快):

  • 純內(nèi)存操作:

這時Redis達到每秒萬級別訪問的重要基礎(chǔ)

  • 單線程:

保證高并發(fā)下數(shù)據(jù)的一致性,避免了多線程下的并發(fā)問題:資源共享、線程切換、死鎖、競態(tài)產(chǎn)生的消耗;

  • 多路I/O復(fù)用,非阻塞I/O:

linux平臺下,Redis使用epoll作為I/O多路復(fù)用技術(shù)的實現(xiàn),在加上Redis自身的事件處理模型將epoll中的鏈接、讀寫、關(guān)閉都轉(zhuǎn)換為事件,不在網(wǎng)絡(luò)I/O上浪費過多的時間;

3.1 源碼層面

C語言編寫,源代碼精簡(2萬行),C語言實現(xiàn)的程序”距離“操作系統(tǒng)更近,執(zhí)行速度快;
Redis源碼里,包含了多種數(shù)據(jù)結(jié)構(gòu)的實現(xiàn):
簡單動態(tài)字符串(SDS)、雙端鏈表、字典、壓縮列表、整數(shù)集合、跳躍表等等;
Redis并沒有直接使用這些數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)鍵值對數(shù)據(jù)庫,而是基于這些數(shù)據(jù)結(jié)構(gòu)創(chuàng)建了一個對象系統(tǒng),這個系統(tǒng)包含了字符串、列表、哈希、集合、有序集合這五種類型的對象,每種對象都用到了至少一種前面所說的數(shù)據(jù)結(jié)構(gòu);
針對不同的使用場景,為對象設(shè)置多種不同的數(shù)據(jù)結(jié)構(gòu)實現(xiàn),從而優(yōu)化對象在不同場景下的使用效率;

image.png

4、除了緩存,Redis能做些什么特別的事 ?

  • 利用redis生成唯一key
  • 分布式鎖
  • 消息隊列

5、利用redis實現(xiàn)分布式鎖

redis是單線程的,利用setnx命令的原子性(判斷指定key是否存在,不存在才能添加成功),實現(xiàn)多線程狀態(tài)下的分布式鎖
SET my_key my_value NX PX milliseconds

引用:如何優(yōu)雅地用Redis實現(xiàn)分布式鎖?

6、redis過期策略

Reference:引用
當redis存了非常多數(shù)據(jù)數(shù)據(jù)時,過期的數(shù)據(jù)不是實時刪除的,如果每次刪除都是掃描全部獲取過期數(shù)據(jù),代價太大,所以很可能當內(nèi)存滿了時,會先刪除沒有過期的

定期刪除

每個一段時間,從設(shè)置了過期的key里面隨機挑選,判斷是否過期,過期則刪除

惰性刪除

通過定期刪除肯定會遺留很多沒刪掉的,所以,當在獲取某個key時,會判斷是否已經(jīng)過期,過期則刪除不返回

8、內(nèi)存淘汰機制(LRU算法)

當內(nèi)存不足以容納新寫入數(shù)據(jù)時,redis會啟用內(nèi)存淘汰機制:

  • allkey_LRU:移除最近最少使用的key(最常用
  • allkey_Random:隨機移除一個key
  • volatile_LRU:在設(shè)置了過期時間的鍵空間中,移除最近最少使用的key
  • volatile_Random:在設(shè)置了過期時間的鍵空間中,隨機移除key
  • volatile_TTL:在設(shè)置了過期時間的鍵空間中,優(yōu)先移除更早過期的key

9、Redis的I/O多路復(fù)用

Reference:引用
下面舉一個例子,模擬一個tcp服務(wù)器處理30個客戶socket。假設(shè)你是一個老師,讓30個學(xué)生解答一道題目,然后檢查學(xué)生做的是否正確,你有下面幾個選擇:

  1. 第一種選擇:按順序逐個檢查,先檢查A,然后是B,之后是C、D。。。這中間如果有一個學(xué)生卡主,全班都會被耽誤。這種模式就好比,你用循環(huán)挨個處理socket,根本不具有并發(fā)能力。
  2. 第二種選擇:你創(chuàng)建30個分身,每個分身檢查一個學(xué)生的答案是否正確。 這種類似于為每一個用戶創(chuàng)建一個進程或者線程處理連接。
  3. 第三種選擇,你站在講臺上等,誰解答完誰舉手。這時C、D舉手,表示他們解答問題完畢,你下去依次檢查C、D的答案,然后繼續(xù)回到講臺上等。此時E、A又舉手,然后去處理E和A。。。

這種就是IO復(fù)用模型,Linux下的select、poll和epoll就是干這個的。將用戶socket對應(yīng)的fd注冊進epoll,然后epoll幫你監(jiān)聽哪些socket上有消息到達,這樣就避免了大量的無用操作。此時的socket應(yīng)該采用非阻塞模式。這樣,整個過程只在調(diào)用select、poll、epoll這些調(diào)用的時候才會阻塞,收發(fā)客戶消息是不會阻塞的,整個進程或者線程就被充分利用起來,這就是事件驅(qū)動,所謂的reactor模式。

java的NIO 是IO的多路復(fù)用; IO多路復(fù)用聽上去好像是多個數(shù)據(jù)可以共享一個IO(socket連接),實際上并非如此。IO多路復(fù)用不是指多個服務(wù)共享一個連接,而僅僅是指多個連接的管理可以在同一進程。

IO多路復(fù)用:各個平臺的底層IO復(fù)用實現(xiàn)各不相同,linux采用select、poll、epoll實現(xiàn),macOS采用kqueue,windows采用IOCP,netty在此基礎(chǔ)上進行了封裝,使其更好用,而底層實際上是調(diào)用的當前平臺的IO復(fù)用接口(native方法),而Redis底層也是依賴平臺的IO復(fù)用

10、redis 集群的三種模式

10.1 主從復(fù)制(無法保證高可用)

image.png

10.2 哨兵模式(無法保證高性能)

Redis主從模式雖然能做到很好的數(shù)據(jù)備份,但是他并不是高可用的。一旦主服務(wù)器點宕機后,只能通過人工去切換主服務(wù)器。因此Redis的哨兵模式也就是為了解決主從模式的高可用方案。


image.png

10.3 集群模式

Redis哨兵模式實現(xiàn)了高可用,讀寫分離,但是其主節(jié)點仍然只有一個,即寫入操作都是在主節(jié)點中,這也成為了性能的瓶頸。
因此Redis在 3.0 后加入了Cluster模式,它采用去無心節(jié)點方式實現(xiàn),集群將會通過分片方式保存數(shù)據(jù)庫中的鍵值對;
每個分片節(jié)點又通過主從方式保證高可用:與哨兵類似,分片中的每個節(jié)點會定期向其他節(jié)點發(fā)送消息,已檢測各個節(jié)點的狀態(tài),如果主節(jié)點被半數(shù)以上節(jié)點檢測到下線,會重新選擇主節(jié)點;


image.png
  • 集群模式下的擴縮容:
    每個分片節(jié)點的分片范圍維護在配置文件中,當增刪節(jié)點時,需要將重新將16383個slots分配到集群中的節(jié)點上(數(shù)據(jù)遷移)

11、一致性Hash算法

先說一個誤區(qū):Redis的集群模式本身沒有使用一致性hash算法,而是使用slots插槽實現(xiàn)了數(shù)據(jù)分片。
一致性Hash算法主要用于解決分布式緩存集群節(jié)點新增,和節(jié)點失效的問題;
我們說的一致性hash都不是緩存機器自身的功能,而是集群前置的代理或客戶端實現(xiàn)的。比如在redis的jedis客戶端jar包就是實現(xiàn)了一致性hash算法(客戶端模式)。

12、緩存穿透、緩存雪崩、緩存擊穿

緩存穿透(不存在key)
  • 概念:訪問一個數(shù)據(jù)庫不存在的key,一般情況不會緩存這種數(shù)據(jù),所以的請求都命中DB;
  • 解決方案
  1. 采用布隆過濾器,使用一個足夠大的bitmap,用于存儲可能訪問的key,不存在的key直接被過濾;引用

  2. 訪問key未在DB查詢到值,也將空值寫進緩存,但可以設(shè)置較短過期時間。

緩存雪崩(同時過期)
  • 概念:大量的key設(shè)置了相同的過期時間,導(dǎo)致在緩存在同一時刻全部失效,造成瞬時DB請求量大、壓力驟增,引起雪崩。
  • 解決方案
    可以給緩存設(shè)置過期時間時加上一個隨機值時間,使得每個key的過期時間分布開來,不會集中在同一時刻失效。
緩存擊穿(熱點key過期)
  • 概念
    一個存在的key,在緩存過期的一刻,同時有大量的請求,這些請求都會擊穿到DB,造成瞬時DB請求量大、壓力驟增。
  • 解決方案
    設(shè)置熱點緩沖永不過期
    緩存預(yù)熱:每次查出來看看是不是要過期了,快過期了則更新緩存
    在訪問key之前,采用SETNX(set if not exists)來設(shè)置另一個短期key來鎖住當前key的訪問,訪問結(jié)束再刪除該短期key。

13、數(shù)據(jù)庫與緩存雙寫一致性問題

引用
一般采用:先更新數(shù)據(jù)庫,再刪除緩存

14、redis的持久化

14.1 redis持久化的意義

redis服務(wù)器宕機的兜底措施,避免宕機時直接訪問數(shù)據(jù)庫、降低請求響應(yīng)速度;

14.2 AOF(Append Only File)日志

寫后日志,類似redo log,每執(zhí)行一次命令,如果成功則保存命令到aof日志文件,在主線程中執(zhí)行,有阻塞風(fēng)險;

  • 三種寫回策略


    AOF三種寫回策略

AOF重寫機制

如果一直往AOF文件里寫日志,日志文件會爆炸;
當AOF文件的大小超過了配置所設(shè)置的闕值時,Redis就會啟動AOF文件壓縮,只保留可以恢復(fù)數(shù)據(jù)的最小指令集,可以使用命令bgrewriteaof;

  • 觸發(fā)機制:Redis會記錄上次重寫時的AOF文件大小,默認配置時當AOF文件大小是上次rewrite后大小的一倍且文件大于64M時觸發(fā)
      auto-aof-rewrite-percentage 100 (一倍)
       auto-aof-rewrite-min-size 64mb
    AOF重寫可避免主線程阻塞:主線程fork一個子線程,同時把主線程的內(nèi)存通過寫時復(fù)制的方式拷貝給子線程,然后子線程在不影響主線程的情況下逐一把拷貝的數(shù)據(jù)寫成操作,記入重寫日志;
    如果在子線程寫日志的過程中主線程產(chǎn)生的數(shù)據(jù),會放到一個緩存區(qū)中,后續(xù)寫入AOF文件;
    AOF非阻塞的重寫過程

14.3 RDB(Redis DataBase)內(nèi)存快照

默認持久化方式

與AOF對比

AOF記錄的是操作命令,不是實際數(shù)據(jù),故障恢復(fù)的時候緩慢;
RDB能夠快速恢復(fù),但是相比于AOF的增量式操作,RDB的全量試操作更耗時耗力;

兩個命令來生成RDB

Redis 提供了兩個命令來生成 RDB 文件,分別是 save 和 bgsave。

  • save:在主線程中執(zhí)行,會導(dǎo)致阻塞;
  • bgsave:創(chuàng)建一個子進程,專門用于寫入 RDB 文件,避免了主線程的阻塞,這也是 Redis RDB 文件生成的默認配置。

AOF和RDB結(jié)合使用

Redis 4.0提出一個混合使用AOF日志和內(nèi)存快照的方法;內(nèi)存快照以一定頻率執(zhí)行,兩次快照間,使用AOF日志記錄這期間的所有命令操作;
這個方法既能享受到 RDB 文件快速恢復(fù)的好處,又能享受到 AOF 只記錄操作命令的簡單優(yōu)勢

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容