鍍金池/ 教程/ 大數(shù)據(jù)/ 分片
使用 Redis 實(shí)現(xiàn) Twitter(上)
集群(下)
使用 Redis 實(shí)現(xiàn) Twitter(下)
使用 Redis 作為 LRU 緩存
高可用(上)
高可用客戶端指引
集群(中)
高可用(下)
持久化
Redis 介紹
集中插入
集群(上)
從入門到精通(上)
從入門到精通(下)
從入門到精通(中)
分片
數(shù)據(jù)類型初探
復(fù)制

分片

分片(partitioning)就是將你的數(shù)據(jù)拆分到多個(gè) Redis 實(shí)例的過程,這樣每個(gè)實(shí)例將只包含所有鍵的子集。本文第一部分將向你介紹分片的概念,第二部分將向你展示 Redis 分片的可選方案。

分片為何有用(Why useful)

Redis 的分片承擔(dān)著兩個(gè)主要目標(biāo):

  • 允許使用很多電腦的內(nèi)存總和來支持更大的數(shù)據(jù)庫。沒有分片,你就被局限于單機(jī)能支持的內(nèi)存容量。
  • 允許伸縮計(jì)算能力到多核或多服務(wù)器,伸縮網(wǎng)絡(luò)帶寬到多服務(wù)器或多網(wǎng)絡(luò)適配器。

分片基礎(chǔ)(Basics)

有很多不同的分片標(biāo)準(zhǔn)(criteria)。假想我們有 4 個(gè) Redis 實(shí)例 R0,R1,R2,R3,還有很多表示用戶的鍵,像 user:1,user:2,… 等等,我們能找到不同的方式來選擇一個(gè)指定的鍵存儲(chǔ)在哪個(gè)實(shí)例中。換句話說,有許多不同的辦法來映射一個(gè)鍵到一個(gè)指定的 Redis 服務(wù)器。

最簡(jiǎn)單的執(zhí)行分片的方式之一是范圍分片(range partitioning),通過映射對(duì)象的范圍到指定的 Redis 實(shí)例來完成分片。例如,我可以假設(shè)用戶從 ID 0 到 ID 10000 進(jìn)入實(shí)例 R0,用戶從 ID 10001 到 ID 20000 進(jìn)入實(shí)例 R1,等等。

這套辦法行得通,并且事實(shí)上在實(shí)踐中被采用,然而,這有一個(gè)缺點(diǎn),就是需要一個(gè)映射范圍到實(shí)例的表格。這張表需要管理,不同類型的對(duì)象都需要一個(gè)表,所以范圍分片在 Redis 中常常并不可取,因?yàn)檫@要比替他分片可選方案低效得多。

一種范圍分片的替代方案是哈希分片(hash partitioning)。這種模式適用于任何鍵,不需要鍵像 object_name: 這樣的餓形式,就像這樣簡(jiǎn)單:

  • 使用一個(gè)哈希函數(shù)(例如,crc32 哈希函數(shù)) 將鍵名轉(zhuǎn)換為一個(gè)數(shù)字。例如,如果鍵是 foobar,crc32(foobar)將會(huì)輸出類似于 93024922 的東西。
  • 對(duì)這個(gè)數(shù)據(jù)進(jìn)行取模運(yùn)算,以將其轉(zhuǎn)換為一個(gè) 0 到 3 之間的數(shù)字,這樣這個(gè)數(shù)字就可以映射到我的 4 臺(tái) Redis 實(shí)例之一。93024922 模 4 等于 2,所以我知道我的鍵 foobar 應(yīng)當(dāng)存儲(chǔ)到 R2 實(shí)例。注意:取模操作返回除法操作的余數(shù),在許多編程語言總實(shí)現(xiàn)為%操作符。

有許多其他的方式可以分片,從這兩個(gè)例子中你就可以知道了。一種哈希分片的高級(jí)形式稱為一致性哈希(consistent hashing),被一些 Redis 客戶端和代理實(shí)現(xiàn)。

分片的不同實(shí)現(xiàn)(Different implementations)

分片可由軟件棧中的不同部分來承擔(dān)。

  • 客戶端分片(Client side partitioning)意味著,客戶端直接選擇正確的節(jié)點(diǎn)來寫入和讀取指定鍵。許多 Redis 客戶端實(shí)現(xiàn)了客戶端分片。
  • 代理協(xié)助分片(Proxy assisted partitioning)意味著,我們的客戶端發(fā)送請(qǐng)求到一個(gè)可以理解 Redis 協(xié)議的代理上,而不是直接發(fā)送請(qǐng)求到 Redis 實(shí)例上。代理會(huì)根據(jù)配置好的分片模式,來保證轉(zhuǎn)發(fā)我們的請(qǐng)求到正確的 Redis 實(shí)例,并返回響應(yīng)給客戶端。Redis 和 Memcached 的代理 Twemproxy 實(shí)現(xiàn)了代理協(xié)助的分片。
  • 查詢路由(Query routing)意味著,你可以發(fā)送你的查詢到一個(gè)隨機(jī)實(shí)例,這個(gè)實(shí)例會(huì)保證轉(zhuǎn)發(fā)你的查詢到正確的節(jié)點(diǎn)。Redis 集群在客戶端的幫助下,實(shí)現(xiàn)了查詢路由的一種混合形式 (請(qǐng)求不是直接從 Redis 實(shí)例轉(zhuǎn)發(fā)到另一個(gè),而是客戶端收到重定向到正確的節(jié)點(diǎn))。

分片的缺點(diǎn)(Disadvantages)

Redis 的一些特性與分片在一起時(shí)玩轉(zhuǎn)的不是很好:

  • 涉及多個(gè)鍵的操作通常不支持。例如,你不能對(duì)映射在兩個(gè)不同 Redis 實(shí)例上的鍵執(zhí)行交集(事實(shí)上有辦法做到,但不是直接這么干)。
  • 涉及多個(gè)鍵的事務(wù)不能使用。
  • 分片的粒度(granularity)是鍵,所以不能使用一個(gè)很大的鍵來分片數(shù)據(jù)集,例如一個(gè)很大的有序集合。
  • 當(dāng)使用了分片,數(shù)據(jù)處理變得更復(fù)雜,例如,你需要處理多個(gè) RDB/AOF 文件,備份數(shù)據(jù)時(shí)你需要聚合多個(gè)實(shí)例和主機(jī)的持久化文件。
  • 添加和刪除容量也很復(fù)雜。例如,Redis 集群具有運(yùn)行時(shí)動(dòng)態(tài)添加和刪除節(jié)點(diǎn)的能力來支持透明地再均衡數(shù)據(jù),但是其他方式,像客戶端分片和代理都不支持這個(gè)特性。但是,有一種稱為預(yù)分片(Presharding)的技術(shù)在這一點(diǎn)上能幫上忙。

數(shù)據(jù)存儲(chǔ)還是緩存(Store or cache)

盡管無論是將 Redis 作為數(shù)據(jù)存儲(chǔ)還是緩存,Redis 的分片概念上都是一樣的,但是作為數(shù)據(jù)存儲(chǔ)時(shí)有一個(gè)重要的局限。當(dāng) Redis 作為數(shù)據(jù)存儲(chǔ)時(shí),一個(gè)給定的鍵總是映射到相同的 Redis 實(shí)例。當(dāng) Redis 作為緩存時(shí),如果一個(gè)節(jié)點(diǎn)不可用而使用另一個(gè)節(jié)點(diǎn),這并不是一個(gè)什么大問題,按照我們的愿望來改變鍵和實(shí)例的映射來改進(jìn)系統(tǒng)的可用性(就是系統(tǒng)回復(fù)我們查詢的能力)。

一致性哈希實(shí)現(xiàn)常常能夠在指定鍵的首選節(jié)點(diǎn)不可用時(shí)切換到其他節(jié)點(diǎn)。類似的,如果你添加一個(gè)新節(jié)點(diǎn),部分?jǐn)?shù)據(jù)就會(huì)開始被存儲(chǔ)到這個(gè)新節(jié)點(diǎn)上。

這里的主要概念如下:

  • 如果 Redis 用作緩存,使用一致性哈希來來實(shí)現(xiàn)伸縮擴(kuò)展(scaling up and down)是很容易的。
  • 如果 Redis 用作存儲(chǔ),使用固定的鍵到節(jié)點(diǎn)的映射,所以節(jié)點(diǎn)的數(shù)量必須固定不能改變。否則,當(dāng)增刪節(jié)點(diǎn)時(shí),就需要一個(gè)支持再平衡節(jié)點(diǎn)間鍵的系統(tǒng),當(dāng)前只有 Redis 集群可以做到這一點(diǎn),但是 Redis 集群現(xiàn)在還處在 beta 階段,尚未考慮再生產(chǎn)環(huán)境中使用。

預(yù)分片(Presharding)

我們已經(jīng)知道分片存在的一個(gè)問題,除非我們使用 Redis 作為緩存,增加和刪除節(jié)點(diǎn)是一件很棘手的事情,使用固定的鍵和實(shí)例映射要簡(jiǎn)單得多。

然而,數(shù)據(jù)存儲(chǔ)的需求可能一直在變化。今天我可以接受 10 個(gè) Redis 節(jié)點(diǎn)(實(shí)例),但是明天我可能就需要 50 個(gè)節(jié)點(diǎn)。

因?yàn)?Redis 只有相當(dāng)少的內(nèi)存占用(footprint)而且輕量級(jí)(一個(gè)空閑的實(shí)例只是用 1MB 內(nèi)存),一個(gè)簡(jiǎn)單的解決辦法是一開始就開啟很多的實(shí)例。即使你一開始只有一臺(tái)服務(wù)器,你也可以在第一天就決定生活在分布式的世界里,使用分片來運(yùn)行多個(gè) Redis 實(shí)例在一臺(tái)服務(wù)器上。

你一開始就可以選擇很多數(shù)量的實(shí)例。例如,32 或者 64 個(gè)實(shí)例能滿足大多數(shù)的用戶,并且為未來的增長(zhǎng)提供足夠的空間。

這樣,當(dāng)你的數(shù)據(jù)存儲(chǔ)需要增長(zhǎng),你需要更多的 Redis 服務(wù)器,你要做的就是簡(jiǎn)單地將實(shí)例從一臺(tái)服務(wù)器移動(dòng)到另外一臺(tái)。當(dāng)你新添加了第一臺(tái)服務(wù)器,你就需要把一半的 Redis 實(shí)例從第一臺(tái)服務(wù)器搬到第二臺(tái),如此等等。

使用 Redis 復(fù)制,你就可以在很小或者根本不需要停機(jī)時(shí)間內(nèi)完成移動(dòng)數(shù)據(jù):

  • 在你的新服務(wù)器上啟動(dòng)一個(gè)空實(shí)例。
  • 移動(dòng)數(shù)據(jù),配置新實(shí)例為源實(shí)例的從服務(wù)。
  • 停止你的客戶端。
  • 更新被移動(dòng)實(shí)例的服務(wù)器 IP 地址配置。
  • 向新服務(wù)器上的從節(jié)點(diǎn)發(fā)送 SLAVEOF NO ONE 命令。
  • 以新的更新配置啟動(dòng)你的客戶端。
  • 最后關(guān)閉掉舊服務(wù)器上不再使用的實(shí)例。

Redis 分片的實(shí)現(xiàn)(Implementations)

到目前為止,我們從理論上討論了 Redis 分片,但是實(shí)踐情況如何呢?你應(yīng)該使用什么系統(tǒng)呢?

Redis 集群(Redis Cluster)

Redis 集群是自動(dòng)分片和高可用的首選方式。當(dāng)前還不能完全用于生產(chǎn)環(huán)境,但是已經(jīng)進(jìn)入了 beta 階段,所以我們推薦你開始小試牛刀。你可以從集群教程(請(qǐng)持續(xù)關(guān)注本公眾賬號(hào)后續(xù)文章,譯者注)中獲取更多 Redis 集群的相關(guān)信息。

一旦 Redis 集群可用,以及支持 Redis 集群的客戶端可用,Redis 集群將會(huì)成為 Redis 分片的事實(shí)標(biāo)準(zhǔn)。

Redis 集群是查詢路由和客戶端分片的混合模式。

Twemproxy

Twemproxy 是 Twitter 開發(fā)的一個(gè)支持 Memcached ASCII 和 Redis 協(xié)議的代理。它是單線程的,由 C 語言編寫,運(yùn)行非常的快。他是基于 Apache 2.0 許可的開源項(xiàng)目。

Twemproxy 支持自動(dòng)在多個(gè) Redis 實(shí)例間分片,如果節(jié)點(diǎn)不可用時(shí),還有可選的節(jié)點(diǎn)排除支持(這會(huì)改變鍵和實(shí)例的映射,所以你應(yīng)該只在將 Redis 作為緩存是才使用這個(gè)特性)。

這并不是單點(diǎn)故障(single point of failure),因?yàn)槟憧梢詥?dòng)多個(gè)代理,并且讓你的客戶端連接到第一個(gè)接受連接的代理。

從根本上說,Twemproxy 是介于客戶端和 Redis 實(shí)例之間的中間層,這就可以在最下的額外復(fù)雜性下可靠地處理我們的分片。這是當(dāng)前我們建議的處理 Redis 分片的方式。你可以閱讀更多關(guān)于 Twemproxy 的信息(作者的這篇博客文章 http://antirez.com/news/44,譯者注)。

支持一致性哈希的客戶端

Twemproxy 之外的可選方案,是使用實(shí)現(xiàn)了客戶端分片的客戶端,通過一致性哈?;蛘邉e的類似算法。有多個(gè)支持一致性哈希的 Redis 客戶端,例如 Redis-rb 和 Predis。

請(qǐng)查看完整的 Redis 客戶端列表,看看是不是有支持你的編程語言的,并實(shí)現(xiàn)了一致性哈希的成熟客戶端。