Redis的slot遷移工具

2020-08-11 15:16:44

原始碼下載:
https://github.com/eyjian/redis-tools/blob/master/move_redis_slot.sh

支援遷移已有的keys。

#!/bin/sh
# 遷移 slot 工具,但一次只能遷移一個 slot
#
# 使用時,需要指定如下幾個參數:
# 1)參數1:必選參數,用於指定被遷移的 slot
# 2)參數2:必選參數,用於指定源節點(格式爲:ip:port)
# 3)參數3:必選參數,用於指定目標節點(格式爲:ip:port)
# 6)參數4:可選參數,用於指定存取 redis 的密碼
#
# 使用範例(將2020從10.9.12.8:1383遷移到10.9.12.9:1386):
# move_redis_slot.sh 2020 10.9.12.8:1383 10.9.12.9:1386
#
# 執行本指令碼時,有兩個「確認」,
# 第一個「確認」是提示參數是否正確,
# 第二個「確認」是提示是否遷移已有的keys,
# 如果輸入非yes則只遷移slot,不遷移已有keys。

# 確保redis-cli可用
REDIS_CLI=${REDIS_CLI:-redis-cli}
which "$REDIS_CLI" > /dev/null 2>&1
if test $? -ne 0; then
    echo "\`redis-cli\` not exists or not executable"
    exit 1
fi

# 參數檢查
if test $# -ne 3 -a $# -ne 4; then
  echo -e "Usage: `basename $0` \033[1;33mslot\033[m source_node destition_node redis_password"
  echo -e "Example1: `basename $0` \033[1;33m2020\033[m 127.0.0.1:6379 127.0.0.1:6380"
  echo -e "Example2: `basename $0` \033[1;33m2020\033[m 127.0.0.1:6379 127.0.0.1:6380 password123456"
  exit 1
fi

SLOT=$1
SRC_NODE="$2"
DEST_NODE="$3"
REDIS_PASSOWRD="$4"

# 得到指定節點的 nodeid
function get_node_id()
{
  node="$1"
  node_ip="`echo $node|cut -d':' -f1`"
  node_port=`echo $node|cut -d':' -f2`

  # 得到對應的 nodeid
  $REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
-h $node_ip -p $node_port \
CLUSTER NODES | awk -v node=$node '{if ($2==node) printf("%s",$1);}'
}

SRC_NODE_ID="`get_node_id $SRC_NODE`"
SRC_NODE_IP="`echo $SRC_NODE|cut -d':' -f1`"
SRC_NODE_PORT=`echo $SRC_NODE|cut -d':' -f2`
DEST_NODE_ID="`get_node_id $DEST_NODE`"
DEST_NODE_IP="`echo $DEST_NODE|cut -d':' -f1`"
DEST_NODE_PORT=`echo $DEST_NODE|cut -d':' -f2`

echo -e "\033[1;33mSource\033[m node: $SRC_NODE_IP:$SRC_NODE_PORT"
echo -e "\033[1;33mDestition\033[m node: $DEST_NODE_IP:$DEST_NODE_PORT"
echo -en "Confirm to continue? [\033[1;33myes\033[m/\033[1;33mno\033[m]"
read -r -p " " input
if test "$input" != "yes"; then
  exit 1
fi
echo "........."

# 目標節點上執行 IMPORTING 操作
# 如果 $SLOT 已在目標節點,則執行時報錯「ERR I'm already the owner of hash slot 1987」
echo -e "\033[1;33mImporting\033[m $SLOT from $SRC_NODE to $DEST_NODE ..."
err=`$REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
-h $DEST_NODE_IP -p $DEST_NODE_PORT \
CLUSTER SETSLOT $SLOT IMPORTING $SRC_NODE_ID`
if test "X$err" != "XOK"; then
  echo "[destition://$DEST_NODE_IP:$DEST_NODE_PORT] $err"
  exit 1
fi

# 源節點上執行 MIGRATING 操作
# 如果 $SLOT 並不在源節點上,則執行時報錯「ERR I'm not the owner of hash slot 1987」
echo -e "\033[1;33mMigrating\033[m $SLOT from $SRC_NODE to $DEST_NODE ..."
err=`$REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
-h $SRC_NODE_IP -p $SRC_NODE_PORT \
CLUSTER SETSLOT $SLOT MIGRATING $DEST_NODE_ID`
if test "X$err" != "XOK"; then
  echo "[source://$SRC_NODE_IP:$SRC_NODE_PORT] $err"
  exit 1
fi

# 是否遷移已有的keys?
echo -en "Migrate keys in slot://$SLOT? [\033[1;33myes\033[m/\033[1;33mno\033[m]"
read -r -p " " input
if test "$input" = "yes"; then
  first=1 # 是否第一輪keys遷移操作
  batch=100 # 一次批次遷移的keys數
  timeout_ms=60000 # 超時時長(單位:毫秒)
  destination_db=0 # 對於redis叢集,取值總是爲0
  num_keys=0

  echo "........."
  echo -e "Migrating keys in slot://$SLOT ..."
  while true
  do
    # 在源節點上執行:
    # 藉助命令「CLUSTER GETKEYSINSLOT」和命令「MIGRATE」遷移已有的keys
    keys="`$REDIS_CLI --raw --no-auth-warning -a '$REDIS_PASSOWRD' \
-h $SRC_NODE_IP -p $SRC_NODE_PORT \
CLUSTER GETKEYSINSLOT $SLOT $batch | tr '\n' ' ' | xargs`"
    if test -z "$keys"; then
      if test $first -eq 1; then
        echo -e "No any keys to migrate in slot://$SLOT"
      else
        echo -e "Finished migrating all keys ($num_keys) in slot://$SLOT"
      fi
      break
    fi
    first=0
    n=`echo "$keys" | tr -cd ' ' | wc -c`
    num_keys=$(($num_keys + $n))

    # 在源節點上執行命令「MIGRATE」遷移到目標節點
    # MIGRATE returns OK on success,
    # or NOKEY if no keys were found in the source instance
    if test -z "$REDIS_PASSOWRD"; then
      err=`$REDIS_CLI --raw \
-h $SRC_NODE_IP -p $SRC_NODE_PORT \
MIGRATE $DEST_NODE_IP $DEST_NODE_PORT "" $destination_db $timeout_ms \
REPLACE KEYS $keys`
    else
      err=`$REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
-h $SRC_NODE_IP -p $SRC_NODE_PORT \
MIGRATE $DEST_NODE_IP $DEST_NODE_PORT "" $destination_db $timeout_ms \
REPLACE AUTH "$REDIS_PASSOWRD" KEYS $keys`
    fi
    if test "X$err" = "XNOKEY"; then
      break
    fi
  done
fi


# 取得所有 master 節點
nodes=(`$REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" -h $DEST_NODE_IP -p $DEST_NODE_PORT \
CLUSTER NODES | awk '{if (match($3,"master")) printf("%s\n",$2);}'`)

# 在所有 master 節點上執行 NODE 操作
# 實際上,只可對源節點和目標節點執行 NODE 操作,
# 新的設定會自動在叢集中傳播。
echo "........."
for node in ${nodes[*]}; do
  node_ip="`echo $node|cut -d':' -f1`"
  node_port=`echo $node|cut -d':' -f2`
  echo -en "NODE: $node_ip:$node_port "
  err=`$REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
-h $node_ip -p $node_port \
CLUSTER SETSLOT $SLOT NODE $DEST_NODE_ID`
  echo -e "\033[1;33m$err\033[m"
done