从零搭建Redis Cluster分片集群

由于整个爬虫系统的爬取队列和去重队列都由Redis负责维护。每隔一个小时,会做一次磁盘持久化的操作。然而由于业务量的逐渐增加,服务器的内存消耗越来越大,并且一次磁盘持久化操作所需要的时间越来越长。目前Redis服务器的配置为8核32G内存,在正常情况下,Redis服务要消耗95%的内存,做一次磁盘持久化操作大约需要8到10分钟。在做磁盘持久化操作,爬虫系统连接Redis的组件可能会报错:

redis.exceptions.ResponseError: MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.

这里出现磁盘持久化错误的原因是由于内存不足,主机暂时没有响应,等持久化结束以后会回复。但是比较明显的是,目前单台高配Redis服务器已经无法满足现在的业务需求。扩展Redis服务器已经是势在必行的任务。

Redis下载和安装

目前我选择最新的稳定版Redis-6.2.4,服务器使用CentOS7.8。从Redis官网下载最新版本到服务器以后,解压编译。

tar -zxvf redis-6.2.4.tar.gz
cd redis-6.2.4/src
make install

另外Redis需要Linux修改系统参数,以满足对于后台持久化的性能优化:

WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.

Redis集群配置文件

如果不修改配置文件,启动的Redis进入standalone模式,如图:
redis standalone

要搭建集群,需要Redis运行在Cluster模式下。修改配置文件以下内容,Redis集群最小化修改:

# 监听端口,注意每一个节点的ip地址不同
bind 127.0.0.1 10.16.99.155
port 6379
pidfile /var/run/redis_6379.pid
cluster-enabled yes
cluster-announce-ip 10.16.99.155

cluster-config-file nodes-6379.conf
masterauth Mypassword
requirepass Mypassword

使用以下命令启动Redis节点(这里我没有设置daemonize):

nohup ./src/redis-server ./redis.conf 2>&1 &

修改并保存好配置文件后重启Redis服务器,如图所示:
redis cluster

创建Redis Cluster集群

目前网上搜索创建Redis Cluster集群,大多数是使用Redis官方提供的Redis集群创建工具redis-trib.rb,并运行以下命令。但实际上该工具在Redis5以后就不在使用了。

# Redis 5 以后就不用这个工具了
./redis-trib.rb create --replicas 0 10.16.99.155:6379 10.16.99.156:6379

本人使用的是目前Redis的最新版本Redis-6.2.4。Redis6使用redis-cli创建集群。如图所示:
create redis cluster by redis-cli

因此使用以下命令创建Redis集群:

redis-cli -a Mypassword --cluster create 10.16.99.155:6379 10.16.99.156:6379 --cluster-replicas 0 

这里需要注意的是,如果Redis集群需要设置密码的话,需要加入-a Mypassword参数。另外,如果某一个Redis集群节点已经有数据了,创建也会失败。因此必须清空所有Redis集群节点数据后重试。

[ERR] Node 10.16.99.155:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

要创建Redis集群,最少要满足三个节点,否则也会失败。

*** ERROR: Invalid configuration for cluster creation.
*** Redis Cluster requires at least 3 master nodes.
*** This is not possible with 2 nodes and 0 replicas per node.
*** At least 3 nodes are required.

因此我使用同样的方式配置了第三台Redis集群主节点,并运行一下命令创建集群:

./redis-cli -a Mypassword --cluster create 10.16.99.155:6379 10.16.99.156:6379 10.16.99.157:6379 --cluster-replicas 0

命令执行成功,如图所示:
setup redis cluster

创建槽位以后,发现和之前的提示不太一样,创建了2个主节点和一个从节点,如图所示:
setup redis cluster result

Redis Cluster基本操作

如上例图中所示,自动划分的主从节点比较乱,因此创建完毕以后,我还是希望能够设置Redis1主机为主节点。

删除节点

所以通过一下命令先删除从节点:

#host:port nodeid
./redis-cli -a Mypassword --cluster del-node 10.16.99.157:6379 45e60386a7490b18b98643df817389e4dd7f59ad

其中的host、port和node id可以连接到集群的任意节点并使用一下命令获得:

127.0.0.1:6379> CLUSTER nodes
22eea4f5056ba641def5b3318b6363a36b69599e 10.16.99.157:6379@16379 master – 0 1624351802169 5 connected 0-16383
45e60386a7490b18b98643df817389e4dd7f59ad 10.16.99.155:6379@16379 myself,slave 22eea4f5056ba641def5b3318b6363a36b69599e 0 1624349479000 5 connected

删除节点成功。信息如下:

>>> Removing node 45e60386a7490b18b98643df817389e4dd7f59ad from cluster 10.16.99.157:6379
>>> Sending CLUSTER FORGET messages to the cluster…
>>> Sending CLUSTER RESET SOFT to the deleted node.

移动slot位置

要删除主节点,需要先把该主节点内的slot全部移到其他主节点,可以通过一下命令完成:

./redis-cli -a Mypassword --cluster reshard 10.16.99.157:6379

增加主节点

如果需要增加主节点,把redis2主机添加到redis1的Redis Cluster集群中。可以使用以下命令:

./redis-cli -a Mypassword --cluster add-node redis2:6379 redis1:6379

注意,默认情况下新增节点会成为主节点。如果需要增加的是从节点的话,命令如下:

./redis-cli -a Mypassword --cluster add-node redis2:6379 redis1:6379 --cluster-slave --cluster-master-id 22eea4f5056ba641def5b3318b6363a36b69599e

添加节点有可能会报错:

>>> Adding node redis2:6379 to cluster redis1:6379
>>> Performing Cluster Check (using node redis1:6379)
M: 22eea4f5056ba641def5b3318b6363a36b69599e redis1:6379
slots:[0-16383] (16384 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots…
>>> Check slots coverage…
[OK] All 16384 slots covered.
[ERR] Node redis2:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

因此需要清空redis2节点中的所有内容后重新增加节点即可。

Redis数据导入需密码验证错误

基于redis-cli的cluster操作的文档非常少。尤其是当源Redis需要密码验证,目标Redis Cluster也需要密码验证时,几乎在网上找不到任何有用的信息。这次再次感叹Google搜索能力,因为一开始使用各种关键字搜索百度和Bing,完全找不到可用的解决方案,直到打开了Google第一条搜索结果。
noauth错误信息
Google Search Result

Redis数据导入目标键已存在错误
另外redis-cli的import功能虽然提供–cluster-copy和–cluster-replace选项,但是第一次导入没有问题。再次导入时,就会报错。

Source 10.16.99.133:6379 replied with error:
ERR Target instance replied with error: BUSYKEY Target key name already exists.

经过多次尝试,发现只有当把–cluster-copy和–cluster-replace选项同时加上,导入命令才能正常执行。

扩展Redis服务器的困难

由于在业务初期并没有非常大的数据量,因此使用了单台8核32G内存的服务器自建Redis服务。随着业务的增长,单台Redis服务器已经无法满足需求,时常会出现内存不足的问题,导致持久化失败,系统卡顿。然而从单台Redis服务器迁移到Redis Cluster集群又困难重重。最主要的问题包括原有应用服务要修改。如何在不停止系统服务的情况先做单台Redis服务器到多节点Redis Cluster集群的数据迁移。

修改服务应用连接Redis Cluster集群

由于Redis提供的官方Python包不支持Redis集群,因此如果要将单台Redis主机扩展为Redis集群,就需要改写所有的爬虫核心代码。因此,在扩容之前,必须统计所有受影响的爬虫。目前,Redis Cluster官方推荐的Python包是redis-py-cluster

数据迁移

目前我们已经有一台Redis以Standalone的模式运行。当我组建了一个Redis Cluster以后,需要做数据迁移。目前我使用的Redis-6.2.4提供了官方的数据导入工具。只要运行以下命令:

./redis-cli -a Mypassword --cluster import 10.16.99.157:6379 --cluster-from 10.16.99.133:6379 --cluster-from-pass Mypassword --cluster-replace

以上命令从现有服务器10.16.99.133:6379导出数据并导入Redis Cluster任意节点。上例中我选了10.16.99.157:6379主节点。结果如图所示:
cluster import data

Redis Cluster分布式集群外网无法连接问题

我的Redis Cluster集群是建在云服务器上的,因此在同一局域网间通过内网连接。Redis Cluster节点对外需要绑定DNAT公网IP提供服务。因此Redis Cluster集群中各节点主机实际上只知道彼此的内网IP。如果需要提供外网服务,就必须使用公网IP组建Redis Cluster集群。但是内网的Redis Cluster节点并没有公网IP。因此,这里需要配置另外一组Redis参数。假设某台节点主机在云平台绑定的外网IP为101.96.1.150,端口不变。那么,配置如下:

cluster-announce-ip 101.96.1.150
cluster-announce-port 6379
cluster-announce-bus-port 16379

其中6379是对外提供服务的端口,16379是集群节点间通信的接口。随后,我们需要使用外网IP重新创建集群:

./redis-cli -a Mypassword --cluster create 101.96.1.150:6379 101.96.1.151:6379 101.96.1.152:6379 --cluster-replicas 0

此时,问题就出现了。在内网中的3台主机节点并无法使用外网IP地址互相通信。需要在每台节点上配置路由,强制把发向其他节点的数据流重定向到对应的内网地址上。首先,确认主机上是否开启了数据转发功能:

cat /proc/sys/net/ipv4/ip_forward #1表示开启,0表示未开启。

如果没有开启,编辑文件/etc/sysctl.conf添加以下内容:

net.ipv4.ip_forward=1

更新系统配置:

sysctl -p /etc/sysctl.conf

随后在每一台节点上添加以下数据流重定向规则(可以添加到.bashrc文件中)

iptables -t nat -A OUTPUT -d 101.96.1.150 -p tcp --dport 16379 -j DNAT --to-destination 10.16.99.100:16379
iptables -t nat -A OUTPUT -d 101.96.1.151 -p tcp --dport 16379 -j DNAT --to-destination 10.16.99.101:16379
iptables -t nat -A OUTPUT -d 101.96.1.152 -p tcp --dport 16379 -j DNAT --to-destination 10.16.99.102:16379

为了方便起见可以忽略端口:

iptables -t nat -A OUTPUT -d 101.96.1.150 -j DNAT --to-destination 10.16.99.100
iptables -t nat -A OUTPUT -d 101.96.1.151 -j DNAT --to-destination 10.16.99.101
iptables -t nat -A OUTPUT -d 101.96.1.152 -j DNAT --to-destination 10.16.99.102

参考文章

  • Redis Cluster官网文档:https://redis.io/topics/cluster-tutorial
  • redis-cli –cluster help手册:https://www.cnblogs.com/zhoujinyi/p/11606935.html
  • redis-cli import noauth解决方案:https://github.com/redis/redis/pull/7994
  • redis-py-cluster:https://github.com/Grokzen/redis-py-cluster
  • Redis分布式集群搭建(旧版):https://www.cnblogs.com/chenkeyu/p/8047811.html
  • Redis缓存雪崩和穿透的解决方法:https://blog.csdn.net/qq_35433716/article/details/86375506
  • Use IPTables/Netfilter:https://jmsliu.com/4426/use-iptablesnetfilter-to-make-server-safer.html
  • 谷歌搜索:netfilter/iptables全攻略
  • Redis Cluster集群主节点故障
  • Redis主从同步数据死循环

快速配置

为了帮助快速搭建Redis集群,综合后期遇到的一些问题,以下是目前配置的一些参数:

bind 127.0.0.1 100.100.64.4
port 6380 #同一台机器部署多个节点
save "" #禁止snapshotting
masterauth password
repl-timeout 3600
repl-backlog-size 5gb
requirepass password
appendonly yes #启用append备份
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-announce-ip 100.100.64.4
cluster-announce-port 6380 #同一台机器部署多个节点
cluster-announce-bus-port 16380 #同一台机器部署多个节点
client-output-buffer-limit replica 0 0 0
Captain QR Code

扫码联系船长

发表回复

您的电子邮箱地址不会被公开。