1. 程式人生 > >Redis Cluster學習筆記

Redis Cluster學習筆記

Redis在3.0版正式引入了叢集這個特性。Redis叢集是一個分散式(distributed)、容錯(fault-tolerant)的 Redis記憶體K/V服務, 叢集可以使用的功能是普通單機 Redis 所能使用的功能的一個子集(subset),比如Redis叢集並不支援處理多個keys的命令,因為這需要在不同的節點間移動資料,從而達不到像Redis那樣的效能,在高負載的情況下可能會導致不可預料的錯誤。

Redis叢集的幾個重要特徵:

(1).Redis 叢集的分片特徵在於將鍵空間分拆了16384個槽位,每一個節點負責其中一些槽位。

(2).Redis提供一定程度的可用性,可以在某個節點宕機或者不可達的情況下繼續處理命令.

(3).Redis 叢集中不存在中心(central)節點或者代理(proxy)節點, 叢集的其中一個主要設計目標是達到線性可擴充套件性(linear scalability)。

1. Redis的資料分片(Sharding)

Redis 叢集的鍵空間被分割為 16384 (2^14)個槽(slot), 叢集的最大節點數量也是 16384 個(推薦的最大節點數量為 1000 個),同理每個主節點可以負責處理1到16384個槽位。

當16384個槽位都有主節點負責處理時,叢集進入”穩定“上線狀態,可以開始處理資料命令。當叢集沒有處理穩定狀態時,可以通過執行重配置(reconfiguration)操作,使得每個雜湊槽都只由一個節點進行處理。

重配置指的是將某個/某些槽從一個節點移動到另一個節點。一個主節點可以有任意多個從節點, 這些從節點用於在主節點發生網路斷線或者節點失效時, 對主節點進行替換。

叢集的使用公式CRC16(Key)&16383計算key屬於哪個槽:

HASH_SLOT = CRC16(key) mod 16384

CRC16其結果長度為16位。

2. Redis叢集節點

部分內容摘自附錄2。Redis 叢集中的節點不僅要記錄鍵和值的對映,還需要記錄叢集的狀態,包括鍵到正確節點的對映。它還具有自動發現其他節點,識別工作不正常的節點,並在有需要時,在從節點中選舉出新的主節點的功能。

為了執行以上列出的任務, 叢集中的每個節點都與其他節點建立起了“叢集連線(cluster bus)”, 該連線是一個 TCP 連線, 使用二進位制協議進行通訊。

節點之間使用 Gossip 協議 來進行以下工作:

a).傳播(propagate)關於叢集的資訊,以此來發現新的節點。

b).向其他節點發送 PING 資料包,以此來檢查目標節點是否正常運作。

c).在特定事件發生時,傳送叢集資訊。

除此之外, 叢集連線還用於在叢集中釋出或訂閱資訊。

叢集節點不能前端代理命令請求, 所以客戶端應該在節點返回 -MOVED或者 -ASK轉向(redirection)錯誤時, 自行將命令請求轉發至其他節點。

客戶端可以自由地向叢集中的任何一個節點發送命令請求, 並可以在有需要時, 根據轉向錯誤所提供的資訊, 將命令轉發至正確的節點, 所以在理論上來說, 客戶端是無須儲存叢集狀態資訊的。但如果客戶端可以將鍵和節點之間的對映資訊儲存起來, 可以有效地減少可能出現的轉向次數, 籍此提升命令執行的效率。

每個節點在叢集中由一個獨一無二的 ID標識, 該 ID 是一個十六進位制表示的 160 位隨機數,在節點第一次啟動時由 /dev/urandom 生成。節點會將它的 ID 儲存到配置檔案, 只要這個配置檔案不被刪除, 節點就會一直沿用這個 ID 。一個節點可以改變它的 IP 和埠號, 而不改變節點 ID 。 叢集可以自動識別出IP/埠號的變化, 並將這一資訊通過 Gossip協議廣播給其他節點知道。

下面是每個節點都有的關聯資訊, 並且節點會將這些資訊傳送給其他節點:

a).節點所使用的 IP 地址和 TCP 埠號。

b).節點的標誌(flags)。

c).節點負責處理的雜湊槽。

b).節點最近一次使用叢集連線傳送 PING 資料包(packet)的時間。

e).節點最近一次在回覆中接收到 PONG 資料包的時間。

f).叢集將該節點標記為下線的時間。

g).該節點的從節點數量。

如果該節點是從節點的話,那麼它會記錄主節點的節點 ID 。 如果這是一個主節點的話,那麼主節點 ID 這一欄的值為 0000000。

在瞭解Redis Cluster的叢集基本特徵後,我們首先搭建出這個Redis Cluster叢集。

3. 安裝Redis 3.0.x

當前最新版為3.0.1

wget http://download.redis.io/releases/redis-3.0.1.tar.gz
tar xvzf redis-3.0.1.tar.gz
cd redis-3.0.1/
make -j
#apt-get install tcl
make test
###將redis安裝到/usr/local/redis3上
#cd src && make PREFIX=/usr/local/redis3 install
###建立符號連結
 #ls /usr/local/redis3/bin/redis-*
/usr/local/redis3/bin/redis-benchmark  /usr/local/redis3/bin/redis-check-dump  /usr/local/redis3/bin/redis-sentinel
/usr/local/redis3/bin/redis-check-aof  /usr/local/redis3/bin/redis-cli         /usr/local/redis3/bin/redis-server
#for i in `cd /usr/local/redis3/bin; ls redis-*`
do
ln -s /usr/local/redis3/bin/$i /usr/local/bin/$i
done;
#mkdir -p /usr/local/redis3/conf
#ln -sf /usr/local/redis3/conf /etc/redis3
###檢查版本資訊
redis-cli -v
redis-cli 3.0.1

4. Redis Cluster配置

執行在叢集模式的Redis例項與普通的Redis例項有所不同,叢集模式需要通過配置啟用cluster特性,開啟叢集模式後的Redis例項便可以使用叢集特有的命令和特性了.下面是一個最少選項的叢集的配置檔案 : 注意如果使用的是單機測試,最好把cluster-config-file nodes.conf設定為對應的 埠 nodes-xxx.conf,還有就是 pid 設定為當前 目錄下 pidfile ./redis.pid

port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

檔案中的 cluster-enabled 選項用於開例項的叢集模式, 而 cluster-conf-file 選項則設定了儲存節點配置檔案的路徑, 預設值為 nodes.conf。該節點配置檔案無須人為修改,它由Redis叢集在啟動時自動建立, 並在有需要時自動進行更新。

若要讓叢集正常運作至少需要三個主節點,我們的環境中,每個主節點附帶一個從節點,所以一共六個節點。埠為7001-7006。

在/app/redis3, 並建立六個以埠號為名字的子目錄, 稍後我們在將每個目錄中執行一個 Redis 例項:

cd /app/redis3
mkdir 7001 7002 7003 7004 7005 7006
 cp /etc/redis3/conf/redis.conf  /app/redis3/7001/
......
cp /etc/redis3/conf/redis.conf  /app/redis3/7006/

將redis.conf裡的埠號修改為對應的埠。下面我們開啟對應的目錄,啟動redis例項即可,啟動的時候要進入到對應的目錄然後啟動。

cd /app/redis3/7001;nohup redis-server redis.conf &
cd /app/redis3/7002;nohup redis-server redis.conf &
......

例項列印的日誌顯示, 因為 nodes.conf 檔案不存在, 所以每個節點都為它自身指定了一個新的 ID ,

/app/redis3/7006# tail -f nohup.out
27040:M 09 May 22:53:50.197 * No cluster configuration found, I'm 1984c27297c6ef50bbfcbd35c11b93cc40ba17e4
/app/redis3/7006# cat nodes.conf 
d2b437ca8b9007dcdb63ac16210f6540860361e3 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0

現在我們已經有了六個正在執行中的 Redis 例項, 接下來我們需要使用這些例項來建立叢集。通過使用 Redis 叢集命令列工具 redis-trib , 編寫節點配置檔案的工作可以非常容易地完成: redis-trib 位於 Redis 原始碼的 src 資料夾中, 它是一個 Ruby 程式, 這個程式通過向例項傳送特殊命令來完成建立新叢集, 檢查叢集, 或者對叢集進行重新分片(reshared)等工作。這裡通過create命令來建立叢集,指定replicas=1,即每一個主例項有一個從例項。redis-trib 會打印出一份預想中的配置給你看, 如果你覺得沒問題的話, 就可以輸入 yes , redis-trib 就會將這份配置應用到叢集當中,讓各個節點開始互相通訊,最後可以得到如下資訊

~/redis-3.0.1/src# apt-get install ruby gem
~/redis-3.0.1/src# gem sources -a http://ruby.taobao.org/
~/redis-3.0.1/src# gem install redis
~/redis-3.0.1/src# cp redis-trib.rb /usr/local/redis3/bin/
~/redis-3.0.1/src# ln -sf /usr/local/redis3/bin/redis-trib.rb /usr/bin/redis-trib.rb
~/redis-3.0.1/src# redis-trib.rb create --replicas 1  127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006
M: 1984c27297c6ef50bbfcbd35c11b93cc40ba17e4 127.0.0.1:7001
   slots:0-5460 (5461 slots) master
M: 481e256be4c724f5a2c64a761e52b4be61ca45b4 127.0.0.1:7002
   slots:5461-10922 (5462 slots) master
M: b5b652fa02d9999861e66c843b01fd2700c02adf 127.0.0.1:7003
   slots:10923-16383 (5461 slots) master
S: 821ec823dc0c2d4f65319e84fe74157fb1014155 127.0.0.1:7004
   replicates 1984c27297c6ef50bbfcbd35c11b93cc40ba17e4
S: b3b8541b9520d707180d56a2fb3cf3ee6895ed10 127.0.0.1:7005
   replicates 481e256be4c724f5a2c64a761e52b4be61ca45b4
S: d2b437ca8b9007dcdb63ac16210f6540860361e3 127.0.0.1:7006
   replicates b5b652fa02d9999861e66c843b01fd2700c02adf
Can I set the above configuration? (type 'yes' to accept):
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

以上資訊的其中一部分可以通過向叢集中的任意節點(主節點或者從節點都可以)傳送 CLUSTER NODES 命令來獲得。該命令還可以獲得節點 ID , IP 地址和埠號, 標誌(flag), 最後傳送 PING 的時間, 最後接收 PONG 的時間, 連線狀態, 節點負責處理的槽。

redis-cli -p 7001 cluster nodes
481e256be4c724f5a2c64a761e52b4be61ca45b4 127.0.0.1:7002 master - 0 1431186119174 2 connected 5461-10922
b3b8541b9520d707180d56a2fb3cf3ee6895ed10 127.0.0.1:7005 slave 481e256be4c724f5a2c64a761e52b4be61ca45b4 0 1431186120677 5 connected
d2b437ca8b9007dcdb63ac16210f6540860361e3 127.0.0.1:7006 slave b5b652fa02d9999861e66c843b01fd2700c02adf 0 1431186119174 6 connected
b5b652fa02d9999861e66c843b01fd2700c02adf 127.0.0.1:7003 master - 0 1431186118673 3 connected 10923-16383
821ec823dc0c2d4f65319e84fe74157fb1014155 127.0.0.1:7004 slave 1984c27297c6ef50bbfcbd35c11b93cc40ba17e4 0 1431186120176 4 connected
1984c27297c6ef50bbfcbd35c11b93cc40ba17e4 127.0.0.1:7001 myself,master - 0 0 1 connected 0-5460

5. 連線Redis叢集

通過上面的輸出,我們可以看出Redis三個主節點的slot範圍。一個 Redis 客戶端可以向叢集中的任意節點(包括從節點)傳送命令請求。我們首先連線第一個節點:

redis-cli -p 7001
127.0.0.1:7001> set a 1 
(error) MOVED 15495 127.0.0.1:7003
127.0.0.1:7001> get a
(error) MOVED 15495 127.0.0.1:7003
127.0.0.1:7001> set b 1
OK

節點會對命令請求進行分析和key的slot計算,並且會查詢這個命令所要處理的鍵所在的槽。如果要查詢的雜湊槽正好就由接收到命令的節點負責處理, 那麼節點就直接執行這個命令。

另一方面, 如果所查詢的槽不是由該節點處理的話, 節點將檢視自身內部所儲存的雜湊槽到節點 ID 的對映記錄, 並向客戶端回覆一個 MOVED 錯誤。上面的錯誤資訊包含鍵 x 所屬的雜湊槽15495, 以及負責處理這個槽的節點的 IP 和埠號 127.0.0.1:7003 。

雖然我們用Node ID來標識叢集中的節點, 但是為了讓客戶端的轉向操作儘可能地簡單, 節點在 MOVED 錯誤中直接返回目標節點的 IP 和埠號, 而不是目標節點的 ID 。客戶端應該記錄槽15495由節點127.0.0.1:7003負責處理“這一資訊, 這樣當再次有命令需要對槽15495執行時, 客戶端就可以加快尋找正確節點的速度。這樣,當叢集處於穩定狀態時,所有客戶端最終都會儲存有一個雜湊槽至節點的對映記錄,使得叢集非常高效: 客戶端可以直接向正確的節點發送命令請求, 無須轉向、代理或者其他任何可能發生單點故障(single point failure)的實體(entiy)。

6.java 連線Redis叢集

使用 jedis-2.7.2.jar jar 包

import java.util.HashSet;
import java.util.Set;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

public class JedisClusterTest {

	public static void main(String[] args) {

		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(20);
		config.setMaxIdle(2);

		HostAndPort hp0 = new HostAndPort("localhost", 7000);
		HostAndPort hp1 = new HostAndPort("localhost", 7001);
		HostAndPort hp2 = new HostAndPort("localhost", 7002);
		HostAndPort hp3 = new HostAndPort("localhost", 7003);
		HostAndPort hp4 = new HostAndPort("localhost", 7004);
		HostAndPort hp5 = new HostAndPort("localhost", 7005);

		Set<HostAndPort> hps = new HashSet<HostAndPort>();
		hps.add(hp0);
		hps.add(hp1);
		hps.add(hp2);
		hps.add(hp3);
		hps.add(hp4);
		hps.add(hp5);

		// 超時,最大的轉發數,最大連結數,最小連結數都會影響到叢集
		JedisCluster jedisCluster = new JedisCluster(hps, 5000, 10, config);

		long start = System.currentTimeMillis();
		for (int i = 0; i < 100; i++) {
			jedisCluster.set("sn" + i, "n" + i);
		}
		long end = System.currentTimeMillis();

		System.out.println("Simple  @ Sharding Set : " + (end - start) / 10000);

		for (int i = 0; i < 1000; i++) {
			System.out.println(jedisCluster.get("sn" + i));
		}

		jedisCluster.close();

	}

}

7.java 連線Redis叢集常見錯誤


7.1.Too many Cluster redirections
這種情況一般情況下都是 redis 繫結ip問題,預設情況下 redis 繫結的 ip 是本機的 127.0.0.1 如果redis 部署在其他機器上,而本地測試程式想要通過網路連結到 redis 叢集,那麼就需要注意,在 redis.conf 檔案中配置 bind xxx.xxx.xxx.xxx 注意這個地方要配置成客戶端連結的 ip

注意:如果 redis 綁定了指定的 ip 地址了,這時候在啟動叢集的時候也需要注意,需要指定ip地址了,本來啟動叢集的方式是這樣的:
./redis-trib.rb create --replicas 1 127.0.0.1:7000  127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
現在就是這樣的
./redis-trib.rb create --replicas 1 xxx.xxx.xxx.xxx:7000  xxx.xxx.xxx.xxx:7001 xxx.xxx.xxx.xxx:7002 xxx.xxx.xxx.xxx:7003 xxx.xxx.xxx.xxx:7004 xxx.xxx.xxx.xxx:7005./redis-trib.rb create --replicas 1 127.0.0.1:7000  127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005