Redis 到底能解決哪些問題

閱讀本文大概需要 7 分鐘。

作者:blackheart

www.cnblogs.com/linianhui

先看一下Redis是一個什麼東西

官方簡介解釋到:Redis 是一個基於 BSD 開源的項目,是一個把結構化的數據放在內存中的一個存儲系統,你可以把它作爲數據庫,緩存和消息中間件來使用。


同時支持strings,lists,hashes,sets,sorted sets,bitmaps,hyperloglogs 和 geospatial indexes 等數據類型。


它還內建了複製,lua 腳本,LRU,事務等功能,通過 redis sentinel實現高可用,通過 redis cluster 實現了自動分片。


以及事務,發佈/訂閱,自動故障轉移等等。

綜上所述,redis 提供了豐富的功能,初次見到可能會感覺眼花繚亂,

這些功能都是幹嘛用的?

都解決了什麼問題?

什麼情況下才會用到相應的功能?

那麼下面從零開始,一步一步的演進來粗略的解釋下。

1 從零開始

最初的需求非常簡單,我們有一個提供熱點新聞列表的API:http://api.xxx.com/hot-news,API 的消費者抱怨說每次請求都要 2 秒左右才能返回結果。

隨後我們就着手於如何提升一下 API 消費者感知的性能,很快最簡單粗暴的第一個方案就出來了:

爲 API 的響應加上基於 HTTP 的緩存控制 cache-control:max-age=600 ,即讓消費者可以緩存這個響應十分鐘

如果api消費者如果有效的利用了響應中的緩存控制信息,則可以有效的改善其感知的性能(10 分鐘以內)。

但是還有 2 個弊端

第一個是在緩存生效的 10 分鐘內,API 消費者可能會得到舊的數據;

第二個是如果 API 的客戶端無視緩存直接訪問 API 依然是需要 2 秒,治標不治本吶。

2 基於本機內存的緩存

爲了解決調用 API 依然需要 2 秒的問題,經過排查,其主要原因在於使用 SQL 獲取熱點新聞的過程中消耗了將近 2 秒的時間

於是乎,我們又想到了一個簡單粗暴的解決方案,即把 SQL 查詢的結果直接緩存在當前 API 服務器的內存中(設置緩存有效時間爲 1 分鐘)。

後續 1 分鐘內的請求直接讀緩存,不再花費 2 秒去執行 SQL 了。

假如這個 API 每秒接收到的請求時 100 個,那麼一分鐘就是 6000 個,也就是隻有前 2 秒擁擠過來的請求會耗時 2 秒,後續的 58 秒中的所有請求都可以做到即使響應,而無需再等 2 秒的時間。

其他 API 的小夥伴發現這是個好辦法,於是很快我們就發現 API 服務器的內存要爆滿了

3 服務端的 Redis

在 API 服務器的內存都被緩存塞滿的時候,我們發現不得不另想解決方案了。

最直接的想法就是我們把這些緩存都丟到一個專門的服務器上吧,把它的內存配置的大大的。

然後我們就盯上了 redis。

至於如何配置部署 redis 這裏不解釋了,redis 官方有詳細的介紹。

隨後我們就用上了一臺單獨的服務器作爲 redis 的服務器,API 服務器的內存壓力得以解決。

3.1 持久化(Persistence)

單臺的 redis 服務器一個月總有那麼幾天心情不好,心情不好就罷工了,導致所有的緩存都丟失了(redis 的數據是存儲在內存的嘛)。

雖然可以把 redis 服務器重新上線,但是由於內存的數據丟失,造成了緩存雪崩,API 服務器和數據庫的壓力還是一下子就上來了。

所以這個時候 redis 的持久化功能就派上用場了,可以緩解一下緩存雪崩帶來的影響。

Redis 的持久化指的是 redis 會把內存的中的數據寫入到硬盤中,在 redis 重新啓動的時候加載這些數據,從而最大限度的降低緩存丟失帶來的影響。

3.2 哨兵(Sentinel)和複製(Replication)

Redis 服務器毫無徵兆的罷工是個麻煩事。

那麼怎麼辦?

答曰:備份一臺,你掛了它上。

那麼如何得知某一臺 redis 服務器掛了,如何切換,如何保證備份的機器是原始服務器的完整備份呢?

這時候就需要 Sentinel 和 Replication 出場了

Sentinel 可以管理多個 redis 服務器,它提供了監控,提醒以及自動的故障轉移的功能;Replication 則是負責讓一個 redis 服務器可以配備多個備份的服務器。

Redis 也是利用這兩個功能來保證 redis 的高可用的。

此外,Sentinel 功能則是對 redis 的發佈和訂閱功能的一個利用。

3.3 集羣(Cluster)

單臺服務器資源的總是有上限的,CPU 資源和 IO 資源我們可以通過主從複製,進行讀寫分離,把一部分 CPU 和 IO 的壓力轉移到從服務器上

但是內存資源怎麼辦,主從模式做到的只是相同數據的備份,並不能橫向擴充內存;單臺機器的內存也只能進行加大處理,但是總有上限的。

所以我們就需要一種解決方案,可以讓我們橫向擴展

最終的目的既是把每臺服務器只負責其中的一部分,讓這些所有的服務器構成一個整體,對外界的消費者而言,這一組分佈式的服務器就像是一個集中式的服務器一樣。

在 redis 官方的分佈式方案出來之前,有 twemproxy 和 codis 兩種方案,這兩個方案總體上來說都是依賴 proxy 來進行分佈式的,也就是說 redis 本身並不關心分佈式的事情,而是交由 twemproxy 和 codis 來負責。

而 redis 官方給出的 cluster 方案則是把分佈式的這部分事情做到了每一個 redis 服務器中,使其不再需要其他的組件就可以獨立的完成分佈式的要求。

我們這裏不關心這些方案的優略,我們關注一下這裏的分佈式到底是要處理那些事情?

也就是 twemproxy 和 codis 獨立處理的處理分佈式的這部分邏輯和 cluster 集成到 redis 服務的這部分邏輯到底在解決什麼問題?

如我們前面所說的,一個分佈式的服務在外界看來就像是一個集中式的服務一樣

那麼要做到這一點就面臨着有一個問題需要解決:

既是增加或減少分佈式服務中的服務器的數量,對消費這個服務的客戶端而言應該是無感的;

那麼也就意味着客戶端不能穿透分佈式服務,把自己綁死到某一個臺的服務器上去,因爲一旦如此,你就再也無法新增服務器,也無法進行故障替換。

解決這個問題有兩個路子

第一個路子最直接,那就是我加一箇中間層來隔離這種具體的依賴

即 twemproxy 採用的方式,讓所有的客戶端只能通過它來消費 redis 服務,通過它來隔離這種依賴(但是你會發現 twermproxy 會成爲一個單點),這種情況下每臺 redis 服務器都是獨立的,它們之間彼此不知對方的存在;

第二個路子是讓 redis 服務器知道彼此的存在,通過重定向的機制來引導客戶端來完成自己所需要的操作

比如客戶端鏈接到了某一個 redis 服務器,說我要執行這個操作,redis 服務器發現自己無法完成這個操作,那麼就把能完成這個操作的服務器的信息給到客戶端,讓客戶端去請求另外的一個服務器。

這時候你就會發現每一個 redis 服務器都需要保持一份完整的分佈式服務器信息的一份資料,不然它怎麼知道讓客戶端去找其他的哪個服務器來執行客戶端想要的操作呢。

上面這一大段解釋了這麼多,不知有沒有發現不管是第一個路子還是第二個路子,都有一個共同的東西存在那就是分佈式服務中所有服務器以及其能提供的服務的信息

這些信息無論如何也是要存在的,區別在於

第一個路子是把這部分信息單獨來管理,用這些信息來協調後端的多個獨立的 redis 服務器

第二個路子則是讓每一個 redis 服務器都持有這份信息,彼此知道對方的存在,來達成和第一個路子一樣的目的,優點是不再需要一個額外的組件來處理這部分事情

Redis Cluster 的具體實現細節則是採用了 Hash 槽的概念,即預先分配出來 16384 個槽:

在客戶端通過對 Key 進行 CRC16(key)% 16384 運算得到對應的槽是哪一個;

在 redis 服務端則是每個服務器負責一部分槽,當有新的服務器加入或者移除的時候,再來遷移這些槽以及其對應的數據。

同時每個服務器都持有完整的槽和其對應的服務器的信息,這就使得服務器端可以進行對客戶端的請求進行重定向處理。

4 客戶端的 Redis

上面的第三小節主要介紹的是 redis 服務端的演進步驟,解釋了 redis 如何從一個單機的服務,進化爲一個高可用的、去中心化的、分佈式的存儲系統。

這一小節則是關注下客戶端可以消費的 redis 服務。

4.1 數據類型

Redis 支持豐富的數據類型,從最基礎的 string 到複雜的常用到的數據結構都有支持:

  • string:最基本的數據類型,二進制安全的字符串,最大 512M。

  • list:按照添加順序保持順序的字符串列表。

  • set:無序的字符串集合,不存在重複的元素。

  • sorted set:已排序的字符串集合。

  • hash:key-value 對的一種集合。

  • bitmap:更細化的一種操作,以 bit 爲單位。

  • hyperloglog:基於概率的數據結構。

這些衆多的數據類型,主要是爲了支持各種場景的需要,當然每種類型都有不同的時間複雜度。

其實這些複雜的數據結構相當於之前我在《解讀REST》這個系列博客基於網絡應用的架構風格中介紹到的遠程數據訪問(Remote Data Access = RDA)的具體實現。

即通過在服務器上執行一組標準的操作命令,在服務端之間得到想要的縮小後的結果集,從而簡化客戶端的使用,也可以提高網絡性能。

比如如果沒有 list 這種數據結構,你就只能把 list 存成一個 string,客戶端拿到完整的 list,操作後再完整的提交給 redis,會產生很大的浪費。

4.2 事務

上述數據類型中,每一個數據類型都有獨立的命令來進行操作,很多情況下我們需要一次執行不止一個命令,而且需要其同時成功或者失敗。

Redis 對事務的支持也是源自於這部分需求,即支持一次性按順序執行多個命令的能力,並保證其原子性。

4.3 Lua腳本

在事務的基礎上,如果我們需要在服務端一次性的執行更復雜的操作(包含一些邏輯判斷),則 lua 就可以排上用場了(比如在獲取某一個緩存的時候,同時延長其過期時間)。

Redis 保證 lua 腳本的原子性,一定的場景下,是可以代替 redis 提供的事務相關的命令的。相當於基於網絡應用的架構風格中介紹到的遠程求值(Remote Evluation = REV)的具體實現。

4.4 管道

因爲 redis 的客戶端和服務器的連接時基於 TCP 的, 默認每次連接都時只能執行一個命令。

管道則是允許利用一次連接來處理多條命令,從而可以節省一些 TCP 連接的開銷。

管道和事務的差異在於管道是爲了節省通信的開銷,但是並不會保證原子性。

4.5 分佈式鎖

官方推薦採用 Redlock 算法,即使用 string 類型,加鎖的時候給的一個具體的 key,然後設置一個隨機的值;取消鎖的時候用使用 lua 腳本來先執行獲取比較,然後再刪除 key。

具體的命令如下:

SET resource_name my_random_value NX PX 30000if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call("del",KEYS[1])else    return 0end

總結

本篇着重從抽象層面來解釋下 redis 的各項功能以及其存在的目的,而沒有關心其具體的細節是什麼。

從而可以聚焦於其解決的問題,依據抽象層面的概念可以使得我們在特定的場景下選擇更合適的方案,而非侷限於其技術細節。

以上均是筆者個人的一些理解,如果不當之處,歡迎指正。

參考

Redis 文檔:https://github.com/antirez/redis-doc

Redis 簡介:https://redis.io/topics/introduction

Redis 持久化(Persistence):https://redis.io/topics/persistence

Redis 發佈/訂閱(Pub/Sub):https://redis.io/topics/pubsub

Redis 哨兵(Sentinel):https://redis.io/topics/sentinel

Redis 複製(Replication):https://redis.io/topics/replication

Redis 集羣(cluster):https://redis.io/topics/cluster-tutorial

RedIs 事務(Transaction):https://redis.io/topics/transactions

Redis 數據類型(data types):https://redis.io/topics/data-types-intro

Redis 分佈式鎖:https://redis.io/topics/distlock

Redis 管道(pipelining ):https://redis.io/topics/pipelining

Redis Lua Script:https://redis.io/commands/eval


·END·

程序員的成長之路

路雖遠,行則必至

本文原發於 同名微信公衆號「程序員的成長之路」,回覆「1024」你懂得,給個讚唄。

回覆 [ 520 ] 領取程序員最佳學習方式

回覆 [ 256 ] 查看 Java 程序員成長規劃


往期精彩回顧

從工廠流水線小-妹到Google上班程序媛,看完後,我跪服了!

消息中間件消費到的消息處理失敗怎麼辦?

任總接受央視採訪:不要煽動民族情緒

程序員,請停止學習框架!

爲什麼 Redis 單線程能支撐高併發?

YouTube 賺錢套路:搬運抖音視頻能月入上萬美金?

江湖又現失傳騙術。下一個中招的,很可能就是你!


16ae27fd122612d7?w=500&h=278&f=jpeg&s=27769



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章