redis學習總結

gooogle發表於2021-01-14

@TOC

redis資料結構原理

待整理~

redis持久化

RDB持久化

  1. 執行流程
  • 父程式執行fork操作建立子程式,這個過程中父程式是阻塞的,Redis不能執行來自客戶端的任何命令。
  • 子程式建立RDB檔案,根據父程式記憶體快照生成臨時快照檔案,完成後對原有檔案進行原子替換,替換後子程式訊號通知主程式
  1. rdb自動持久化配置:
    時間策略要按照實際情況配置多條,資料的儲存時不均勻的,高峰期短時間間隔要存一次,低峰期長時間間隔要存一次。
    # 檔名稱
    dbfilename dump.rdb
    

save 900 1
save 300 10
save 60 10000

dir /etc/redis/data/

stop-writes-on-bgsave-error yes

rdbcompression yes

rdbchecksum yes

3. rdb策略將記憶體中的資料生成快照儲存到磁碟,是全量儲存,記憶體大的話會比較耗時,大量磁碟io。
4. 持久化的觸發方式:
- save命令:client向server傳送save命令,同步阻塞。
- bgsave命令:server中執行命令,非同步子程式執行。
- 自動持久化,透過save配置項完成。
5. rdb檔案恢復:如果開啟了aof,則不生效。如果沒開啟aof,則尋找dir配置下的rdb檔案。
### AOF持久化
1. aof同步步驟:
- Redis收到寫命令後首先會追加到AOF緩衝區aof_buf(write)
- 透過一定時機(配置決定),呼叫系統函式 fsync()AOF緩衝區的資料刷到磁碟
2. 落盤時機配置(fsync):
always: 命令寫入aof緩衝區後立即呼叫系統fsync操作同步到AOF檔案,資料絕對安全。
no:有作業系統決定何時呼叫fsync()
everysec:每秒調fsync()一次。 若損失資料只損失1秒,對於大多數系統來說足夠了。
3. 檔案重寫rewrite
- 由父程式fork子程式進行重寫
- 使用寫時重寫技術,在重寫完成期間,Redis的寫命令同時追加到aof_buf和aof_rewirte_buf兩個緩衝區。
- 重寫檔案完成後,將aof_rewirte_buf資料輸入新檔案
- 用新aof檔案替換老aof檔案。

## redis叢集三種模式
### 主從模式(實現主從分離,提高吞吐,多機備份)
從資料庫一般都是隻讀的,並且接收主資料庫同步過來的資料
要點:
1. 主從複製還是哨兵和叢集能夠實施的基礎,主從複製是Redis高可用的基礎。
2. 再看CAP,由於redis複製是非同步複製,會導致短時的資料不一致,所以無法滿足一致性C,但可以保證當網路分割槽發生時,各個節點依舊可用。redis主從模式是CP,而不是AP。redis會使用最終一致性策略,保證主從同步資料一致。
3. 主從複製實現原理:
- 6大步驟: 1. 設定主節點ip及埠、2. 建立套接字socket、 3. 傳送ping命令、 4. 許可權驗證 、 5. 同步 6. 命令傳播
- 設定主節點ip及埠: 使用slaveof命令,可配置檔案使用、可命令列使用
- 複製階段:從節點向主節點傳送psync或sync命令(2.8版本之前),可以實現全量複製與增量複製
- 命令傳播:當透過複製階段後,主從節點進入命令傳播階段,主節點將自己執行的寫命令傳送給從節點,從節點接收命令並執行,從而保證主從節點資料的一致性。命令傳播是非同步的,主節點並不會等從節點同步執行完命令,這樣會導致“延遲不一致”現象。
4. 延遲不一致現象:網路波動、寫命令頻率過高,會導致資料延遲不一致。同時,repl-disable-tcp-nodelay配置也會影響,設定為yes,會將代發資料合併傳送,延遲大概40ms(取決於系統),如果設定為no,寫命令實時傳送同步。
5. 全量複製和部分複製
- 全量複製:用於初次複製或其他無法進行部分複製的情況,將主節點中的所有資料都傳送給從節點,是一個非常重型的操作
- 部分複製:用於網路中斷等情況後的複製,只同步網路斷開期間的快取寫資料,如果網路中斷時間過長,導致主節點沒有能夠完整地儲存中斷期間執行的寫命令,則無法進行部分複製,仍使用全量複製。
6. 全量複製原理剖析:
fork子程式,開始執行bgsave,生成RDB檔案,同時開闢緩衝區記錄從現在開始的寫命令。2. 將rdb檔案傳輸給從伺服器,從伺服器執行完畢後,主節點將緩衝區寫資料同步到從節點,保證最終一致性。3. 如果從節點開啟了AOF,會觸發bgrewriteof,從而保證從節點的aof檔案最新。
7. 部分複製原理剖析:
- 複製偏移量:與mysql類似,主從節點各自維護offset欄位,不一致則複製
- 複製積壓緩衝區:底層結構是定長、先進先出FIFO的佇列,可以repl-backlog-size引數設定大小。 當主從節點offset的差距過大超過緩衝區長度時,將無法執行部分複製,只能執行全量複製。
- 伺服器執行id(runid):每個從節點都儲存著主節點同步下來的runid,當網路分割槽發生時,重連後slave節點會判斷同步的runid是否存在,如果存在優先考慮增量複製,如果不存在,則全量複製。
8. psync命令複製原理:主節點收到psync命令後,進行判斷,如果命令為psync命令,則執行全量複製。如果收到的為psync {runid} {offset}命令,則執行增量複製。執行增量複製過程中如果offset差超過了buffer,則執行全量複製。
9. 心跳檢測機制及其作用:1. 檢測主從連結 2. 輔助實現min-slaves選項 3. 檢測命令丟失,保證資料一致
- 透過判斷線上的從節點數量,實現min-slaves。
min-slaves概念,保證高可用:
未達到下面兩個條件時,寫操作就不會被執行
min-slaves-to-write 3 #最少包含的從伺服器
min-slaves-max-lag 10 #延遲值
- 心跳會返回offset資訊,透過offset判斷從節點的資料同步是否及時,不及時則透過offset補發資料
10. 主從複製超時原理:斷開連線後會嘗試重連。
- 主節點判斷超時:每秒1次呼叫複製定時函式replicationCron(),如果時間大於到上次REPLCONF ACK的時長,則斷開連線,釋放資源(緩衝器、連線、頻寬),超時時間由引數repl-timeout值控制。
- 從節點判斷超時:主要也是由repl-timeout引數控制。1. 連線建立階段,若大於時間則斷開連線。 2. 全量複製階段:如果收到rdb的時間超時,則斷開連線。 3. 命令傳播階段:如果收到主節點ping的時間過長,超過timeout,則斷開連線。
- 如果rdb檔案過大,會導致一直同步失敗,會無線重連,應適當調大timeout值
11. 主庫掛了發生什麼?
不影響slave的讀,但redis不再提供寫服務,master重啟後redis將重新對外提供寫服務。
如果slave-serve-stale-data引數設定為yes,主節點掛掉後從節點可以繼續提供服務,如果設定為no,主節點掛掉後從節點不再提供讀資料服務,僅提供info、slaveof等少量命令。在強一致場景需要考慮設定為no,如分散式鎖,如果主節點掛掉,資料沒來得及同步從節點,會導致從節點讀不到,鎖失效。
重啟master節點需要保證rdb檔案或者aof檔案是最新。
12. redis如何保證主從伺服器連線正常且資料最終一致?
命令傳播階段,從伺服器會利用心跳檢測機制定時的向主服務傳送訊息。
13. 如果提高資料實時一致性?
最佳化主從節點之間的網路環境(如在同機房部署);監控主從節點延遲(透過offset)判斷,如果從節點延遲過大,通知應用不再透過該從節點讀取資料;
14. java客戶端連線redis如何實現讀寫分離,讀負載均衡?
常見的客戶端有jredis,lettuce,redission。以lettuce為例,可以使用

```java
StatefulRedisMasterSlaveConnection connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisURI);
connection.setReadFrom(ReadFrom.NEAREST);
// MASTER 主讀
// SLAVE 從讀
// MASTER_PREFERRED 優先master -> slave
// SLAVE_PREFERRED 優先slave -> master
// NEAREST 最近節點讀
// 實現很簡單,重寫ReadFrom的select方法,自帶的方法均無法實現負載均衡
static final class ReadFromSlavePreferred extends ReadFrom {

        @Override
        public List<RedisNodeDescription> select(Nodes nodes) {

            List<RedisNodeDescription> result = new ArrayList<>(nodes.getNodes().size());
            //優先新增slave節點
            for (RedisNodeDescription node : nodes) {
                if (node.getRole() == RedisInstance.Role.SLAVE) {
                    result.add(node);
                }
            }
            //最後新增master節點
            for (RedisNodeDescription node : nodes) {
                if (node.getRole() == RedisInstance.Role.MASTER) {
                    result.add(node);
                }
            }
            return result;
        }
// 自定義負載均衡
@Bean(destroyMethod = "close")
   StatefulRedisMasterSlaveConnection<String, String> statefulRedisMasterSlaveConnection(RedisClient redisClient, RedisURI redisURI) {
       StatefulRedisMasterSlaveConnection connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisURI);
       connection.setReadFrom(new ReadFrom() {
           @Override
           public List<RedisNodeDescription> select(Nodes nodes) {
               List<RedisNodeDescription> list = nodes.getNodes();
               Collections.shuffle(list);
               return list;
           }
       });
       return connection;
   }

哨兵模式(主從高可用,自動化修復)

  1. 高可用架構一般將sentinel與redis例項部署在不同的伺服器上。
  2. 工作機制:每個sentinel以每秒鐘一次的頻率向它所知的master,slave以及其他sentinel例項傳送一個 PING 命令 。如果響應超過 down-after-milliseconds,則標記為主觀下線。其它sentinel以每秒鐘一次的頻率ping主觀下線的例項,如果足夠多的(透過配置指定)sentinel確認該例項的主觀下線狀態,則置為客觀下線。Sentinel 和其他 Sentinel 協商 主節點 的狀態,如果 主節點 處於 SDOWN 狀態,則投票自動選出新的 主節點。將剩餘的 從節點 指向 新的主節點 進行 資料複製
  3. 當使用sentinel模式的時候,客戶端就不要直接連線Redis,而是連線sentinel的ip和port,由luttuce自動獲取redis叢集拓撲,從而獲取master節點與slave節點的資訊。並透過監聽redis事件更新拓撲資訊。
  4. 哨兵模式切換主節點導致資料不一致,“腦裂問題”的解決方案:
  • 當網路分割槽發生時,會導致master下線,實際master沒有掛,選舉出新的master後會出現兩個master,當網路分割槽修好後,舊master會變為slave同步新master的資料,導致資料丟失。
    配置:
    min-slaves-to-write 1 # 必須至少要有1個slave
    min-slaves-max-lag 10 # 資料複製和同步延遲不能超過10秒
    不符合以上情況,master將拒絕接受請求。將資料丟失控制在10秒。
    從業務上看,及其重要的資料,可以進行處理:
    進redis前可以在其它地方臨時儲存。
    可以加閘道器,降低流入速度,防止瞬間進入過多資料。

    叢集模式(資料分片,解決了寫操作無法負載均衡,單機容量儲存、併發問題)

  1. 一致性hash與hash槽
  • 簡單hash演算法,透過計算hash值 % 機器數量,得到資料的實際對應節點。當增加或刪除節點時,會導致大量的快取失效,甚至導致雪崩。
  • 一致性雜湊演算法中,整個雜湊空間是一個虛擬圓環,共計2^32 - 1 個節點。將每個redis節點的ip地址進行hash計算後落位虛擬節點。每條資料透過計算hash值後順時針落位對應節點。當某個節點增、刪、不可用時,只會影響下一個節點,而不會影響所有節點。
    一致性hash演算法的問題是會導致資料傾斜,如果節點數過少,在盤中的分佈又不均勻,可能會導致所有資料都落在極少的節點上。
  • 雜湊槽,redis包含了16384個雜湊槽,每個 key 透過計算後都會落在具體一個槽位上,而這個槽位是屬於哪個儲存節點的,則由使用者自己定義分配。
    對於槽位的轉移和分派,redis 叢集是不會自動進行的,而是需要人工配置的。所以 redis 叢集的高可用是依賴於節點的主從複製與主從間的自動故障轉移。
  1. redis-cluster是redis叢集的官方實現版,具有投票、容錯性等特點。
  • 叢集中可以有多個master節點,需要保證所有master節點恰好覆蓋所有hash槽,否則叢集不能啟動。新增、刪除節點,只需要更改對應的雜湊槽範圍。
    node1 : [1,15000]
    node2 : [15001,20000]
  • 何時master節點算掛掉?
    半數以上master節點與master節點通訊超時(cluster-node-timeout),認為當前master節點掛掉,如果此時該master節點由slave節點,則叢集依舊正常執行,對應掛掉的節點不再提供寫入服務。
  • 何時叢集掛掉?

1.只要某個槽段失效,叢集就會進入不可用狀態。主、從都掛。
2.超過半數的master節點掛掉,不管slave是否正常,叢集不可用。

  • 一般情況,各個槽段的主、從redis節點可以交叉部署,部署在不同的伺服器上。

redis應用場景

分散式鎖

  1. 單機版分散式鎖:
  • 加鎖終極版:set nx px + 事務id
  • 解鎖終極版:使用lua指令碼實現。
    if redis.call(“get”,KEYS[1]) == ARGV[1] then
    return redis.call(“del”,KEYS[1])
    else
    return 0
    end
  • 為何解鎖需要用lua,而加鎖不需要?
    加鎖過程,set命令的 nx 引數 = set if not exist,get value->判斷不存在->set value。set 命令的 px引數,設定過期時間。相當於三條命令保持原子性。
    解鎖過程,get獲取鍵值,判斷value是否相等,如果此時JVM

redis架構思維

待整理~

本作品採用《CC 協議》,轉載必須註明作者和本文連結