Redis Cluster 搭建

我的 blog: iyuhp.top
原文連接: Redis Cluster 集羣搭建html

關於 redis 集羣,通常有如下幾種模式node

  • 主從複製模式(Master-Slave) : 該模式下,全部的事務操做,都會異步同步到其餘 slave 上,每臺 redis 都存儲相同數據
  • 哨兵模式(Sentinel) : 經過增長 sentinel 集羣,管理 master-slave 下的節點,能夠在 master 宕機時,選舉新的 master 節點
  • Jedis sharding :在客戶端,對 key 進行 hash ,散列到不一樣的 redis 實例上
  • 相關中間件如 codis , twemproxy 等

在 redis 3.0 版本其,官方提供了 redis cluster 功能。git

redis cluster 簡介

redis cluster 主要基於 CRC16 算法對 key 進行 hash ,而後散列到不一樣散列槽。github

redis cluster 總共提供 16384 個hash 槽(slot) ,理論上,集羣的最大節點數量最大樂意爲 16384 個。不過 redis 官方給出的建議是不要超過 1000 的量級。redis

每一個 redis instance 會負責這個散列槽中的一部分。算法

新增刪除節點,對於 redis cluster 而言就是對 slot 進行 reshard,redis cluster 保證 slot 平滑移動bash

1. key hash 計算

redis cluster 會對大括號 {} 中的值計算 hash 值進行散列。異步

  • 只計算第一次出現 {} 的值,若是沒有,則計算整個 key 的 hash 值, 如
    • key{key1}{key2} : 值計算 key1 的hash
    • keykey : 計算整個 keykey 的 hash
  • 若是隻有 { ,沒有 } ,或者 {} 中沒有內容,則計算整個 key 的 hash 值, 如
    • key{12 : 計算整個 hash
    • key{}key : 計算整個 hash,這意味着全部以 {} 開頭的 key ,都是整個計算 hash

源碼以下:工具

unsigned int keyHashSlot(char *key, int keylen) {
    int s, e; /* start-end indexes of { and } */

    for (s = 0; s < keylen; s++)
        if (key[s] == '{') break;

    /* No '{' ? Hash the whole key. This is the base case. */
    if (s == keylen) return crc16(key,keylen) & 0x3FFF;

    /* '{' found? Check if we have the corresponding '}'. */
    for (e = s+1; e < keylen; e++)
        if (key[e] == '}') break;

    /* No '}' or nothing between {} ? Hash the whole key. */
    if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;

    /* If we are here there is both a { and a } on its right. Hash * what is in the middle between { and }. */
    return crc16(key+s+1,e-s-1) & 0x3FFF;
}
複製代碼

2. 集羣拓撲結構

redis cluster 是一個網狀拓撲結構,每一個 redis instance ,都會與其餘 redis instance 創建鏈接,也就是說若是集羣中有 n 個節點,則總共會有 n(n-1) 個鏈接,這些 TCP 鏈接會被永久保存,並不是按需建立。ui

redis cluster struct

3. 集羣工做

client 發送請求 ,集羣中某個節點接收到請求後,會查找內部 slot , node id 映射,若是是本身,執行命令返回結果,若是是其餘節點,則會給 client 返回一個 MOVED slot_id target_ip:port 的錯誤。客戶端須要根據返回的節點信息,再次執行命令。

注意,若是在此期間,目標節點出現故障,slot 發生了轉移,則 client 仍須要根據返回的 ip:port 再次查詢

redis cluster 搭建

1. 安裝 redis

wget https://github.com/antirez/redis/archive/5.0.7.tar.gz
tar -xvf ./5.0.7.tar.gz
複製代碼

下載並解壓後,你能夠 make 一把,但你經過上面下載後,其實在 src 包裏已經有可使用的 redis server 等各個工具,能夠直接使用。

Redis Github 上的安裝步驟很詳細,這裏再也不贅述

ps: 我是由於 make 以後跑 make test 時,一直掛掉,由於我內存實在過小... 因此就直接用了

2. 編輯配置文件

# 該集羣階段的端口
port 7000
# 是否後臺啓動
daemonize yes
# 我沒有內存,這裏設置 50m
maxmemory 50m

# 爲每個集羣節點指定一個 pid_file
pidfile /home/dylan/tmp/scripts-redis/cluster/pid/pid_7000.pid

#在bind指令後添加本機的ip
bind 127.0.0.1

####################################### file #######################
loglevel notice
logfile /home/dylan/tmp/scripts-redis/cluster/log/log_7000.log
dbfilename "dump.rdb"
dir "/home/dylan/tmp/scripts-redis/cluster/data/7000"

#################################### cluster ####################################
cluster-enabled yes # 啓用集羣模式
# 集羣的配置,配置文件首次啓動自動生成 , 不能人工編輯,是集羣節點自動維護的文件
cluster-config-file "/home/dylan/tmp/scripts-redis/cluster/config/nodes_7000.conf"
cluster-node-timeout 5000

# 配置後,經過 cli 鏈接須要經過 -a 123456 鏈接
requirepass "123456"
masterauth "123456"
複製代碼

3. 複製多份配置文件並修改對應值

redis cluster folder

如圖所示,將 redis.conf 配置文件 copy 6 份(由於 redis 要求最少 3 master ,咱們要每一個 master 一個 slave ,那麼這裏就須要 6 個實例),而後建好 conf 文件裏配置的各個目錄

4. 啓動 redis 實例

redis-server redis.conf ,這個命令想必你們都很熟悉,這裏就再也不囉嗦。這裏咱們只須要依次啓動 6 個實例便可。

我我的寫了個小腳本,後面會貼上來。

5. 建立集羣

redis 提供了 cluster create 命令幫助咱們建立 redis cluster

redis-cli -a 123456 --cluster create 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 --cluster-replicas 1
複製代碼

執行該命令後,會有以下輸出:

redis create cluster

經過 redis 輸出的信息能看到,如今是 3 master 3 slave,每一個 master 一個 slave 節點

6. 查看集羣信息

# 注意 -c 參數,表示以集羣方式登入
redis-cli -a 123456 -c -p 7000
複製代碼

cluster 相關的命令,請參考 這裏

這個時候咱們 set 幾個數據:

127.0.0.1:7000> set a a
-> Redirected to slot [15495] located at 127.0.0.1:7002
OK
127.0.0.1:7002> set b b
-> Redirected to slot [3300] located at 127.0.0.1:7000
OK
127.0.0.1:7000> keys *
1) "b"
複製代碼

能夠看到,第一次 set a 時, 被路由到了 7002 節點,第二次 set b 時,路由到了 7000 也就是本身這兒,經過 keys * (請不要在生產環境使用該命令) 查看時, 只有 key b (kb...) , key a 不會顯示,由於它在 7002 節點那兒

小腳本

沒有作多少重構,將就着用...

./run.sh start all : 分別以 conf 目錄下的配置文件啓動 redis 實例

./run.sh stop all : 中止全部 conf 目錄下配置文件產生的 redis 實例

./run.sh cluster : 在啓動全部 redis 實例後,經過 cluster 命令建立集羣,須要手動修改 ip:port

./run.sh status : 查看 redis 實例,這裏沒有提供查看單個 redis 實例 status 的方法

#!/usr/bin/env bash 
# redis server / redis cli 所在目錄
workdir="/home/dylan/tmp/redis-5.0.8/src"
# 腳本所在目錄
scriptdir="/home/dylan/tmp/scripts-redis"
# cluster 各個目錄
cluster_dir="${scriptdir}/cluster"
cluster_pid_dir="${cluster_dir}/pid"
cluster_conf_dir="${cluster_dir}/conf"

# $1 is redis config file name looks like ./conf/redis.1234.conf
# return pid file name looks like pid_1234.pid
function get_redis_pid_file(){
        rport=$(echo "$1" | awk -F"." '{print $(NF-1)}')
        echo -e "${cluster_pid_dir}/pid_$rport.pid"
}

function get_redis_conf_file() {
        conf=$(echo "$1" | awk -F"/" '{print $NF}')
        echo -e "${cluster_conf_dir}/$conf"
}

# $1 should be redis config file name looks like redis.1234.conf
function redis_start(){
        # conf file looks like redis.port.conf
        # pid file looks like pid_port.pid
        redis_conf_path=$1
        redis_conf=$(get_redis_conf_file ${redis_conf_path})
        redis_conf_path_forshow="Redis with config $(echo ${redis_conf} | awk -F"/" '{print $NF}')"
        pidfile=$(get_redis_pid_file ${redis_conf_path})

        if [ -f $pidfile ]; then
                if kill -0 `cat $pidfile` > /dev/null 2>&1; then
                        echo REDIS already running as process `cat $pidfile`.
                        return
                fi
        fi
        # 配置中已經配置了 daemonize yes
        # nohup ${workdir}/redis-server $conffile > $logfile 2>&1 < /dev/null &
        ${workdir}/redis-server $redis_conf
        if [ $? -eq 0 ]; then
                sleep 1
                if [ -f $pidfile ]; then
                        pid=$(cat $pidfile)
                        if ps -p $pid > /dev/null 2>&1; then
                                echo ${redis_conf_path_forshow} STARTED
                        else
                                echo ${redis_conf_path_forshow} FAILED TO START
                                exit 1
                        fi
                else
                        echo ${redis_conf_path_forshow} start failed
                        exit 1
                fi
        else
                echo ${redis_conf_path_forshow} DID NOT START
                exit 1
        fi

}

conffile=$2
case $1 in
        start)
                # 若是 $2 爲 all ,則啓動全部
                if [ "all" == "$conffile" ]; then
                        echo "Run all files in $cluster_conf"
                        for rfile in ${cluster_conf_dir}/*.conf
                        do
                                redis_start $rfile
                                if [ $? -ne 0 ]; then
                                        exit 1
                                fi
                        done
                elif [ ! -f "$conffile" ]; then # 輸入的文件不存在
                        echo Can not find $conffile.
                        exit 1
                elif [ -z $conffile ]; then
                        redis_start $conffile
                else
                        echo "Usage $0 start (all|redis_config_file)"
                        exit 1
                fi
                exit 0
                ;;
        stop)
                if [ "all" == "$conffile" ]; then
                        echo Stopping all...
                        for rfile in ${cluster_conf_dir}/*.conf
                        do
                                pid=$(get_redis_pid_file $rfile)
                                if [ ! -f "$pid" ]; then
                                        echo
                                        echo Redis with config "$rfile" already stopped
                                        continue
                                fi
                                kill `cat $pid`
                                sleep 1
                                echo
                                echo Redis with config $rfile STOPPED
                        done
                elif [ ! -f "$conffile" ]; then
                        echo Can not find $conffile.
                        exit 1
                elif [ -f $conffile ]; then
                        pid=$(get_redis_pid_file $rfile)
                        kill `cat $pid`
                        sleep 1
                        echo STOPPED
                else
                        echo "Usage $0 stop (all|redis_config_file)"
                        exit 1
                fi
                ;;
        status)
                echo "Checking status..."
                echo Found `ls ${cluster_conf_dir} | wc -l` config file
                echo
                for rfile in ${cluster_conf_dir}/*.conf
                do
                        pid=$(get_redis_pid_file $rfile)
                        if [ -f $pid ]; then
                                if [ ! -s ${pid} ]; then
                                        echo file $pid is empty. skip...
                                        echo "Please check $pid."
                                        echo
                                        continue
                                fi
                                if ps -p `cat $pid` > /dev/null 2>&1;then
                                        echo REDIS $pid with pid `cat $pid` is running
                                else
                                        echo "Not running with pid `cat $pid`, Please check it"
                                        exit 1
                                fi
                        else
                                echo "Not running with config file $rfile. No pid file exists."
                        fi
                done
                ;;

        cluster)
                echo "Start clusting..."
                ${workdir}/redis-cli -a 123456 --cluster create 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 --cluster-replicas 1
                exit 0
                ;;
        test)
                echo
                echo "Test Success"
                exit 0
                ;;
        *)
                echo
                echo -n "Usage: "
                echo -n "$0 status or "
                echo "$0 {start|stop} conf_file_path" >&2
                ;;
esac
複製代碼
相關文章
相關標籤/搜索