快取系列-Redis入門教程
Redis是什麼?
Redis (REmote DIctionary Server)是一個開源(BSD許可),記憶體儲存的資料結構伺服器,可用作資料庫,快取記憶體和訊息佇列,是一個高效能的key-value資料庫。
Redis與其他key-value快取產品有以下三個特點:
- Redis支援資料的持久化,可以將記憶體中的資料保持在磁碟中,重啟的時候可以再次載入進行使用。
- Redis不僅支援簡單的key-value型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。
- Redis支援資料的備份,即master-slave模式的資料備份。
為什麼要用Redis
- 效能極高 – Redis讀的速度是110000次/s,寫的速度是81000次/s 。
- 豐富的資料型別 – Redis支援Strings, Lists, Hashes, Sets 及 Ordered Sets 資料型別操作。
- 原子 – Redis的所有操作都是原子性的,即要麼成功執行要麼失敗完全不執行。單個操作是原子性的。多個操作也支援事務,即原子性,通過MULTI和EXEC指令包起來。
- 豐富的特性 – Redis還支援publish/subscribe, key過期等特性。
Redis的資料型別及使用場景
Redis支援五種資料型別:string(字串),hash(雜湊),list(列表),set(集合)及zset(sorted set:有序集合)。每種資料型別有其適合的使用場景,下面具體介紹.
String(字串)
string 是 redis 最基本的型別,你可以理解成與 Memcached 一模一樣的型別,一個 key 對應一個 value。string 型別是二進位制安全的。意思是 redis 的 string 可以包含任何資料。比如jpg圖片或者序列化的物件。string 型別是 Redis 最基本的資料型別,string 型別的值最大能儲存 512MB。
使用方法
SET key value 設定指定 key 的值 GET key 獲取指定 key 的值。 SETEX key seconds value 將值 value 關聯到 key ,並將 key 的過期時間設為 seconds (以秒為單位)。
使用場景
1.會話快取
使用者登入系統後,使用Redis儲存使用者的Session資訊,每次使用者查詢登入資訊都直接從Redis中獲取。
2.計數器
- 比如登入系統會限制密碼錯誤次數,當一個使用者在一定時間內連續輸入密碼錯誤,就不能登入,需要一段時間後才能登入,我們可以使用redis,把username作為key,錯誤的次數作為value,同時設定過期時間即可.
- 手機驗證碼限收到簡訊的次數
- 統計其他計數
3.定時器
redis的key可以設定過期時間,我們基於此特性設定一個定時器.
4.物件
我們把物件序列化後,可以使用redis儲存該物件,然後在獲取物件資訊的時候,反序列化value
5.分散式鎖
redis提供了setnx()方法,即SET IF NOT EXIST,只有在key不存在的時候才能set成功,這就意味著同一時間多個請求只有一個請求能儲存成功,這塊的可以自行搜尋redis的分散式鎖
Hash(雜湊)
Redis hash 是一個鍵值(key=>value)對集合,即程式語言中的Map型別.
Redis hash 是一個 string 型別的 field 和 value 的對映表.
使用方法
HSET key field value
將雜湊表 key 中的欄位 field 的值設為 value 。
HGET key field
獲取儲存在雜湊表中指定欄位的值。
HKEYS key
獲取所有雜湊表中的欄位
HMSET key field1 value1 [field2 value2 ]
同時將多個 field-value (域-值)對設定到雜湊表 key 中。
使用場景
hash 特別適合用於儲存物件,並且可以像資料庫中update一個屬性一樣只修改某一項屬性值(Memcached中需要取出整個字串反序列化成物件修改完再序列化存回去)
List(列表)
Redis 列表是簡單的字串列表,按照插入順序排序。你可以新增一個元素到列表的頭部(左邊)或者尾部(右邊)。
使用方法
LPUSHX key value
將一個值插入到已存在的列表頭部
LPUSH key value1 [value2]
將一個或多個值插入到列表頭部
LPOP key
移出並獲取列表的第一個元素
BLPOP key1 [key2 ] timeout
移出並獲取列表的第一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。
使用場景
1.訊息佇列
Redis的lpush+brpop命令組合即可實現阻塞佇列,生產者客戶端使用lrpush從列表左側插入元素,多個消費者客戶端使用brpop命令阻塞式的"搶"列表尾部的元素,多個客戶端保證了消費的負載均衡和高可用性。
2.類目/文章/活動等列表
最常見的就是各個系統的首頁資料,包括電商系統的商品類目,拼團活動列表,部落格園的首頁文章列表等
3.其他
根據push和pop的方式不同,有以下組合方式
lpush + lpop = Stack(棧)
lpush + rpop = Queue(佇列)
lpush + ltrim = Capped Collection(有限集合)
lpush + brpop = Message Queue(訊息佇列)
Set(集合)
Redis 的 Set 是 String 型別的無序集合。集合成員是唯一的,這就意味著集合中不能出現重複的資料。
Redis 中集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是 O(1)。
使用方法
SADD key member1 [member2]
向集合新增一個或多個成員
SDIFF key1 [key2]
返回給定所有集合的差集
SINTER key1 [key2]
返回給定所有集合的交集
SMEMBERS key
返回集合中的所有成員
使用場景
1.標籤(tag)
比如在點餐評價系統中,使用者給某商家評價,商家會有多個評價標籤,但是不會重複的,如果100萬人給某商家評價打了標籤,如果使用MySQL資料庫獲取大資料量去重後的評價標籤,會影響資料庫的效能和系統的併發量.
2.相同點/異同點
利用交集、並集、差集等操作,可以計算兩個人的共同喜好,全部的喜好,自己獨有的喜好等功能。
zset(sorted set:有序集合)
Redis 有序集合和集合一樣也是string型別元素的集合,且不允許重複的成員。
不同的是每個元素都會關聯一個double型別的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。
有序集合的成員是唯一的,但分數(score)卻可以重複。
使用方法
ZADD key score1 member1 [score2 member2]
向有序集合新增一個或多個成員,或者更新已存在成員的分數
ZCARD key
獲取有序集合的成員數
ZREM key member [member ...]
移除有序集合中的一個或多個成員
使用場景
1.排行榜
例如部落格園需要對使用者發表的文章做排行榜,榜單的維度可能是多個方面的:按照時間、按照點贊數、按照熱度,瀏覽數等
Redis持久化
Redis 提供了不同級別的持久化方式:
- RDB持久化,該方式能夠在指定的時間間隔能對資料進行快照儲存.
- AOF持久化,該方式記錄每次對伺服器寫的操作,當伺服器重啟的時候會重新執行這些命令來恢復原始的資料,AOF命令以redis協議追加儲存每次寫的操作到檔案末尾.Redis還能對AOF檔案進行後臺重寫,使得AOF檔案的體積不至於過大.
- 不持久化,如果你只希望資料在伺服器執行的時候存在,你也可以不使用任何持久化方式.
- RDB+AOF模式, 在這種情況下, 當redis重啟的時候會優先載入AOF檔案來恢復原始的資料,因為在通常情況下AOF檔案儲存的資料集要比RDB檔案儲存的資料集要完整.
RDB的優點
- RDB是一個非常緊湊的檔案,它儲存了某個時間點得資料集,非常適用於資料集的備份,比如你可以在每個小時報儲存一下過去24小時內的資料,同時每天儲存過去30天的資料,這樣即使出了問題你也可以根據需求恢復到不同版本的資料集.
- 與AOF相比,在恢復大的資料集的時候,RDB方式會更快一些.
RDB的缺點
- 如果你希望在redis意外停止工作(例如電源中斷)的情況下丟失的資料最少的話,那麼RDB不適合你.雖然你可以配置不同的save時間點(例如每隔5分鐘並且對資料集有100個寫的操作),是Redis要完整的儲存整個資料集是一個比較繁重的工作,你通常會每隔5分鐘或者更久做一次完整的儲存,萬一在Redis意外宕機,你可能會丟失幾分鐘的資料.
- RDB 需要經常fork子程序來儲存資料集到硬碟上,當資料集比較大的時候,fork的過程是非常耗時的,可能會導致Redis在一些毫秒級內不能響應客戶端的請求.
AOF優點
- 使用AOF 會讓你的Redis更加耐久: 你可以使用不同的fsync策略:無fsync,每秒fsync,每次寫的時候fsync.使用預設的每秒fsync策略,Redis的效能依然很好(fsync是由後臺執行緒進行處理的,主執行緒會盡力處理客戶端請求),一旦出現故障,你最多丟失1秒的資料.
AOF缺點
- 對於相同的資料集來說,AOF 檔案的體積通常要大於 RDB 檔案的體積。
- 根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。
如何選擇持久化方式
如果你非常關心你的資料, 但仍然可以承受數分鐘以內的資料丟失, 那麼你可以只使用 RDB 持久化。
有很多使用者都只使用 AOF 持久化,但我們並不推薦這種方式: 因為定時生成 RDB 快照(snapshot)非常便於進行資料庫備份, 並且 RDB 恢復資料集的速度也要比 AOF 恢復的速度要快
一般來說,如果想達到足以媲美 PostgreSQL 的資料安全性, 你應該同時使用兩種持久化功能.
使用Redis出現的問題
在一個高頻訪問的應用系統中,每次使用者的請求需要去DB中獲取資料,會對資料庫造成很大的壓力、容易導致資料庫的奔潰。所以才會出現快取來分擔一部分的資料庫的壓力。 但是使用快取也帶來了一系列問題:
1.快取一致性問題
當資料時效性要求很高時,需要保證快取中的資料與資料庫中的保持一致,而且需要保證快取節點和副本中的資料也保持一致,不能出現差異現象。這就比較依賴快取的過期和更新策略。一般會在資料發生更改的時,主動移除對應的快取。 所以需要通過事物機制來保證快取的一致性。
2.快取雪崩問題
在高併發場景下,有多個請求去共同請求一份相同的業務資料。有可能多個請求先去從快取中獲取資料、獲取不到的併發的去從資料庫獲取資料,對後端資料庫造成極大的衝擊,甚至導致 “雪崩”現象。
- 方案一: 可以做一個隨機的等待、錯峰去訪問快取的資訊。這樣就能保證同一時刻高併發的訪問、經過時間離散之後只有小部分的請求訪問資料庫、大部分的請求去命中快取。
- 方案二: 可以按照比例限制有部分資料直接訪問資料庫然後更新快取、大部分的資料直接請求快取。
按照實際的場景去做判斷例如 1%的場景直接訪問資料庫,99%的可以通過快取獲取到資料。
3.快取擊穿的問題
在系統設計的的時候預期是通過快取來減輕資料庫的壓力、防止資料奔潰的情況。在某個實際發生的場景中、大量的請求並沒有命中快取而導致了大量請求達到資料庫、從而導致資料庫有巨大沖擊和壓力。
3.1快取中沒有資料
在某個大促活動中有大量的熱點資料,互動一開始需要訪問這些資料。由於活動開始的時候洪峰流量到來,所有的請求快取、快取直接擊穿,訪問資料庫導致資料庫直接cpu 100%,業務系統直接奔潰。
對於這種場景可以提前對資料進行預熱,開活動開始前先將資料推送到快取中。
3.2快取集中失效
由於我們在快取使用的過程中會設定快取的失效時間、如果設定的不合理可能會導致資料集中失效的情況。由於快取集中失效會導致系統快取穿透、在同一時刻高併發的訪問資料,造成資料雪崩。
解決這種場景的可以將失效的時間由固定值+隨機值來構成。EXPIRETIME=FIXTIME+RUND_TIME 例如你想保證整個EXPIRETIME是5S 左右,可以 通過EXPIRETIME=4000+Random(1000)
Redis的過期策略
通常Redis keys建立時沒有設定相關過期時間,他們會一直存在,除非使用顯示的命令移除,例如,使用DEL命令。
EXPIRE一類命令能關聯到一個有額外記憶體開銷的key。當key執行過期操作時,Redis會確保按照規定時間刪除他們。
key的過期時間和永久有效性可以通過EXPIRE和PERSIST命令(或者其他相關命令)來進行更新或者刪除過期時間。
Redis keys過期有兩種方式:定期刪除和惰性刪除。
1.定期刪除
定時刪除,用一個定時器來負責監視key,當key過期則自動刪除key。
雖然記憶體及時釋放,但是十分消耗CPU資源。在大併發請求下,會影響redis的效能.
2.惰性刪除
客戶端嘗試訪問key時,key會被發現並主動的過期. 但是這樣是不夠的,因為有些過期的keys,永遠不會訪問他們,那麼他們就永遠不會被刪除,而佔用記憶體,導致redis記憶體被過期的key佔用.
3.定期刪除+惰性刪除
redis預設每100ms檢查一次,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每100ms將所有的key檢查一次,而是隨機抽取20個keys進行過期檢查,同時刪除已經過期的keys,如果有多於25%的keys過期,重複抽取。直到過期的keys的百分比低於25%,這意味著,在任何給定的時刻,最多會清除1/4的過期keys
那麼問題來了,採用定期刪除+惰性刪除就能保證過期的key會全部刪除掉麼?
記憶體淘汰機制
如果定期刪除沒刪除key。然後也沒去請求key,也就是說惰性刪除也沒生效。這樣,redis的記憶體會越來越高。那麼就應該採用記憶體淘汰機制。
在redis.conf中有配置
maxmemory-policy volatile-lru
記憶體淘汰策略如下:
- noeviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯,不建議使用
- allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的key。推薦使用
- allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個key
- volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的key
- volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個key
- volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的key優先移除