一、redis為什么那么快
- QPS達到10萬/秒
- 用C語言實現
- 基于內存
- 單線程,不用線程上下文切換及加鎖
- String,常見的緩存,存儲登錄session等
- hash,存儲對象,單獨修改對象屬性
- List,有序列表,可實現簡單的消息隊列,阻塞隊列
- Set,分布式去重
- Zset,也叫做sorted set,有序集合,關聯一個double類型的分數,根據分數排序,可實現排行榜、延時隊列
- Stream,redis 5.0后的新數據類型,消費者可分組,一條消息只能被同組的一個消費者消費,但可以被不同組的多個消費者重復消費,借此實現可持久化的發布&訂閱功能
- Cache Aside Pattern
- 應用程序同時對接緩存、數據庫
- 查詢時先查詢緩存,緩存未命中則查詢數據庫,同時更新數據庫
- 更新時先更新數據庫,在刪除緩存緩存
- 最常用的模式
- 會有數據不一致性問題
- 適用讀多寫少的場景
- 兩種加載緩存的方式,讀到再加載,或者啟動時就加載
- 為什么是先更新數據庫再刪除緩存,而不是先刪除緩存再更新數據庫?
- 為什么是更新數據庫后刪除緩存,而不是更新緩存?
- Read/Write Through Pattern
- 應用程序只對接緩存,由緩存對接數據庫,相當于Cache Provider中封裝了數據庫
- 查詢時若緩存未命中則,則Cache Provider去查詢數據庫,設置到緩存后再返回
- 更新時,由Cache Provider同時更新緩存和數據庫,這里有事務保證
- 此模式很耗時,但能保證數據一致性
- 少見的模式
- Write Behind
- 應用程序只更新緩存,不直接更新數據庫
- 在一定時間觸發異步的方式寫入數據庫
- 類似于MySQL InnoDB 緩沖池的模式
- 在寫入數據庫前斷電掛機會有丟失數據可能
- 放大了數據不一致性,但速度很快
- 適用于高并發寫,但對數據一致性要求不高且允許丟失的場景
電商首頁熱點數據會做緩存,定時任務刷新,所有key失效時間一樣。
熱點key大面積集中失效,大量請求一下子打到數據庫,導致數據庫掛掉。
- 失效時間加隨機值,不讓它們集中失效
- 設置熱點key永不過期,有更新時就更新緩存
- 如果Redis是集群部署,可讓熱點key分布在不同的Redis庫中
緩存和數據庫中都不存在的數據,被攻擊者利用,如id = -1,發起攻擊的時候會繞過換過,不斷查詢數據庫。
- 對參數合法性進行檢驗
- 使用布隆過濾器,會有一定誤判
- 數據庫查詢為null時,可以緩存約定的數據,如“請稍后重試”,緩存時間設置短點,如30秒(防止正常了這個id下有數據了也無法正常使用)
- 限流
一個熱點key,在失效的瞬間,遭遇高并發,大量請求在緩存中查不到,會直接去查數據庫
- 設置熱點key永不過期
- 使用分布式互斥鎖,保證在緩存失效時,只有一個請求能查到數據庫
數據一致性就是指數據庫和緩存的數據一致的問題。
根據三種緩存模式可知,在數據一致性和效率是兩個極端,只能取一個中間平衡點。
一般是采用旁路緩存模式,且采用先更新數據庫,后刪除緩存的方式。
但就這時候還是會有少量請求因為刪除緩存不及時而讀到舊數據,不過一般都能順利刪除緩存,這已經是對業務影響最輕的做法。
這時候如果允許短期的數據不?致不會影響業務,那么只要下次更新時可以成功,能保證最終?致性就可以,那么可以不用再做處理。
如果還要再完美,可以捕捉刪除緩存異常增加重試,對耗時敏感的可以進行異步補償重試,即放到mq里面監聽,但是這樣對業務侵入性比較大,也可以采用監聽MySQL binlog日志的方式進行重試。
十、布隆過濾器
原理:
一個元素被加入到集合時,通過k個哈希函數將這個元素映射成一個位數組中的k個點,把它們設置為1。
檢索時,看這些位置是不是為1就知道這個元素在不在集合中了,如果都是1則可能存在,如果有一個是0則一定不存在。
缺點:
- 存在誤判的肯定,可通過建立白名單來存儲誤判的元素
- 刪除困難,初始化要把所有合法元素加到過濾器中,刪除時設置為0可能會影響其他元素的判斷,可通過Count Bloom Filter
- 使用zookeeper分布式鎖保證線程安全
- 如果也要更新數據庫,涉及到雙寫,就會出現數據一致性問題,可以參考上面的刪除key
- 如果不能刪除key,則在更新緩存時比較數據的更新時間
- 記錄內存快照的方式
- 使用bgsave,fork一個子進程進行,不會阻塞set操作,類似于GC的守護進行
- Copy On Write,寫時復制機制,備份的時候發生寫入操作,則備份的是寫入之前的數據,所以會有數據丟失
- 定期進行,一般是5分鐘一次,斷電可能會丟失較多數據
- 恢復塊、備份久
- 可能把RDB快照文件定期放到遠程存儲,一般做冷備
- RDB備份的文件體積小,恢復很快
- 日志追加的方式,類似于MySQL innoDB引擎中redo.log,備份當前操作命令
- 恢復慢、備份塊
- 會不會丟失數據取決于Appendfsync配置,配置為實時備份則每次寫操作都會備份,性能低,一般是配置為每秒一次,這樣最多是丟失一秒的數據
- 適合做災備
- 隨著時間增長,AOF文件會越來越大,Redis提供了日志重寫功能,可以壓縮命令,重寫后新的AOF文件僅包含舊AOF文件命令的最小集合
- AOF備份的文件體積大,即使經過重寫,仍然很大,恢復很慢
Redis 4.0后使用了RDB+AOF混合持久化模式,生成RDB文件重新記錄,這時AOF日志不再是全量的,而是增量的日志記錄,體積很小。
十四、Redis過期策略
Redis需要刪除失效的數據以清空內存,過期策略就是怎么刪除過期數據。
- 定期刪除:默認每隔100ms隨機抽取部門設置了過期時間的key,檢查key是否失效,失效了就刪除。(不全部檢查是因為效率低,類似于MySQL全表掃描)
- 惰性刪除:當應用程序來查key的時候,檢查到key失效就會刪除,未失效就返回。
Redis使用定期刪除+惰性刪除,能保證最終一定會刪除過期的key,但是定期刪除會有漏網之魚,而應用程序又很久沒來查詢就會導致長時間滯留在內存之中,這時需要用到內存淘汰機制。
十五、Redis內存淘汰機制
FIFO:First In First Out,先進先出
LRU:Least Recently Used,最近最少使用,從時間上看很久沒有使用的被淘汰
LFU:Least Frequently Used,最不經常使用,從次數上看使用得最少的被淘汰
- volatile-lru:將設定了超時時間的數據,采用LRU算法將數據提前刪除
- allkeys-lru:對所有的數據采用LRU算法進行刪除
- volatile-lfu:設定超時時間的數據采用LFU算法刪除
- allkeys-lfu:對所有數據采用LFU算法刪除
- volatile-random:設定了超時時間的數據隨機刪除
- allkeys-random:所有數據隨機刪除
- volatile-ttl:設定了超時時間的數據根據剩余時間少的刪除數據
- noeviction:不刪除內存數據,如果內存溢出報錯返回(默認策略)
全量同步主要發生在Slave初始化階段,當啟動一臺Slave時,它需要連接到Master,把Master數據都復制一份。
- Slave連接上Master,發送sync命令給到Master。
- Master執行bgsave,按照全量備份方式生成一份RDB快照,并用內存緩沖區記錄此后執行的所有寫命令。
- Master向Slave發送RDB快照。
- Slave收到RDB文件后,丟棄所有舊數據,并載入收到的快照文件。
- Master發送完RDB快照就接著發緩沖區中的寫命令。
- Slave載入完RDB快照,就開始接收&執行Master發送過來的寫命令。
Master每執行一個寫命令就會向Slave發送相同的寫命令,Slave接收&執行收到的寫命令。
十八、Redis主從復制
主從剛剛連接的時候,進行全量同步;全同步結束后,進行增量同步。當然,如果有需要,slave 在任何時候都可以發起全量同步。redis策略是,無論如何,首先會嘗試進行增量同步,如不成功,再要求從機進行全量同步。
主從復制,只是實現了容災備份,不能故障轉移,不是實現高可用。
十九、Redis高可用方案A:哨兵模式+主動復制
哨兵是什么?
- 哨兵是一個獨立的進程。
- 哨兵的作用主要有兩個,A:通過心跳機制監控Redis服務器運行狀態,包括Master和Slave。B:當哨兵監測到master宕機,會自動將slave切換成master,然后通過發布訂閱模式通知其他的哨兵、slave,修改配置文件,讓它們切換主機。
- 當一個哨兵監測到Master宕機,系統并不會馬上進行故障切換,僅僅是哨兵1主觀的認為主服務器不可用,這個現象成為“主觀下線”。
- 當后面的哨兵也檢測到主服務器不可用,并且數量達到一定值時,那么哨兵之間就會進行一次投票,投票的結果由一個哨兵發起,進行故障切換。
- 切換成功后,就會通過發布訂閱模式,讓各個哨兵把自己監控的從服務器實現切換主機,這個過程稱為“客觀下線”。
- 優點:實現了容災備份和自動故障切換,是高可用方案。
- 缺點:不好在線擴容(Slave可以隨時配置多個,提高讀并發,但Master只有一個,提高不了寫并發),配置麻煩,只有一個主節點對外提供服務,沒法支持很高的并發量。
Redis集群是一個由多個主從節點群組組成的分布式服務集群,他具有復制、高可用、分片特性,Redis集群不需要sentinel哨兵,也能完成節點移除和故障轉移的功能,需要將每個節點設置成集群模式,這種集群模式沒有中心節點,可水平擴展;Redis集群的性能和高可用均優于之前版本的哨兵模式,且集群配置非常簡單。
故障切換過程是怎么樣的?
- Redis的所有節點都會保存當前redis集群中的全部主從狀態信息,并且每個節點都能夠相互通信。
- 當一個節點發生宕機,則集群中的其他節點通過心跳機制檢查Redis節點是否宕機。
- 當有半數以上的節點認為宕機,則認為主節點宕機,同時由Redis剩余的主節點進入選舉機制,投票選舉鏈接宕機的主節點的從機,實現故障遷移。
- 集群中如果主機宕機,那么從機可以繼續提供服務,當主機中沒有從機時,則向其它主機借用多余的從機,繼續提供服務,如果主機宕機時沒有從機可用,則集群崩潰。即:每個節點都至少保持是“一主一從”。
數據存儲原理是什么?
- hash槽存儲原理,所有的鍵根據哈希函數(CRC16[key]&16383)映射到0-16384槽內。
- 當向redis集群中插入數據時,首先將key進行計算.之后將計算結果匹配到具體的某一個槽的區間內,之后再將數據set到管理該槽的節點中。
二十一、keys命令
- keys命令可以列出所有符合給定模式 pattern的key
- 單因為redis是單線程的,使用keys命令會導致線程阻塞一段時間,線上服務停頓,知道指令執行完畢,服務才能恢復,如列出10億個相同前綴的key時,影響特別大。
- 可以使用scan指令代替,但會有一定重復,通過代碼去重就好。
- 使用String類型緩存用戶登錄狀態
- 使用Hash類型緩存一張配置表、字典表
- 使用setnx+expire+Lua實現分布式鎖
- 使用List類型實現高性能的分頁(如文章的評論列表)、簡單的消息隊列功能
- 使用Set類型實現分布式全局去重
- 使用Zset類型實現熱榜、排行榜、延時隊列功能
- 使用pub/sub實現簡單的發布&訂閱功能(不可持久化)
- 使用Stream類型實現有消費組的發布&訂閱功能(可持久化)
- 使用Bitmap(位圖)實現簽到、布隆過濾器功能
- 使用HyperLogLog實現的不精確的去重統計,如PV(頁面訪問)、UV(用戶訪問)
- 使用Geospatial保存地理位置,計算位置距離,實現附近的人功能
- 使用Pipeline(管道)把一批命令打包好發送到redis一次性執行,減少客戶端與 redis 的通信次數來實現降低往返延時時間
- 使用Lua腳本保證原子性,實現秒殺場景扣除商品庫存
- 使用Set類型實現標簽系統