共計 4650 個字符,預計需要花費 12 分鐘才能閱讀完成。
這篇文章主要為大家展示了“Redis 的各項功能主要解決了哪些問題”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓丸趣 TV 小編帶領大家一起研究并學習一下“Redis 的各項功能主要解決了哪些問題”這篇文章吧。
先看一下 Redis 是一個什么東西。官方簡介解釋到:
Redis 是一個基于 BSD 開源的項目,是一個把結構化的數(shù)據(jù)放在內存中的一個存儲系統(tǒng),你可以把它作為數(shù)據(jù)庫,緩存和消息中間件來使用。同時支持 strings,lists,hashes,sets,sorted sets,bitmaps,hyperloglogs 和 geospatial indexes 等數(shù)據(jù)類型。它還內建了復制,lua 腳本,LRU,事務等功能,通過 redis sentinel 實現(xiàn)高可用,通過 redis cluster 實現(xiàn)了自動分片。以及事務,發(fā)布 / 訂閱,自動故障轉移等等。
綜上所述,Redis 提供了豐富的功能,初次見到可能會感覺眼花繚亂,這些功能都是干嘛用的?都解決了什么問題?什么情況下才會用到相應的功能?那么下面從零開始,一步一步的演進來粗略的解釋下。
1 從零開始
最初的需求非常簡單,我們有一個提供熱點新聞列表的 api:http://api.xxx.com/hot-news,api 的消費者抱怨說每次請求都要 2 秒左右才能返回結果。
隨后我們就著手于如何提升一下 api 消費者感知的性能,很快最簡單粗暴的第一個方案就出來了:為 API 的響應加上基于 HTTP 的緩存控制 cache-control:max-age=600,即讓消費者可以緩存這個響應十分鐘。
如果 api 消費者如果有效的利用了響應中的緩存控制信息,則可以有效的改善其感知的性能(10 分鐘以內)。但是還有 2 個弊端:第一個是在緩存生效的 10 分鐘內,api 消費者可能會得到舊的數(shù)據(jù);第二個是如果 api 的客戶端無視緩存直接訪問 API 依然是需要 2 秒,治標不治本吶。
2 基于本機內存的緩存
為了解決調用 API 依然需要 2 秒的問題,經(jīng)過排查,其主要原因在于使用 SQL 獲取熱點新聞的過程中消耗了將近 2 秒的時間,于是乎,我們又想到了一個簡單粗暴的解決方案,即把 SQL 查詢的結果直接緩存在當前 api 服務器的內存中(設置緩存有效時間為 1 分鐘)。后續(xù) 1 分鐘內的請求直接讀緩存,不再花費 2 秒去執(zhí)行 SQL 了。
假如這個 api 每秒接收到的請求時 100 個,那么一分鐘就是 6000 個,也就是只有前 2 秒擁擠過來的請求會耗時 2 秒,后續(xù)的 58 秒中的所有請求都可以做到即使響應,而無需再等 2 秒的時間。
其他 API 的小伙伴發(fā)現(xiàn)這是個好辦法,于是很快我們就發(fā)現(xiàn) API 服務器的內存要爆滿了。。。
3 服務端的 Redis
在 API 服務器的內存都被緩存塞滿的時候,我們發(fā)現(xiàn)不得不另想解決方案了。最直接的想法就是我們把這些緩存都丟到一個專門的服務器上吧,把它的內存配置的大大的。然后我們就盯上了 redis。。。至于如何配置部署 redis 這里不解釋了,redis 官方有詳細的介紹。隨后我們就用上了一臺單獨的服務器作為 Redis 的服務器,API 服務器的內存壓力得以解決。
3.1 持久化(Persistence)
單臺的 Redis 服務器一個月總有那么幾天心情不好,心情不好就罷工了,導致所有的緩存都丟失了(redis 的數(shù)據(jù)是存儲在內存的嘛)。雖然可以把 Redis 服務器重新上線,但是由于內存的數(shù)據(jù)丟失,造成了緩存雪崩,API 服務器和數(shù)據(jù)庫的壓力還是一下子就上來了。所以這個時候 Redis 的持久化功能就派上用場了,可以緩解一下緩存雪崩帶來的影響。redis 的持久化指的是 redis 會把內存的中的數(shù)據(jù)寫入到硬盤中,在 redis 重新啟動的時候加載這些數(shù)據(jù),從而最大限度的降低緩存丟失帶來的影響。
3.2 哨兵(Sentinel)和復制(Replication)
Redis 服務器毫無征兆的罷工是個麻煩事。那么怎辦辦?答曰:備份一臺,你掛了它上。那么如何得知某一臺 redis 服務器掛了,如何切換,如何保證備份的機器是原始服務器的完整備份呢?這時候就需要 Sentinel 和 Replication 出場了。Sentinel 可以管理多個 Redis 服務器,它提供了監(jiān)控,提醒以及自動的故障轉移的功能;Replication 則是負責讓一個 Redis 服務器可以配備多個備份的服務器。Redis 也是利用這兩個功能來保證 Redis 的高可用的。此外,Sentinel 功能則是對 Redis 的發(fā)布和訂閱功能的一個利用。
3.3 集群(Cluster)
單臺服務器資源的總是有上限的,CPU 資源和 IO 資源我們可以通過主從復制,進行讀寫分離,把一部分 CPU 和 IO 的壓力轉移到從服務器上。但是內存資源怎么辦,主從模式做到的只是相同數(shù)據(jù)的備份,并不能橫向擴充內存;單臺機器的內存也只能進行加大處理,但是總有上限的。所以我們就需要一種解決方案,可以讓我們橫向擴展。最終的目的既是把每臺服務器只負責其中的一部分,讓這些所有的服務器構成一個整體,對外界的消費者而言,這一組分布式的服務器就像是一個集中式的服務器一樣(之前在解讀 REST 的博客中解釋過分布式于基于網(wǎng)絡的差異:基于網(wǎng)絡應用的架構)。
在 Redis 官方的分布式方案出來之前,有 twemproxy 和 codis 兩種方案,這兩個方案總體上來說都是依賴 proxy 來進行分布式的,也就是說 redis 本身并不關心分布式的事情,而是交由 twemproxy 和 codis 來負責。而 redis 官方給出的 cluster 方案則是把分布式的這部分事情做到了每一個 redis 服務器中,使其不再需要其他的組件就可以獨立的完成分布式的要求。我們這里不關心這些方案的優(yōu)略,我們關注一下這里的分布式到底是要處理那些事情? 也就是 twemproxy 和 codis 獨立處理的處理分布式的這部分邏輯和 cluster 集成到 redis 服務的這部分邏輯到底在解決什么問題?
如我們前面所說的,一個分布式的服務在外界看來就像是一個集中式的服務一樣。那么要做到這一點就面臨著有一個問題需要解決:既是增加或減少分布式服務中的服務器的數(shù)量,對消費這個服務的客戶端而言應該是無感的;那么也就意味著客戶端不能穿透分布式服務,把自己綁死到某一個臺的服務器上去,因為一旦如此,你就再也無法新增服務器,也無法進行故障替換。
解決這個問題有兩個路子:
第一個路子最直接,那就是我加一個中間層來隔離這種具體的依賴,即 twemproxy 采用的方式,讓所有的客戶端只能通過它來消費 redsi 服務,通過它來隔離這種依賴(但是你會發(fā)現(xiàn) twermproxy 會成為一個單點),這種情況下每臺 redis 服務器都是獨立的,它們之間彼此不知對方的存在;
第二個路子是讓 redis 服務器知道彼此的存在,通過重定向的機制來引導客戶端來完成自己所需要的操作,比如客戶端鏈接到了某一個 redis 服務器,說我要執(zhí)行這個操作,redis 服務器發(fā)現(xiàn)自己無法完成這個操作,那么就把能完成這個操作的服務器的信息給到客戶端,讓客戶端去請求另外的一個服務器,這時候你就會發(fā)現(xiàn)每一個 redis 服務器都需要保持一份完整的分布式服務器信息的一份資料,不然它怎么知道讓客戶端去找其他的哪個服務器來執(zhí)行客戶端想要的操作呢。
上面這一大段解釋了這么多,不知有沒有發(fā)現(xiàn)不管是第一個路子還是第二個路子,都有一個共同的東西存在,那就是分布式服務中所有服務器以及其能提供的服務的信息。這些信息無論如何也是要存在的,區(qū)別在于第一個路子是把這部分信息單獨來管理,用這些信息來協(xié)調后端的多個獨立的 redis 服務器;第二個路子則是讓每一個 redis 服務器都持有這份信息,彼此知道對方的存在,來達成和第一個路子一樣的目的,優(yōu)點是不再需要一個額外的組件來處理這部分事情。
Redis Cluster 的具體實現(xiàn)細節(jié)則是采用了 Hash 槽的概念,即預先分配出來 16384 個槽:在客戶端通過對 Key 進行 CRC16(key)% 16384 運算得到對應的槽是哪一個;在 redis 服務端則是每個服務器負責一部分槽,當有新的服務器加入或者移除的時候,再來遷移這些槽以及其對應的數(shù)據(jù),同時每個服務器都持有完整的槽和其對應的服務器的信息,這就使得服務器端可以進行對客戶端的請求進行重定向處理。
4 客戶端的 Redis
上面的第三小節(jié)主要介紹的是 Redis 服務端的演進步驟,解釋了 Redis 如何從一個單機的服務,進化為一個高可用的、去中心化的、分布式的存儲系統(tǒng)。這一小節(jié)則是關注下客戶端可以消費的 redis 服務。
4.1 數(shù)據(jù)類型
redis 支持豐富的數(shù)據(jù)類型,從最基礎的 string 到復雜的常用到的數(shù)據(jù)結構都有支持:
string:最基本的數(shù)據(jù)類型,二進制安全的字符串,最大 512M。
list:按照添加順序保持順序的字符串列表。
set:無序的字符串集合,不存在重復的元素。
sorted set:已排序的字符串集合。
hash:key-value 對的一種集合。
bitmap:更細化的一種操作,以 bit 為單位。
hyperloglog:基于概率的數(shù)據(jù)結構。
這些眾多的數(shù)據(jù)類型,主要是為了支持各種場景的需要,當然每種類型都有不同的時間復雜度。其實這些復雜的數(shù)據(jù)結構相當于之前我在《解讀 REST》這個系列博客基于網(wǎng)絡應用的架構風格中介紹到的遠程數(shù)據(jù)訪問(Remote Data Access = RDA)的具體實現(xiàn),即通過在服務器上執(zhí)行一組標準的操作命令,在服務端之間得到想要的縮小后的結果集,從而簡化客戶端的使用,也可以提高網(wǎng)絡性能。比如 如果沒有 list 這種數(shù)據(jù)結構,你就只能把 list 存成一個 string,客戶端拿到完整的 list,操作后再完整的提交給 redis,會產(chǎn)生很大的浪費。
4.2 事務
上述數(shù)據(jù)類型中,每一個數(shù)據(jù)類型都有獨立的命令來進行操作,很多情況下我們需要一次執(zhí)行不止一個命令,而且需要其同時成功或者失敗。redis 對事務的支持也是源自于這部分需求,即支持一次性按順序執(zhí)行多個命令的能力,并保證其原子性。
4.3 Lua 腳本
在事務的基礎上,如果我們需要在服務端一次性的執(zhí)行更復雜的操作(包含一些邏輯判斷),則 lua 就可以排上用場了(比如在獲取某一個緩存的時候,同時延長其過期時間)。redis 保證 lua 腳本的原子性,一定的場景下,是可以代替 redis 提供的事務相關的命令的。相當于基于網(wǎng)絡應用的架構風格中介紹到的遠程求值(Remote Evluation = REV)的具體實現(xiàn)。
4.4 管道
因為 redis 的客戶端和服務器的連接時基于 TCP 的,默認每次連接都時只能執(zhí)行一個命令。管道則是允許利用一次連接來處理多條命令,從而可以節(jié)省一些 tcp 連接的開銷。管道和事務的差異在于管道是為了節(jié)省通信的開銷,但是并不會保證原子性。
4.5 分布式鎖
官方推薦采用 Redlock 算法,即使用 string 類型,加鎖的時候給的一個具體的 key,然后設置一個隨機的值;取消鎖的時候用使用 lua 腳本來先執(zhí)行獲取比較,然后再刪除 key。具體的命令如下:
SET resource_name my_random_value NX PX 30000
if redis.call(get ,KEYS[1]) == ARGV[1] then
return redis.call(del ,KEYS[1])
return 0
end
以上是“Redis 的各項功能主要解決了哪些問題”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注丸趣 TV 行業(yè)資訊頻道!