Redis核心概念

image.png

概述

Redis 與其他 key - value 緩存產(chǎn)品有以下三個特點(diǎn):

  1. Redis支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟的時候可以再次加載進(jìn)行使用。
  2. Redis不僅僅支持簡單的key-value類型的數(shù)據(jù),同時還提供list,set,zset,hash等數(shù)據(jù)結(jié)構(gòu)的存儲。
  3. Redis支持?jǐn)?shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份。
概念 說明
Redis 優(yōu)勢 1. 性能極高– Redis能讀的速度是110000次/s,寫的速度是81000次/s 。

2. 豐富的數(shù)據(jù)類型 – Redis支持二進(jìn)制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 數(shù)據(jù)類型操作。

3. 原子 – Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全并后的原子性執(zhí)行。

4. 豐富的特性 – Redis還支持 publish/subscribe, 通知, key 過期等等特性。

Redis與其他key-value存儲有什么不同? 1. Redis有著更為復(fù)雜的數(shù)據(jù)結(jié)構(gòu)并且提供對他們的原子性操作,這是一個不同于其他數(shù)據(jù)庫的進(jìn)化路徑。Redis的數(shù)據(jù)類型都是基于基本數(shù)據(jù)結(jié)構(gòu)的同時對程序員透明,無需進(jìn)行額外的抽象。

2. Redis運(yùn)行在內(nèi)存中但是可以持久化到磁盤,所以在對不同數(shù)據(jù)集進(jìn)行高速讀寫時需要權(quán)衡內(nèi)存,應(yīng)為數(shù)據(jù)量不能大于硬件內(nèi)存。在內(nèi)存數(shù)據(jù)庫方面的另一個優(yōu)點(diǎn)是, 相比在磁盤上相同的復(fù)雜的數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中操作起來非常簡單,這樣Redis可以做很多內(nèi)部復(fù)雜性很強(qiáng)的事情。 同時,在磁盤格式方面他們是緊湊的以追加的方式產(chǎn)生的,因?yàn)樗麄儾⒉恍枰M(jìn)行隨機(jī)訪問。

數(shù)據(jù)類型

概念 說明
String(字符串) string是redis最基本的類型,你可以理解成與Memcached一模一樣的類型,一個key對應(yīng)一個value。

string類型是二進(jìn)制安全的。意思是redis的string可以包含任何數(shù)據(jù)。比如jpg圖片或者序列化的對象 。

string類型是Redis最基本的數(shù)據(jù)類型,一個鍵最大能存儲512MB。

Hash(哈希) Redis hash 是一個鍵值對集合。

Redis hash是一個string類型的field和value的映射表,hash特別適合用于存儲對象。

每個 hash 可以存儲 2 *32 - 1鍵值對(40多億)。
List(列表) Redis 列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素導(dǎo)列表的頭部(左邊)或者尾部(右邊)。

列表最多可存儲 2*32 - 1元素 (4294967295, 每個列表可存儲40多億)。
Set(集合) Redis的Set是string類型的無序集合。

集合是通過哈希表實(shí)現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是O(1)。

集合中最大的成員數(shù)為 2* 32 - 1(4294967295, 每個集合可存儲40多億個成員)。

zset(sorted set:有序集合) Redis zset 和 set 一樣也是string類型元素的集合,且不允許重復(fù)的成員。

不同的是每個元素都會關(guān)聯(lián)一個double類型的分?jǐn)?shù)。redis正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序。

zset的成員是唯一的,但分?jǐn)?shù)(score)卻可以重復(fù)。

基本概念

概念 說明
HyperLogLog Redis 在 2.8.9 版本添加了 HyperLogLog 結(jié)構(gòu)。

Redis HyperLogLog 是用來做基數(shù)統(tǒng)計的算法,HyperLogLog 的優(yōu)點(diǎn)是,在輸入元素的數(shù)量或者體積非常非常大時,計算基數(shù)所需的空間總是固定 的、并且是很小的。

在 Redis 里面,每個 HyperLogLog 鍵只需要花費(fèi) 12 KB 內(nèi)存,就可以計算接近 2^64 個不同元素的基 數(shù)。這和計算基數(shù)時,元素越多耗費(fèi)內(nèi)存就越多的集合形成鮮明對比。

但是,因?yàn)?HyperLogLog 只會根據(jù)輸入元素來計算基數(shù),而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。

什么是基數(shù)?

比如數(shù)據(jù)集 {1, 3, 5, 7, 5, 7, 8}, 那么這個數(shù)據(jù)集的基數(shù)集為 {1, 3, 5 ,7, 8}, 基數(shù)(不重復(fù)元素)為5。 基數(shù)估計就是在誤差可接受的范圍內(nèi),快速計算基數(shù)。

發(fā)布訂閱 Redis 發(fā)布訂閱(pub/sub)是一種消息通信模式:發(fā)送者(pub)發(fā)送消息,訂閱者(sub)接收消息。

Redis 客戶端可以訂閱任意數(shù)量的頻道。

下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關(guān)系:

image.png


當(dāng)有新消息通過 PUBLISH 命令發(fā)送給頻道 channel1 時, 這個消息就會被發(fā)送給訂閱它的三個客戶端:

image.png
Redis 事務(wù) Redis 事務(wù)可以一次執(zhí)行多個命令, 并且?guī)в幸韵聝蓚€重要的保證:

1. 事務(wù)是一個單獨(dú)的隔離操作:事務(wù)中的所有命令都會序列化、按順序地執(zhí)行。事務(wù)在執(zhí)行的過程中,不會被其他客戶端發(fā)送來的命令請求所打斷。

2. 事務(wù)是一個原子操作:事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。

一個事務(wù)從開始到執(zhí)行會經(jīng)歷以下三個階段:

a. 開始事務(wù)。

b. 命令入隊。

c. 執(zhí)行事務(wù)。

復(fù)制(Replication)

Redis 支持簡單且易用的主從復(fù)制(master-slave replication)功能, 該功能可以讓從服務(wù)器(slave server)成為主服務(wù)器(master server)的精確復(fù)制品。

以下是關(guān)于 Redis 復(fù)制功能的幾個重要方面:

  1. Redis 使用異步復(fù)制。 從 Redis 2.8 開始, 從服務(wù)器會以每秒一次的頻率向主服務(wù)器報告復(fù)制流(replication stream)的處理進(jìn)度。

  2. 一個主服務(wù)器可以有多個從服務(wù)器。

  3. 不僅主服務(wù)器可以有從服務(wù)器, 從服務(wù)器也可以有自己的從服務(wù)器, 多個從服務(wù)器之間可以構(gòu)成一個圖狀結(jié)構(gòu)。

  4. 復(fù)制功能不會阻塞主服務(wù)器: 即使有一個或多個從服務(wù)器正在進(jìn)行初次同步, 主服務(wù)器也可以繼續(xù)處理命令請求。

  5. 復(fù)制功能也不會阻塞從服務(wù)器: 只要在redis.conf文件中進(jìn)行了相應(yīng)的設(shè)置, 即使從服務(wù)器正在進(jìn)行初次同步, 服務(wù)器也可以使用舊版本的數(shù)據(jù)集來處理命令查詢。

    不過, 在從服務(wù)器刪除舊版本數(shù)據(jù)集并載入新版本數(shù)據(jù)集的那段時間內(nèi), 連接請求會被阻塞。
    你還可以配置從服務(wù)器, 讓它在與主服務(wù)器之間的連接斷開時, 向客戶端發(fā)送一個錯誤。

  6. 復(fù)制功能可以單純地用于數(shù)據(jù)冗余(data redundancy), 也可以通過讓多個從服務(wù)器處理只讀命令請求來提升擴(kuò)展性(scalability): 比如說, 繁重的SORT 命令可以交給附屬節(jié)點(diǎn)去運(yùn)行。

  7. 可以通過復(fù)制功能來讓主服務(wù)器免于執(zhí)行持久化操作: 只要關(guān)閉主服務(wù)器的持久化功能, 然后由從服務(wù)器去執(zhí)行持久化操作即可。

概念 說明
復(fù)制功能的運(yùn)作原理 無論是初次連接還是重新連接, 當(dāng)建立一個從服務(wù)器時, 從服務(wù)器都將向主服務(wù)器發(fā)送一個 SYNC 命令。

接到 SYNC 命令的主服務(wù)器將開始執(zhí)行 BGSAVE , 并在保存操作執(zhí)行期間, 將所有新執(zhí)行的寫入命令都保存到一個緩沖區(qū)里面。

當(dāng) BGSAVE 執(zhí)行完畢后, 主服務(wù)器將執(zhí)行保存操作所得的 .rdb 文件發(fā)送給從服務(wù)器, 從服務(wù)器接收這個 .rdb 文件, 并將文件中的數(shù)據(jù)載入到內(nèi)存中。

之后主服務(wù)器會以 Redis 命令協(xié)議的格式, 將寫命令緩沖區(qū)中積累的所有內(nèi)容都發(fā)送給從服務(wù)器。

你可以通過 telnet 命令來親自驗(yàn)證這個同步過程: 首先連上一個正在處理命令請求的 Redis 服務(wù)器, 然后向它發(fā)送 SYNC 命令, 過一陣子, 你將看到 telnet 會話(session)接收到服務(wù)器發(fā)來的大段數(shù)據(jù)(.rdb 文件), 之后還會看到, 所有在服務(wù)器執(zhí)行過的寫命令, 都會重新發(fā)送到 telnet 會話來。

即使有多個從服務(wù)器同時向主服務(wù)器發(fā)送 SYNC , 主服務(wù)器也只需執(zhí)行一次 BGSAVE 命令, 就可以處理所有這些從服務(wù)器的同步請求。

從服務(wù)器可以在主從服務(wù)器之間的連接斷開時進(jìn)行自動重連, 在 Redis 2.8 版本之前, 斷線之后重連的從服務(wù)器總要執(zhí)行一次完整重同步(full resynchronization)操作, 但是從 Redis 2.8 版本開始, 從服務(wù)器可以根據(jù)主服務(wù)器的情況來選擇執(zhí)行完整重同步還是部分重同步(partial resynchronization)。

部分重同步 從 Redis 2.8 開始, 在網(wǎng)絡(luò)連接短暫性失效之后, 主從服務(wù)器可以嘗試?yán)^續(xù)執(zhí)行原有的復(fù)制進(jìn)程(process), 而不一定要執(zhí)行完整重同步操作。

這個特性需要主服務(wù)器為被發(fā)送的復(fù)制流創(chuàng)建一個內(nèi)存緩沖區(qū)(in-memory backlog), 并且主服務(wù)器和所有從服務(wù)器之間都記錄一個復(fù)制偏移量(replication offset)和一個主服務(wù)器 ID (master run id), 當(dāng)出現(xiàn)網(wǎng)絡(luò)連接斷開時, 從服務(wù)器會重新連接, 并且向主服務(wù)器請求繼續(xù)執(zhí)行原來的復(fù)制進(jìn)程:

1. 如果從服務(wù)器記錄的主服務(wù)器 ID 和當(dāng)前要連接的主服務(wù)器的 ID 相同, 并且從服務(wù)器記錄的偏移量所指定的數(shù)據(jù)仍然保存在主服務(wù)器的復(fù)制流緩沖區(qū)里面, 那么主服務(wù)器會向從服務(wù)器發(fā)送斷線時缺失的那部分?jǐn)?shù)據(jù), 然后復(fù)制工作可以繼續(xù)執(zhí)行。

2. 否則的話, 從服務(wù)器就要執(zhí)行完整重同步操作。

Redis 2.8 的這個部分重同步特性會用到一個新增的 PSYNC 內(nèi)部命令, 而 Redis 2.8 以前的舊版本只有 SYNC 命令, 不過, 只要從服務(wù)器是 Redis 2.8 或以上的版本, 它就會根據(jù)主服務(wù)器的版本來決定到底是使用 PSYNC 還是 SYNC

1. 如果主服務(wù)器是 Redis 2.8 或以上版本,那么從服務(wù)器使用 PSYNC 命令來進(jìn)行同步。

2. 如果主服務(wù)器是 Redis 2.8 之前的版本,那么從服務(wù)器使用 SYNC 命令來進(jìn)行同步。

只讀從服務(wù)器 從 Redis 2.6 開始, 從服務(wù)器支持只讀模式, 并且該模式為從服務(wù)器的默認(rèn)模式。

只讀模式由 redis.conf 文件中的 slave-read-only 選項控制, 也可以通過 CONFIG SET 命令來開啟或關(guān)閉這個模式。

只讀從服務(wù)器會拒絕執(zhí)行任何寫命令, 所以不會出現(xiàn)因?yàn)椴僮魇д`而將數(shù)據(jù)不小心寫入到了從服務(wù)器的情況。

即使從服務(wù)器是只讀的, DEBUG 和 CONFIG 等管理式命令仍然是可以使用的, 所以我們還是不應(yīng)該將服務(wù)器暴露給互聯(lián)網(wǎng)或者任何不可信網(wǎng)絡(luò)。 不過, 使用 redis.conf 中的命令改名選項, 我們可以通過禁止執(zhí)行某些命令來提升只讀從服務(wù)器的安全性。

你可能會感到好奇, 既然從服務(wù)器上的寫數(shù)據(jù)會被重同步數(shù)據(jù)覆蓋, 也可能在從服務(wù)器重啟時丟失, 那么為什么要讓一個從服務(wù)器變得可寫呢?

原因是, 一些不重要的臨時數(shù)據(jù), 仍然是可以保存在從服務(wù)器上面的。 比如說, 客戶端可以在從服務(wù)器上保存主服務(wù)器的可達(dá)性(reachability)信息, 從而實(shí)現(xiàn)故障轉(zhuǎn)移(failover)策略。

主服務(wù)器只在有至少 N 個從服務(wù)器的情況下,才執(zhí)行寫操作 從 Redis 2.8 開始, 為了保證數(shù)據(jù)的安全性, 可以通過配置, 讓主服務(wù)器只在有至少 N 個當(dāng)前已連接從服務(wù)器的情況下, 才執(zhí)行寫命令。

不過, 因?yàn)?Redis 使用異步復(fù)制, 所以主服務(wù)器發(fā)送的寫數(shù)據(jù)并不一定會被從服務(wù)器接收到, 因此, 數(shù)據(jù)丟失的可能性仍然是存在的。

以下是這個特性的運(yùn)作原理:

1 從服務(wù)器以每秒一次的頻率 PING 主服務(wù)器一次, 并報告復(fù)制流的處理情況。

2 主服務(wù)器會記錄各個從服務(wù)器最后一次向它發(fā)送 PING 的時間。

3 用戶可以通過配置, 指定網(wǎng)絡(luò)延遲的最大值 , 以及執(zhí)行寫操作所需的至少從服務(wù)器數(shù)量如果至少有 min-slaves-to-write 個從服務(wù)器, 并且這些服務(wù)器的延遲值都少于 min-slaves-max-lag 秒, 那么主服務(wù)器就會執(zhí)行客戶端請求的寫操作。

你可以將這個特性看作 CAP 理論中的 C 的條件放寬版本: 盡管不能保證寫操作的持久性, 但起碼丟失數(shù)據(jù)的窗口會被嚴(yán)格限制在指定的秒數(shù)中。

另一方面, 如果條件達(dá)不到min-slaves-to-write 和 min-slaves-max-lag 所指定的條件, 那么寫操作就不會被執(zhí)行, 主服務(wù)器會向請求執(zhí)行寫操作的客戶端返回一個錯誤。

以下是這個特性的兩個選項和它們所需的參數(shù):

1 min-slaves-to-write

2 min-slaves-max-lag

詳細(xì)的信息可以參考 Redis 源碼中附帶的 redis.conf 示例文件。

事務(wù)(transaction)

MULTI 、 EXEC 、 DISCARDWATCH 是 Redis 事務(wù)的基礎(chǔ)。
事務(wù)可以一次執(zhí)行多個命令, 并且?guī)в幸韵聝蓚€重要的保證:

  1. 事務(wù)是一個單獨(dú)的隔離操作:事務(wù)中的所有命令都會序列化、按順序地執(zhí)行。事務(wù)在執(zhí)行的過程中,不會被其他客戶端發(fā)送來的命令請求所打斷。
  2. 事務(wù)是一個原子操作:事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。
    EXEC 命令負(fù)責(zé)觸發(fā)并執(zhí)行事務(wù)中的所有命令:
  • 如果客戶端在使用 MULTI 開啟了一個事務(wù)之后,卻因?yàn)閿嗑€而沒有成功執(zhí)行 EXEC ,那么事務(wù)中的所有命令都不會被執(zhí)行。
  • 另一方面,如果客戶端成功在開啟事務(wù)之后執(zhí)行 EXEC ,那么事務(wù)中的所有命令都會被執(zhí)行。
    當(dāng)使用 AOF 方式做持久化的時候, Redis 會使用單個 write(2) 命令將事務(wù)寫入到磁盤中。

然而,如果 Redis 服務(wù)器因?yàn)槟承┰虮还芾韱T殺死,或者遇上某種硬件故障,那么可能只有部分事務(wù)命令會被成功寫入到磁盤中。
如果 Redis 在重新啟動時發(fā)現(xiàn) AOF 文件出了這樣的問題,那么它會退出,并匯報一個錯誤。
使用 redis-check-aof 程序可以修復(fù)這一問題:它會移除 AOF 文件中不完整事務(wù)的信息,確保服務(wù)器可以順利啟動。
從 2.2 版本開始,Redis 還可以通過樂觀鎖(optimistic lock)實(shí)現(xiàn) CAS (check-and-set)操作,具體信息請參考文檔的后半部分。

概念 說明
事務(wù)中的錯誤 使用事務(wù)時可能會遇上以下兩種錯誤:

1. 事務(wù)在執(zhí)行 EXEC 之前,入隊的命令可能會出錯。比如說,命令可能會產(chǎn)生語法錯誤(參數(shù)數(shù)量錯誤,參數(shù)名錯誤,等等),或者其他更嚴(yán)重的錯誤,比如內(nèi)存不足(如果服務(wù)器使用 maxmemory 設(shè)置了最大內(nèi)存限制的話)。

2. 命令可能在 EXEC 調(diào)用之后失敗。舉個例子,事務(wù)中的命令可能處理了錯誤類型的鍵,比如將列表命令用在了字符串鍵上面,諸如此類。

對于發(fā)生在 EXEC 執(zhí)行之前的錯誤,客戶端以前的做法是檢查命令入隊所得的返回值:如果命令入隊時返回 QUEUED ,那么入隊成功;否則,就是入隊失敗。如果有命令在入隊時失敗,那么大部分客戶端都會停止并取消這個事務(wù)。

不過,從 Redis 2.6.5 開始,服務(wù)器會對命令入隊失敗的情況進(jìn)行記錄,并在客戶端調(diào)用 EXEC 命令時,拒絕執(zhí)行并自動放棄這個事務(wù)。

在 Redis 2.6.5 以前, Redis 只執(zhí)行事務(wù)中那些入隊成功的命令,而忽略那些入隊失敗的命令。 而新的處理方式則使得在流水線(pipeline)中包含事務(wù)變得簡單,因?yàn)榘l(fā)送事務(wù)和讀取事務(wù)的回復(fù)都只需要和服務(wù)器進(jìn)行一次通訊。

至于那些在 EXEC 命令執(zhí)行之后所產(chǎn)生的錯誤, 并沒有對它們進(jìn)行特別處理: 即使事務(wù)中有某個/某些命令在執(zhí)行時產(chǎn)生了錯誤, 事務(wù)中的其他命令仍然會繼續(xù)執(zhí)行。

最重要的是記住這樣一條, 即使事務(wù)中有某條/某些命令執(zhí)行失敗了, 事務(wù)隊列中的其他命令仍然會繼續(xù)執(zhí)行 —— Redis 不會停止執(zhí)行事務(wù)中的命令。

Redis 不支持回滾 如果你有使用關(guān)系式數(shù)據(jù)庫的經(jīng)驗(yàn), 那么 “Redis 在事務(wù)失敗時不進(jìn)行回滾,而是繼續(xù)執(zhí)行余下的命令”這種做法可能會讓你覺得有點(diǎn)奇怪。

以下是這種做法的優(yōu)點(diǎn):

1. Redis 命令只會因?yàn)殄e誤的語法而失敗(并且這些問題不能在入隊時發(fā)現(xiàn)),或是命令用在了錯誤類型的鍵上面:這也就是說,從實(shí)用性的角度來說,失敗的命令是由編程錯誤造成的,而這些錯誤應(yīng)該在開發(fā)的過程中被發(fā)現(xiàn),而不應(yīng)該出現(xiàn)在生產(chǎn)環(huán)境中。

2. 因?yàn)椴恍枰獙貪L進(jìn)行支持,所以 Redis 的內(nèi)部可以保持簡單且快速。

有種觀點(diǎn)認(rèn)為 Redis 處理事務(wù)的做法會產(chǎn)生 bug , 然而需要注意的是, 在通常情況下, 回滾并不能解決編程錯誤帶來的問題。 舉個例子, 如果你本來想通過 INCR 命令將鍵的值加上 1 , 卻不小心加上了 2 , 又或者對錯誤類型的鍵執(zhí)行了 INCR , 回滾是沒有辦法處理這些情況的。

鑒于沒有任何機(jī)制能避免程序員自己造成的錯誤, 并且這類錯誤通常不會在生產(chǎn)環(huán)境中出現(xiàn), 所以 Redis 選擇了更簡單、更快速的無回滾方式來處理事務(wù)。

樂觀鎖 WATCH 命令可以為 Redis 事務(wù)提供 check-and-set (CAS)行為。

WATCH 的鍵會被監(jiān)視,并會發(fā)覺這些鍵是否被改動過了。 如果有至少一個被監(jiān)視的鍵在 EXEC 執(zhí)行之前被修改了, 那么整個事務(wù)都會被取消, EXEC 返回空多條批量回復(fù)(null multi-bulk reply)來表示事務(wù)已經(jīng)失敗。

舉個例子, 假設(shè)我們需要原子性地為某個值進(jìn)行增 1 操作(假設(shè) INCR 不存在)。

首先我們可能會這樣做:

val = GETmykey

val = val + 1

SET mykey val<br><br> 上面的這個實(shí)現(xiàn)在只有一個客戶端的時候可以執(zhí)行得很好。 但是, 當(dāng)多個客戶端同時對同一個鍵進(jìn)行這樣的操作時, 就會產(chǎn)生競爭條件。<br><br> 舉個例子, 如果客戶端 A 和 B 都讀取了鍵原來的值, 比如 10 , 那么兩個客戶端都會將鍵的值設(shè)為 11 , 但正確的結(jié)果應(yīng)該是 12 才對。<br><br> 有了 [WATCH](http://doc.redisfans.com/transaction/watch.html#watch) , 我們就可以輕松地解決這類問題了:<br><br> WATCH mykey<br><br> val = GET mykey<br><br> val = val + 1<br><br> MULTI<br><br> SET mykeyval

EXEC

使用上面的代碼, 如果在 WATCH 執(zhí)行之后, EXEC 執(zhí)行之前, 有其他客戶端修改了 mykey 的值, 那么當(dāng)前客戶端的事務(wù)就會失敗。 程序需要做的, 就是不斷重試這個操作, 直到?jīng)]有發(fā)生碰撞為止。

這種形式的鎖被稱作樂觀鎖, 它是一種非常強(qiáng)大的鎖機(jī)制。 并且因?yàn)榇蠖鄶?shù)情況下, 不同的客戶端會訪問不同的鍵, 碰撞的情況一般都很少, 所以通常并不需要進(jìn)行重試。

WATCH WATCH 使得 EXEC 命令需要有條件地執(zhí)行: 事務(wù)只能在所有被監(jiān)視鍵都沒有被修改的前提下執(zhí)行, 如果這個前提不能滿足的話,事務(wù)就不會被執(zhí)行。

如果你使用 WATCH 監(jiān)視了一個帶過期時間的鍵, 那么即使這個鍵過期了, 事務(wù)仍然可以正常執(zhí)行, 關(guān)于這方面的詳細(xì)情況,請看這個帖子: http://code.google.com/p/redis/issues/detail?id=270

WATCH 命令可以被調(diào)用多次。 對鍵的監(jiān)視從 WATCH 執(zhí)行之后開始生效, 直到調(diào)用 EXEC 為止。

用戶還可以在單個 WATCH 命令中監(jiān)視任意多個鍵, 就像這樣:

redis> WATCH key1 key2 key3

OK

當(dāng) EXEC 被調(diào)用時, 不管事務(wù)是否成功執(zhí)行, 對所有鍵的監(jiān)視都會被取消。

另外, 當(dāng)客戶端斷開連接時, 該客戶端對鍵的監(jiān)視也會被取消。

使用無參數(shù)的 UNWATCH 命令可以手動取消對所有鍵的監(jiān)視。 對于一些需要改動多個鍵的事務(wù), 有時候程序需要同時對多個鍵進(jìn)行加鎖, 然后檢查這些鍵的當(dāng)前值是否符合程序的要求。 當(dāng)值達(dá)不到要求時, 就可以使用 UNWATCH 命令來取消目前對鍵的監(jiān)視, 中途放棄這個事務(wù), 并等待事務(wù)的下次嘗試。

WATCH 可以用于創(chuàng)建 Redis 沒有內(nèi)置的原子操作。

Redis 腳本和事務(wù) 從定義上來說, Redis 中的腳本本身就是一種事務(wù), 所以任何在事務(wù)里可以完成的事, 在腳本里面也能完成。 并且一般來說, 使用腳本要來得更簡單,并且速度更快。

因?yàn)槟_本功能是 Redis 2.6 才引入的, 而事務(wù)功能則更早之前就存在了, 所以 Redis 才會同時存在兩種處理事務(wù)的方法。

不過我們并不打算在短時間內(nèi)就移除事務(wù)功能, 因?yàn)槭聞?wù)提供了一種即使不使用腳本, 也可以避免競爭條件的方法, 而且事務(wù)本身的實(shí)現(xiàn)并不復(fù)雜。

不過在不遠(yuǎn)的將來, 可能所有用戶都會只使用腳本來實(shí)現(xiàn)事務(wù)也說不定。 如果真的發(fā)生這種情況的話, 那么我們將廢棄并最終移除事務(wù)功能。

sentinel

Redis 的 Sentinel 系統(tǒng)用于管理多個 Redis 服務(wù)器(instance), 該系統(tǒng)執(zhí)行以下三個任務(wù):

  1. 監(jiān)控(Monitoring): Sentinel 會不斷地檢查你的主服務(wù)器和從服務(wù)器是否運(yùn)作正常。
  2. 提醒(Notification): 當(dāng)被監(jiān)控的某個 Redis 服務(wù)器出現(xiàn)問題時, Sentinel 可以通過 API 向管理員或者其他應(yīng)用程序發(fā)送通知。
  3. 自動故障遷移(Automatic failover): 當(dāng)一個主服務(wù)器不能正常工作時, Sentinel 會開始一次自動故障遷移操作, 它會將失效主服務(wù)器的其中一個從服務(wù)器升級為新的主服務(wù)器, 并讓失效主服務(wù)器的其他從服務(wù)器改為復(fù)制新的主服務(wù)器; 當(dāng)客戶端試圖連接失效的主服務(wù)器時, 集群也會向客戶端返回新主服務(wù)器的地址, 使得集群可以使用新主服務(wù)器代替失效服務(wù)器。

Redis Sentinel 是一個分布式系統(tǒng), 你可以在一個架構(gòu)中運(yùn)行多個 Sentinel 進(jìn)程(progress), 這些進(jìn)程使用流言協(xié)議(gossip protocols)來接收關(guān)于主服務(wù)器是否下線的信息, 并使用投票協(xié)議(agreement protocols)來決定是否執(zhí)行自動故障遷移, 以及選擇哪個從服務(wù)器作為新的主服務(wù)器。
雖然 Redis Sentinel 釋出為一個單獨(dú)的可執(zhí)行文件 redis-sentinel , 但實(shí)際上它只是一個運(yùn)行在特殊模式下的 Redis 服務(wù)器, 你可以在啟動一個普通 Redis 服務(wù)器時通過給定 --sentinel 選項來啟動 Redis Sentinel 。
Redis Sentinel 目前仍在開發(fā)中, 這個文檔的內(nèi)容可能隨著 Sentinel 實(shí)現(xiàn)的修改而變更。
Redis Sentinel 兼容 Redis 2.4.16 或以上版本, 推薦使用 Redis 2.8.0 或以上的版本。

概念 說明
主觀下線和客觀下線 Redis 的 Sentinel 中關(guān)于下線(down)有兩個不同的概念:

1. 主觀下線(Subjectively Down, 簡稱 SDOWN)指的是單個 Sentinel 實(shí)例對服務(wù)器做出的下線判斷。

2. 客觀下線(Objectively Down, 簡稱 ODOWN)指的是多個 Sentinel 實(shí)例在對同一個服務(wù)器做出 SDOWN 判斷, 并且通過 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服務(wù)器下線判斷。 (一個 Sentinel 可以通過向另一個 Sentinel 發(fā)送 SENTINEL is-master-down-by-addr 命令來詢問對方是否認(rèn)為給定的服務(wù)器已下線。)

如果一個服務(wù)器沒有在 master-down-after-milliseconds 選項所指定的時間內(nèi), 對向它發(fā)送 PING 命令的 Sentinel 返回一個有效回復(fù)(valid reply), 那么 Sentinel 就會將這個服務(wù)器標(biāo)記為主觀下線。

服務(wù)器對 PING 命令的有效回復(fù)可以是以下三種回復(fù)的其中一種:

1. 返回 +PONG 。

2. 返回 -LOADING 錯誤。

3. 返回 -MASTERDOWN 錯誤。

如果服務(wù)器返回除以上三種回復(fù)之外的其他回復(fù), 又或者在指定時間內(nèi)沒有回復(fù) PING 命令, 那么 Sentinel 認(rèn)為服務(wù)器返回的回復(fù)無效(non-valid)。

注意, 一個服務(wù)器必須在 master-down-after-milliseconds 毫秒內(nèi), 一直返回?zé)o效回復(fù)才會被 Sentinel 標(biāo)記為主觀下線。

舉個例子, 如果 master-down-after-milliseconds 選項的值為 30000 毫秒(30 秒), 那么只要服務(wù)器能在每 29 秒之內(nèi)返回至少一次有效回復(fù), 這個服務(wù)器就仍然會被認(rèn)為是處于正常狀態(tài)的。

從主觀下線狀態(tài)切換到客觀下線狀態(tài)并沒有使用嚴(yán)格的法定人數(shù)算法(strong quorum algorithm), 而是使用了流言協(xié)議: 如果 Sentinel 在給定的時間范圍內(nèi), 從其他 Sentinel 那里接收到了足夠數(shù)量的主服務(wù)器下線報告, 那么 Sentinel 就會將主服務(wù)器的狀態(tài)從主觀下線改變?yōu)榭陀^下線。 如果之后其他 Sentinel 不再報告主服務(wù)器已下線, 那么客觀下線狀態(tài)就會被移除。

客觀下線條件只適用于主服務(wù)器: 對于任何其他類型的 Redis 實(shí)例, Sentinel 在將它們判斷為下線前不需要進(jìn)行協(xié)商, 所以從服務(wù)器或者其他 Sentinel 永遠(yuǎn)不會達(dá)到客觀下線條件。

只要一個 Sentinel 發(fā)現(xiàn)某個主服務(wù)器進(jìn)入了客觀下線狀態(tài), 這個 Sentinel 就可能會被其他 Sentinel 推選出, 并對失效的主服務(wù)器執(zhí)行自動故障遷移操作。

每個 Sentinel 都需要定期執(zhí)行的任務(wù) 1. 每個 Sentinel 以每秒鐘一次的頻率向它所知的主服務(wù)器、從服務(wù)器以及其他 Sentinel 實(shí)例發(fā)送一個 PING 命令。

2. 如果一個實(shí)例(instance)距離最后一次有效回復(fù) PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 那么這個實(shí)例會被 Sentinel 標(biāo)記為主觀下線。 一個有效回復(fù)可以是: +PONG 、 -LOADING 或者 -MASTERDOWN 。

3. 如果一個主服務(wù)器被標(biāo)記為主觀下線, 那么正在監(jiān)視這個主服務(wù)器的所有 Sentinel 要以每秒一次的頻率確認(rèn)主服務(wù)器的確進(jìn)入了主觀下線狀態(tài)。

4. 如果一個主服務(wù)器被標(biāo)記為主觀下線, 并且有足夠數(shù)量的 Sentinel (至少要達(dá)到配置文件指定的數(shù)量)在指定的時間范圍內(nèi)同意這一判斷, 那么這個主服務(wù)器被標(biāo)記為客觀下線。

5. 在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有主服務(wù)器和從服務(wù)器發(fā)送 INFO 命令。 當(dāng)一個主服務(wù)器被 Sentinel 標(biāo)記為客觀下線時, Sentinel 向下線主服務(wù)器的所有從服務(wù)器發(fā)送 INFO 命令的頻率會從 10 秒一次改為每秒一次。

6. 當(dāng)沒有足夠數(shù)量的 Sentinel 同意主服務(wù)器已經(jīng)下線, 主服務(wù)器的客觀下線狀態(tài)就會被移除。 當(dāng)主服務(wù)器重新向 Sentinel 的 PING命令返回有效回復(fù)時, 主服務(wù)器的主管下線狀態(tài)就會被移除。

自動發(fā)現(xiàn) Sentinel 和從服務(wù)器 一個 Sentinel 可以與其他多個 Sentinel 進(jìn)行連接, 各個 Sentinel 之間可以互相檢查對方的可用性, 并進(jìn)行信息交換。

你無須為運(yùn)行的每個 Sentinel 分別設(shè)置其他 Sentinel 的地址, 因?yàn)?Sentinel 可以通過發(fā)布與訂閱功能來自動發(fā)現(xiàn)正在監(jiān)視相同主服務(wù)器的其他 Sentinel , 這一功能是通過向頻道 sentinel:hello 發(fā)送信息來實(shí)現(xiàn)的。

與此類似, 你也不必手動列出主服務(wù)器屬下的所有從服務(wù)器, 因?yàn)?Sentinel 可以通過詢問主服務(wù)器來獲得所有從服務(wù)器的信息。

1. 每個 Sentinel 會以每兩秒一次的頻率, 通過發(fā)布與訂閱功能, 向被它監(jiān)視的所有主服務(wù)器和從服務(wù)器的 sentinel:hello 頻道發(fā)送一條信息, 信息中包含了 Sentinel 的 IP 地址、端口號和運(yùn)行 ID (runid)。

2. 每個 Sentinel 都訂閱了被它監(jiān)視的所有主服務(wù)器和從服務(wù)器的 sentinel:hello 頻道, 查找之前未出現(xiàn)過的 sentinel (looking for unknown sentinels)。 當(dāng)一個 Sentinel 發(fā)現(xiàn)一個新的 Sentinel 時, 它會將新的 Sentinel 添加到一個列表中, 這個列表保存了 Sentinel 已知的, 監(jiān)視同一個主服務(wù)器的所有其他 Sentinel 。

3. Sentinel 發(fā)送的信息中還包括完整的主服務(wù)器當(dāng)前配置(configuration)。 如果一個 Sentinel 包含的主服務(wù)器配置比另一個 Sentinel 發(fā)送的配置要舊, 那么這個 Sentinel 會立即升級到新配置上。

4. 在將一個新 Sentinel 添加到監(jiān)視主服務(wù)器的列表上面之前, Sentinel 會先檢查列表中是否已經(jīng)包含了和要添加的 Sentinel 擁有相同運(yùn)行 ID 或者相同地址(包括 IP 地址和端口號)的 Sentinel , 如果是的話, Sentinel 會先移除列表中已有的那些擁有相同運(yùn)行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel 。

故障轉(zhuǎn)移 一次故障轉(zhuǎn)移操作由以下步驟組成:

1. 發(fā)現(xiàn)主服務(wù)器已經(jīng)進(jìn)入客觀下線狀態(tài)。

2. 對我們的當(dāng)前紀(jì)元進(jìn)行自增(詳情請參考 Raft leader election ), 并嘗試在這個紀(jì)元中當(dāng)選。

3. 如果當(dāng)選失敗, 那么在設(shè)定的故障遷移超時時間的兩倍之后, 重新嘗試當(dāng)選。 如果當(dāng)選成功, 那么執(zhí)行以下步驟。

4. 選出一個從服務(wù)器,并將它升級為主服務(wù)器。

5. 向被選中的從服務(wù)器發(fā)送 SLAVEOF NO ONE 命令,讓它轉(zhuǎn)變?yōu)橹鞣?wù)器。

6. 通過發(fā)布與訂閱功能, 將更新后的配置傳播給所有其他 Sentinel , 其他 Sentinel 對它們自己的配置進(jìn)行更新。

7. 向已下線主服務(wù)器的從服務(wù)器發(fā)送 SLAVEOF 命令, 讓它們?nèi)?fù)制新的主服務(wù)器。

8. 當(dāng)所有從服務(wù)器都已經(jīng)開始復(fù)制新的主服務(wù)器時, 領(lǐng)頭 Sentinel 終止這次故障遷移操作。

每當(dāng)一個 Redis 實(shí)例被重新配置(reconfigured) —— 無論是被設(shè)置成主服務(wù)器、從服務(wù)器、又或者被設(shè)置成其他主服務(wù)器的從服務(wù)器 —— Sentinel 都會向被重新配置的實(shí)例發(fā)送一個 CONFIG REWRITE 命令, 從而確保這些配置會持久化在硬盤里。

Sentinel 使用以下規(guī)則來選擇新的主服務(wù)器

1. 在失效主服務(wù)器屬下的從服務(wù)器當(dāng)中, 那些被標(biāo)記為主觀下線、已斷線、或者最后一次回復(fù) PING 命令的時間大于五秒鐘的從服務(wù)器都會被淘汰。

2. 在失效主服務(wù)器屬下的從服務(wù)器當(dāng)中, 那些與失效主服務(wù)器連接斷開的時長超過 down-after 選項指定的時長十倍的從服務(wù)器都會被淘汰。

3. 在經(jīng)歷了以上兩輪淘汰之后剩下來的從服務(wù)器中, 我們選出復(fù)制偏移量(replication offset)最大的那個從服務(wù)器作為新的主服務(wù)器; 如果復(fù)制偏移量不可用, 或者從服務(wù)器的復(fù)制偏移量相同, 那么帶有最小運(yùn)行 ID 的那個從服務(wù)器成為新的主服務(wù)器。

Sentinel 自動故障遷移的一致性特質(zhì)

Sentinel 自動故障遷移使用 Raft 算法來選舉領(lǐng)頭(leader) Sentinel , 從而確保在一個給定的紀(jì)元(epoch)里, 只有一個領(lǐng)頭產(chǎn)生。

這表示在同一個紀(jì)元中, 不會有兩個 Sentinel 同時被選中為領(lǐng)頭, 并且各個 Sentinel 在同一個紀(jì)元中只會對一個領(lǐng)頭進(jìn)行投票。

更高的配置紀(jì)元總是優(yōu)于較低的紀(jì)元, 因此每個 Sentinel 都會主動使用更新的紀(jì)元來代替自己的配置。

簡單來說, 我們可以將 Sentinel 配置看作是一個帶有版本號的狀態(tài)。 一個狀態(tài)會以最后寫入者勝出(last-write-wins)的方式(也即是,最新的配置總是勝出)傳播至所有其他 Sentinel 。

舉個例子, 當(dāng)出現(xiàn)網(wǎng)絡(luò)分割(network partitions)時, 一個 Sentinel 可能會包含了較舊的配置, 而當(dāng)這個 Sentinel 接到其他 Sentinel 發(fā)來的版本更新的配置時, Sentinel 就會對自己的配置進(jìn)行更新。

如果要在網(wǎng)絡(luò)分割出現(xiàn)的情況下仍然保持一致性, 那么應(yīng)該使用 min-slaves-to-write 選項, 讓主服務(wù)器在連接的從實(shí)例少于給定數(shù)量時停止執(zhí)行寫操作, 與此同時, 應(yīng)該在每個運(yùn)行 Redis 主服務(wù)器或從服務(wù)器的機(jī)器上運(yùn)行 Redis Sentinel 進(jìn)程。

Sentinel 狀態(tài)的持久化

Sentinel 的狀態(tài)會被持久化在 Sentinel 配置文件里面。

每當(dāng) Sentinel 接收到一個新的配置, 或者當(dāng)領(lǐng)頭 Sentinel 為主服務(wù)器創(chuàng)建一個新的配置時, 這個配置會與配置紀(jì)元一起被保存到磁盤里面。

這意味著停止和重啟 Sentinel 進(jìn)程都是安全的。

Sentinel 在非故障遷移的情況下對實(shí)例進(jìn)行重新配置

即使沒有自動故障遷移操作在進(jìn)行, Sentinel 總會嘗試將當(dāng)前的配置設(shè)置到被監(jiān)視的實(shí)例上面。 特別是:

1. 根據(jù)當(dāng)前的配置, 如果一個從服務(wù)器被宣告為主服務(wù)器, 那么它會代替原有的主服務(wù)器, 成為新的主服務(wù)器, 并且成為原有主服務(wù)器的所有從服務(wù)器的復(fù)制對象。

2. 那些連接了錯誤主服務(wù)器的從服務(wù)器會被重新配置, 使得這些從服務(wù)器會去復(fù)制正確的主服務(wù)器。

不過, 在以上這些條件滿足之后, Sentinel 在對實(shí)例進(jìn)行重新配置之前仍然會等待一段足夠長的時間, 確??梢越邮盏狡渌?Sentinel 發(fā)來的配置更新, 從而避免自身因?yàn)楸4媪诉^期的配置而對實(shí)例進(jìn)行了不必要的重新配置。

TILT 模式 Redis Sentinel 嚴(yán)重依賴計算機(jī)的時間功能: 比如說, 為了判斷一個實(shí)例是否可用, Sentinel 會記錄這個實(shí)例最后一次相應(yīng) PING 命令的時間, 并將這個時間和當(dāng)前時間進(jìn)行對比, 從而知道這個實(shí)例有多長時間沒有和 Sentinel 進(jìn)行任何成功通訊。

不過, 一旦計算機(jī)的時間功能出現(xiàn)故障, 或者計算機(jī)非常忙碌, 又或者進(jìn)程因?yàn)槟承┰蚨蛔枞麜r, Sentinel 可能也會跟著出現(xiàn)故障。

TILT 模式是一種特殊的保護(hù)模式: 當(dāng) Sentinel 發(fā)現(xiàn)系統(tǒng)有些不對勁時, Sentinel 就會進(jìn)入 TILT 模式。

因?yàn)?Sentinel 的時間中斷器默認(rèn)每秒執(zhí)行 10 次, 所以我們預(yù)期時間中斷器的兩次執(zhí)行之間的間隔為 100 毫秒左右。 Sentinel 的做法是, 記錄上一次時間中斷器執(zhí)行時的時間, 并將它和這一次時間中斷器執(zhí)行的時間進(jìn)行對比:

1. 如果兩次調(diào)用時間之間的差距為負(fù)值, 或者非常大(超過 2 秒鐘), 那么 Sentinel 進(jìn)入 TILT 模式。

2. 如果 Sentinel 已經(jīng)進(jìn)入 TILT 模式, 那么 Sentinel 延遲退出 TILT 模式的時間。

當(dāng) Sentinel 進(jìn)入 TILT 模式時, 它仍然會繼續(xù)監(jiān)視所有目標(biāo), 但是:

1. 它不再執(zhí)行任何操作,比如故障轉(zhuǎn)移。

2. 當(dāng)有實(shí)例向這個 Sentinel 發(fā)送 SENTINEL is-master-down-by-addr 命令時, Sentinel 返回負(fù)值: 因?yàn)檫@個 Sentinel 所進(jìn)行的下線判斷已經(jīng)不再準(zhǔn)確。

如果 TILT 可以正常維持 30 秒鐘, 那么 Sentinel 退出 TILT 模式。

其他

集群教程
Redis 集群規(guī)范


個人介紹:

高廣超:多年一線互聯(lián)網(wǎng)研發(fā)與架構(gòu)設(shè)計經(jīng)驗(yàn),擅長設(shè)計與落地高可用、高性能、可擴(kuò)展的互聯(lián)網(wǎng)架構(gòu)。

本文首發(fā)在 http://www.itdecent.cn/u/2766e4cfc391轉(zhuǎn)載請注明!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容