天天看點

remote dictionary server資料類型内置政策釋出訂閱資料持久化主從複制sentinelRedis-Cluster緩存問題

資料類型

字元串類型

字元串類型是二進制安全的,能存儲包括二進制資料等任何形式的字元串,一個字元類型鍵允許存儲的最大容量是512M。

當redis遇到incr,decr等操作時會轉成數值型進行計算。

在Redis内部,String類型通過 int、SDS(simple dynamic string)作為結構存儲,int用來存放整型資料,sds存放位元組/字元串和浮點型資料。

hash類型

Hash是一個健值對集合,類似于在redis中存儲對象,Hash結構可以使你像在資料庫中 Update一個屬性一樣隻修改某一項屬性值。

用hash類型可以儲存結構化的資料,而使用String的話,則需要增加對字元串進行序列化與反序列化的步驟。

清單類型

3.2版本後采用quicklist結構存儲list,quicklist是一個雙向連結清單,其每個節點都是一個ziplist,相當于linkedlist和ziplist的結合。

雙向連結清單在連結清單兩端進行push和pop操作,在插入節點上複雜度比較低,但是記憶體開

銷比較大。

當list的元素個數和單個元素的長度比較小的時候,ziplist可以減少記憶體占用。

ziplist存儲在一段連續的記憶體上,存儲效率很高,但是插入和删除都需要頻繁申請和釋放記憶體。

使用List結構可以實作熱點資料排行等功能,例如視訊網站可以用來存儲最新視訊資訊。

集合類型

set類型中不能有重複資料并且集合中的資料是無序的,内部結構是是hashtable,查找操作的時間複雜度都是O(1)。

set可以用于存儲使用者關注的人以及使用者粉絲等。使用Redis為集合提供的求交集、并集、差集等操作,可以實作如共同關注的人等功能。

有序集合

sorted set,相比set的差別就是多了有序的功能。

有序集合為每個元素關聯了一個分數,可以用來做使用者得分排行功能,還能獲得分數最高(或最低)的前N個元素、獲得指定分數範圍内的元素等與分數有關的操作。

内置政策

過期删除政策

redis 删除過期存儲項的政策主要有兩種。

消極方式: 在主鍵被通路時如果發現它已經失效,那麼就删除它。

積極方式: 周期性地從設定了失效時間的主鍵中選擇一部分失效的主鍵删除。           

消極方式的隐患是,如果一個key過期了但是之後從來沒有被通路,就會導緻資源浪費。積極方式則是周期性地随機測試一些key,将已過期的key删掉。Redis每秒會進行10次操作,如下:

1. 随機測試 20 個帶有timeout資訊的key;
2. 删除其中已經過期的key;
3. 如果超過25%的key被删除,則重複執行步驟1;           

redis同時使用了積極和消極兩種方式。

記憶體淘汰政策

記憶體淘汰政策用于訓示redis記憶體不足時如何處理,淘汰政策如下:

noeviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯

allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的key

allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,随機移除某個key

volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的key

volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,随機移除某個key

volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的key優先移除           

預設政策:

# The default is:
# maxmemory-policy noeviction           

需要注意的是,redis的最近最少算法是基于采樣計算的,并不完全準确,即被淘汰的鍵可能并不是真正的最近最少使用的,因為在所有的資料中搜尋最近最少無疑會增加系統的開銷。

釋出訂閱

redis釋出訂閱中,釋出者向指定的頻道發送消息,所有訂閱此頻道的訂閱者都會收到該消息,訂閱者可以訂閱多個頻道。

釋出者釋出消息:

publish channel_name message           

例如:

publish cctv hello # 向cctv頻道發送“hello”           

訂閱者訂閱消息:

subscribe channel [channel …]           
subscribe cctv           

執行subscribe指令後用戶端會進入訂閱狀态并接收消息,但是消息不會持久化,是以訂閱者不會收到訂閱之前的消息。

channel分為普通channel和pattern channel。例如釋出者釋出了一條消息:

publish abc hello           

redis會發給所有訂閱abc這個普通channel的訂閱者,同時還會發送給pattern channel *bc上的所有訂閱者。

值得注意的是,相比于其他專業釋出訂閱中間來說,redis釋出訂閱支援的協定少、不能持久化、不支援復原、不支援可靠性投遞以及消息确認,如果需要以上特性則需使用專業消息中間件。

資料持久化

redis支援RDB和AOF(append-only-file)兩種方式。兩種持久化方式可以單獨使用,也可以結合使用。

RDB方式

當符合預定規則時,redis會fork一個與目前程序完全相同的子程序将資料寫入到一個臨時檔案中,然後将這個臨時檔案替換掉上次持久化好的檔案。整個過程中,主程序不進行任何IO操作,保證了主程序性能。

政策配置:

save 900 1
save 300 10
save 60 10000

dbfilename dump.rdb # 檔案名稱
dir /home/work/app/redis/data/ # 檔案儲存路徑

stop-writes-on-bgsave-error yes
rdbcompression no # 是否開啟壓縮           

預設規則表示如果900s内有1條寫入,或300秒内有10條寫入,或60秒内有10000條寫入,就觸發一次快照操作。因為redis各時段讀寫請求不是均衡的,使用者還可以自由定制配置規則。

stop-writes-on-bgsave-error

表示當備份程序出錯時,主程序是否停止接受新的寫入操作,這是為了保障持久化資料一緻性問題。

RDB方式的缺點是,當伺服器當機後,redis會丢失最後一次持久化後的資料。但是如果進行大規模資料恢複,且資料的完整性不是很敏感,RDB方式比AOF方式更高效。

RDB方式的備份觸發分為手動執行指令觸發和自動觸發,手動觸發指令如下:

save:阻塞目前Redis伺服器,直到持久化完成,線上禁止使用

bgsave:fork一個子程序進行持久化操作,隻在fork子程序時阻塞           

自動觸發場景基本如下:

根據 save m n 配置規則觸發

從節點全量複制時,主節點觸發bgsave,然後将備份後的rdb檔案發給從節點

執行flushall清除redis在記憶體中的所有資料時,如果save規則不為空,就會執行一次快照,如果沒有save規則不會執行快照

執行shutdown時,如果沒有開啟aof,也會觸發           

關閉RBD:

#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""           

配置處寫

save ""

即可。

AOF方式

AOF方式可以最大化的降低程序終止時導緻的資料丢失數量。AOF方式将Redis執行

的每條寫指令追加到硬碟檔案中。

相關配置:

appendonly yes # 開啟aof,預設為no

appendfilename "appendonly.aof" # 檔案名稱

appendfsync everysec

no-appendfsync-on-rewrite no # aof重寫期間是否同步

# 重寫觸發配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加載aof時如果有錯如何處理
aof-load-truncated yes

# 檔案重寫政策
aof-rewrite-incremental-fsync yes           

appendfsync

有三種模式:

always:把每個寫指令都立即同步到aof,慢,但是很安全

everysec:每秒同步一次,是折中方案

no:redis不處理交給OS來處理,快,但是也最不安全           

一般采用everysec配置兼顧速度與安全,最多損失1s的資料。

aof-load-truncated

如果為yes,在加載時發現aof尾部不正确是,會向用戶端寫入log然後繼續執行,如果為no,發現錯誤立即停止,必須修複後才能重新加載。

aof檔案的儲存位置和RDB檔案位置相同,都通過dir參數設定的,預設檔案名

apendonly.aof

AOF實作

AOF檔案以純文字記錄redis執行的寫指令,例如:

set test 1
set test 2
set test 3
get test           

前3條寫指令會儲存到AOF檔案。但是這3條指令中的前2條指令其實是不必要的,但是依然會被記錄下來。随着記錄的指令越來越多,AOF檔案的大小會越來越大,但是記憶體中實際的資料可能沒有多少。無效的指令記錄導緻磁盤空間浪費以及redis資料還原的過程低效的問題。

可以通過配置規則重寫AOF檔案:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb           

auto-aof-rewrite-percentage

表示當目前的AOF檔案大小超過上一次重寫時的百分之多少時會再次進行重寫,如果之前沒有重寫過,則以啟動時AOF檔案大小為依據。

auto-aof-rewrite-min-size

表示限制允許重寫的最小AOF檔案大小,因為AOF檔案很小時即使其中有很多備援指令我們也不太關心。

AOF重寫後的新AOF檔案包含了目前資料集所需的最小指令集合。重寫時,主程序會fork一個子程序執行重寫操作,重寫時并不基于原有aof檔案進行整理,而是全量周遊記憶體中的資料,然後逐個序列到aof檔案中。

在fork子程序這個過程中,服務端仍能對外提供服務,在此過程中,主程序的資料更新操作都會緩存到

aof_rewrite_buf

中,當子程序重寫完後再把緩存中的資料追加到新的aof檔案。

當所有的資料全部追加到新的aof檔案中後,把新的aof檔案重命名為

appendfilename

配置中的名稱,此後所有的操作都會被寫入新的aof檔案。這樣在rewrite過程中如果出現故障,不會影響原aof檔案的正常工作。

aof重寫的操作也分為手動觸發和自動觸發,手動觸發則為執行指令:

bgrewriteaof           

自動觸發則是根據配置規則來觸發。

資料恢複

重新開機redis,例如故障恢複時會自動加載恢複檔案進行資料恢複,如果一台伺服器上同時存在RDB檔案和AOF檔案,AOF檔案的優先級要高于RDB檔案,因為一般情況下AOF檔案儲存的資料完整性要高于RDB檔案。即啟動時會先檢查AOF檔案,如果AOF檔案不存在就嘗試加載RDB。

主從複制

master/slave模式,master進行讀寫操作,當寫操作導緻資料發生變化時會自動将資料同步給slave。一般slave被設定為隻讀屬性。

主從複制分為全量複制和增量複制。

全量複制

slave在初始化時會進行一次全量複制,具體流程為slave向master發起資料同步請求,master收到請求後立即進行bgsave,将生成的快照發送給slave進行加載,master對在gbsave過程中的寫請求會單獨記錄,然後将記錄發給slave進行同步。

slave初始化成功後就能接收來自使用者的讀請求了。

redis的主從同步采用異步方式,master執行完用戶端的寫請求後會立即傳回結果給用戶端,然後異步的方式把指令同步給slave,來特征保證master性能不受主從複制影響。

如果在資料同步至slave期間出現網絡分區,此時master無法知道有多少個slave同步成功了。這種情況可以通過哨兵來緩解。

主從相關配置:

# It is possible for a master to stop accepting writes if there are less than
# N replicas connected, having a lag less or equal than M seconds.
#
# The N replicas need to be in "online" state.
#
# The lag in seconds, that must be <= the specified value, is calculated from
# the last ping received from the replica, that is usually sent every second.
#
# This option does not GUARANTEE that N replicas will accept the write, but
# will limit the window of exposure for lost writes in case not enough replicas
# are available, to the specified number of seconds.
#
# For example to require at least 3 replicas with a lag <= 10 seconds use:
#
min-replicas-to-write 3
min-replicas-max-lag 10           

增量複制

每次同步後,slave會記錄同步偏移量offset,下次同步會從offset處開始同步,而不是從頭開始的全量同步。

master會在記憶體中建立一個replication backlog,master和slave都會儲存一個replica offset和master id。如果master和slave網絡重新連接配接,slave會讓master從上次的replica offset開始繼續複制,但是如果沒有找到對應的offset,那麼就會執行一次全量複制。

例如檢視master資訊:

# Replication
role:master
connected_slaves:2
slave0:ip=192.168.47.130,port=6379,state=online,offset=14,lag=1
slave1:ip=192.168.47.128,port=6379,state=online,offset=14,lag=1
master_replid:034a4acf5e3ffd21e8c07d1b9605d44924c452f2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14           

slave資訊:

# Replication
role:slave
master_host:192.168.47.128
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:638f8e23b6d4313147b605e2a7a7330b68143542
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14           

replication backlog

:複制積壓隊列,一個固定大小的循環隊列。循環隊列就是說插入資料時,一旦到達了積壓隊列的尾部,則重新從頭部開始插入,覆寫最早插入的内容。

master_repl_offset

:全局性的計數器,每次收到用戶端發來長度為

len

的請求指令時,

master_repl_offset

就會增加

len

,這就是master的複制偏移量。當slave收到master傳回的消息後,會将該值儲存為自己的

master_repl_offset

增量複制時,master收到用戶端的指令請求,則将指令的長度增加到

master_repl_offset

,然後将指令傳播給從節點,slave收到後,也會将指令長度加到master_repl_offset,進而保證了master和slave的複制偏移量的一緻性。

repl_backlog_size

:積壓隊列的總容量。

repl_backlog_histlen

:積壓隊列中目前累積資料量的大小。該值不會超過積壓隊列的總容

sentinel

哨兵。主從模式下,當master故障後,需要重新選舉新的master,redis通過哨兵機制來完成這一過程。哨兵是一個或多個獨立的程序,用于監控redis的運作狀況,master故障時選舉出新的master。

remote dictionary server資料類型内置政策釋出訂閱資料持久化主從複制sentinelRedis-Cluster緩存問題

哨兵叢集模式下,哨兵不僅會監控master和slave,還會互相監控。多個哨兵會在master節點訂閱同一個頻道,并向該頻道注冊自己的資訊進而達到互相發現。新加入的哨兵會和其他節點建立長連接配接通信。

哨兵會定期向master節點發送心跳包來判斷存活狀态,一旦master節點沒有正确響應,哨兵會把master設定為“主觀不可用狀态”,然後它會把“主觀不可用”發送給其他的哨兵去确認,當确認的哨兵數大于>quorum時,則會認為是“客觀不可用”,然後開始選舉新master流程。

quorum配置:

# sentinel parallel-syncs <master-name> <numreplicas>
#
# How many replicas we can reconfigure to point to the new replica simultaneously
# during the failover. Use a low number if you use the replicas to serve query
# to avoid that all the replicas will be unreachable at about the same
# time while performing the synchronization with the master.
sentinel monitor mymaster 192.168.47.129 6379 1           

哨兵節點中通過raft算法投票選舉出leader來做決策,類似paxos算法,隻要保證過半數節點通過提議即可。

Redis-Cluster

Redis Cluster由多個Redis節點組構成,節點組内分為master和slave兩類節點,隻有master能對外提供寫服務,slave為隻讀服務。每個節點組就是一個分片。

redis-cluster是基于gossip的去中心化的叢集,各個節點對整個叢集狀态的認知來自于節點之間的資訊互動。在Redis Cluster,這個資訊互動是通過Redis Cluster Bus來完成的。

Redis Cluster資料分區規則采用虛拟槽分區方式,使用分散度良好的哈希函數把所有的資料映射到一個固定範圍内的整數集合,整數定義為槽(slot),槽是叢集内資料管理和遷移的基本機關,采用槽可以友善資料的拆分和叢集的擴充。Redis Cluster槽的範圍是0~16383。

hash tag

通過分片手段,可以将資料合理的劃分到不同的節點上,但是有時我們希望對相關聯的業務key存在同一個分片上。

例如單節點上執行MSET,它是一個原子性的操作,所有給定的key會在同一時間内被設定,不可能出現某些指定的key被更新另一些指定的key沒有改變的情況。但是在叢集環境下不再是原子操作,可能存在某些key更新失敗情況,因為有些key可能會被配置設定到不同的機器上。

根據分片規則,我們要求key盡可能的分散在不同機器,但業務上我們又需要某些相關聯的key配置設定到相同機器。

因為分片是對key做hash取模然後配置設定機器,在redis中引入了HashTag的概念,可以使hash算法根據key的某一個部分進行計算,讓相關的key落到同一個資料分片。例如對于以下key:

users-zhangsan-fans
users-zhangsan-follows           

使用hashtag:

users-{zhangsan}-fans
users-{zhangsan}-follows           

當一個key包含 {} 的時候,此時不對整個key做hash,而僅對

{}

包括的字元串做hash,這時能達到把這些key配置設定到相同機器的目的。

重定向

Redis Cluster不會代理查詢,如果用戶端通路了一個不存在指定key的節點,該節點會傳回如下資訊:

-MOVED 1256 192.168.47.129:6379           

表示1256槽在IP為

192.168.47.129

,端口

6379

的redis執行個體上;如果根據key計算出的槽恰好在目前節點,則立即傳回結果。

分片遷移

在某些情況下,節點和分片映射關系會變化:

新加入master節點時

某個節點當機時           

此時需要将16384個槽重新配置設定,槽中的鍵值也要遷移。遷移過程中不必當機,但會出現不穩定狀态,需要遷入的slot狀态為IMPORTING,需要遷出的slot狀态為MIGRATING。

為了保證slot資料的一緻性,如果用戶端的通路的Key還沒有遷移出去,則正常處理該請求。如果key已經遷移或者不存該key,則回複用戶端ASK資訊去跳轉到其他節點查詢。

當來自用戶端的正常通路不是從ASK跳轉過來的,說明用戶端還不知道遷移正在進行,很有可能操作了一個目前還沒遷移完成的key,會傳回MOVED指令讓用戶端重定向。這樣保證了同一個key在遷移前總是在源節點上執行,遷移後總是在目标節點上執行。

緩存問題

緩存雪崩

緩存雪崩是指大量緩存在同一時刻同時失效,一般是采用了相同的過期時間或伺服器當機導緻。緩存失效後,請求全部打到了資料庫,資料庫由于瞬間壓力增大而導緻崩潰。

解決方式:

對緩存的通路,如果發現從緩存中取不到值,通過加鎖或隊列方式保證加載緩存時為單程序操作

将緩存失效的時間分散,降低每一個緩存過期時間的重複率

如果是因為緩存伺服器故障導緻的問題,需要提高伺服器的可用性           

緩存穿透

緩存穿透是指查詢根本不存在的資料,這時緩存和資料源都不會命中。緩存穿透可能會使後端資料源負載加大甚至當機。

如果查詢資料庫也為空,直接設定一個預設值存放到緩存,這樣第二次到緩沖中擷取就有值了,而不會繼續通路資料庫

根據緩存資料Key的設計規則,将不符合規則的key進行過濾,例如采用布隆過濾器,将所有可能存在的資料哈希到一個足夠大的BitSet中,不存在的資料将會被攔截掉,進而避免對底層存儲系統的查詢壓力           

布隆過濾器主要用來判斷一個元素是否在集合中存在。因為是機率型的算法,是以也會存在一定的誤差,如果傳入一個值去布隆過濾器中檢索,可能會出現檢測存在的結果但是實際上可能是不存在的。

繼續閱讀