我的 blog: iyuhp.top
原文連接: Redis Cluster 集羣搭建html
關於 redis 集羣,通常有如下幾種模式node
在 redis 3.0 版本其,官方提供了 redis cluster 功能。git
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
redis cluster 會對大括號 {} 中的值計算 hash 值進行散列。異步
key{key1}{key2}
: 值計算 key1 的hashkeykey
: 計算整個 keykey 的 hashkey{12
: 計算整個 hashkey{}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;
}
複製代碼
redis cluster 是一個網狀拓撲結構,每一個 redis instance ,都會與其餘 redis instance 創建鏈接,也就是說若是集羣中有 n 個節點,則總共會有 n(n-1) 個鏈接,這些 TCP 鏈接會被永久保存,並不是按需建立。ui
client 發送請求 ,集羣中某個節點接收到請求後,會查找內部 slot , node id 映射,若是是本身,執行命令返回結果,若是是其餘節點,則會給 client 返回一個 MOVED slot_id target_ip:port
的錯誤。客戶端須要根據返回的節點信息,再次執行命令。
注意,若是在此期間,目標節點出現故障,slot 發生了轉移,則 client 仍須要根據返回的 ip:port 再次查詢
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 時,一直掛掉,由於我內存實在過小... 因此就直接用了
# 該集羣階段的端口
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"
複製代碼
如圖所示,將 redis.conf 配置文件 copy 6 份(由於 redis 要求最少 3 master ,咱們要每一個 master 一個 slave ,那麼這裏就須要 6 個實例),而後建好 conf 文件裏配置的各個目錄
redis-server redis.conf
,這個命令想必你們都很熟悉,這裏就再也不囉嗦。這裏咱們只須要依次啓動 6 個實例便可。
我我的寫了個小腳本,後面會貼上來。
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 輸出的信息能看到,如今是 3 master 3 slave,每一個 master 一個 slave 節點
# 注意 -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
複製代碼