Redis集羣簡介

 

1.數據分佈

1.1 順序分區

1.2 哈希分區

1.2.1 節點取餘

1.2.2 一致性哈希

1.2.3 虛擬槽分區

2 安裝集羣

2.1 安裝步驟

2.2 原生命令安裝

2.2.1 配置節點

2.2.2 節點互通

2.2.3 分配槽位

2.3 官方工具安裝

2.3.1 安裝ruby工具包

3 集羣伸縮

3.1 擴容集羣

3.1.1 準備新節點

3.1.2 加入集羣

3.1.3 遷移槽和數據

3.1.4 集羣擴容

3.2 收縮集羣

4 客戶端路由

4.1 moved重定向

4.2 ask重定向

4.3 smart客戶端

4.4 多節點命令實現

4.5 如何實現批量操作

5 故障轉移 &集羣運維

5.1 故障發現

5.1.1 主觀下線

5.1.2 客觀下線

5.2 故障恢復

5.3 集羣運維

5.3.1 集羣完整性

5.3.2 網絡帶寬

5.3.3 集羣傾斜

5.3.4 讀寫分離

5.3.5 數據遷移

5.3.6 集羣的限制

6.

6.1 收益&代價

6.2 緩存更新策略

6.3 緩存穿透問題

 

1.數據分佈

1.1 順序分區

產品:HBase 、BigTable

1.2 哈希分區

1.2.1 節點取餘

缺點:擴容節點時,遷移率非常高。建議翻倍擴容(3--->6),可降低到50%(也很高)

1.2.2 一致性哈希

一致性哈希:每次擴容或者縮容隻影響附近的節點;

缺點:沒有辦法實現數據的負載均衡,建議翻倍伸縮

1.2.3 虛擬槽分區

產品:Redis集羣

虛擬槽:每個槽映射一個數字子集,一般比節點數大

 

 

2 安裝集羣

2.1 安裝步驟

 

  1. 節點:開啓集羣模式:cluster-enabled:yes
  2. meet:節點間相互通信,獲取彼此的信息
  3. 指派槽:指定該節點負責的槽位
  4. 主從複製: 主從複製不依賴於哨兵,通過節點間的相互監控

2.2 原生命令安裝

2.2.1 配置節點

port 7000 daemonize yes dir "./" logfile "${port}.log" dbfilename "dump-${port}.rdb" cluster-enabled yes #是否開啓集羣 cluster-config-file nodes-${port}.conf #集羣配置文件 cluster-node-timeout 15000 #15s,節點超時時間 cluster-require-full-coverage no #當有節點宕機時,集羣是否不可用

 

複製配置文件:

sed 's/7000/7001/g' 源目錄 目標目錄 sed 's/7000/70002/g' 7000/redis.conf > 7002/redis.conf

 

啓動服務:

redis-server redis.conf redis-cli -p 端口號 cat node.conf //查看節點配置文件 redis-cli -p 7000 cluster nodes //查看節點信息 redis-cli -p 7000 cluster info //查看集羣信息

 

查看信息

cluster info //查看集羣信息 cluster nodes // 查看節點信息

 

2.2.2 節點互通

只要一個節點meet其他節點,所有節點就都連通了。

# cluster meet ip port redis-cli -p 7000 cluster meet 127.0.0.1 7001 //7000meet7001 cluster nodes// 可以查看節點信息

 

2.2.3 分配槽位

cluster addslots slot cluster slots //查看槽位信息

 

腳本:

#!/bin/bash start=${1} end=${2} port=${3} for slot in `seq ${start} ${end}` do echo "slot:${slot}" redis-cli -p ${port} cluster addslots ${slot} done

2.2.4 設置主從

cluster replicate 主節點NodeId nodeId不同於runId,nodeId不會因爲機器重啓而改變

 

2.3 官方工具安裝

2.3.1 安裝ruby工具包

//下載壓縮包 yum install ruby wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz //解壓 tar -zxvf 目錄 ./configure -prefix=/usr/local/ruby //安裝 make & make install // 下載rubygems wget http://rubygems.org/downloads/redis-3.3.0.gem # 安裝rubygems gem install -l redis-3.3.0.gem gem list --check redis gem # 複製配置文件到usr/local/bin下 將redis-trib.rb文件複製到usr/local/bin下 cp ${Redus_Home}/src/redis-trib.rb /usr/local/bin

 

2.2.2 啓動集羣

// 啓動Redis redis-server redis.conf // 創建集羣 redis-trib.rb create --replicas 1(從節點個數) 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005

 

3 集羣伸縮

3.1 擴容集羣

  • 加入主節點實現擴容
  • 加入從節點實現故障轉移

3.1.1 準備新節點

配置文件-> 啓動服務 ->孤立節點

3.1.2 加入集羣

// 命令式 cluster meet IP地址 端口號 // 工具式 【好處:在meet前會檢測當期節點是否是孤立節點,避免兩個集羣狼狽爲奸】 redis-trib.rb add-node new_host:new_port existing_host:existing_port --slave --mastser-id

3.1.3 遷移槽和數據

  1. 對目標節點發送:cluster setslot {slot} importing {sourceNodeId}命令,讓目標節點準備導入槽的數據;
  2. 對源節點發生:cluster setslot {slot} migrating {targetNodeId}命令,讓源節點準備遷出槽的數據;
  3. 源節點循環執行cluster getkeysinslot {slot} {count}命令,每次獲取count個屬於槽的鍵;
  4. 在源節點執行 migrate {targetIp} {targetPort} key 0(數據庫,集羣下只有一個數據庫 db0) {timeout} 命令把指定的key遷移走
  5. 重複執行3,4直到槽下所有的鍵數據遷移到目標節點
  6. 向集羣內所有主節點發送cluster setslot {slot} node {targetNodeId} 命令,通知槽分配給了目標節點

Redis3.0.6 支持pipeline功能,可以批量遷移key,但是有bug(如果同時混合有過期數據和非過期數據,會將非過期數據置爲過期數據,導致數據丟失),3.2.8修復該bug

3.1.4 集羣擴容

//1.複製配置文件 sed "s/8000/8007/g" redis-8000.conf > redis-8007.conf //2.檢查配置文件 cat redis-8007.conf //3.啓動服務 redis-server redis-8007.conf //4.添加節點 //4.1 添加主節點 redis-trib.rb add-node 127.0.0.1:8006 127.0.0.1:8000 cluseter nodes //檢查節點信息 //4.2 添加從節點 redis-trib.rb add-node new_host:new_port existing_host:existing_port --slave --master-id 主節點NodeId cluseter nodes //檢查節點信息 // 主從複製 cluster replicate masterNodeId //5.分配槽位&遷移數據 redis-trib.rb reshard existing_host:existing_port How many slots do you want to move (from 1 to 16384)? //打算遷移多個個槽(總和) What is the receiving node ID? //接收NodeId soureId: //從哪個節點遷移,all--代表全部 cluseter nodes

 

3.2 收縮集羣

 

忘記節點 :cluster forget {downNodeId}

//1.查看當前節點信息 cluster nodes //2.遷移數據 redis-trib.rb reshard --from 源節點NodeId --to 目標節點NodeId --slots 遷移槽位數 existing_host:existing_port //3.下線節點 [先下從節點,再下主節點;先下主節點會觸發故障自動轉移] redis-trib.rb del-node existing_host:existing_port 下線節點NodeId

 

 

4 客戶端路由

4.1 moved重定向

 

/** redis-cli -p 端口號 [非集羣模式] **/ //1.計算槽位 cluster keyslot key //計算槽位 //2.set值 set php best (error) MOVED 9244 127.0.0.1:8001 //重定向 //3.手動切換& 設值 redis-cli -p new_port /** redis-cli -c -p 端口號 [集羣模式] **/ //自動跳轉&執行命令 127.0.0.1:8000> set php hello -> Redirected to slot [9244] located at 127.0.0.1:8001 OK 127.0.0.1:8001>

4.2 ask重定向

 

4.3 smart客戶端

public class JedisClusterFactory { /** * JedisCluster */ private JedisCluster jedisCluster; /** * 主機:端口號 */ private List<String> hostPortList; /** * 超時時間 */ private int timeout; /** * Init方法 */ public void init() { // 配置 JedisPoolConfig poolConfig = new JedisPoolConfig(); // 節點集 Set<HostAndPort> nodeSet = new HashSet<HostAndPort>(); for (String hostPort : hostPortList) { String[] arr = hostPort.split(":"); if (arr.length != 2) { continue; } nodeSet.add(new HostAndPort(arr[0], Integer.valueOf(arr[1]))); } //創建JedisCluster try { jedisCluster = new JedisCluster(nodeSet, timeout, poolConfig); } catch (Exception e) { e.printStackTrace(); } } /** * 銷燬 */ public void destory() { if (jedisCluster != null) { jedisCluster.close(); } } /** * 獲取客戶端 * @return */ public JedisCluster getJedisCluster() { return jedisCluster; } public void setHostPortList(List<String> hostPortList) { this.hostPortList = hostPortList; } public void setTimeout(int timeout) { this.timeout = timeout; } }

 

4.4 多節點命令實現

//獲取所有節點JedisPool Map<String, JedisPool> jedisPoolMap = jedisCluster.getClusterNodes(); for (Map.Entry<String, JedisPool> entry : jedisPoolMap.entrySet()) { //獲取每個Jedis連接 Jedis jedis = entry.getValue().getResource(); //過濾非主節點 if(!isMaster(jedis)){ continue; } //do something }

4.5 如何實現批量操作

mget、mset無法保證所有key都在同一槽上;

  • 方案一:串行mget

for循環遍歷所有的key,時間複雜度 O(key個數)

  • 方案二:串行IO

將同一槽位的數據存儲在同一個隊列裏,然後執行pipeline,時間複雜度O(節點個數)

  • 並行IO

在串行IO的基本上,啓動多線程,時間複雜度O(1)

  • hash_tag

使用hash,包裝同一key,放在同一節點,時間複雜度O(1)

 

5 故障轉移 &集羣運維

5.1 故障發現

5.1.1 主觀下線

某一節點人爲某節點不可用

5.1.2 客觀下線

半數以上持有槽的主節點都標記某節點下線

5.2 故障恢復

主管下線 --> 客觀下線 --> 故障恢復【資格檢查(與主節點斷線超過150s[默認配置]的從節點不具備競選資格) -> 準備選舉時間(與主節點斷線時間最短的節點先選舉) -->選舉投票(獲得半數以上主節點的從節點,選舉爲主節點) --> 替換主節點 (slaveof no one 、然後把主節點的槽分配給自己、廣播自己的信息)】

5.3 集羣運維

5.3.1 集羣完整性

cluster-require-full-coverage no//是否槽位都可用時,集羣纔可用。

5.3.2 網絡帶寬

建議:

  • 大集羣轉爲多集羣
  • cluster-node-timeout:故障轉移時間和網絡帶寬(1/2 timeout會進行一次節點互通)的權衡

5.3.3 集羣傾斜

  • 節點和槽分配不均
  • 不同槽對應的鍵值數量差異較大
  • 包含bigkey (redis-cli -bigkeys)
  • 內存配置不一樣

5.3.4 讀寫分離

5.3.5 數據遷移

 

redis-trib.rb import --from sourceHost:sourcePort --copy[--replace] tartgetHost:targetPort

note:官方工具是離線的(在遷移期間的新增的數據不一定會同步過去,因爲採用的是scan)

5.3.6 集羣的限制

 

6.

6.1 收益&代價

收益:

  • 加速讀寫
  • 降低後端負載(Mysql)負載

成本:

  • 數據不一致:緩存層和數據層的數據不一致
  • 代碼維護成本:多了一層緩存邏輯
  • 運維成本:RedisCluster

使用場景:

  • 加速請求響應時間
  • 降低後端負載
  • 大量寫合併爲批量寫(eg:計數器)

6.2 緩存更新策略

  1. LRU/FIFO算法剔除:
  2. 超時剔除:設置過期時間
  3. 主動更新

低一致性:最大內存和淘汰策略

高一致性:超時剔除和主動更新結合

6.3 緩存穿透問題

 

不足:1.需要更多的鍵 2.緩存層和存儲層數據短期不一致

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

maven座標:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.1</version> </dependency>

 

連通性測試:

public static void main(String[] args) {

String host ="192.168.3.12";

int port =7000;

Jedis redis = new Jedis(host,port);

System.out.println(redis.ping());

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章