Java重要面試名詞整理(五):Redis

文章目錄

    • Redis高級命令
    • Redis持久化
      • RDB快照(snapshot)
      • **AOF(append-only file)**
      • **Redis 4.0 混合持久化**
    • 管道(Pipeline)
    • **StringRedisTemplate與RedisTemplate詳解**
    • Redis集群方案
      • gossip
      • 腦裂
    • Redis Lua
    • Redis MultiLock
    • Redis紅鎖
    • 緩存相關問題
      • **緩存穿透**
      • **緩存失效(擊穿)**
      • **緩存雪崩**
      • **熱點緩存key重建優化**
      • **緩存與數據庫雙寫不一致**
      • BigKey問題
    • 主動清理策略
    • Redis隊列與Stream
      • Stream
      • Redis隊列幾種實現
        • **基于List的 LPUSH+BRPOP 的實現**
        • **基于Sorted-Set的實現**
    • Redis中的線程和IO模型
      • 什么是Reactor模式 ?
        • **單線程Reactor模式流程**
        • **單線程Reactor,工作者線程池**
        • **多Reactor線程模式**
      • Redis中的線程和IO概述
        • 事件的類型(套接字)
    • HyperLogLog
    • Redis事務
      • **Redis事務**
      • **Pipeline和事務的區別**
    • Redis 復制緩存區相關問題分析
      • 多從庫時主庫內存占用過多
        • **OutputBuffer 拷貝和釋放的堵塞問題**
        • **ReplicationBacklog 的限制**
      • Redis7.0共享復制緩存區的設計與實現
        • **簡述**
        • **堵塞問題和限制問題的解決**
      • 數據結構選取
          • **rax樹**
          • **Trie樹**
    • 熱Key
      • **什么是熱key**

Redis的 IO多路復用:redis利用epoll來實現IO多路復用,將連接信息和事件放到隊列中,依次放到文件事件分派器,事件分派器將事件分發給事件處理器。

Redis高級命令

scan:漸進式遍歷鍵

SCAN cursor [MATCH pattern] [COUNT count]

scan 參數提供了三個參數,第一個是 cursor 整數值(hash桶的索引值),第二個是 key 的正則模式,第三個是一次遍歷的key的數量(參考值,底層遍歷的數量不一定),并不是符合條件的結果數量。第一次遍歷時,cursor 值為 0,然后將返回結果中第一個整數值作為下一次遍歷的 cursor。一直遍歷到返回的 cursor 值為 0 時結束。

注意:但是scan并非完美無瑕, 如果在scan的過程中如果有鍵的變化(增加、 刪除、 修改) ,那么遍歷效果可能會碰到如下問題: 新增的鍵可能沒有遍歷到, 遍歷出了重復的鍵等情況, 也就是說scan并不能保證完整的遍歷出來所有的鍵, 這些是我們在開發時需要考慮的。

Redis持久化

RDB快照(snapshot)

RDB持久化是把當前進程數據生成快照保存到硬盤的過程。RDB 就是 Redis DataBase 的縮寫。

bgsave的寫時復制(COW)機制

Redis 借助操作系統提供的寫時復制技術(Copy-On-Write, COW),在生成快照的同時,依然可以正常處理寫命令。簡單來說,bgsave 子進程是由主線程 fork 生成的,可以共享主線程的所有內存數據。bgsave 子進程運行后,開始讀取主線程的內存數據,并把它們寫入 RDB 文件。此時,如果主線程對這些數據也都是讀操作,那么,主線程和 bgsave子進程相互不影響。但是,如果主線程要修改一塊數據,那么,這塊數據就會被復制一份,生成該數據的副本。然后,bgsave 子進程會把這個副本數據寫入 RDB 文件,而在這個過程中,主線程仍然可以直接修改原來的數據。

配置自動生成rdb文件后臺使用的是bgsave方式。

AOF(append-only file)

快照功能并不是非常耐久(durable): 如果 Redis 因為某些原因而造成故障停機, 那么服務器將丟失最近寫入、且仍未保存到快照中的那些數據。從 1.1 版本開始, Redis 增加了一種完全耐久的持久化方式: AOF 持久化,將修改的每一條指令記錄進文件appendonly.aof中(先寫入os cache,每隔一段時間fsync到磁盤)

AOF會定期根據內存的最新數據生成aof文件

注意,AOF重寫redis會fork出一個子進程去做(與bgsave命令類似),不會對redis正常命令處理有太多影響

Redis 4.0 混合持久化

重啟 Redis 時,我們很少使用 RDB來恢復內存狀態,因為會丟失大量數據。我們通常使用 AOF 日志重放,但是重放 AOF 日志性能相對 RDB來說要慢很多,這樣在 Redis 實例很大的情況下,啟動需要花費很長的時間。 Redis 4.0 為解決這個問題,帶來了一個新的持久化選項——混合持久化。

如果開啟了混合持久化,AOF在重寫時,不再是單純將內存數據轉換為RESP命令寫入AOF文件,而是將重寫這一刻之前的內存做RDB快照處理,并且將RDB快照內容和增量的AOF修改內存數據的命令存在一起,都寫入新的AOF文件,新的文件一開始不叫appendonly.aof,等到重寫完新的AOF文件才會進行改名,覆蓋原有的AOF文件,完成新舊兩個AOF文件的替換。

于是在 Redis 重啟的時候,可以先加載 RDB 的內容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重啟效率大幅得到提升。

復制風暴是指大量從節點對同一主節點或者對同一臺機器的多個主節點短時間內發起全量復制的過程。復制風暴對發起復制的主節點或者機器造成大量開銷,導致 CPU、內存、帶寬消耗。因此我們應該分析出復制風暴發生的場景,提前采用合理的方式規避。規避方式有如下幾個。

管道(Pipeline)

客戶端可以一次性發送多個請求而不用等待服務器的響應,待所有命令都發送完后再一次性讀取服務的響應,這樣可以極大的降低多條命令執行的網絡傳輸開銷,管道執行多條命令的網絡開銷實際上只相當于一次命令執行的網絡開銷。需要注意到是用pipeline方式打包命令發送,redis必須在處理完所有命令前先緩存起所有命令的處理結果。打包的命令越多,緩存消耗內存也越多。所以并不是打包的命令越多越好。

StringRedisTemplate與RedisTemplate詳解

spring 封裝了 RedisTemplate 對象來進行對redis的各種操作,它支持所有的 redis 原生的 api。在RedisTemplate中提供了幾個常用的接口方法的使用

StringRedisTemplate繼承自RedisTemplate,也一樣擁有這些操作。

StringRedisTemplate默認采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。

RedisTemplate默認采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

Redis集群方案

  • 哨兵模式

在redis3.0以前的版本要實現集群一般是借助哨兵sentinel工具來監控master節點的狀態,如果master節點異常,則會做主從切換,將某一臺slave作為master,哨兵的配置略微復雜,并且性能和高可用性等各方面表現一般,特別是在主從切換的瞬間存在訪問瞬斷的情況,而且哨兵模式只有一個主節點對外提供服務,沒法支持很高的并發,且單個主節點內存也不宜設置得過大,否則會導致持久化文件過大,影響數據恢復或主從同步的效率

  • 高可用集群模式

redis集群是一個由多個主從節點群組成的分布式服務器群,它具有復制、高可用和分片特性。Redis集群不需要sentinel哨兵·也能完成節點移除和故障轉移的功能。需要將每個節點設置成集群模式,這種集群模式沒有中心節點,可水平擴展,據官方文檔稱可以線性擴展到上萬個節點(官方推薦不超過1000個節點)。redis集群的性能和高可用性均優于之前版本的哨兵模式,且集群配置非常簡單

槽位定位算法

Redis Cluster 將所有數據劃分為 16384 個 slots(槽位),每個節點負責其中一部分槽位。槽位的信息存儲于每個節點中。

Cluster 默認會對 key 值使用 crc16 算法進行 hash 得到一個整數值,然后用這個整數值對 16384 進行取模來得到具體槽位。

跳轉重定位

當客戶端向一個錯誤的節點發出了指令,該節點會發現指令的 key 所在的槽位并不歸自己管理,這時它會向客戶端發送一個特殊的跳轉指令攜帶目標操作的節點地址,告訴客戶端去連這個節點去獲取數據。客戶端收到指令后除了跳轉到正確的節點上去操作,還會同步更新糾正本地的槽位映射表緩存,后續所有 key 將使用新的槽位映射表。

gossip

gossip協議包含多種消息,包括ping,pong,meet,fail等等。

meet:某個節點發送meet給新加入的節點,讓新節點加入集群中,然后新節點就會開始與其他節點進行通信;

ping:每個節點都會頻繁給其他節點發送ping,其中包含自己的狀態還有自己維護的集群元數據,互相通過ping交換元數據(類似自己感知到的集群節點增加和移除,hash slot信息等);

pong: 對ping和meet消息的返回,包含自己的狀態和其他信息,也可以用于信息廣播和更新;

fail: 某個節點判斷另一個節點fail之后,就發送fail給其他節點,通知其他節點,指定的節點宕機了。

gossip協議的優點在于元數據的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到所有節點上去更新,有一定的延時,降低了壓力;缺點在于元數據更新有延時可能導致集群的一些操作會有一些滯后。

腦裂

redis集群沒有過半機制會有腦裂問題,網絡分區導致腦裂后多個主節點對外提供寫服務,一旦網絡分區恢復,會將其中一個主節點變為從節點,這時會有大量數據丟失。

腦裂:一般來說是指一個分布式系統中有兩個子集,然后每個子集都有一個自己的主節點(Leader/Master)。那么整個分布式系統就會存在多個主節點了,而且每個都認為自己是正常的,這就會導致數據不一致或重復寫入的問題。

Redis Lua

EVAL命令對Lua腳本進行求值。EVAL命令的格式如下:

EVAL script numkeys key [key ...] arg [arg ...]

script參數是一段Lua腳本程序,它會被運行在Redis服務器上下文中,這段腳本不必(也不應該)定義為一個Lua函數。numkeys參數用于指定鍵名參數的個數。鍵名參數key [key …]從EVAL的第三個參數開始算起,表示在腳本中所用到的那些Redis鍵(key),這些鍵名參數可以在Lua中通過全局變量KEYS數組,用1為基址的形式訪問( KEYS[1], KEYS[2],以此類推)。

在命令的最后,那些不是鍵名參數的附加參數arg [arg …],可以在Lua中通過全局變量ARGV數組訪問,訪問的形式和KEYS變量類似(ARGV[1]、ARGV[2],諸如此類)。例如

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]]" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

在Lua腳本中,可以使用redis.call()函數來執行Redis命令

Redis MultiLock

基于 Redis 的 Redisson 分布式聯鎖 RedissonMultiLock 對象可以將多個 RLock 對象關聯為一個聯鎖,每個 RLock 對象實例可以來自于不同的 Redisson 實例。它會嘗試同時獲取這些鎖,只有當所有鎖都成功獲取時,才算加鎖成功。

Redis紅鎖

redis分布式鎖在集群中會出現一些問題

假設線程1在主節點加鎖成功,主節點在同步數據到從節點的過程中宕機,重新選舉從節點為主節點,這個時候新的主節點是不存在線程1的鎖的,這個時候線程2過來加鎖成功執行邏輯完成,再來一個線程過來加鎖成功,而線程1并發問題還沒執行完成,這樣的話就又會出現“超賣”的問題,這樣的問題我們稱為redis主從架構鎖失效問題

根據CAP理論,redis集群著重滿足AP,zk集群著重滿足CP

紅鎖主要是為減少redis主從架構鎖失效問題。就是對集群的每個節點進行加鎖,如果大多數(N/2+1)加鎖成功了,則認為獲取鎖成功。

Redisson RedLock 是基于聯鎖 MultiLock 實現的,但是使用過程中需要自己判斷 key 落在哪個節點上,對使用者不是很友好。

Redisson RedLock 已經被棄用,直接使用普通的加鎖即可,會基于 wait 機制將鎖同步到從節點,但是也并不能保證一致性。僅僅是最大限度的保證一致性。

緩存相關問題

緩存穿透

緩存穿透是指查詢一個根本不存在的數據, 緩存層和存儲層都不會命中, 通常出于容錯的考慮, 如果從存儲層查不到數據則不寫入緩存層。

緩存穿透將導致不存在的數據每次請求都要到存儲層去查詢, 失去了緩存保護后端存儲的意義。

造成緩存穿透的基本原因有兩個:

第一, 自身業務代碼或者數據出現問題。

第二, 一些惡意攻擊、 爬蟲等造成大量空命中。

解決方案:

1、緩存空對象

2、布隆過濾器

布隆過濾器就是一個大型的位數組和幾個不一樣的無偏 hash 函數。所謂無偏就是能夠把元素的 hash 值算得比較均勻。

當布隆過濾器說某個值存在時,這個值可能不存在;當它說不存在時,那就肯定不存在。

緩存失效(擊穿)

由于大批量緩存在同一時間失效可能導致大量請求同時穿透緩存直達數據庫,可能會造成數據庫瞬間壓力過大甚至掛掉,對于這種情況我們在批量增加緩存時最好將這一批數據的緩存過期時間設置為一個時間段內的不同時間。

緩存雪崩

緩存雪崩指的是緩存層支撐不住或宕掉后, 流量會像奔逃的野牛一樣, 打向后端存儲層。

由于緩存層承載著大量請求, 有效地保護了存儲層, 但是如果緩存層由于某些原因不能提供服務(比如超大并發過來,緩存層支撐不住,或者由于緩存設計不好,類似大量請求訪問bigkey,導致緩存能支撐的并發急劇下降), 于是大量請求都會打到存儲層, 存儲層的調用量會暴增, 造成存儲層也會級聯宕機的情況。

預防和解決緩存雪崩問題, 可以從以下三個方面進行著手。

1) 保證緩存層服務高可用性,比如使用Redis Sentinel或Redis Cluster。

2) 依賴隔離組件為后端限流熔斷并降級。比如使用Sentinel或Hystrix限流降級組件。

比如服務降級,我們可以針對不同的數據采取不同的處理方式。當業務應用訪問的是非核心數據(例如電商商品屬性,用戶信息等)時,暫時停止從緩存中查詢這些數據,而是直接返回預定義的默認降級信息、空值或是錯誤提示信息;當業務應用訪問的是核心數據(例如電商商品庫存)時,仍然允許查詢緩存,如果緩存缺失,也可以繼續通過數據庫讀取。

3) 提前演練。 在項目上線前, 演練緩存層宕掉后, 應用以及后端的負載情況以及可能出現的問題, 在此基礎上做一些預案設定。

熱點緩存key重建優化

開發人員使用“緩存+過期時間”的策略既可以加速數據讀寫, 又保證數據的定期更新, 這種模式基本能夠滿足絕大部分需求。 但是有兩個問題如果同時出現, 可能就會對應用造成致命的危害:

  • 當前key是一個熱點key(例如一個熱門的娛樂新聞),并發量非常大。
  • 重建緩存不能在短時間完成, 可能是一個復雜計算, 例如復雜的SQL、 多次IO、 多個依賴等。

在緩存失效的瞬間, 有大量線程來重建緩存, 造成后端負載加大, 甚至可能會讓應用崩潰。

要解決這個問題主要就是要避免大量線程同時重建緩存。

我們可以利用互斥鎖來解決,此方法只允許一個線程重建緩存, 其他線程等待重建緩存的線程執行完, 重新從緩存獲取數據即可。

緩存與數據庫雙寫不一致

在大并發下,同時操作數據庫與緩存會存在數據不一致性問題

解決方案:

1、對于并發幾率很小的數據(如個人維度的訂單數據、用戶數據等),這種幾乎不用考慮這個問題,很少會發生緩存不一致,可以給緩存數據加上過期時間,每隔一段時間觸發讀的主動更新即可。

2、就算并發很高,如果業務上能容忍短時間的緩存數據不一致(如商品名稱,商品分類菜單等),緩存加上過期時間依然可以解決大部分業務對于緩存的要求。

3、如果不能容忍緩存數據不一致,可以通過加分布式讀寫鎖保證并發讀寫或寫寫的時候按順序排好隊,讀讀的時候相當于無鎖

4、也可以用阿里開源的canal通過監聽數據庫的binlog日志及時的去修改緩存,但是引入了新的中間件,增加了系統的復雜度。

總結:能保障強一致性:**延時雙刪、分布式鎖;**不能保障強一致性,只能保障最終的一致性:異步通知

BigKey問題

實際中如果下面兩種情況,一般認為它是bigkey。

  1. 字符串類型:它的big體現在單個value值很大,一般認為超過10KB就是bigkey。
  2. 非字符串類型:哈希、列表、集合、有序集合,它們的big體現在元素個數太多。

非字符串的bigkey,不要使用del刪除,使用hscan、sscan、zscan方式漸進式刪除,同時要注意防止bigkey過期時間自動刪除問題(例如一個200萬的zset設置1小時過期,會觸發del操作,造成阻塞)

主動清理策略

當前已用內存超過maxmemory限定時,觸發主動清理策略。

a) 針對設置了過期時間的key做處理:

  1. volatile-ttl:在篩選時,會針對設置了過期時間的鍵值對,根據過期時間的先后進行刪除,越早過期的越先被刪除。
  2. volatile-random:就像它的名稱一樣,在設置了過期時間的鍵值對中,進行隨機刪除。
  3. volatile-lru:會使用 LRU 算法篩選設置了過期時間的鍵值對刪除。
  4. volatile-lfu:會使用 LFU 算法篩選設置了過期時間的鍵值對刪除。

b) 針對所有的key做處理:

  1. allkeys-random:從所有鍵值對中隨機選擇并刪除數據。
  2. allkeys-lru:使用 LRU 算法在所有數據中進行篩選刪除。
  3. allkeys-lfu:使用 LFU 算法在所有數據中進行篩選刪除。

c) 不處理:

  1. noeviction:不會剔除任何數據,拒絕所有寫入操作并返回客戶端錯誤信息"(error) OOM command not allowed when used memory",此時Redis只響應讀操作。

Redis隊列與Stream

Redis5.0 最大的新特性就是多出了一個數據結構 Stream,它是一個新的強大的支持多播的可持久化的消息隊列,作者聲明Redis Stream地借鑒了 Kafka 的設計。

Stream

每個 Stream 都有唯一的名稱,它就是 Redis 的 key,在我們首次使用 XADD 指令追加消息時自動創建。

  1. Consumer Group消費者組,消費者組記錄了Stream的狀態,使用 XGROUP CREATE 命令手動創建,在同一個Stream內消費者組名稱唯一。一個消費組可以有多個消費者(Consumer)同時進行組內消費,所有消費者共享Stream內的所有信息,但同一條消息只會有一個消費者消費到,不同的消費者會消費Stream中不同的消息,這樣就可以應用在分布式的場景中來保證消息消費的唯一性。
  2. last_delivered_id游標,用來記錄某個消費者組在Stream上的消費位置信息,每個消費組會有個游標,任意一個消費者讀取了消息都會使游標 last_delivered_id 往前移動。創建消費者組時需要指定從Stream的哪一個消息ID(哪個位置)開始消費,該位置之前的數據會被忽略,同時還用來初始化 last_delivered_id 這個變量。這個last_delivered_id一般來說就是最新消費的消息ID。
  3. pending_ids消費者內部的狀態變量,作用是維護消費者的未確認的消息ID。pending_ids記錄了當前已經被客戶端讀取,但是還沒有 ack (Acknowledge character:確認字符)的消息。 目的是為了保證客戶端至少消費了消息一次,而不會在網絡傳輸的中途丟失而沒有對消息進行處理。如果客戶端沒有 ack,那么這個變量里面的消息ID 就會越來越多,一旦某個消息被ack,它就會對應開始減少。這個變量也被 Redis 官方稱為 PEL (Pending Entries List)。

Redis隊列幾種實現

基于List的 LPUSH+BRPOP 的實現

足夠簡單,消費消息延遲幾乎為零,但是需要處理空閑連接的問題。

如果線程一直阻塞在那里,Redis客戶端的連接就成了閑置連接,閑置過久,服務器一般會主動斷開連接,減少閑置資源占用,這個時候blpop和brpop或拋出異常,所以在編寫客戶端消費者的時候要小心,如果捕獲到異常需要重試。

基于Sorted-Set的實現

多用來實現延遲隊列,當然也可以實現有序的普通的消息隊列,但是消費者無法阻塞的獲取消息,只能輪詢,不允許重復消息。

Redis中的線程和IO模型

什么是Reactor模式 ?

“反應”器名字中”反應“的由來:

“反應”即“倒置”,“控制逆轉”,具體事件處理程序不調用反應器,而向反應器注冊一個事件處理器,表示自己對某些事件感興趣,有時間來了,具體事件處理程序通過事件處理器對某個指定的事件發生做出反應;這種控制逆轉又稱為“好萊塢法則”(不要調用我,讓我來調用你)

單線程Reactor模式流程

服務器端的Reactor是一個線程對象,該線程會啟動事件循環,并使用Acceptor事件處理器關注ACCEPT事件,這樣Reactor會監聽客戶端向服務器端發起的連接請求事件(ACCEPT事件)。

客戶端向服務器端發起一個連接請求,Reactor監聽到了該ACCEPT事件的發生并將該ACCEPT事件派發給相應的Acceptor處理器來進行處理。建立連接后關注的READ事件,這樣一來Reactor就會監聽該連接的READ事件了。

當Reactor監聽到有讀READ事件發生時,將相關的事件派發給對應的處理器進行處理。比如,讀處理器會通過讀取數據,此時read()操作可以直接讀取到數據,而不會堵塞與等待可讀的數據到來。

在目前的單線程Reactor模式中,不僅I/O操作在該Reactor線程上,連非I/O的業務操作也在該線程上進行處理了,這可能會大大延遲I/O請求的響應。所以我們應該將非I/O的業務邏輯操作從Reactor線程上卸載,以此來加速Reactor線程對I/O請求的響應。

單線程Reactor,工作者線程池

與單線程Reactor模式不同的是,添加了一個工作者線程池,并將非I/O操作從Reactor線程中移出轉交給工作者線程池來執行。這樣能夠提高Reactor線程的I/O響應,不至于因為一些耗時的業務邏輯而延遲對后面I/O請求的處理。

但是對于一些小容量應用場景,可以使用單線程模型,對于高負載、大并發或大數據量的應用場景卻不合適,主要原因如下:

① 一個NIO線程同時處理成百上千的鏈路,性能上無法支撐,即便NIO線程的CPU負荷達到100%,也無法滿足海量消息的讀取和發送;

② 當NIO線程負載過重之后,處理速度將變慢,這會導致大量客戶端連接超時,超時之后往往會進行重發,這更加重了NIO線程的負載,最終會導致大量消息積壓和處理超時,成為系統的性能瓶頸;

多Reactor線程模式

Reactor線程池中的每一Reactor線程都會有自己的Selector、線程和分發的事件循環邏輯。

mainReactor可以只有一個,但subReactor一般會有多個。mainReactor線程主要負責接收客戶端的連接請求,然后將接收到的SocketChannel傳遞給subReactor,由subReactor來完成和客戶端的通信。

多Reactor線程模式將“接受客戶端的連接請求”和“與該客戶端的通信”分在了兩個Reactor線程來完成。mainReactor完成接收客戶端連接請求的操作,它不負責與客戶端的通信,而是將建立好的連接轉交給subReactor線程來完成與客戶端的通信,這樣一來就不會因為read()數據量太大而導致后面的客戶端連接請求得不到即時處理的情況。并且多Reactor線程模式在海量的客戶端并發請求的情況下,還可以通過實現subReactor線程池來將海量的連接分發給多個subReactor線程,在多核的操作系統中這能大大提升應用的負載和吞吐量。

Redis中的線程和IO概述

Redis 基于 Reactor 模式開發了自己的網絡事件處理器 - 文件事件處理器(file event handler,后文簡稱為 FEH),而該處理器又是單線程的,所以redis設計為單線程模型。

采用I/O多路復用同時監聽多個socket,根據socket當前執行的事件來為 socket 選擇對應的事件處理器。

當被監聽的socket準備好執行accept、read、write、close等操作時,和操作對應的文件事件就會產生,這時FEH就會調用socket之前關聯好的事件處理器來處理對應事件。

所以雖然FEH是單線程運行,但通過I/O多路復用監聽多個socket,不僅實現高性能的網絡通信模型,又能和 Redis 服務器中其它同樣單線程運行的模塊交互,保證了Redis內部單線程模型的簡潔設計。

事件的類型(套接字)

I/O多路復用程序可以監聽多個套接字的ae.h/AE_READABLE事件和ae.h/AE_WRITABLE事件

這兩類事件和套接字操作之間的對應關系如下:

套接字變得可讀時(客戶端對套接字執行write操作,或者執行close操作),或者有新的可應答(acceptable)套接字出現時(客戶端對服務器的監聽套接字執行connect操作),套接字產生AE_READABLE事件

當套接字變得可寫時(客戶端對套接字執行read操作),套接字產生AE_WRITABLE事件

I/O多路復用程序允許服務器同時監聽套接字的AE_READABLE事件和AE_WRITABLE 事件,如果一個套接字同時產生了這兩種事件,那么文件事件分派器會優先處理 AE_READABLE事件,等到AE_READABLE事件處理完之后,才處理AE_WRITABLE事件。這也就是說,如果一個套接字又可讀又可寫的話,那么服務器將先讀套接字,后寫套接字

HyperLogLog

HyperLogLo并不是一種新的數據結構(實際類型為字符串類型),而是一種基數算法,通過HyperLogLog可以利用極小的內存空間完成獨立總數的統計,數據集可以是IP、Email、ID等。

HyperLogLog 提供不精確的去重計數方案,雖然不精確但是也不是非常不精確,Redis官方給出標準誤差是 0.81%

HyperLogLog基于概率論中伯努利試驗并結合了極大似然估算方法,并做了分桶優化。

實際上目前還沒有發現更好的在大數據場景中準確計算基數的高效算法,因此在不追求絕對準確的情況下,使用概率算法算是一個不錯的解決方案。概率算法不直接存儲數據集合本身,通過一定的概率統計方法預估值,這種方法可以大大節省內存,同時保證誤差控制在一定范圍內。目前用于基數計數的概率算法包括:

Linear Counting(LC):早期的基數估計算法,LC在空間復雜度方面并不算優秀;

LogLog Counting(LLC):LogLog Counting相比于LC更加節省內存,空間復雜度更低;

HyperLogLog Counting(HLL):HyperLogLog Counting是基于LLC的優化和改進,在同樣空間復雜度情況下,能夠比LLC的基數估計誤差更小。

Redis事務

Redis事務

大家應該對事務比較了解,簡單地說,事務表示一組動作,要么全部執行,要么全部不執行。例如在社交網站上用戶A關注了用戶B,那么需要在用戶A的關注表中加入用戶B,并且在用戶B的粉絲表中添加用戶A,這兩個行為要么全部執行,要么全部不執行,否則會出現數據不一致的情況。

Redis提供了簡單的事務功能,將一組需要一起執行的命令放到multi和exec兩個命令之間。multi(['m?lti]) 命令代表事務開始,exec(美[?ɡ?zek])命令代表事務結束,如果要停止事務的執行,可以使用discard命令代替exec命令即可。

可以看到Redis并不支持回滾功能,開發人員需要自己修復這類問題。

有些應用場景需要在事務之前,確保事務中的key沒有被其他客戶端修改過,才執行事務,否則不執行(類似樂觀鎖)。Redis 提供了watch命令來解決這類問題。

Pipeline和事務的區別

簡單來說,

1、pipeline是客戶端的行為,對于服務器來說是透明的,可以認為服務器無法區分客戶端發送來的查詢命令是以普通命令的形式還是以pipeline的形式發送到服務器的;

2、而事務則是實現在服務器端的行為,用戶執行MULTI命令時,服務器會將對應這個用戶的客戶端對象設置為一個特殊的狀態,在這個狀態下后續用戶執行的查詢命令不會被真的執行,而是被服務器緩存起來,直到用戶執行EXEC命令為止,服務器會將這個用戶對應的客戶端對象中緩存的命令按照提交的順序依次執行。

3、應用pipeline可以提高服務器的吞吐能力,并提高Redis處理查詢請求的能力。

但是這里存在一個問題,當通過pipeline提交的查詢命令數據較少,可以被內核緩沖區所容納時,Redis可以保證這些命令執行的原子性。然而一旦數據量過大,超過了內核緩沖區的接收大小,那么命令的執行將會被打斷,原子性也就無法得到保證。因此pipeline只是一種提升服務器吞吐能力的機制,如果想要命令以事務的方式原子性的被執行,還是需要事務機制,或者使用更高級的腳本功能以及模塊功能。

4、可以將事務和pipeline結合起來使用,減少事務的命令在網絡上的傳輸時間,將多次網絡IO縮減為一次網絡IO。

Redis提供了簡單的事務,之所以說它簡單,主要是因為它不支持事務中的回滾特性,同時無法實現命令之間的邏輯關系計算,當然也體現了Redis 的“keep it simple”的特性。

Redis 復制緩存區相關問題分析

多從庫時主庫內存占用過多

OutputBuffer 拷貝和釋放的堵塞問題

Redis 為了提升多從庫全量復制的效率和減少 fork 產生 RDB 的次數,會盡可能的讓多個從庫共用一個 RDB

當已經有一個從庫觸發 RDB BGSAVE 時,后續需要全量同步的從庫會共享這次 BGSAVE 的 RDB,為了從庫復制數據的完整性,會將之前從庫的 OutputBuffer 拷貝到請求全量同步從庫的 OutputBuffer 中。

其中的copyClientOutputBuffer 可能存在堵塞問題,因為 OutputBuffer 鏈表上的數據可達數百 MB 甚至數 GB 之多,對其拷貝可能使用百毫秒甚至秒級的時間,而且該堵塞問題沒法通過日志或者 latency 觀察到,但對Redis性能影響卻很大。

同樣地,當 OutputBuffer 大小觸發 limit 限制時,Redis 就是關閉該從庫鏈接,而在釋放 OutputBuffer 時,也需要釋放數百 MB 甚至數 GB 的數據,其耗時對 Redis 而言也很長。

ReplicationBacklog 的限制

我們知道復制積壓緩沖區 ReplicationBacklog 是 Redis 實現部分重同步的基礎,如果從庫可以進行增量同步,則主庫會從 ReplicationBacklog 中拷貝從庫缺失的數據到其 OutputBuffer。拷貝的數據量最大當然是 ReplicationBacklog 的大小,為了避免拷貝數據過多的問題,通常不會讓該值過大,一般百兆左右。但在大容量實例中,為了避免由于主從網絡中斷導致的全量同步,又希望該值大一些,這就存在矛盾了。

而且如果重新設置 ReplicationBacklog 大小時,會導致 ReplicationBacklog 中的內容全部清空,所以如果在變更該配置期間發生主從斷鏈重連,則很有可能導致全量同步。

Redis7.0共享復制緩存區的設計與實現

簡述

每個從庫在主庫上單獨擁有自己的 OutputBuffer,但其存儲的內容卻是一樣的,一個最直觀的想法就是主庫在命令傳播時,將這些命令放在一個全局的復制數據緩沖區中,多個從庫共享這份數據,不同的從庫對引用復制數據緩沖區中不同的內容,這就是『共享復制緩存區』方案的核心思想。實際上,復制積壓緩沖區(ReplicationBacklog)中的內容與從庫 OutputBuffer 中的數據也是一樣的,所以該方案中,ReplicationBacklog 和從庫一樣共享一份復制緩沖區的數據,也避免了 ReplicationBacklog 的內存開銷。

『共享復制緩存區』方案中復制緩沖區 (ReplicationBuffer) 的表示采用鏈表的表示方法,將 ReplicationBuffer 數據切割為多個 16KB 的數據塊 (replBufBlock),然后使用鏈表來維護起來。為了維護不同從庫的對 ReplicationBuffer 的使用信息,在 replBufBlock 中存在字段:

refcount:block 的引用計數

id:block 的唯一標識,單調遞增的數值

repl_offset:block 開始的復制偏移

ReplicationBuffer 由多個 replBufBlock 組成鏈表,當 復制積壓區 或從庫對某個 block 使用時,便對正在使用的 replBufBlock 增加引用計數,上圖中可以看到,復制積壓區正在使用的 replBufBlock refcount 是 1,從庫 A 和 B 正在使用的 replBufBlock refcount 是 2。當從庫使用完當前的 replBufBlock(已經將數據發送給從庫)時,就會對其 refcount 減 1 而且移動到下一個 replBufBlock,并對其 refcount 加 1。

堵塞問題和限制問題的解決

多從庫消耗內存過多的問題通過共享復制緩存區方案得到了解決,對于OutputBuffer 拷貝和釋放的堵塞問題和 ReplicationBacklog 的限制問題是否解決了呢?

首先來看 OutputBuffer 拷貝和釋放的堵塞問題問題, 這個問題很好解決,因為ReplicationBuffer 是個鏈表實現,當前從庫的 OutputBuffer 只需要維護共享 ReplicationBuffer 的引用信息即可。所以無需進行數據深拷貝,只需要更新引用信息,即對正在使用的 replBufBlock refcount 加 1,這僅僅是一條簡單的賦值操作,非常輕量。OutputBuffer 釋放問題呢?在當前的方案中釋放從庫 OutputBuffer 就變成了對其正在使用的 replBufBlock refcount 減 1,也是一條賦值操作,不會有任何阻塞。

對于ReplicationBacklog 的限制問題也很容易解決了,因為 ReplicatonBacklog 也只是記錄了對 ReplicationBuffer 的引用信息,對 ReplicatonBacklog 的拷貝也僅僅成了找到正確的 replBufBlock,然后對其 refcount 加 1。這樣的話就不用擔心 ReplicatonBacklog 過大導致的拷貝堵塞問題。而且對 ReplicatonBacklog 大小的變更也僅僅是配置的變更,不會清掉數據。

數據結構選取

rax樹

Redis中還有其他地方使用了Rax樹,比如我們前面學習過的streams 這個類型里面的 consumer group(消費者組) 的名稱還有和Redis集群名稱存儲。

RAX叫做基數樹(前綴壓縮樹),就是有相同前綴的字符串,其前綴可以作為一個公共的父節點,什么又叫前綴樹?

Trie樹

即字典樹,也有的稱為前綴樹,是一種樹形結構。廣泛應用于統計和排序大量的字符串(但不僅限于字符串),所以經常被搜索引擎系統用于文本詞頻統計。它的優點是最大限度地減少無謂的字符串比較,查詢效率比較高。

Trie的核心思想是空間換時間,利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。

當從庫嘗試與主庫進行增量重同步時,會發送自己的 repl_offset,主庫在每個 replBufBlock 中記錄了該其第一個字節對應的 repl_offset,但如何高效地從數萬個 replBufBlock 的鏈表中找到特定的那個

**最終使用 rax 樹實現了對 replBufBlock 固定區間間隔的索引,每 64 個記錄一個索引點。**一方面,rax 索引占用的內存較少;另一方面,查詢效率也是非常高,理論上查找比較次數不會超過 100,耗時在 1 毫秒以內。

Radix樹:壓縮后的Trie樹,將不可分叉的單支分支合并,也就是壓縮。

熱Key

什么是熱key

1 、MySQL等數據庫會被頻繁訪問的熱數據

如爆款商品的skuId。

2 、redis的被密集訪問的key

如爆款商品的各維度信息,skuId、shopId等。

3 、機器人、爬蟲、刷子用戶

如用戶的userId、uuid、ip等。

4 、某個接口地址

如/sku/query或者更精細維度的。

5、 用戶id+接口信息

如userId + /sku/query,這代表某個用戶訪問某個接口的頻率。

6 、服務器id+接口信息

如ip + /sku/query,這代表某臺服務器某個接口被訪問的頻率。

7 、用戶id+接口信息+具體商品

如userId + /sku/query + skuId,這代表某個用戶訪問某個商品的頻率。

以上我們都稱之為有風險的key,注意,我們的熱key探測框架只關心key,其實就是一個字符串,隨意怎么組合成這個字符串由使用者自己決定,所以該框架具備非常強的靈活性,可以完成熱數據探測、限流熔斷、統計等多種功能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/64207.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/64207.shtml
英文地址,請注明出處:http://en.pswp.cn/web/64207.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Amazon Bedrock 實踐 - 利用 Llama 3.2 模型分析全球糖尿病趨勢

黃浩文 資深開發者布道師 亞馬遜云科技 擁有電信、互聯網以及云計算等行業超過 20 年的豐富經驗,曾任職于微軟、Sun 和中國電信。他目前專注于生成式 AI、大型語言模型 (LLM)、機器學習和數據科學等領域的技術內容創作和實踐分享,致力于賦能全球開發者。…

期權懂|如何計算期權賣方平倉后的盈利?

錦鯉三三每日分享期權知識,幫助期權新手及時有效地掌握即市趨勢與新資訊! 如何計算期權賣方平倉后的盈利? 期權賣方平倉后的盈利計算涉及多個因素,包括期權的交易價格、平倉價格以及權利金的變動等。 交易價格:期權賣…

【連續學習之VCL算法】2017年論文:Variational continual learning

1 介紹 年份:2017 期刊: arXiv preprint Nguyen C V, Li Y, Bui T D, et al. Variational continual learning[J]. arXiv preprint arXiv:1710.10628, 2017. 本文提出的算法是變分連續學習(Variational Continual Learning, VCL&#xf…

多視圖 (Multi-view) 與多模態 (Multi-modal)

多視圖 (Multi-view) 與多模態 (Multi-modal) 是兩種不同的數據處理方式,它們在機器學習和數據分析中有著重要的應用。盡管這兩者有一些相似之處,但它們關注的角度和處理方法有所不同。 多視圖 (Multi-view) 定義:多視圖指的是同一數據對象…

MySQL 性能瓶頸,為什么 MySQL 表的數據量不能太大?

MySQL的性能瓶頸(為什么MySQL有幾萬的qps,怎么來的?性能分析 為什么 MySQL 表不能太大網上大部分人的說法:問題的關鍵: B樹層數對查詢性能的影響到底有多大? 是什么導致的 MySQL 查詢緩慢?如何解決: MySQL的性能瓶頸(為什么MySQL有幾萬的qps,怎么來的? 一個全表掃描的查詢…

Linux 實用命令 grep、wc

grep 命令詳解 grep [選項] ‘模式’ 文件名 grep [參數] [選項] [操作對象]grep ‘error’ -c 5 --color info.log [模式]:是要搜索的字符串或正則表達式。 [選項]:是可選的,用于定制grep的行為。 [操作對象]:是要搜索的文件…

【Transformer】深入淺出自注意力機制

寫在前面:博主本人也是剛接觸計算機視覺領域不久,本篇文章是為了記錄自己的學習,大家一起學習,有問題歡迎大家指出。(博主本人的習慣是看文章看到不懂的有立馬去看不懂的那塊,所以博文可能內容比較雜&#…

HarmonyOS NEXT 實戰之元服務:靜態案例效果---教育培訓服務

背景: 前幾篇學習了元服務,后面幾期就讓我們開發簡單的元服務吧,里面豐富的內容大家自己加,本期案例 僅供參考 先上本期效果圖 ,里面圖片自行替換 效果圖1完整代碼案例如下: import { authentication } …

3.阿里云flinkselectdb-py作業

1.概述 Python API中文文檔 本文介紹在阿里云實時計算flink中使用python作業,把oss中的數據同步數據到阿里云selectdb的過程。python簡單的語法特性更適合flink作業的開發; 先說結論: 在實際開發中遇到了很多問題,導致python作業基本基本無法…

互聯網視頻云平臺EasyDSS無人機推流直播技術如何助力野生動植物保護工作?

在當今社會,隨著科技的飛速發展,無人機技術已經廣泛應用于各個領域,為我們的生活帶來了諸多便利。而在動植物保護工作中,無人機的應用更是為這一領域注入了新的活力。EasyDSS,作為一款集視頻處理、分發、存儲于一體的綜…

51c視覺~YOLO~合集8

我自己的原文哦~ https://blog.51cto.com/whaosoft/12897680 1、Yolo9 1.1、YOLOv9SAM實現動態目標檢測和分割 主要介紹基于YOLOv9SAM實現動態目標檢測和分割 背景介紹 在本文中,我們使用YOLOv9SAM在RF100 Construction-Safety-2 數據集上實現自定義對象檢測模…

Docker Container 可觀測性最佳實踐

Docker Container 介紹 Docker Container( Docker 容器)是一種輕量級、可移植的、自給自足的軟件運行環境,它在 Docker 引擎的宿主機上運行。容器在許多方面類似于虛擬機,但它們更輕量,因為它們不需要模擬整個操作系統…

氣相色譜-質譜聯用分析方法中的常用部件,分流平板更換

分流平板,是氣相色譜-質譜聯用分析方法中的一個常用部件,它可以實現氣相色譜柱流與MS檢測器流的分離和分流。常見的氣質聯用儀分流平板有很多種,如單層T型分流平板、雙層T型分流平板、螺旋分流平板等等。 操作視頻http://www.spcctech.com/v…

易基因: BS+ChIP-seq揭示DNA甲基化調控非編碼RNA(VIM-AS1)抑制腫瘤侵襲性|Exp Mol Med

大家好,這里是專注表觀組學十余年,領跑多組學科研服務的易基因。 肝細胞癌(hepatocellular carcinoma,HCC)早期復發仍然是一個具有挑戰性的領域,其中涉及的機制尚未完全被理解。盡管微血管侵犯&#xff08…

鴻蒙系統文件管理基礎服務的設計背景和設計目標

有一定經驗的開發者通常對文件管理相關的api應用或者底層邏輯都比較熟悉,但是關于文件管理服務的設計背景和設計目標可能了解得不那么清楚,本文旨在分享文件管理服務的設計背景及目標,方便廣大開發者更好地理解鴻蒙系統文件管理服務。 1 鴻蒙…

如何配置 Java 環境變量:設置 JAVA_HOME 和 PATH

目錄 一、什么是 Java 環境變量? 二、配置 Java 環境變量 1. 下載并安裝 JDK 2. 配置 JAVA_HOME Windows 系統 Linux / macOS 系統 3. 配置 PATH Windows 系統 Linux / macOS 系統 4. 驗證配置 三、常見問題與解決方案 1. 無法識別 java 或 javac 命令 …

Doris 數據庫外部表-JDBC 外表,Oracle to Doris

簡介 提供了 Doris 通過數據庫訪問的標準接口 (JDBC) 來訪問外部表,外部表省去了繁瑣的數據導入工作,讓 Doris 可以具有了訪問各式數據庫的能力,并借助 Doris 本身的 OLAP 的能力來解決外部表的數據分析問題: 支持各種數據源接入…

分布式 IO 模塊助力沖壓機械臂產線實現智能控制

在當今制造業蓬勃發展的浪潮中,沖壓機械臂產線的智能化控制已然成為提升生產效率、保障產品質量以及增強企業競爭力的關鍵所在。而分布式 IO 模塊的應用,正如同為這條產線注入了一股強大的智能動力,開啟了全新的高效生產篇章。 傳統挑戰 沖壓…

CSS系列(37)-- Overscroll Behavior詳解

前端技術探索系列:CSS Overscroll Behavior詳解 📱 致讀者:探索滾動交互的藝術 👋 前端開發者們, 今天我們將深入探討 CSS Overscroll Behavior,這個強大的滾動行為控制特性。 基礎概念 🚀 …

深度學習中的并行策略概述:4 Tensor Parallelism

深度學習中的并行策略概述:4 Tensor Parallelism 使用 PyTorch 實現 Tensor Parallelism 。首先定義了一個簡單的模型 SimpleModel,它包含兩個全連接層。然后,本文使用 torch.distributed.device_mesh 初始化了一個設備網格,這代…