Redis 這麼火,它都解決了哪些問題?

先看一下Redis是一個什麼東西。官方簡介解釋到:html

Redis是一個基於BSD開源的項目,是一個把結構化的數據放在內存中的一個存儲系統,你能夠把它做爲數據庫,緩存和消息中間件來使用。同時支持strings,lists,hashes,sets,sorted sets,bitmaps,hyperloglogs和geospatial indexes等數據類型。它還內建了複製,lua腳本,LRU,事務等功能,經過redis sentinel實現高可用,經過redis cluster實現了自動分片。以及事務,發佈/訂閱,自動故障轉移等等。git

綜上所述,Redis提供了豐富的功能,初次見到可能會感受眼花繚亂,這些功能都是幹嗎用的?都解決了什麼問題?什麼狀況下才會用到相應的功能?那麼下面從零開始,一步一步的演進來粗略的解釋下。github

1.從0開始redis

最初的需求很是簡單,咱們有一個提供熱點新聞列表的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

爲了解決調用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的壓力轉移到從服務器上。可是內存資源怎麼辦,主從模式作到的只是相同數據的備份,並不能橫向擴充內存;單臺機器的內存也只能進行加大處理,可是總有上限的。

因此咱們就須要一種解決方案,可讓咱們橫向擴展。最終的目的既是把每臺服務器只負責其中的一部分,讓這些全部的服務器構成一個總體,對外界的消費者而言,這一組分佈式的服務器就像是一個集中式的服務器同樣(以前在解讀REST的博客中解釋過度布式於基於網絡的差別:基於網絡應用的架構)。

在Redis官方的分佈式方案出來以前,有twemproxy和codis兩種方案,這兩個方案整體上來講都是依賴proxy來進行分佈式的,也就是說redis自己並不關心分佈式的事情,而是交由twemproxy和codis來負責。而redis官方給出的cluster方案則是把分佈式的這部分事情作到了每個redis服務器中,使其再也不須要其餘的組件就能夠獨立的完成分佈式的要求。

咱們這裏不關心這些方案的優略,咱們關注一下這裏的分佈式究竟是要處理那些事情?也就是twemproxy和codis獨立處理的處理分佈式的這部分邏輯和cluster集成到redis服務的這部分邏輯到底在解決什麼問題?

如咱們前面所說的,一個分佈式的服務在外界看來就像是一個集中式的服務同樣。那麼要作到這一點就面臨着有一個問題須要解決:既是增長或減小分佈式服務中的服務器的數量,對消費這個服務的客戶端而言應該是無感的;那麼也就意味着客戶端不能穿透分佈式服務,把本身綁死到某一個臺的服務器上去,由於一旦如此,你就再也沒法新增服務器,也沒法進行故障替換。

解決這個問題有兩個路子:

第一個路子最直接,那就是我加一箇中間層來隔離這種具體的依賴,即twemproxy採用的方式,讓全部的客戶端只能經過它來消費redsi服務,經過它來隔離這種依賴(可是你會發現twermproxy會成爲一個單點),這種狀況下每臺redis服務器都是獨立的,它們之間彼此不知對方的存在;

第二個路子是讓redis服務器知道彼此的存在,經過重定向的機制來引導客戶端來完成本身所須要的操做,好比客戶端連接到了某一個redis服務器,說我要執行這個操做,redis服務器發現本身沒法完成這個操做,那麼就把能完成這個操做的服務器的信息給到客戶端,讓客戶端去請求另外的一個服務器,這時候你就會發現每個redis服務器都須要保持一份完整的分佈式服務器信息的一份資料,否則它怎麼知道讓客戶端去找其餘的哪一個服務器來執行客戶端想要的操做呢。

上面這一大段解釋了這麼多,不知有沒有發現無論是第一個路子仍是第二個路子,都有一個共同的東西存在,那就是分佈式服務中全部服務器以及其能提供的服務的信息。這些信息不管如何也是要存在的,區別在於第一個路子是把這部分信息單獨來管理,用這些信息來協調後端的多個獨立的redis服務器;第二個路子則是讓每個redis服務器都持有這份信息,彼此知道對方的存在,來達成和第一個路子同樣的目的,優勢是再也不須要一個額外的組件來處理這部分事情。

Redis Cluster的具體實現細節則是採用了Hash槽的概念,即預先分配出來16384個槽:在客戶端經過對Key進行CRC16(key)% 16384運算獲得對應的槽是哪個;在redis服務端則是每一個服務器負責一部分槽,當有新的服務器加入或者移除的時候,再來遷移這些槽以及其對應的數據,同時每一個服務器都持有完整的槽和其對應的服務器的信息,這就使得服務器端能夠進行對客戶端的請求進行重定向處理。公衆號後臺回覆關鍵字:Redis,能夠獲取更多棧長整理的 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 30000 

if redis.call("get",KEYS[1]) == ARGV[1then 

return redis.call("del",KEYS[1]) 

else 

return 0


5.總結

本篇着重從抽象層面來解釋下redis的各項功能以及其存在的目的,而沒有關心其具體的細節是什麼。從而能夠聚焦於其解決的問題,依據抽象層面的概念可使得咱們在特定的場景下選擇更合適的方案,而非侷限於其技術細節。

以上均是筆者我的的一些理解,若是不當之處,歡迎指正。

6.參考

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

原文:https://www.cnblogs.com/linianhui/p/what-problem-does-redis-solve.html

做者:blackheart

相關文章
相關標籤/搜索