1. 程式人生 > >分散式-分散式快取Redis

分散式-分散式快取Redis

一、Redis常用五大資料型別

1.1 String(字串)

  • string型別是二進位制安全的。意思是redis的string可以包含任何資料。比如jpg圖片或者序列化的物件 。
  • string型別是Redis最基本的資料型別,一個redis中字串value最多可以是512M

1.2 Hash(雜湊)

  • Redis hash 是一個鍵值對集合。
  • Redis hash是一個string型別的field和value的對映表,hash特別適合用於儲存物件。
  • 類似Java裡面的Map

1.33 List(列表)

  • Redis 列表是簡單的字串列表,按照插入順序排序。
    你可以新增一個元素導列表的頭部(左邊)或者尾部(右邊)。它的底層實際是個連結串列

1.4 Set(集合)

  • Redis的Set是string型別的無序集合。它是通過HashTable實現實現的,

1.5 zset(sorted set:有序集合)

  • Redis zset 和 set 一樣也是string型別元素的集合,且不允許重複的成員。
  • 不同的是每個元素都會關聯一個double型別的分數。
    redis正是通過分數來為集合中的成員進行從小到大的排序。zset的成員是唯一的,但分數(score)卻可以重複。

二、redis應用場景

2.1 快取——熱資料

熱點資料(經常會被查詢,但是不經常被修改或者刪除的資料),首選是使用redis快取,畢竟強大到冒泡的QPS和極強的穩定性不是所有類似工具都有的,而且相比於memcached還提供了豐富的資料型別可以使用,另外,記憶體中的資料也提供了AOF和RDB等持久化機制可以選擇,要冷、熱的還是忽冷忽熱的都可選。

結合具體應用需要注意一下:很多人用spring的AOP來構建redis快取的自動生產和清除,過程可能如下:

  • Select 資料庫前查詢redis,有的話使用redis資料,放棄select 資料庫,沒有的話,select 資料庫,然後將資料插入redis
  • update或者delete資料庫錢,查詢redis是否存在該資料,存在的話先刪除redis中資料,然後再update或者delete資料庫中的資料

上面這種操作,如果併發量很小的情況下基本沒問題,但是高併發的情況請注意下面場景:

為了update先刪掉了redis中的該資料,這時候另一個執行緒執行查詢,發現redis中沒有,瞬間執行了查詢SQL,並且插入到redis中一條資料,回到剛才那個update語句,這個悲催的執行緒壓根不知道剛才那個該死的select執行緒犯了一個彌天大錯!於是這個redis中的錯誤資料就永遠的存在了下去,直到下一個update或者delete。

2.2 計數器

諸如統計點選數等應用。由於單執行緒,可以避免併發問題,保證不會出錯,而且100%毫秒級效能!爽。

命令:INCRBY

當然爽完了,別忘記持久化,畢竟是redis只是存了記憶體!

2.3 佇列

相當於訊息系統,ActiveMQ,RocketMQ等工具類似,但是個人覺得簡單用一下還行,如果對於資料一致性要求高的話還是用RocketMQ等專業系統。

由於redis把資料新增到佇列是返回新增元素在佇列的第幾位,所以可以做判斷使用者是第幾個訪問這種業務

佇列不僅可以把併發請求變成序列,並且還可以做佇列或者棧使用

2.4 位操作(大資料處理)

用於資料量上億的場景下,例如幾億使用者系統的簽到,去重登入次數統計,某使用者是否線上狀態等等。

想想一下騰訊10億使用者,要幾個毫秒內查詢到某個使用者是否線上,你能怎麼做?千萬別說給每個使用者建立一個key,然後挨個記(你可以算一下需要的記憶體會很恐怖,而且這種類似的需求很多,騰訊光這個得多花多少錢。。)好吧。這裡要用到位操作——使用setbit、getbit、bitcount命令。

原理是:

redis內構建一個足夠長的陣列,每個陣列元素只能是0和1兩個值,然後這個陣列的下標index用來表示我們上面例子裡面的使用者id(必須是數字哈),那麼很顯然,這個幾億長的大陣列就能通過下標和元素值(0和1)來構建一個記憶系統,上面我說的幾個場景也就能夠實現。用到的命令是:setbit、getbit、bitcount

2.5 分散式鎖與單執行緒機制

驗證前端的重複請求(可以自由擴充套件類似情況),可以通過redis進行過濾:每次請求將request Ip、引數、介面等hash作為key儲存redis(冪等性請求),設定多長時間有效期,然後下次請求過來的時候先在redis中檢索有沒有這個key,進而驗證是不是一定時間內過來的重複提交

秒殺系統,基於redis是單執行緒特徵,防止出現數據庫“爆破”

全域性增量ID生成,類似“秒殺”

redis分佈鎖缺陷

現有的實現在叢集上表現不好

原因
官方推薦的實現有三個特性
一,互斥,任何時候只能有一個客戶端獲得鎖
二,可用,無死鎖,只要等待下去最終都能獲得鎖
三,容錯

2.6 最新列表

例如新聞列表頁面最新的新聞列表,如果總數量很大的情況下,儘量不要使用select a from A limit 10這種low貨,嘗試redis的 LPUSH命令構建List,一個個順序都塞進去就可以啦。不過萬一記憶體清掉了咋辦?也簡單,查詢不到儲存key的話,用mysql查詢並且初始化一個List到redis中就好了。

2.7 排行榜

誰得分高誰排名往上。命令:ZADD(有續集,sorted set)

最近在研究股票,發現量化交易是個非常好的辦法,通過臆想出來規律,用程式對歷史資料進行驗證,來判斷這個臆想出來的規律是否有效,這玩意真牛!有沒有哪位玩這個的給我留個言,交流一下唄。

三、redis使用規範

3.1 鍵值規範設計

3.1.1 key名設計

(1)【建議】: 可讀性和可管理性
以業務名(或資料庫名)為字首(防止key衝突),用冒號分隔,比如業務名:表名:idugc:video:1

**(2)【建議】:簡潔性
保證語義的前提下,控制key的長度,當key較多時,記憶體佔用也不容忽視
例如:user:{uid}:friends:messages:{mid}簡化為u:{uid}:fr:m:{mid}。

(3)【強制】:不要包含特殊字元
反例:包含空格、換行、單雙引號以及其他轉義字元

3.1.2 value設計

【強制】:拒絕bigkey(防止網絡卡流量、慢查詢)
string型別控制在10KB以內,hash、list、set、zset元素個數不要超過5000。

反例:一個包含200萬個元素的list。

非字串的bigkey,不要使用del刪除,使用hscan、sscan、zscan方式漸進式刪除,同時要注意防止bigkey過期時間自動刪除問題(例如一個200萬的zset設定1小時過期,會觸發del操作,造成阻塞,而且該操作不會不出現在慢查詢中(latency可查)),查詢方法和刪除方法

【推薦】:選擇適合的資料型別。

反例:

set user:1:name tom
set user:1:age 19
set user:1:favor football

正例:

hmset user:1 name tom age 19 favor football

3.2 設定超時時間[最重要]

控制key的生命週期,redis不是垃圾桶。
建議使用expire設定過期時間(條件允許可以打散過期時間,防止集中過期),不過期的資料重點關注idletime。

  • 目前有許多key沒有設定超時時間,導致一直佔用記憶體。
  • 需要增加操作步驟,設定超時時間。時間儘量短。
  • 某些業務要求key長期有效。

可以在每次寫入時,都設定超時時間,讓超時時間順延。(比如token/session機制 授權通過每呼叫一次口,授權時間增加30m)

  • 短的超時時間,如 5分鐘,10分鐘,30分鐘,1小時,3小時,1天等
  • 長的超時時間,如 7天,15天,1個月,3個月,6個月等
  • 示例程式碼如下:
    • 設定有效期初始設定有效期
    • 如果存在key,設定有效期

3.3 高頻和低頻分離

  • 高頻資料存入Redis快取,低頻資料不要存入Redis快取。
    高頻資料是經常訪問的資料,在這裡做好壓力緩衝就行了。對於大量資料和列表資料尤其適用。如,某商店的所有評價資料,總共有5000條之多,最近的30條(高頻)可能是最常訪問的,可以存入Redis快取,其他的資料(低頻)都不需要存快取。

3.4 合理使用資料型別

  • 結合具體業務,設定合理的資料結構,找出更好的選擇。
  • 集合結構還可以減少key的個數。

例如:實體型別(要合理控制和使用資料結構記憶體編碼優化配置,例如ziplist,但也要注意節省記憶體和效能之間的平衡)

3.5 儘量使用字串格式

  • 視覺化,便於檢視和管理。
  • 特別是在大批量資料的時候,效果明顯。

3.5 合理設定key的格式

  • 多系統在共用快取,需要key唯一。
  • 合適的key,便於檢視,統計,排錯。
  • key的格式,如:系統名+業務名+業務資料+其他

3.7 減少key的個數

  • 為了過期管理,合理減少key。
    比如,把key-value資料聚合,放到map、list裡面。一個集合結構裡面就可以包含很多個小資料。

3.8 精細化運營

之前的使用方法一直是粗放式。業務量小時,沒問題;業務量大了,就各種問題。為了未來系統穩定,為了每個人的職業成長,需要學會精細化運營。深入分析業務流程,合理安排資料結構,合理使用公共資源,優化讀寫效率,提高系統抗風險能力。

四、redis和memcached的區別

觀點一

  • 1、Redis和Memcache都是將資料存放在記憶體中,都是記憶體資料庫。不過memcache還可用於快取其他東西,例如圖片、視訊等等;
  • 2、Redis不僅僅支援簡單的k/v型別的資料,同時還提供list,set,hash等資料結構的儲存;
  • 3、虛擬記憶體–Redis當實體記憶體用完時,可以將一些很久沒用到的value 交換到磁碟;
  • 4、過期策略–memcache在set時就指定,例如set key1 0 0 8,即永不過期。Redis可以通過例如expire 設定,例如expire name 10;
  • 5、分散式–設定memcache叢集,利用magent做一主多從;redis可以做一主多從。都可以一主一從;
  • 6、儲存資料安全–memcache掛掉後,資料沒了;redis可以定期儲存到磁碟(持久化);
  • 7、災難恢復–memcache掛掉後,資料不可恢復; redis資料丟失後可以通過aof恢復;
  • 8、Redis支援資料的備份,即master-slave模式的資料備份;

4.1 什麼時候傾向於選擇redis?

業務需求決定技術選型,當業務有這樣一些特點的時候,選擇redis會更加適合。

4.1.1 複雜資料結構

  • value是雜湊,列表,集合,有序集合這類複雜的資料結構時,會選擇redis,因為mc無法滿足這些需求。
    最典型的場景,使用者訂單列表,使用者訊息,帖子評論列表等。

4.1.2 持久化

  • mc無法滿足持久化的需求,只得選擇redis。

但是,這裡要提醒的是,真的使用對了redis的持久化功能麼?

千萬不要把redis當作資料庫用:

  • (1)redis的定期快照不能保證資料不丟失

  • (2)redis的AOF會降低效率,並且不能支援太大的資料量

不要期望redis做固化儲存會比mysql做得好,不同的工具做各自擅長的事情,把redis當作資料庫用,這樣的設計八成是錯誤的。

4.1.3 快取場景,開啟固化功能,有什麼利弊?

如果只是快取場景,資料存放在資料庫,快取在redis,此時如果開啟固化功能:

優點是,redis掛了再重啟,記憶體裡能夠快速恢復熱資料,不會瞬時將壓力壓到資料庫上,沒有一個cache預熱的過程。

缺點是,在redis掛了的過程中,如果資料庫中有資料的修改,可能導致redis重啟後,資料庫與redis的資料不一致。

因此,只讀場景,或者允許一些不一致的業務場景,可以嘗試開啟redis的固化功能。

4.1.4 天然高可用

redis天然支援叢集功能,可以實現主動複製,讀寫分離。

redis官方也提供了sentinel叢集管理工具,能夠實現主從服務監控,故障自動轉移,這一切,對於客戶端都是透明的,無需程式改動,也無需人工介入。

而memcache,要想要實現高可用,需要進行二次開發,例如客戶端的雙讀雙寫,或者服務端的叢集同步。

但是,這裡要提醒的是,大部分業務場景,快取真的需要高可用麼?

  • (1)快取場景,很多時候,是允許cache miss

  • (2)快取掛了,很多時候可以通過DB讀取資料

所以,需要認真剖析業務場景,高可用,是否真的是對快取的主要需求?

畫外音:即時通訊業務中,使用者的線上狀態,就有高可用需求。

4.1.5 儲存的內容比較大

memcache的value儲存,最大為1M,如果儲存的value很大,只能使用redis。

4.2 什麼時候傾向於memcache?

4.2.1 純KV,資料量非常大,併發量非常大的業務,使用memcache或許更適合。

這要從mc與redis的底層實現機制差異說起。

記憶體分配

memcache使用預分配記憶體池的方式管理記憶體,能夠省去記憶體分配時間。

redis則是臨時申請空間,可能導致碎片。

從這一點上,mc會更快一些。

虛擬記憶體使用

memcache把所有的資料儲存在實體記憶體裡。

redis有自己的VM機制,理論上能夠儲存比實體記憶體更多的資料,當資料超量時,會引發swap,把冷資料刷到磁碟上。

從這一點上,資料量大時,mc會更快一些。

網路模型

memcache使用非阻塞IO複用模型,redis也是使用非阻塞IO複用模型。

但由於redis還提供一些非KV儲存之外的排序,聚合功能,在執行這些功能時,複雜的CPU計算,會阻塞整個IO排程。

從這一點上,由於redis提供的功能較多,mc會更快一些。

執行緒模型

memcache使用多執行緒,主執行緒監聽,worker子執行緒接受請求,執行讀寫,這個過程中,可能存在鎖衝突。

redis使用單執行緒,雖無鎖衝突,但難以利用多核的特性提升整體吞吐量。

從這一點上,mc會快一些。

4.3 總結

4.3.1 程式碼可讀性,程式碼質量對比

看過mc和redis的程式碼,從可讀性上說,redis是我見過程式碼最清爽的軟體,甚至沒有之一,或許簡單是redis設計的初衷,編譯redis甚至不需要configure,不需要依賴第三方庫,一個make就搞定了。

而memcache,可能是考慮了太多的擴充套件性,多系統的相容性,程式碼不清爽,看起來費勁。

例如網路IO的部分,redis原始碼1-2個檔案就搞定了,mc使用了libevent,一個fd傳過來傳過去,又pipe又執行緒傳遞的,特別容易把人繞暈。

畫外音:理論上,mc只支援kv,而redis支援了這麼多功能,mc效能應該高非常多非常多,但實際並非如此,真的可能和程式碼質量有關。

4.3.2 水平擴充套件的支援

不管是mc和redis,服務端叢集沒有天然支援水平擴充套件,需要在客戶端進行分片,這其實對呼叫方並不友好。如果能服務端叢集能夠支援水平擴充套件,會更完美一些。