Redis適合用來作什麼?

Redis是個流行的in-momery存儲。接口好用,性能也很強,還支持多種數據結構,加上各類HA和Cluster方案,實在是居家旅行、殺人滅口、必備良藥。redis

必備良藥
可是就是由於太好用了,好用到讓不少人都暈了腦子:數據庫

用Redis性能就大大提升了
用Redis能夠保證原子性
用Redis能夠實現事務
用Redis能夠當隊列
……
這就好像一個股民,在手機上操做買賣幾筆股票,賺了一些,而後感嘆道"股市就是爲我發財而存在的啊"!!編程

他的下場可想而知。後端

Redis的種種優點源自於他的設計——簡單直接的單線程內存操做。但這些優點是有前提的。緩存

Redis的性能高嗎?

Redis的性能很是高。有些評測說用Redis能夠達到幾十萬QPS(好比這裏http://skipperkongen.dk/2013/...)。你們可能在網文上記住了這個NB的數字,卻不多關心這個數值怎麼來的。這就像是你買手機評測光看跑分同樣不靠譜。服務器

Redis要達到高性能須要作到:網絡

Value儘量的小。通常的測評都會用比較小的value,好比一個整數或者不長的字符串。可是若是用Redis作緩存,那麼緩存的大小的可能偏離這個數字。好比一個頁面幾十KB;再好比,一個5年的市場價格序列數據可能高達幾MB。這麼大的數據量在帶寬的限制下直接的效果就是QPS驟降,跌到幾百都絕不意外。畢竟Redis不是神仙,不能改變物理定律。此外,由於Redis是單線程的,過大尺寸的數據訪問會block全部其餘的操做。數據結構

使用Pipeline或Lua Script。Redis通常被用作網絡服務。全部的請求都是跨網絡進行的。因此TCP Round Trip的長短對Redis的性能表現很重要。儘可能減小Round Trip能夠有效的提升吞吐。因此,一般的優化方法是使用Pipeline,使得客戶端能夠一次性把一組Redis命令發給Redis Server;或者預先在Redis Server中定義Lua Script,使用時直接調用。但不管是Pipeline仍是Lua Script,都會受到業務需求的制約——不是全部業務都適合用Pipeline/Lua Script的。併發

使用快一些的網絡。不少Redis的測評爲了彰顯其NB,都是在本地同時跑客戶端和服務器的。也就是說,它們要麼使用了loopback網絡(localhost),要麼使用了Unix Socket。這根本就不能反映通常分佈式的網絡場景下的狀況。同時,一些Redis的HA/Sharding方案會選擇用Twemproxy這樣的代理來實現。代理的加入會讓性能進一步的打折扣。app

不開啓RDB或者AOF。RDB和AOF是Redis的持久化方案。開啓他們會對Redis的性能表現有損耗。好比RDB在開始執行時,會fork一個新的用於寫入rdb文件的進程。這個fork的過程和內存空間的複製會讓Redis卡頓一下;AOF每次sync數據到磁盤,也會block一小會。若是爲了確保數據嚴格持久化,開啓了AOF的appendfsync=everysec設置,使得每一個寫入指令都要馬上sync到磁盤,就會打破Redis快的前提——內存數據操做。簡單來講,開啓任何一種持久化方案都會影響Redis的性能表現。

因此,若是想真實評價Redis的性能,必定要把你的場景設計好,而後用Redis自帶redis-benckmark(見https://redis.io/topics/bench...),設定value的尺寸、要測試的Redis命令、和Pipeline的開啓狀況,再把Redis Server按照生產環境的樣子配置好。而後跑一下壓測,看看Redis的實際表現究竟是怎樣的。

Redis能夠保證原子性嗎?

咱們先定義一下什麼是原子性:

通常編程語言這麼定義:原子性是指一組操做在執行過程當中,不受其餘併發操做的干擾。這樣進行的數據操做的值不會被相互覆蓋。
數據庫事務中ACID的A這麼定義:原子性是指一組操做,要不完成,要不沒作,不存在改了一半的狀態。沒完成的操做能夠回滾。
很顯然,Redis並不支持回滾,因此第二條確定沒戲。

那麼第一條呢?

Redis是單線程執行的。在完成一個操做以前,不會有其餘的操做被執行。這的確是真的。可是,在業務開發中,須要的不是一個簡單操做的原子性,而須要實現一個臨界區的原子性。

業務中對數據的操做每每都不是簡單的一個set,一個incr就能夠搞定的。一個複雜的業務邏輯,每每須要多個帶有邏輯判斷的寫入指令。業務中要保證的是這一組指令是原子的。好比下面的邏輯,但願一個value只能越設置越大。

(async function setBiggerV(v) {
    let currentV = parseInt(await redis.get('key'));
    if (currentV < v) {
        await redis.set('key', v);
    }
})();

這實際上是有bug的,考慮到以下執行序列(假設v一開始是5):

client A: 嘗試將v設置爲7 client B:嘗試將v設置爲8
讀取key,獲得5
讀取key,獲得5
設置key,爲8
設置key,爲7
最終,Redis中v的值被設置爲7,這就違反了這段邏輯的設計。若是這個機制被應用於協調一個分佈式系統,那麼整個系統就會所以掛掉。set這個命令是否是原子並不能讓這段業務代碼變成原子的。咱們須要的是讓get和set這個總體原子。

在Redis中,能夠用Redis事務或者Lua Script來實現原子性。Redis事務和Lua Script均可以保證一組指令執行不受其餘指令的打擾。好比上面的例子,用Lua Script實現,就能夠正確運行。

但若是業務邏輯涉及到其餘存儲,Redis事務和Lua Script就幫不上忙。好比,在Redis中放一個庫存的數字。用戶下單時,要在Redis中扣減庫存,而且在另一個數據庫中INSERT一條交易記錄。這段邏輯是無法作到原子的——除非你自行實現了某種分佈式事務的機制。而分佈式事務的實現複雜度每每會超過Redis帶來的好處。

用Redis能夠實現事務嗎?

咱們通常場景下說的事務的意思每每指的是數據庫系統中的」ACID事務「。(見https://www.jianshu.com/p/cb9...)。ACID事務是計算機科學中一個很是重要的抽象。它極大地簡化了編寫業務代碼的難度。沒有ACID事務,開發人員須要花大量精力處理因爲併發和系統意外崩潰帶來的數據一致性問題。

Redis也有一個「事務」的概念。原文見https://redis.io/topics/trans...。大體含義是:Redis將MULTI指令和EXEC指令之間的多個指令視做一個事務;一旦Redis看到了EXEC就開始執行這一組指令,並保證執行過程當中不被打斷——除非Redis自己或者所在機器crash掉。若是發生了,就可能出現只有部分指令被執行的狀況。

因此,Redis事務與ACID事務是徹底不一樣的!

Redis的事務只支持Isolation,不支持ACD。

有人說,AOF的appendfsync=everysec是能夠持久化的。但這種持久化只在單機狀況下有效。多機狀況下,Redis是沒有一個機制可以將數據修改同步sync到其餘節點的,即使是Redis Cluster的WAIT指令也不行。

在這種限制下,在Redis中實現業務邏輯差很少就只有兩種可能:

不在乎ACID事務——數據丟了沒事,改錯了也沒大關係
基於Redis的接口實現本身的ACID,或者ACID的某種子集
緩存屬於第一個場景。數據丟了沒事,從數據庫裏從新加載就好了。

但若是是第二種場景,你要本身搞一個ACID。不是不可能,但要反覆確認這樣作的必要性。你是否具備專業的存儲開發技能,你能投入多少精力在ACID上,你的公司能給你多少資源作開發測試,這些都須要仔細考慮。

用Redis能夠當隊列嗎?

Redis實現了一個List的數據結構。藉助它,能夠實現出隊,入隊的功能。實際上不少人早就熟練使用Redis作隊列。好比Sidekiq就是使用Redis做爲異步job隊列的存儲。然而,這樣靠譜嗎?

靠譜不靠譜,得看你怎麼定義「隊列」的要求:

隊列可不可能丟東西?好比,若是隊列短期掛掉。此時,producer是必須中止服務,仍是繼續服務但再也不插入隊列(這樣就會丟東西),或者說producer有某種機制能夠在本地先暫時堆積一下,直到隊列恢復工做?
隊列的consumer是否須要一個「commit」的語義,表示處理完了一個事件?仍是說,只要從隊列裏取出來就能夠了,萬一沒處理也沒所謂?
是否有事件重放的須要?好比上線了一個版本的consumer而後發現有bug,處理錯了3個小時的數據。修復後,但願能從新處理一遍以前出錯的數據,那麼這個隊列能不能作事件的」重放「?
若是consumer處理失敗怎麼處理?是直接丟棄,仍是從新插入到隊列中?
隊列是否是須要有最大的長度限制?若是到了最大長度,說明Consumer跟不上Producer的速度;此時,須要卡住Producer嗎?
……
Redis的List基本上對於全部這些問題都是徹底無論的。也就是說,它不能給你任何的保證。更嚴重的是,就算你能接受必定程度的數據丟失,可是Redis沒法告訴你他丟了多少東西,而且找不回來(MySQL還能翻翻binlog)!到最後,到底丟了多少,形成多少損失,是沒法監控,是沒法衡量的。

在業務上,「保證」一個事情可以發生至關重要。試想一下,你的界面容許用戶下一筆訂單,用戶已經看到了「成功下單」的界面,結果以後卻發現什麼訂單也沒有。用戶是否是有一句MMP不知道當講不當講。

也許,你會說,"個人場景不須要這麼嚴格的一致性,數據丟了沒所謂,也不須要事件重放,數據處理錯了就錯了"。這個Redis的確能夠辦到,並且能夠作得很好。但我建議你和你的產品經理聊一下,看看需求是否是真的這樣。也許他會有不一樣的意見 ; - )

通常來說,一個技術公司須要兩大類「隊列」。一種是業務事件隊列。這種隊列絕對不能丟東西,並且可能須要exactly once語義,須要高可用。爲了保證可用性,多節點的部署是必須的。而引入了多節點,就必須解決複製的問題和分佈式一致的問題,主從切換的問題,分片的問題等。這種隊列的典型表明是Rabbit MQ和Kafka。

另一種隊列是收集服務先後端業務事件的隊列(好比登錄、註冊、下單成功、下單失敗……)。經過隊列,這些事件會被收集到數據分析中心,支持錯誤分析、客服、數據分析等功能。這種隊列能夠容忍一些數據丟失,也能容忍數據延遲性比較大,但要求吞吐巨大。這種隊列的典型表明是Fluentd和Logstash。

也許你一開始在用Redis的List作隊列,可是若是這個業務是認真的,你的系統必定會逐漸演進到這兩者之一。

Redis 4.2計劃引入Disque做爲新的隊列實現。也許可以扭轉這個狀況。但4.2離發佈還要好久,而且成熟到能夠在生產使用,也至少要到4.4版本——大概在2019年甚至更晚。因此目前觀望一下就好,沒必要特別在乎。
更新一下:Redis 5.0beta引入了Stream Date Type。實現了相似於Kafka的append only數據結構和API。不過極可能要到5.2才能在生產中使用(2019年年末)。見https://redis.io/topics/strea...
Redis適合用來作什麼?

在我看來,Redis適合如下場景:

  • 共享Cache ,不怕丟數據,丟了能夠從DB中reload;
  • 共享Session ,不怕丟數據,丟了能夠從新登陸;
  • batch job的中間結果。不怕丟數據,丟了從新跑job就能夠了;
  • 一些簡單數據的存儲,低頻改動,可是會被頻繁讀取。好比首頁推薦的產品列表。但此時必須增長HA的防禦,sentinel、cluster或者自定義的機制均可以;
  • 一些更加複雜存儲的building block,好比分佈式鎖,此時須要多節點來實現一個簡單的quorum

其餘場景,每每有更好的、更成熟的方案。

特別注意,不要用Redis存儲任何須要「認真對待」的數據,請用支持ACID事務的數據庫。

Redis是很是優秀的工具,但非是銀彈。只有認真的瞭解業務對「保證」的要求,認真的瞭解所用工具的工做原理,才能作出正確的設計決策。

做者:大寬寬
連接:https://www.jianshu.com/p/9ce...
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

---------------------------------Z

相關文章
相關標籤/搜索