分散式-分散式快取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,服務端叢集沒有天然支援水平擴充套件,需要在客戶端進行分片,這其實對呼叫方並不友好。如果能服務端叢集能夠支援水平擴充套件,會更完美一些。