我时常需要在云服务器上搭建测试环境,每次入手新的服务器配置集群环境时,跟着笔记敲一遍命令行挺麻烦的。学了 shell 脚本后,我尝试使用脚本在单机服务器上部署 Redis 集群。

安装 Redis 服务器

参照 Redis 主从集群及自启动配置 实现自动安装,首先判断是否已安装,若存在 Redis 服务程序则跳过,否则安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Check redis command
if [ ! -f "/usr/local/bin/redis-server" ]; then
echo "Redis not ready, please install redis firstly!"
echo ""
echo "===== Install redis as follows ====="
wget http://download.redis.io/releases/redis-5.0.7.tar.gz -P /usr/local/src
cd /usr/local/src/
tar -zxvf redis-5.0.7.tar.gz
cd redis-5.0.7
install GCC if not exists
yum install -y gcc-c++
make MALLOC=libc install

echo ""
echo "Redis Server Installation Finished!"
fi

通过 if [ ! -f "/usr/local/bin/redis-server" ] 判断 server 程序是否存在,若存在则认为 Redis 已安装。

采用源码编译的方式安装 Redis,已指定 5.0.7 版本,防止出现兼容性问题。

生成 Redis 实例的配置文件

常量定义

1
2
3
4
# Constants
BASE_DIR=/usr/local/redis-cluster
PORTS=`seq 7000 7005`
START_UP=$BASE_DIR/startup.sh
  • BASE_DIR 为集群配置 directory;
  • PORTS 为集群在本地所占用的端口列表,seq 7000 7005 生成 7000-7005 的整数数组;
  • START_UP 为集群实例启动脚本,集群需要启动 6 个 Redis 实例,使用脚本简化操作。

准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建工作目录,在工作目录下进行后续操作
mkdir -p $BASE_DIR
cd $BASE_DIR

# 准备集群实例启动脚本,在每次实例配置循环时将启动命令追加到脚本中
echo "#!/bin/bash" > $START_UP
servers=
for port in $PORTS; do
# 设置每个实例的工作目录, data为数据存放目录
mkdir -p $BASE_DIR/$port/data
# 生成单个实例的配置文件
generate_instance_conf $port
# 追加实例启动命令到启动脚本
echo "/usr/local/bin/redis-server $BASE_DIR/$port/redis.conf" >> $START_UP
# 记录所有的实例位置,用于之后的集群启动
servers="$servers 127.0.0.1:$port "
done

配置文件

每个实例的配置文件相似,只是端口号和工作目录不同。还有一点需要注意,要想可以从其它外部主机访问此主机上的 Redis 集群,需要将实例的集群地址设置为主机的外网地址,因此需要用户手动输入主机外网地址。

使用 shell 实现外网地址获取的交互模式:

1
2
3
# User custom setting
echo -n "Enter your host's public address(default 127.0.0.1):"
read cluster_address

将实例配置依次写入 redis.conf 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# generate configuration files
function generate_instance_conf() {
echo "configuring server $1"

# clean conf file
echo "" > $1/redis.conf
# write conf
echo "port $1" >> $1/redis.conf
echo "bind 0.0.0.0" >> $1/redis.conf
echo "dir $BASE_DIR/$port/data" >> $1/redis.conf
echo "cluster-enabled yes" >> $1/redis.conf
echo "cluster-config-file nodes-$1.conf" >> $1/redis.conf
echo "cluster-node-timeout 5000" >> $1/redis.conf
if [ -n "$cluster_address" ]; then
echo "cluster-announce-ip $cluster_address" >> $1/redis.conf
else
echo "cluster-announce-ip 127.0.0.1" >> $1/redis.conf
fi
echo "appendonly yes" >> $1/redis.conf
echo "daemonize yes" >> $1/redis.conf
}
chmod +x $START_UP

如上步骤完成后,集群工作目录如下:

1
2
3
4
5
6
7
drwxr-xr-x 3 root root 4096 Apr 19 11:07 7000
drwxr-xr-x 3 root root 4096 Apr 19 11:07 7001
drwxr-xr-x 3 root root 4096 Apr 19 11:07 7002
drwxr-xr-x 3 root root 4096 Apr 19 11:07 7003
drwxr-xr-x 3 root root 4096 Apr 19 11:07 7004
drwxr-xr-x 3 root root 4096 Apr 19 11:07 7005
-rwxr-xr-x 1 root root 426 Apr 19 11:07 startup.sh

启动实例并建立集群

启动 Redis 服务实例

1
2
3
4
5
# startup instances
echo "starting servers..."
$START_UP
sleep 5s
echo "servers ready!"

执行启动脚本,启动 6 个 Redis 实例,并保持休眠 5 秒,等待集群启动完成,执行结果如下:

1
2
3
4
5
6
7
8
9
starting servers...
27764:C 19 Apr 2020 11:07:58.874 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
27764:C 19 Apr 2020 11:07:58.874 # Redis version=5.0.7, ..
27764:C 19 Apr 2020 11:07:58.874 # Configuration loaded
...
27777:C 19 Apr 2020 11:07:58.885 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
27777:C 19 Apr 2020 11:07:58.885 # Redis version=5.0.7, ..
27777:C 19 Apr 2020 11:07:58.885 # Configuration loaded
servers ready!

建立集群

1
2
3
4
# create cluster
echo "configuring cluster..."
/usr/local/bin/redis-cli --cluster create $servers --cluster-replicas 1
echo "configured!"

使用 redis-cli 建立集群,指定所要包含实例的 host 和 port,$servers 中就是这些信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
configuring cluster...
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
...
Can I set the above configuration? (type 'yes' to accept): yes
...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
configured!

集群建立时需要用户手动确认槽数划分,输入 yes 即可。

至此集群部署完成,下面配置集群开机自启动。

systemd 自启动

使用 systemd 配置 Redis 集群启动服务,首先配置 service 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# generate redis-cluster service file
cat << EOT > $BASE_DIR/redis-cluster.service
[Unit]
Description=Redis 5.0 Cluster Service
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/redis-cluster/startup.sh

[Install]
WantedBy=default.target
EOT

cat << EOT > $BASE_DIR/redis-cluster.service 表示向 redis-cluster.service 文件中覆盖一段内容,内容为下一行至 EOT 之间的内容。

1
2
3
4
5
# create service
echo "Creating redis cluster service..."
# 在 systemd 文件夹下创建 service 的软连接并启动 redis 集群服务
ln -s $BASE_DIR/$SERVICE /etc/systemd/system/$SERVICE
sudo systemctl daemon-reload && sudo systemctl enable $SERVICE && sudo systemctl start $SERVICE

集群删除

使用 --remove 参数执行集群删除操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Remove redis cluster
function remove_cluster() {
# 终止各 Reids 实例进程
ps -ef | grep redis-server | grep cluster | awk '{print $2}' | xargs kill -9
# 移除 systemd 服务
systemctl disable redis-cluster.service
# 移除工作目录(配置文件和数据)
if [ -d $BASE_DIR ]; then
rm -rf $BASE_DIR
fi
}

if [ "$1" = "--remove" ]; then
remove_cluster
exit 0
fi

通过过滤器 grep 和流式编辑器 awk 查询到 Redis 实例的 pid,使用 kill 命令结束进程。

其中 awk '{print $2}'awk -F ' ' '{print $2}' 的缩写,-F 表示分隔符参数,默认为空格,`awk -F ' ' 将如下内容分割为数组:

1
root 27765 1 0 11:07 ? 00:00:03 /usr/local/bin/redis-server 0.0.0.0:7000 [cluster]

{print $2} 表述输出第 2 个元素,$0 表述输出整行。

完整的 shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/bin/bash

BASE_DIR=/usr/local/redis-cluster

PORTS=`seq 7000 7005`

START_UP=$BASE_DIR/startup.sh

SERVICE=redis-cluster.service

# Remove redis cluster
function remove_cluster() {
# kill redis servers
ps -ef | grep redis-server | grep cluster | awk '{print $2}' | xargs kill -9
# disable systemd
systemctl disable redis-cluster.service
# rm cluster data
if [ -d $BASE_DIR ]; then
rm -rf $BASE_DIR
fi
}

if [ "$1" = "--remove" ]; then
remove_cluster
exit 0
fi

# Check redis command
if [ ! -f "/usr/local/bin/redis-server" ]; then
echo "Redis not ready, please install redis firstly!"
echo ""
echo "===== Install redis as follows ====="
wget http://download.redis.io/releases/redis-5.0.7.tar.gz -P /usr/local/src
cd /usr/local/src/
tar -zxvf redis-5.0.7.tar.gz
cd redis-5.0.7
install GCC if not exists
yum install -y gcc-c++
make MALLOC=libc install

echo ""
fi

# User custom setting
echo -n "Enter your host's public address(default 127.0.0.1):"
read cluster_address

# enter work directory
mkdir -p $BASE_DIR
cd $BASE_DIR

# generate configuration files
function generate_instance_conf() {
echo "configuring server $1"

# clean conf file
echo "" > $1/redis.conf
# write conf
echo "port $1" >> $1/redis.conf
echo "bind 0.0.0.0" >> $1/redis.conf
echo "dir $BASE_DIR/$1/data" >> $1/redis.conf
echo "cluster-enabled yes" >> $1/redis.conf
echo "cluster-config-file nodes-$1.conf" >> $1/redis.conf
echo "cluster-node-timeout 5000" >> $1/redis.conf
if [ -n "$cluster_address" ]; then
echo "cluster-announce-ip $cluster_address" >> $1/redis.conf
else
echo "cluster-announce-ip 127.0.0.1" >> $1/redis.conf
fi
echo "appendonly yes" >> $1/redis.conf
echo "daemonize yes" >> $1/redis.conf
}


# mkdir dirs and setup startup.sh
echo "#!/bin/bash" > $START_UP
servers=
for port in $PORTS; do
mkdir -p $BASE_DIR/$port/data
# generate conf files
generate_instance_conf $port
#
echo "/usr/local/bin/redis-server $BASE_DIR/$port/redis.conf" >> $START_UP
# servers
servers="$servers 127.0.0.1:$port "
done

# startup instances
chmod +x $START_UP
echo "starting servers..."
$START_UP
sleep 5s
echo "servers ready!"

# create cluster
echo "configuring cluster..."
/usr/local/bin/redis-cli --cluster create $servers --cluster-replicas 1
echo "configured!"

# generate redis-cluster service file
cat << EOT > $BASE_DIR/redis-cluster.service
[Unit]
Description=Redis 5.0 Cluster Service
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/redis-cluster/startup.sh

[Install]
WantedBy=default.target
EOT

# create service
echo "Creating redis cluster service..."
ln -s $BASE_DIR/$SERVICE /etc/systemd/system/$SERVICE
sudo systemctl daemon-reload && sudo systemctl enable $SERVICE && sudo systemctl start $SERVICE

# Cluster OK
echo ""
echo "Completed!"
echo ""
echo "Test cluster with: /usr/local/bin/redis-cli -h 127.0.0.1 -p 7000"
echo ""
echo "127.0.0.1:7000>cluster nodes"

脚本下载:http://pic.blackist.top/202004232347_404.sh

参考

https://blackist.org/2020/04/03/redis-cluster-masterandslave/

https://stackoverflow.com/questions/13910087/shell-script-to-capture-process-id-and-kill-it-if-exist

(完)