一、Redis
1、簡介
【官方簡介地址:】 https://redis.io/topics/introduction
看不懂不要緊,先混個眼熟,慢慢來...,
【初步認識 Redis:】 Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. 【翻譯:】 Redis 是一個開源的、基于記憶體的資料存盤結構,可以作為資料庫、快取、訊息中間件, 【重點:】 基于記憶體、支持多種資料結構、常用于快取,
2、為什么使用 Redis 作為快取?
(1)為什么要使用快取?
對于一個系統來說,若直接操作資料庫,每次讀寫都經過磁盤操作,當并發量過高時,磁盤讀寫速度極大地影響系統的性能,使用快取,即在訪問磁盤前設定一個緩沖區,若緩沖區沒有資料,再去資料庫進行操作,這樣可以極大地減少磁盤操作,從而提高系統性能,
(2)Redis 是基于記憶體的、一個高性能的 key - value 資料庫(非關系型資料庫),
記憶體的處理速度比操作磁盤快,可以提高性能,
快取分擔了部分請求,減少了資料庫訪問壓力,提高了并發量,
說起 key - value 庫,容易想到 Java 中的 Map,map 實作的是本地快取(即每臺機器各自擁有自己的快取),容量有限,隨著 JVM 存在、消失,而 Redis 實作的是分布式快取(即多臺機器可以共享一份快取資料),其資料可以持久化到硬碟中,可以自定義快取過期機制,
3、Redis 的資料結構?使用場景?
(1)常用命令:
【參考地址:】 http://doc.redisfans.com/ https://www.cnblogs.com/l-y-h/p/12656614.html
(2)常用資料結構:
Redis 是由 C 語言撰寫的,其存盤是以 key - value 的形式,key 為字串,value 為 Redis 的資料結構,常用資料結構為:string、list、set、hash、sortedset,
底層實作原理,以后有空再去研究...
不同資料結構,若采用不同的編碼格式,底層會有不同的實作,
(3)常用資料結構使用場景(舉例,可能不太恰當,大致理解一下):
String 使用場景:
比如:一些博客、文章的閱讀量、點贊數等,
可以根據 文章 ID 生成一個鍵,當某用戶閱讀、點贊后,在相應的 value 上加 1,
比如 :
key 為 文章閱讀量:文章id,
value 為對應的 文章閱讀量,
可以通過 incr、decr 等進行加減閱讀量,
【根據文章ID 生成一個 key:(每個文章都有不同的 id,從而區分不同的 key)】 set article:readcount:1001 0 文章 id 為 1001 的文章當前閱讀量為 0 set article:readcount:1002 0 文章 id 為 1002 的文章當前閱讀量為 0 【閱讀時,數量增 1:】 incr article:readcount:1001 文章 id 為 1001 的文章閱讀量加 1 【獲取閱讀量:】 get article:readcount:1001 獲取文章 id 為 1001 的文章閱讀量

Hash 使用場景:
比如:電商網站的購物車,
可以根據 用戶ID 生成一個 key,商品 ID 為 field,商品數量為 field 對應的 value,
可以使用 hgetall 獲取所有的 field - value,即實作全選,
可以使用 hincrby 對指定的 field 修改數量,
可以使用 hlen 獲取當前購物車商品的種類,等等操作,
比如:
key 為 用戶 ID user:用戶 ID
field 為 商品 ID wares:商品 ID
value 為 商品數量 商品數量
注:
其余資訊可以通過 ajax 根據 用戶 ID 、商品 ID 進行查詢并回傳顯示,
【根據用戶 ID、商品 ID、商品數量 生成一個 key,】 hset user:10001 wares:3001 1 給 10001 用戶 添加 一個 3001 商品, hset user:10001 wares:3002 2 給 10001 用戶 添加 兩個 3002 商品, 【全選操作:】 hgetall user:10001 獲取 10001 用戶所有的 商品(field)以及數量(value) 【增加商品數量:】 hincrby user:10001 wares:3002 3 給 10001 用戶再增加 3 個 3002 商品

List 使用場景:
比如:微信訂閱號推送的訊息,
不同的公眾號推送訊息有先有后,最后是按照時間順序進行排序顯示(最近的時間顯示在最上面),
可以使用 List 存盤接收的訊息 ID,每接受一個 公眾號訊息 的 ID,就 LPUSH 進 List 中,最后使用 LRANGE 去獲取最新的推送訊息,
【接收公眾號推送訊息的 ID:】 LPUSH msg:我的訂閱號-id 安徽共青團:10001 LPUSH msg:我的訂閱號-id 唐唐頻道:20001 LPUSH msg:我的訂閱號-id 全是黑科技:34811 LPUSH msg:我的訂閱號-id 程式人生:2233 LPUSH msg:我的訂閱號-id 共青團中央:32345 【展示公眾號 ID:】 LRANGE msg:我的訂閱號-id 0 -1

Set 使用場景:
比如:抽獎小程式,獲取朋友圈點贊的用戶資訊,可能關注的人(需要使用并集等操作)等,
抽獎就是在一堆用戶中隨機抽取用戶,由于 Set 不可重復性,可以保證用戶唯一,
使用 SADD 可以添加用戶 ID 到 set 中,
使用 SMEMBERS 可以查看當前參與抽獎的所有元素,
使用 SRANDMEMBER、SPOP 可以抽取獲獎者用戶,
【添加用戶:】 sadd user 1001 1002 1003 1004 【查看所有用戶:】 smembers user 【抽選用戶,不洗掉用戶:】 srandmember user 3 【抽選用戶,洗掉用戶:】 spop user 3

sortedset(zset)使用場景:
比如:微博熱搜榜、百度熱議榜等,

二、Redis 持久化、資料庫、單執行緒
1、Redis 資料庫
Redis 默認有 16 個庫,庫編號為 db0 - db15,資料庫之間的資料是相互隔離的、互不影響的,
Redis 是 C/S 結構,有一個 redis-cli 和 redis-server, redis-server 用于啟動 Redis 服務,默認資料庫數量為 16,可以修改,redis-cli 用于連接某個資料庫,
資料庫中采用哈希表存盤鍵值對,其中 value 可以為不同型別的資料結構,
2、Redis 鍵過期處理
(1)為什么進行過期處理?
Redis 是基于記憶體的,記憶體容量比較有限,如果長期將 key - value 存放在 記憶體中,會占用大量記憶體,這樣肯定是不行的,所以需要對 key 設定過期時間,當 key 過期后,系統回應并將其洗掉,從而減少記憶體的占用,
(2)過期策略:
定時洗掉:到某個時間點,就進行洗掉 過期鍵 的操作,對 記憶體 友好,對 CPU 不友好,
惰性洗掉:每次獲取鍵時,判斷該鍵是否過期,過期則洗掉,對 CPU 友好,對 記憶體 不友好,
定期洗掉:每過一段時間,就去洗掉 過期鍵,
Redis 中采用 惰性洗掉 + 定期洗掉,即意味著 某個鍵 到了過期時間,也不一定會被立即洗掉,
(3)記憶體淘汰機制:
由于 Redis 可能會不及時的洗掉過期 key,導致 記憶體里堆積了很多沒用的 key,會消耗大量記憶體,此時,需要通過記憶體淘汰機制,選擇不需要的 key,并將其洗掉,
比如:設定消耗記憶體最大值,當超過記憶體最大值后,進行資料淘汰,將最近最少使用的 key 資料淘汰(一般應用于熱搜排行榜的場景),
【常見記憶體淘汰機制:】 allkeys-lru: 在所有 key 中,移除最近最少使用的 key(常用) allkeys-random: 在所有 key 中,隨機移除 key, volatile-lru: 在設定過期時間的 key 中,移除最近最少使用的 key volatile-random: 在設定過期時間的 key 中,隨機移除 key, volatile-ttl: 在設定過期時間的 key 中,優先移除 即將過期 的 key,
3、資料持久化 -- RDB
Redis 是基于記憶體的,Redis 一旦重啟,所有資料都會丟失,所以一般會將資料持久化到硬碟中,Redis 重啟后可以通過硬碟恢復資料,
Redis 采用兩種方法進行資料持久化 -- RDB 、AOF,
(1)RDB(Redis DataBase)
RDB 基于快照,可以指定時間間隔、將某一時刻的所有資料保存到一個 RDB 檔案中,是一個二進制檔案,默認為 dump.rdb,Redis 啟動時,若發現存在 rdb 檔案,則會自動載入該檔案(載入的程序是一個阻塞的狀態),
(2)通過三種方式可以實作 RDB,
Method1:SAVE 命令觸發
客戶端執行 SAVE 命令后,會阻塞當前 Redis 服務器(即 Redis 不能處理其他命令),直到 RDB 程序結束,若存在舊的 RDB 檔案,會進行替換,(此方式若資料量過大,會影響系統性能)
Method2:BGSAVE 命令觸發
客戶端執行 BGSAVE 命令后,會創建一個子行程,由子行程來創建 RDB 檔案,不會阻塞當前 Redis 服務器,
Method3:redis.conf 組態檔中配置
【save 格式:】 save m n 指的是 m 時間間隔內,至少出現了 n 次 key 變化,則進行保存 【舉例:】 save 60 10000 指的是 60 秒內,至少出現了 10000 次 key 變化,則保存
(3)SAVE 與 BGSAVE 比較:
SAVE 屬于 同步操作,會阻塞當前 Redis 服務器,但不會消耗額外記憶體,
BGSAVE 屬于 異步操作,不會阻塞當前 Redis 服務器,但會消耗額外記憶體(創建子行程),
(4)RDB 優缺點:
優點:
RDB 是全量備份,將資料壓縮到二進制檔案中,格式緊湊(檔案小),適合資料備份以及恢復,
RDB 可以使用子行程去創建 RDB 檔案,主行程不進行 磁盤操作,
缺點:
子行程進行持久化時,父行程若修改記憶體中的資料,子行程不會知曉,此時可能造成資料丟失,
4、資料持久化 -- AOF
(1)AOF(Append Only File)
AOF 指當 Redis 服務器執行寫命令時,會將寫命令 保存到 AOF 檔案中(可以理解為日志記錄),
(2)AOF 執行流程:
Step1:命令追加到緩沖區
遇到寫命令時,將命令寫入 aof_buf 緩沖區,
Step2:確認是否需要將緩沖區內容寫入檔案,
通過組態檔 redis.conf 中 appendfsync 去確定是否將緩沖區內容寫入檔案,
appendfsync always # 每次有資料修改發生時都會寫入AOF檔案(磁盤開銷大), appendfsync everysec # 每秒鐘同步一次,該策略為AOF的默認策略(丟失 1 秒資料), appendfsync no # 從不同步,高效但是資料不會被持久化(資料丟失),
Step3:檔案從緩沖區寫入到檔案,
將緩沖區的內容寫入到 aof 檔案中,
不停的執行寫命令操作后,會使得 aof 檔案變得越來越大,可以使用 BGREWRITEAOF 命令進行 AOF 重寫(可以合并 寫操作命令,減少檔案內容冗余),此重寫基于當前 資料庫資料重寫,不需要讀取舊的 aof 檔案,
BGREWRITEAOF 命令會創建子行程,由子行程進行 AOF 重寫,其會存在一個 AOF 重寫緩沖區,重寫緩沖區用于 記錄 創建子行程后 主行程執行的 寫操作,當子行程執行完 AOF 重寫后,向父行程發送請求,將重寫緩沖區的資料寫入新的 aof 檔案中,從而使 當前資料庫 與 AOF 檔案寫操作一致,
(3)AOF優缺點:
優點:
可以更好的保護資料,默認進行 1 秒同步一次的操作,最多丟失 1 秒資料,
缺點:
AOF 檔案過大,恢復資料速度較慢,
(4)AOF、RDB 如何選擇?
AOF、RDB 可以同時使用,但服務器優先使用 AOF 檔案進行資料還原,
AOF:丟失資料少(視 appendfsync 而定),檔案體積大,恢復資料速度較慢,
RDB:可能丟失一部分資料,檔案體積小,恢復資料速度較快,
5、為什么 Redis 是單執行緒?速度為什么快?
(1)為什么 Redis 是單執行緒的?
Redis 基于記憶體進行操作,CPU 不是 Redis 的瓶頸,且單執行緒 比 多執行緒容易實作,
(2)速度為什么快?
基于記憶體操作,讀寫速度快,
單執行緒操作,避免頻繁背景關系切換,
采用了非阻塞 I/O 多路復用機制,保證系統高吞吐量,
注:
非阻塞 I/O 多路復用機制,用來保證多個連接時的系統吞吐量(此處不展開,有時間再總結),
多路 指的是 多個 socket 連接,
復用 指的是 共用 同一個執行緒,
簡單的講,就是使單執行緒高效的處理多個連接請求,
6、Redis 和 memcached 區別?
(1)Redis 可以將資料持久化到硬碟中,memcached 只能將資料存盤在記憶體中(斷電后消失),
(2)Redis 支持多種資料型別,memcached 支持型別簡單,
三、快取雪崩、快取穿透、快取與資料庫讀寫一致
1、快取穿透是什么?如何解決?
(1)快取穿透是什么?
快取穿透指查詢一個不存在的資料,且資料不在快取中,則查詢會從資料庫查詢,而資料庫查不到資料,則不會將資料存盤在快取中,以致于每次查詢都會繞過快取,從資料庫查資料,使快取失效,
(2)快取穿透的可能原因?解決?
原因:
請求的引數不合理,
比如資料庫的 id 自增,且從 100 開始,但是每次請求都是 100 以下的 id 或者 負數的 id,則每次查詢,快取中沒有值,直接去查資料庫,而資料庫查不到值,就不會將資料保存到快取中,從而使快取失效,
解決:
方式一:對引數進行過濾處理(比如 BloomFilter),不合法的引數不會訪問到資料庫,
方式二:當資料庫找不到資料時,回傳一個空物件到快取中,并設定一個過期時間,這樣就可以從快取中獲取資料了,
2、快取雪崩是什么?如何解決?
(1)快取雪崩是什么?
快取雪崩指的是由于某種原因,導致緩沖層出現了問題,所有的請求(大量請求)直接訪問資料庫(可以理解為發生大量資料穿透),從而使資料庫宕機,
(2)快取雪崩的可能原因?解決?
原因一:
Redis 服務掛掉了,即快取失效,所有請求不經過快取直達資料庫,資料庫反應不過來而宕機,
如何解決:
Step1:應該盡量避免 Redis 服務掛掉,
為了實作 Redis 高可用,應該使用 主從模式 + 哨兵模式(或者采用 Redis 集群),盡量避免 Redis 服務掛掉,
Step2:應該盡量避免 資料庫 掛掉,
萬一 Redis 服務真的掛了,應當進行 熔斷、降低、限流等操作,盡量避免資料庫被干掉,至少要保證服務還能正常運行,
Step3:資料恢復,
對 Redis 資料進行持久化,重啟 Redis 服務后,加載磁盤資料進行資料恢復,
原因二:
Redis 對資料設定了過期時間,同一時間這些資料失效,此時恰巧有大量請求同時訪問這些資料,會穿過快取直接訪問資料庫,造成大量快取穿透,從而導致資料庫宕機,
如何解決:
快取的同時,將過期時間設定成隨機值,此時能極大避免大量資料 過期時間一致,
3、快取、資料庫讀寫一致
(1)讀操作流程:
Step1:查詢快取中是否存在資料,存在資料則直接回傳,
Step2:快取中不存在資料,則查詢資料庫中是否存在資料,存在資料,則將資料保存在快取中,并回傳資料,
(2)讀寫操作同時進行時可能出現資料不一致,
造成讀寫不一致的情況有很多,
比如一件商品,開始時 資料庫、快取里顯示的庫存數量均為 1000,此時讀操作并沒有問題,現在賣出一件商品,需要更新資料庫,假如更新資料庫資料成功,但是更新快取資料失敗 ,即此時資料庫顯示庫存數量為 999,而快取顯示數量為 1000,則下次操作,獲取到的商品數量仍為 1000,此時就造成了讀寫不一致,
(3)如何解決讀寫不一致?
方式一:一般給快取的資料設定過期時間,資料過期則被洗掉,下次會從資料庫查詢并更新快取,
方式二:保證資料庫、快取更新的原子性(分布式事務),要么同時成功、要么同時失敗,
(4)更新快取、資料庫的兩種方式:
方式一:先洗掉快取,再更新資料庫,
方式二:先更新資料庫,再洗掉快取,
注:
對于更新快取,一般直接洗掉某個資料,簡單粗暴,下次讀取時從資料庫讀取并保存到快取中,
對于方式一(單執行緒情況):
若洗掉快取失敗,可以直接拋出例外,此時資料庫與快取資料均無變化,即資料一致,
若洗掉快取成功,但是更新資料庫失敗,此時快取中沒有該資料,下次讀取時,從資料庫中讀取并保存到快取中,從而資料一致,
若洗掉快取、更新資料庫均成功,下次讀取資料肯定一致,
對于方式一(高并發情況):
執行緒 A 進行更新操作,執行緒 B 進行讀操作,
執行緒 A 洗掉快取,此時執行緒 B 進行讀取,發現快取不存在,則直接從資料庫中讀取,并將該值存入快取,
執行緒 A 對資料庫資料進行更新,此時快取中的值 與 資料庫的值不一致了,
如何解決上述的資料不一致:
將命令操作積壓到佇列中(先進先出),進行串行化,比如先洗掉快取,再更新資料庫,最后再進行讀取,
對于方式二(單執行緒情況):
若更新資料庫失敗,則直接拋出例外,此時資料庫與快取資料均無變化,即資料一致,
若更新資料庫成功,但洗掉快取失敗,則資料庫的資料為新資料,與快取資料不一致了,
若更新資料庫、洗掉快取均成功,則下次讀寫的資料肯定一致,
如何解決上述的資料不一致:
不斷重復洗掉 key,直至可以洗掉,
對于方式二(高并發情況):
執行緒 A 進行查詢操作,執行緒 B 進行更新操作,
執行緒 A 查詢時,恰好快取失效,直接通過資料庫進行查詢,此時 執行緒 B 更新資料庫資料,并進行快取洗掉,然后 執行緒 A 將從資料庫獲取的資料寫入快取中,此時快取資料與資料庫資料不一致了,
上例情況發生概率很低,畢竟寫操作的速度慢于讀操作,且讀操作要先于寫操作進入資料庫,且慢于寫操作操作快取,同時滿足這個情況的概率只能說是走了狗屎運,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/6056.html
標籤:NoSQL
上一篇:關于redis單執行緒的分析
下一篇:MongoDB基礎入門
