1.1 布隆過濾器
1、布隆過濾器是什么?(判斷某個key一定不存在)
1. 本質上布隆過濾器是一種數據結構,比較巧妙的概率型數據結構
2. 特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”。
3. 相比於傳統的 List、Set、Map 等數據結構,它更高效、占用空間更少,但是缺點是其返回的結果是概率性的,而不是確切的。
使用:
1. 布隆過濾器在NoSQL數據庫領域中應用的非常廣泛
2. 當用戶來查詢某一個row時,可以先通過內存中的布隆過濾器過濾掉大量不存在的row請求,然后去再磁盤進行查詢
3. 布隆過濾器說某個值不存在時,那肯定就是不存在,可以顯著降低數據庫IO請求數量
2、應用場景
1)場景1(給用戶推薦新聞)
1. 當用戶看過的新聞,肯定會被過濾掉,對於沒有看多的新聞,可能會過濾極少的一部分(誤判)。
2. 這樣可以完全保證推送給用戶的新聞都是無重復的。
2)場景2(爬蟲url去重)
1. 在爬蟲系統中,我們需要對url去重,已經爬取的頁面不再爬取
2. 當url高達幾千萬時,如果一個集合去裝下這些URL地址非常浪費空間
3. 使用布隆過濾器可以大幅降低去重存儲消耗,只不過也會使爬蟲系統錯過少量頁面
3、布隆過濾器原理
1. 每個布隆過濾器對應到Redis的數據結構是一個大型的數組和幾個不一樣的無偏hash函數
2. 如下圖:f、g、h就是這樣的hash函數(無偏差指讓hash映射到數組的位置比較隨機)
添加:值到布隆過濾器
1)向布隆過濾器添加key,會使用 f、g、h hash函數對key算出一個整數索引,然后對長度取余
2)每個hash函數都會算出一個不同的位置,把算出的位置都設置成1就完成了布隆過濾器添加過程
查詢:布隆過濾器值
1)當查詢某個key時,先用hash函數算出一個整數索引,然后對長度取余
2)當你有一個不為1時肯定不存在這個key,當全部都為1時可能有這個key
3)這樣內存中的布隆過濾器過濾掉大量不存在的row請求,然后去再磁盤進行查詢,減少IO操作
刪除:不支持
1)目前我們知道布隆過濾器可以支持 add 和 isExist 操作
2)如何解決這個問題,答案是計數刪除,但是計數刪除需要存儲一個數值,而不是原先的 bit 位,會增大占用的內存大小。
3)增加一個值就是將對應索引槽上存儲的值加一,刪除則是減一,判斷是否存在則是看值是否大於0。
1.2 redis事物
1、redis事物介紹
1. redis事物是可以一次執行多個命令,本質是一組命令的集合。
2. 一個事務中的所有命令都會序列化,按順序串行化的執行而不會被其他命令插入
作用:一個隊列中,一次性、順序性、排他性的執行一系列命令
2、redis事物基本使用
1. 下面指令演示了一個完整的事物過程,所有指令在exec前不執行,而是緩存在服務器的一個事物隊列中
2. 服務器一旦收到exec指令才開始執行事物隊列,執行完畢后一次性返回所有結果
3. 因為redis是單線程的,所以不必擔心自己在執行隊列是被打斷,可以保證這樣的“原子性”
注:redis事物在遇到指令失敗后,后面的指令會繼續執行
mysql的rollback與redis的discard的區別:
1) mysql回滾為sql全部成功才執行,一條sql失敗則全部失敗,執行rollback后所有語句造成的影響消失
2) redis的discard只是結束本次事務,正確命令造成的影響仍然還在.
# Multi 命令用於標記一個事務塊的開始事務塊內的多條命令會按照先后順序被放進一個隊列當中,最后由 EXEC 命令原子性( atomic )地執行
> multi(開始一個redis事物) incr books incr books > exec (執行事物) > discard (丟棄事物)

[root@redis ~]# redis-cli 127.0.0.1:6379> multi OK 127.0.0.1:6379> set test 123 QUEUED 127.0.0.1:6379> exec 1) OK 127.0.0.1:6379> get test "123" 127.0.0.1:6379> multi OK 127.0.0.1:6379> set test 456 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get test "123" 127.0.0.1:6379>

#定義ip host = 'localhost' #建立服務連接 r = redis.Redis(host=host) pipe = r.pipeline() #開啟事務 pipe.multi() #存儲子命令 pipe.set('key2', 4) #執行事務 pipe.execute() print(r.get('key2'))
3、watch指令
實質:WATCH 只會在數據被其他客戶端搶先修改了的情況下通知執行命令的這個客戶端(通過 WatchError 異常)但不會阻止其他客戶端對數據的修改
1. watch其實就是redis提供的一種樂觀鎖,可以解決並發修改問題
2. watch會在事物開始前盯住一個或多個關鍵變量,當服務器收到exec指令要順序執行緩存中的事物隊列時
3. redis會檢查關鍵變量自watch后是否被修改(包括當前事物所在的客戶端)
4. 如果關鍵變量被人改動過,exec指令就會返回null回復告知客戶端事物執行失敗,這個時候客戶端會選擇重試
注:redis禁用在multi和exec之間執行watch指令,必須在multi之前盯住關鍵變量,否則會出錯
1.3 redis事物與分布式鎖
1、redis事物
1. 嚴格意義來講,Redis的事務和我們理解的傳統數據庫(如mysql)的事務是不一樣的;
2. Redis的事務實質上是命令的集合,在一個事務中要么所有命令都被執行,要么所有命令都不執行。
需要注意的是:
1.Redis的事務沒有關系數據庫事務提供的回滾(rollback),所以開發者必須在事務執行失敗后進行后續的處理;
2.如果在一個事務中的命令出現錯誤,那么所有的命令都不會執行;
3.如果在一個事務中出現運行錯誤,那么正確的命令會被執行。
2、redis原子操作
1. 原子操作是指不會被線程調度機制打斷的操作
2. 這種操作一旦開始,就會一直運行到結束,中間不會切換任何進程
3、分布式鎖
1. 分布式鎖本質是占一個坑,當別的進程也要來占坑時發現已經被占,就會放棄或者稍后重試
2. 占坑一般使用 setnx(set if not exists)指令,只允許一個客戶端占坑
3. 先來先占,用完了在調用del指令釋放坑
> setnx lock:codehole true .... do something critical .... > del lock:codehole
4. 但是這樣有一個問題,如果邏輯執行到中間出現異常,可能導致del指令沒有被調用,這樣就會陷入死鎖,鎖永遠無法釋放
5. 為了解決死鎖問題,我們拿到鎖時可以加上一個expire過期時間,這樣即使出現異常,當到達過期時間也會自動釋放鎖
> setnx lock:codehole true > expire lock:codehole 5 .... do something critical .... > del lock:codehole
6. 這樣又有一個問題,setnx和expire是兩條指令而不是原子指令,如果兩條指令之間進程掛掉依然會出現死鎖
7. 為了治理上面亂象,在redis 2.8中加入了set指令的擴展參數,使setnx和expire指令可以一起執行
> set lock:codehole true ex 5 nx ''' do something ''' > del lock:codehole
1.4 redis五種數據結構
說明:
1. redis中所有數據結構都以唯一的key字符串作為名稱,然后通過這個唯一的key來獲取對應的value
2. 不同的數據類型數據結構差異就在於value的結構不一樣
1、 字符串(string)
1)value的數據結構(數組)
1. 字符串value數據結構類似於數組,采用與分配容易空間來減少內存頻繁分配
2. 當字符串長度小於1M時,擴容就是加倍現有空間
3. 如果字符串長度操作1M時,擴容時最多擴容1M空間,字符串最大長度為 512M
2)字符串的使用場景(緩存)
1. 字符串一個常見的用途是緩存用戶信息,我們將用戶信息使用JSON序列化成字符串
2. 取用戶信息時會經過一次反序列化的過程
2、list(列表)
1)value的數據結構(雙向鏈表)
1. 列表的數據結構是雙向鏈表,這意味着插入和刪除的時間復雜度是0(1),索引的時間復雜度位0(n)
2. 當列表彈出最后一個元素后,該數據結構會被自動刪除,內存被回手
2)列表的使用場景(隊列、棧)
3、hash(字典)
1)value的數據結構(HashMap)
1. redis中的字典也是HashMap(數組+列表)的二維結構
2. 不同的是redis的字典的值只能是字符串
2)hash的使用場景(緩存)
1. hash結構也可以用來緩存用戶信息,與字符串一次性全部序列化整個對象不同,hash可以對每個字段進行單獨存儲
2. 這樣可以部分獲取用戶信息,節約網絡流量
3. hash也有缺點,hash結構的存儲消耗要高於單個字符串
4、set(集合)
1)value的數據結構(字典)
1. redis中的集合相當於一個特殊的字典,字典的所有value都位null
2. 當集合中的最后一個元素被移除后,數據結構會被自動刪除,內存被回收
2)set使用場景
1. set結構可以用來存儲某個活動中中獎的用戶ID,因為有去重功能,可以保證同一用戶不會中間兩次
5、zset(有序集合)
1)value的數據結構(跳躍列表)
1. zset一方面是一個set,保證了內部的唯一性
2. 另一方面它可以給每一個value賦予一個score,代表這個value的權重
3. zset內部實現用的是一種叫做“跳躍列表”的數據結構
4. zset最后一個元素被移除后,數據結構就會被自動刪除,內存也會被回收
2)zset應用場景
1. 粉絲列表:value(粉絲ID),score(關注時間),這樣可以輕松按關注事件排序
2. 學生成績:value(學生ID),score(考試成績),這樣可以輕松對成績排序
1.5 redis雪崩&穿透&擊穿
1、緩存穿透
1)定義
1. 緩存穿透是指查詢一個一定不存在的數據,由於緩存不命中,接着查詢數據庫也無法查詢出結果,
2. 雖然也不會寫入到緩存中,但是這將會導致每個查詢都會去請求數據庫,造成緩存穿透;
2)解決方法 :布隆過濾
1. 對所有可能查詢的參數以hash形式存儲,在控制層先進行校驗,不符合則丟棄,從而避免了對底層存儲系統的查詢壓力;
2、緩存雪崩
1)定義
1. 緩存雪崩是指,由於緩存層承載着大量請求,有效的保護了存儲層,但是如果緩存層由於某些原因整體不能提供服務
2. 於是所有的請求都會達到存儲層,存儲層的調用量會暴增,造成存儲層也會掛掉的情況。
2)解決方法
1. 保證緩存層服務高可用性:比如 Redis Sentinel 和 Redis Cluster 都實現了高可用
2. 依賴隔離組件為后端限流並降級:比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。
3、緩存擊穿
1)定義:
1. 緩存擊穿,就是說某個 key 非常熱點,訪問非常頻繁,處於集中式高並發訪問的情況
2. 當這個 key 在失效的瞬間,大量的請求就擊穿了緩存,直接請求數據庫,就像是在一道屏障上鑿開了一個洞。
2)解決方法
1. 解決方式也很簡單,可以將熱點數據設置為永遠不過期;
2. 或者基於 redis or zookeeper 實現互斥鎖,等待第一個請求構建完緩存之后,再釋放鎖,進而其它請求才能通過該 key 訪問數據。
11111