天天看點

Redis Cluster 主要特點和設計原理

作者:後端開發進階

Redis cluster目标

Redis Cluster 是分布式Redis, 按重要性排序, 需要實作以下幾個目标:

  • 高性能、可線性擴充至1000個節點。不使用代理, 沒有異步複制, 不需要任何值的合并操作
  • 比較高的寫安全度: 系統嘗試(以最大努力的方式)保留來自與大多數主節點連接配接的用戶端的所有寫入。通常有一些小視窗,确認的寫入可能會丢失,隻有小部分分區會丢失已确認寫入的視窗。
  • 可用性: Redis叢集能夠在大多數主節點可通路的分區中生存,并且對于每個不再可通路的主節點,至少有一個可通路的從節點。此外,使用副本遷移時, 當某一個主沒有任何從複制時, 可以從另外一個有多個從的主那裡擷取到新的從

Redis Cluster 是3.0版本以後支援的。

Redis Cluster是單機功能的子集

Cluster實作了所有單機Redis的指令。對于一些複雜的多key批量操作比如集合的并集和交集也是支援的, 隻是需要這些keys能hash到一個槽上(slot)。

Redis Cluster實作了一個稱為哈希标簽的概念,可以使用它來強制将某些keys存儲在同一哈希槽中。但是,在手動重分片期間,多鍵操作可能在一段時間内不可用,而單鍵操作始終可用。

Redis Cluster 不支援單機redis的那種多database的功能。叢集模式隻有database 0 是以 切換database的SELECT 指令是不支援的。

Redis Cluster協定中用戶端和服務端角色

節點負責儲存資料,并擷取叢集的狀态,包括将keys映射到正确的節點。叢集節點還能夠自動發現其他節點,檢測非工作節點,并在需要時将從屬節點提升為主節點,以便在發生故障時繼續運作。

為了執行任務,所有叢集節點都使用TCP總線和稱為Redis叢集總線的二進制協定連接配接。每個節點都使用叢集總線連接配接到叢集中的每個其他節點。節點使用gossip協定來傳播關于叢集的資訊,以便發現新節點,發送ping包以確定所有其他節點都正常工作,并發送叢集消息以通知特定條件。叢集總線還用于在叢集中傳播釋出/訂閱消息,并在使用者請求時協調手動故障切換(手動故障切換不是由Redis叢集故障檢測器發起,而是由系統管理者直接發起)。

由于群集節點無法代理請求,是以可能會使用重定向錯誤-MOVED和-ASK将用戶端重定向到其他節點。理論上,客戶機可以自由地向叢集中的所有節點發送請求,如果需要,可以重定向,是以客戶機不需要保持叢集的狀态。然而,能夠緩存鍵和節點之間映射的用戶端可以以合理的方式提高性能。

寫安全

Redis叢集在節點之間使用異步複制,最後一次故障轉移赢得了隐式合并功能。這意味着最後選擇的主資料集最終将替換所有其他副本。分區期間可能丢失寫操作的時間視窗總是存在的。然而,在連接配接到大多數主機的用戶端和連接配接到少數主機的用戶端的情況下,這些視窗非常不同。

Redis叢集更努力地保留由連接配接到大多數主機的用戶端執行的寫入,而不是在少數側執行的寫入。以下是導緻在故障期間大多數分區中收到的确認寫入丢失的場景示例:

1. 寫入可能到達主節點,但雖然主節點可能能夠回複用戶端,但寫入可能不會通過主節點和從節點之間使用的異步複制傳播到從節點。如果主在寫入未到達從節點的情況下死亡,則如果主無法在足夠長的時間内可通路,那麼其一個從節點被提升為主,則寫入将永遠丢失。在主節點完全、突然故障的情況下,通常很難觀察到這一點,因為主節點嘗試在幾乎同時回複用戶端(确認寫入)和從節點(傳播寫入)。然而,這是一種真實的故障模式。

2. 另外一種理論上存在的寫入失敗可能性如下:

  • 主因為分區不可達
  • 這時候被一個從故障轉移了
  • 一段時間後主又恢複了
  • 一個用戶端寫入請求會帶上過期的路由資訊請求到老的主, 然後新的主沒來得及通知自己已經變成了主, 然後新的主會丢失部分資料

第二種故障模式不太可能發生,因為在足夠長的時間内無法與大多數其他主機通信以進行故障切換的主節點将不再接受寫入,并且當分區固定時,寫入仍會被拒絕一小段時間,以允許其他節點通知配置更改。此故障模式還要求用戶端的路由表尚未更新。

針對分區少數側的寫入具有較大的丢失視窗。例如,Redis叢集在有少數主機和至少一個或多個用戶端的分區上丢失了大量寫入,因為如果主機在多數端發生故障,發送到主機的所有寫入都可能丢失。

具體而言,對于要進行故障轉移的主機,大多數主機必須至少在NODE_TIMEOUT内無法通路它,是以,如果在此時間之前分區已固定,則不會丢失任何寫入。當分區持續超過NODE_TIMEOUT時,在少數端執行的所有寫入可能會丢失。然而,Redis叢集的少數方将在NODE_TIMEOUT時間未與多數方接觸的情況下立即開始拒絕寫入,是以有一個最大視窗,在此之後,少數方不再可用。是以,在此時間之後,不會接受或丢失寫入。

可用性

Redis叢集在分區的少數端不可用。在分區的多數側,假設每個不可通路的主至少有大多數主和一個從,叢集在NODE_TIMEOUT時間加上從節點選舉和故障切換到其主所需的幾秒鐘後再次可用(故障切換通常在1或2秒鐘内執行)。

這意味着Redis Cluster被設計為能夠在叢集中的幾個節點發生故障時存活,但對于在發生大網絡分裂時需要可用性的應用程式來說,它不是一個合适的解決方案。

在由N個主節點組成的叢集的示例中,其中每個節點具有單個從節點,并且當兩個節點被分開時,将以1-(1/(N*2-1))的機率保持可用(在第一個節點發生故障之後,我們總共剩下N*2-1個節點,并且沒有從的單主節點發生故障的機率為1/(N*2-2))。

例如,在具有5個節點和每個節點一個從節點的叢集中,存在1/(5*2-1)=11.11%的機率,即在将兩個節點從多數節點分離後,叢集将不再可用。

由于Redis叢集的一項稱為副本遷移的功能,在許多現實場景中,由于副本遷移到孤立主機(主機不再具有副本),叢集可用性得到了提高。是以,在每個成功的故障事件中,叢集可以重新配置從屬布局,以便更好地抵禦下一個故障。

性能

在Redis叢集中,節點不會将指令代理給負責給定key的節點,而是将用戶端重定向到服務于給定key空間部分的正确節點。

最終,用戶端獲得叢集的最新表示,以及哪個節點服務于哪個key子集,是以在正常操作期間,用戶端直接連接配接正确的節點,以便發送給定的指令。

由于使用異步複制,節點不會等待其他節點的寫入确認(如果沒有使用wait指令明确請求)。

此外,由于多key指令僅限于相近key,是以除非重新分區,否則資料不會在節點之間移動。

正常操作的處理方式與單個Redis執行個體完全相同。這意味着,在具有N個主節點的Redis叢集中,因為是線性擴容的設計,您可以預期得到單個Redis執行個體乘以N的性能。同時,查詢通常在一次往返中執行,因為用戶端通常保留與節點的持久連接配接,是以延遲資料也與單個獨立Redis節點的情況相同。

非常高的性能和可擴充性,同時保持較弱但合理的資料安全和可用性是Redis叢集的主要目标。

為什麼合并操作是被禁止的

Redis Cluster 的設計避免了在多個節點中同一鍵值對的版本沖突,Redis資料模型決定了,合并操作并不總是理想的。Redis中的值通常非常大,常見的情況是,清單或排序集包含數百萬個元素。資料類型在語義上也是複雜的。傳輸和合并這些類型的值可能是一個主要瓶頸,可能需要應用程式端非核心邏輯更新、使用額外記憶體存儲中繼資料等等。

這裡沒有嚴格的技術限制。CRDT或同步複制狀态機可以模拟類似于Redis的複雜資料類型。然而,這些系統的實際運作時行為與Redis叢集不同。Redis叢集的設計是為了涵蓋非叢集Redis版本的确切用例。

思考題

  • Redis Cluster滿足了CAP理論的哪幾條?
  • Redis的無Proxy的架構有什麼優缺點 ?

附錄: CRC16 C代碼實作

/*
 * Copyright 2001-2010 Georges Menie (www.menie.org)
 * Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the University of California, Berkeley nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* CRC16 implementation according to CCITT standards.
 *
 * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
 * following parameters:
 *
 * Name                       : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
 * Width                      : 16 bit
 * Poly                       : 1021 (That is actually x^16 + x^12 + x^5 + 1)
 * Initialization             : 0000
 * Reflect Input byte         : False
 * Reflect Output CRC         : False
 * Xor constant to output CRC : 0000
 * Output for "123456789"     : 31C3
 */

static const uint16_t crc16tab[256]= {
    0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
    0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
    0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
    0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
    0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
    0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
    0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
    0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
    0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
    0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
    0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
    0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
    0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
    0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
    0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
    0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
    0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
    0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
    0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
    0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
    0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
    0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
    0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
    0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
    0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
    0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
    0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
    0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
    0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
    0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
    0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
    0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};

uint16_t crc16(const char *buf, int len) {
    int counter;
    uint16_t crc = 0;
    for (counter = 0; counter < len; counter++)
            crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
    return crc;
}           

原文連結:https://zhuanlan.zhihu.com/p/577422724

繼續閱讀