1、准备三台机器,修改主机名,配置hosts文件,设置互信 hostnamectl set-hostname db03 cat >/etc/hosts <<EOF 172.16.190.129 db01 172.16.190.128 db02 172.16.190.130 db03 EOF #ssh-keygen [root@db02 ~]# ssh-copy-id -i .ssh/id_rsa.pub root@db02 #ansible认证需要自己信任自己
/data/soft #软件下载路径 /opt/redis_cluster/redis_{PORT}/{conf,logs,pid} #redis安装路径 /data/redis_cluster/redis_{PORT}/redis_{PORT}.rdb #redis数据目录 /root/scripts/redis_shell.sh #运维脚本
mkdir /data/soft -p mkdir /opt/redis_cluster/redis -p mkdir /opt/redis_cluster/redis_6379/{conf,logs,pid} -p mkdir /data/redis_cluster/redis_6379 -p wget http://download.redis.io/releases/redis-6.2.6.tar.gz -P /data/soft/ #-P 指定下载位置 tar -zxf /data/soft/redis-6.2.6.tar.gz -C /opt/redis_cluster/ ln -s /opt/redis_cluster/redis-6.2.6/ /opt/redis_cluster/redis #建立软连接方便后期升级 make #编译 make install #编译安装,将编译文件可执行命令复制到$PATH环境变量目录下,使命令可执行
### 配置文件放在了安装目录 cat > /opt/redis_cluster/redis_6379/conf/redis_6379.conf <<EOF ### 以守护进程模式启动 daemonize yes ### 绑定的主机地址 bind $(hostname -i) ### 监听端口 port 6379 ### pid 文件和 log 文件的保存地址 pidfile /opt/redis_cluster/redis_6379/pid/redis_6379.pid logfile /opt/redis_cluster/redis_6379/logs/redis_6379.log ### 设置数据库的数量,默认数据库为 0 databases 16 ### 指定本地持久化文件的文件名,默认是 dump.rdb dbfilename redis_6379.rdb ### 本地数据库的目录 dir /data/redis_cluster/redis_6379 EOF
#redis-server /opt/redis_cluster/redis_6379/conf/redis_6379.conf #redis-cli -h db01 shutdown
6652:C 18 Jan 2022 11:21:41.615 # Can't chdir to '/data/redis_cluster/redis_6379': No such file or directory #缺少数据目录,新建即可 6659:M 18 Jan 2022 11:22:45.613 * monotonic clock: POSIX clock_gettime 6659:M 18 Jan 2022 11:22:45.617 # Warning: Could not create server TCP listening socket ###:6379: Name or service not known 6659:M 18 Jan 2022 11:22:45.617 # Failed listening on port 6379 (TCP), aborting. # 检查配置文件发现配置文件串行
127.0.0.1:6379> set k2 100 OK 127.0.0.1:6379> incr k2 #INCR命令将字符串值解析成整型,默认加1并保存为新字符串 (integer) 101 127.0.0.1:6379> incrby k2 100 #解析成整型 加的值在后面 (integer) 201 127.0.0.1:6379> get k2 "201"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #一次存储多个值 OK 127.0.0.1:6379> mget k1 k2 k3 #一次获取多个值 1) "v1" 2) "v2" 3) "v3
127.0.0.1:6379> EXISTS k1 #检查键值是否存在 (integer) 1 127.0.0.1:6379> del k1 #删除键值 (integer) 1 127.0.0.1:6379> EXISTS k1 (integer) 0 127.0.0.1:6379> type k2 #返回键值存储类型 string 127.0.0.1:6379> ttl k2 #检测键值过期时间,-1 永不过期 -2键值已删除 (integer) -1 127.0.0.1:6379> EXPIRE k2 10 #设置键值过期时长为10s (integer) 1 127.0.0.1:6379> ttl k2 (integer) 6 127.0.0.1:6379> ttl k2 (integer) -2 127.0.0.1:6379> get k2 (nil)
127.0.0.1:6379> RPUSH k6 A #RPUSH 命令可向 list 的右边(尾部)添加一个新元素 (integer) 1 127.0.0.1:6379> LPUSH k6 b #LPUSH 命令可向 list 的左边(头部)添加一个新元素 (integer) 2 127.0.0.1:6379> LRANGE k6 0 -1 1) "b" 2) "A" 127.0.0.1:6379> LRANGE k6 0 1 #LRANGE 可以从 list 中取出一定范围的元素 1) "b" 2) "A 127.0.0.1:6379> LRANGE k6 0 5 ##完整列表 1) "d" 2) "c" 3) "b" 4) "A" 127.0.0.1:6379> rpop k6 1 #从右边删除一个元素 1) "A" 127.0.0.1:6379> lpop k6 1 #从左边边删除一个元素 1) "d" 127.0.0.1:6379> LRANGE k6 0 5 ##完整列表 1) "c" 2) "b"
127.0.0.1:6379> hmset test:1999 name wtl age 28 job daye #HMSET 指令设置 hash 中的多个域 OK 127.0.0.1:6379> hget test:1999 name #hget获取单个域 "wtl" 127.0.0.1:6379> hmget test:1999 name age job #hmget 取回一系列值 1) "wtl" 2) "28" 3) "daye" 127.0.0.1:6379> hget test:1999 age; #hget只能取到第一个值 其他值获取不到 (nil) 127.0.0.1:6379> HMSET test:1999 qq 1116 #可以继续插入写的列(hash不一定需要指定冒号和数字) OK 127.0.0.1:6379> HGETALL test:1999 #HGETALL获取域内所有值 1) "name" 2) "wtl" 3) "age" 4) "28" 5) "job" 6) "daye" 7) "qq" 8) "1116"
127.0.0.1:6379> sadd set 123 321 #SADD 指令把新的元素添加到 set 中 (integer) 1 127.0.0.1:6379> SMEMBERS set #获取值 1) "123" 2) "321" 127.0.0.1:6379> sadd k7 123 321 222 333 123 ##set集合不能出现重复数据,出现的重复数据不会被插入 (integer) 4 127.0.0.1:6379> SMEMBERS k7 #无序插入 1) "123" 2) "222" 3) "321" 4) "333" 127.0.0.1:6379> SREM k7 123 #删除集合的指定值 (integer) 1 127.0.0.1:6379> SMEMBERS k7 1) "222" 2) "321" 3) "333" 127.0.0.1:6379> sadd k11 1 2 3 4 (integer) 4 127.0.0.1:6379> sadd k12 1 5 (integer) 2 127.0.0.1:6379> SDIFF k11 k12 #差集 1) "2" 2) "3" 3) "4" 127.0.0.1:6379> SINTER k11 k12 #交集 1) "1" 127.0.0.1:6379> SUNION k11 k12 #并集 1) "1" 2) "2" 3) "3" 4) "4" 5) "5"
redis持久化分为rdb和aof方式; rdb:基于快照的持久化,速度更快,一般用作备份,主从复制也是依赖于 rdb 持久化功能 aof:以追加的方式记录 redis 操作日志的文件。可以最大程度的保证 redis 数据安全,类似于 mysql 的 binlog
#rdb dir /data/redis_cluster/redis_6379 dbfilename redis_6379.rdb save 900 1 #900 秒(15 分钟)内有 1 个更改 save 300 10 #300 秒(5 分钟)内有 10 个更改 save 60 10000 #60 秒内有 10000 个更改 #AOF appendonly yes #是否打开 aof 日志功能 appendfsync always #每 1 个命令,都立即同步到 aof appendfsync everysec #每秒写 1 次 appendfsync no #写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到 aof.
###rdb 1、cp redis_6379.conf{,bak} #备份配置文件 2、配置文件新增rdb配置 3、插入1000条数据,观察rdb文件变化 for i in {1..1000}; do redis-cli set k_${i} v_${i}; echo "${i} is ok"; done changes in 300 seconds. Saving... #100-300秒 开始保存 Background saving started by pid 1927 #备份pid建立 DB saved on disk #数据保存到磁盘上 RDB: 4 MB of memory used by copy-on-write #RDB:通过复制写入的4 MB内存 Background saving terminated with success #保存背景终止了成功 从命令执行到文件保存到磁盘经过了大概5分钟 ###可以通过修改参数缩短内存写入磁盘时间 ###AOF 1、取消rdb相关配置,修改为aof相关配置 2、数据持续写入 观察aof文件变化
protected-mode yes/no (保护模式,是否只允许本地访问) 127.0.0.1:6379> CONFIG GET protected-mode 1) "protected-mode" 2) "yes"
127.0.0.1:6379> CONFIG GET bind 1) "bind" 2) "127.0.0.1 172.16.190.129" 如果本地使用则只配置127
127.0.0.1:6379> CONFIG SET requirepass "123456" OK 127.0.0.1:6379> CONFIG GET requirepass 1) "requirepass" 2) "123456" 127.0.0.1:6379> get k1111 ## 没有密码认证不能执行命令 (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> get k1111 (nil) 取消密码认证 #配置文件中注释相关行即可
在分布式系统中解决单点问题,通常把数据复制多个副本到其他机器,满足故障恢复和负载均衡等要求;
复制功能是高可用的redis基础,哨兵和集群都是复制的基础上实现高可用的。
1、创建对应目录将单节点编译文件拷贝到从节点 mkdir /opt/redis_cluster/redis_6379/{conf,logs,pid} -p mkdir /data/redis_cluster/redis_6379 -p mkdir /opt/redis_cluster/redis -p scp -r /opt/redis_cluster/redis/* db03:/opt/redis_cluster/redis make install 2、增加防火墙策略 firewall-cmd --add-port=6379/tcp --permanent firewall-cmd --reload 3、修改配置文件 cat > /opt/redis_cluster/redis_6379/conf/redis_6379.conf <<EOF ### 以守护进程模式启动 daemonize yes ### 绑定的主机地址 bind $(hostname -i) ### 监听端口 port 6379 ### pid 文件和 log 文件的保存地址 pidfile /opt/redis_cluster/redis_6379/pid/redis_6379.pid logfile /opt/redis_cluster/redis_6379/logs/redis_6379.log ### 设置数据库的数量,默认数据库为 0 databases 16 ### 指定本地持久化文件的文件名,默认是 dump.rdb dbfilename redis_6379.rdb ### 本地数据库的目录 dir /data/redis_cluster/redis_6379 EOF 4、确认redis各节点服务启动,验证单节点是否可用 5、从节点输入SLAVEOF db01 6379 即可复制主节点的数据 #info replication 查看主从复制状态 127.0.0.1:6379> info replication # Replication role:slave master_host:db01 master_port:6379 master_link_status:up master_last_io_seconds_ago:4 master_sync_in_progress:0 slave_read_repl_offset:10359 slave_repl_offset:10359 slave_priority:100 slave_read_only:1 replica_announced:1 connected_slaves:0 master_failover_state:no-failover master_replid:018cf29ebea94cdb190f45111ca60c929ac9e030 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:10359 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:43 repl_backlog_histlen:10317 6、主节点插入数据从节点读取验证
1、主备验证完成后关闭主节点进程,模仿故障发生(先备份rdb数据) [root@db01 redis_6379]# redis-cli 127.0.0.1:6379> bgsave Background saving started 2、关闭进程后查看复制状态信息 127.0.0.1:6379> info replication # Replication role:slave master_host:db01 master_port:6379 master_link_status:down master_last_io_seconds_ago:-1 3、手动关闭主从复制 db03:6379> SLAVEOF no one OK 4、再次备份从节点数据并在剩余的节点中找出主节点,将从节点提升为主节点 db02:6379> SLAVEOF db03 6379 OK 5、插入数据模拟业务恢复,并验证从节点数据是否复制过去 for i in {1..1000}; do redis-cli -h db03 set w_${i} v_${i}; echo "${i} is ok"; done 6、启动db01进程,并加入从节点复制数据 db01:6379> SLAVEOF db03 6379 db01:6379> get w_1101 #最新数据验证获取成功 "v_1101" 7、关闭db02、db01、db03复制,恢复到单机版本,在db02和db03节点执行命令将db01 设置为主 db01:6379> SLAVEOF no one OK db02:6379> SLAVEOF db01 6379 OK db01:6379> info replication # Replication role:master --显示db01为主,有两个从节点 connected_slaves:2 slave0:ip=172.16.190.128,port=6379,state=online,offset=56315,lag=0 slave1:ip=172.16.190.130,port=6379,state=online,offset=56315,lag=1 master_failover_state:no-failover master_replid:07d47c052ae0fed9ef1bfd64f925b5b603950b14 master_replid2:f0cf44ff24ce12b3b559d1d91b0fcc1587a45b21 master_repl_offset:56315 second_repl_offset:56288 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:48473 repl_backlog_histlen:7843
断开复制主要流程: 1.断开与主节点复制关系 2.从节点晋升为主节点 从节点断开复制后不会抛弃原有数据,只是无法再获取主节点上的数据变化. 通过 slaveof 命令还可以实现切主操作,所谓切主是指把当前从节点对主节点的复制切换到另一个主节点. 执行 slaveof {newMasterIp} {newMasterPort}命令即可.
切主操作流程如下: 1.断开与旧主节点的复制关系 2.与新主节点建立复制关系 3.删除从节点当前所有数据 4.对新主节点进行复制操作 提示: 线上操作一定要小心,因为切主后会清空之前所有的数据.
redis的主从模式下,主节点出现故障不能提供服务,需要人工干预,将从节点晋升为主节点,还需要修改客户端的配置(连接ip等),哨兵主要解决redis主从需要人工干预的问题。
###哨兵的缺点 1、配置复杂2、中断时间长3、资源利用率低4、依赖于redis数据节点5、主库压力比较大,性能有瓶颈
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance)该系统执行以下三个任务:
1、监控 Sentinel 会不断地定期检查你的主服务器和从服务器是否运作正常。
2、提醒 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
3、自动故障迁移 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从 服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失 效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器
角色 | ip | 端口 |
---|---|---|
Master | 192.168.190.129 | 6379 |
Sentinel-01 | 192.168.190.129 | 26379 |
Master | 192.168.190.128 | 6379 |
Sentinel-02 | 192.168.190.128 | 26379 |
Master | 192.168.190.130 | 6379 |
Sentinel-03 | 192.168.190.130 | 26379 |
哨兵是继续主从复制,所以先要配置好主从复制,具体步骤如下:
1、先配置和创建好1台服务器的节点和哨兵
2、使用传输命令发送到另外两台机器
3、修改另外两台机器的ip地址
1、启动db01、db02、db03的redis节点,在db02、db03上执行主从复制的命令,并通过info replication命令查看主从状态 2、在各节点上执行以下命令创建哨兵的目录和配置文件,注意各节点的bind ip配置 mkdir -p /data/redis_cluster/redis_26379 mkdir -p /opt/redis_cluster/redis_26379/{conf,pid,logs} cat > /opt/redis_cluster/redis_26379/conf/redis_26379.conf<<EOF bind 172.16.190.129 port 26379 ##守护进程模式启动 daemonize yes logfile /opt/redis_cluster/redis_26379/logs/redis_26379.log dir /data/redis_cluster/redis_26379 #mymaster 是主节点别名 节点的ip 和端口 2是判断主节点失败,需要两个sentinel 节点同意 sentinel monitor mymaster 172.16.190.129 6379 2 #断线超时时间,超过这个时间就认为是断线 sentinel down-after-milliseconds mymaster 3000 #向新主节点发起复制操作的从节点个数,多个从同时传输会导致主节点服务器压力上升,写1则顺序复制,写总节点数则同时向主复制 sentinel parallel-syncs mymaster 1 #故障转移超时则判定为失败,设置值需要考虑网络环境、rdb备份文件大小 sentinel failover-timeout mymaster 18000 EOF yum -y install rsync #安装文件传输命令
启动哨兵,3台机器都操作 # redis-sentinel /opt/redis_cluster/redis_26379/conf/redis_26379.conf [root@db01 ~]# ps -ef|grep redis root 1633 1 0 09:49 ? 00:00:11 redis-server 127.0.0.1:6379 #26379端口服务变成了哨兵节点 root 4050 1 0 11:03 ? 00:00:00 redis-sentinel 172.16.190.129:26379 [sentinel] [root@db01 conf]# more redis_26379.conf #取消了sentinel down-after-milliseconds mymaster 3000默认配置。新增了纪元相关参数 bind 172.16.190.129 port 26379 daemonize yes logfile "/opt/redis_cluster/redis_26379/logs/redis_26379.log" dir "/data/redis_cluster/redis_26379" sentinel monitor mymaster 172.16.190.129 6379 2 sentinel down-after-milliseconds mymaster 3000 sentinel failover-timeout mymaster 1800 # Generated by CONFIG REWRITE protected-mode no pidfile "/var/run/redis.pid" user default on nopass ~* &* +@all sentinel myid 93d33bce801f09b3172d22e7eb60c1a1e819e03d sentinel config-epoch mymaster 0 sentinel leader-epoch mymaster 0 sentinel current-epoch 0 sentinel known-replica mymaster 172.16.190.128 6379 sentinel known-sentinel mymaster 172.16.190.128 26379 86a2a69e6fd008214d42ace1fe 7dcef956d6196d 数据持续写入 观察日志变化,验证数据读取
1、停掉某个节点,观察其他节点日志变化
1、启动db01 2、启动哨兵 3、设置权重,db01大于其他节点 4、重新发起选举 redis-cli -h db01 -p 26379 Sentinelfailover mymaster 5、观察主从复制是否正常 #config get slaveof 6、恢复db01权重,下次公平选举
1、槽位分配slot 2、16384个槽位 3、每一个槽位都必须分配到位,有一个没有分配,整个集群都不可用 4、序号顺序不一定要连续,最重要的是每个节点的槽位梳理要大致相同,允许2%的误差 5、集群通讯端口为配置文件里的port加10000,比如6380的通讯端口就是16380 6、故障转移切换全自动,不需要人工干预 7、集群配置文件动态更新,不要手动修改 8、程序连接redis集群需要插件支持,插件版本要一致 9、集群内消息传递是同步的 10、集群内的所有一发现的节点配置文件是自动更新的 11、hash分配算法是足够随机和足够平均的 足够稳定
# redis 安装目录 /opt/redis_cluster/redis_{PORT}/{conf,logs,pid} # redis 数据目录 /data/redis_cluster/redis_{PORT}/redis_{PORT}.rdb # redis 运维脚本 /root/scripts/redis_shell.sh
部署一套服务器上的2个集群节点 ,发送完成后修改其他服务器的配置ip地址
##db01操作 mkdir -p /opt/redis_cluster/redis_{6380,6381}/{conf,logs,pid} mkdir –p /data/redis_cluster/redis_{6380,6381} cat >/opt/redis_cluster/redis_6380/conf/redis_6380.conf<<EOF bind 172.16.190.129 port 6380 daemonize yes pidfile "/opt/redis_cluster/redis_6380/pid/redis_6380.pid" logfile "/opt/redis_cluster/redis_6380/logs/redis_6380.log" dbfilename "redis_6380.rdb" dir "/data/redis_cluster/redis_6380/" cluster-enabled yes cluster-config-file nodes_6380.conf cluster-node-timeout 15000 EOF cp /opt/redis_cluster/redis_6380/conf/redis_6380.conf /opt/redis_cluster/redis_6381/conf/redis_6381.conf sed -i 's#6380#6381#g' /opt/redis_cluster/redis_6381/conf/redis_6381.conf rsync -avz /opt/redis_cluster/redis_638* db02:/opt/redis_cluster/ rsync -avz /opt/redis_cluster/redis_638* db03:/opt/redis_cluster/ redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf
###db02 操作 find /opt/redis_cluster/redis_638* -type f -name "*.conf"|xargs sed -i "/bind/s#129#128#g" mkdir -p /data/redis_cluster/redis_{6380,6381} redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf
###db03 操作 find /opt/redis_cluster/redis_638* -type f -name "*.conf"|xargs sed -i "/bind/s#129#130#g" mkdir -p /data/redis_cluster/redis_{6380,6381} redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf
所有节点都启动后进程会有cluster标注,登录cluster nodes 查看每个节点只有自己,没有发现其他节点的信息,通过节点发现将其他节点的id自动写到集群配置文件内,redis除了原有的配置文件,又添加了一份配置文件,当集群信息发生变化,集群节点下线、故障转移等操作redis会自动记录在配置文件,不要手动修改集群的配置文件。
[root@db03 redis_cluster]# ps -ef|grep redis root 2625 1 0 15:19 ? 00:00:00 redis-server 172.16.190.130:6381 [cluster] root 2631 1 0 15:19 ? 00:00:00 redis-server 172.16.190.130:6380 [cluster] [root@db03 redis_cluster]# redis-cli -h db03 -p 6380 db03:6380> CLUSTER nodes d93ddcc484b2bef8462a7163436b95dcdcc20a35 :6380@16380 myself,master - 0 0 0 connected
[root@db03 ~]# sh redis_shell.sh login 6380 #登录集群内任意一台执行节点发现命令 172.16.190.130:6380> CLUSTER MEET 172.16.190.129 6381 #使用meet命令跟节点地址 发现对应节点 OK 172.16.190.130:6380> CLUSTER MEET 172.16.190.129 6380 OK 172.16.190.130:6380> CLUSTER MEET 172.16.190.128 6380 OK 172.16.190.130:6380> CLUSTER MEET 172.16.190.128 6381 OK 172.16.190.130:6380> CLUSTER MEET 172.16.190.130 6381 OK 172.16.190.130:6380> CLUSTER NODES #查看集群节点信息 97d428a549976784f0b1e4a18895f611d7bb56d1 172.16.190.128:6381@16381 master - 0 1642666748131 4 connected ……
查看集群配置文件是否写入节点信息
[root@db02 redis_cluster]# cat redis_6381/nodes_6381.conf --节点配置文件存储在数据目录 122d0aa0329a16ce11d62460ab57162f777761a4 172.16.190.129:6380@16380 master - 0 1642666710257 2 connected 46272fa2df81f4dac2f0c1ac54d68f6727baae69 172.16.190.128:6380@16380 master - 0 1642666709245 ……
分布式存储中需要提供维护节点元数据信息,元数据包括:节点负责哪些数据、是否出现故障灯状态信息;redis集群采用Gossip(流言)协议,协议工作原理就是节点不断交换信息。
1、集群中的每一个节点都会单独开辟一个tcp通道,用户节点之间彼此通信,通信端口在基础端口上佳1w 2、每个机电在固定周期内通过特定规则选择结构节点发送ping消息 3、接手到ping消息的节点用pong消息作为响应,集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能只知道部分节点,只要这些节点彼此可以正常通信,最终会形成一致状态,当节点出现故障、新节点加入、主从角色变化等,通过ping/pong达到部门消息的目的。
gossip协议常见消息分为: ping: 用于检测节点是否在线及节点信息交换 pong:接手到ping、meet信息是给相应节点发送确认信息,也可以向集群发起广播自身费pong消息来通知整个集群对状态进行更新 meet:用于通知新节点加入 fail:当判定节点下线后通过广播向集群内发送fail消息,其他节点收到fail消息后也会更新对应节点状态未下线。
节点互相发现后需要分配槽位,并且所有槽位分配完毕后集群才可以使用,如果有一个槽位没有分配则整个集群不可用
[root@db03 ~]# sh redis_shell.sh login 6380 172.16.190.130:6380> set r3 1 (error) CLUSTERDOWN Hash slot not served 172.16.190.130:6380> CLUSTER info #查看集群状态 cluster_state:fail # cluster_slots_assigned:0 ……
redis-cli -h db01 -p 6380 cluster addslots {0..5461} redis-cli -h db02 -p 6380 cluster addslots {5462..10922} redis-cli -h db03 -p 6380 cluster addslots {10923..16383} [root@db03 ~]# sh redis_shell.sh login 6380 172.16.190.130:6380> CLUSTER info cluster_state:ok #状态恢复 172.16.190.128:6380> CLUSTER nodes #集群节点信息 46272fa2df81f4dac2f0c1ac54d68f6727baae69 172.16.190.128:6380@16380 myself,master - 0 1642678322000 3 connected 5462-10922 ……
###节点信息解释 格式:<id> <ip:port> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot> id: 节点ID,是一个40字节的随机字符串,这个值在节点启动的时候创建,并且永远不会改变(除非使用CLUSTER RESET HARD命令)。 ip:port: 客户端与节点通信使用的地址. flags: 逗号分割的标记位,可能的值有: myself, master, slave, fail?, fail, handshake, noaddr, noflags. 下一部分将详细介绍这些标记. master: 如果节点是slave,并且已知master节点,则这里列出master节点ID,否则的话这里列出”-“。 ping-sent: 最近一次发送ping的时间,这个时间是一个unix毫秒时间戳,0代表没有发送过. pong-recv: 最近一次收到pong的时间,使用unix时间戳表示. config-epoch: 节点的epoch值(or of the current master if the node is a slave)。每当节点发生失败切换时,都会创建一个新的,独特的,递增的epoch。如果多个节点竞争同一个哈希槽时,epoch值更高的节点会抢夺到。 link-state: node-to-node集群总线使用的链接的状态,我们使用这个链接与集群中其他节点进行通信.值可以是 connected 和 disconnected. slot: 哈希槽值或者一个哈希槽范围. 从第9个参数开始,后面最多可能有16384个 数(limit never reached)。代表当前节点可以提供服务的所有哈希槽值。如果只是一个值,那就是只有一个槽会被使用。如果是一个范围,这个值表示为起始槽-结束槽,节点将处理包括起始槽和结束槽在内的所有哈希槽。 #集群信息解释 172.16.190.130:6381> CLUSTER info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:7 cluster_my_epoch:6 cluster_stats_messages_ping_sent:16902 cluster_stats_messages_pong_sent:17202 cluster_stats_messages_fail_sent:4 cluster_stats_messages_auth-req_sent:5 cluster_stats_messages_sent:34113 cluster_stats_messages_ping_received:17197 cluster_stats_messages_pong_received:16897 cluster_stats_messages_fail_received:6 cluster_stats_messages_auth-req_received:1 cluster_stats_messages_auth-ack_received:2 cluster_stats_messages_received:34103 ########### cluster_state: ok状态表示集群可以正常接受查询请求。fail 状态表示,至少有一个哈希槽没有被绑定(说明有哈希槽没有被绑定到任意一个节点),或者在错误的状态(节点可以提供服务但是带有FAIL 标记),或者该节点无法联系到多数master节点。. cluster_slots_assigned: 已分配到集群节点的哈希槽数量(不是没有被绑定的数量)。16384个哈希槽全部被分配到集群节点是集群正常运行的必要条件. cluster_slots_ok: 哈希槽状态不是FAIL 和 PFAIL 的数量. cluster_slots_pfail: 哈希槽状态是 PFAIL的数量。只要哈希槽状态没有被升级到FAIL状态,这些哈希槽仍然可以被正常处理。PFAIL状态表示我们当前不能和节点进行交互,但这种状态只是临时的错误状态。 cluster_slots_fail: 哈希槽状态是FAIL的数量。如果值不是0,那么集群节点将无法提供查询服务,除非cluster-require-full-coverage被设置为no . cluster_known_nodes: 集群中节点数量,包括处于握手状态还没有成为集群正式成员的节点. cluster_size: 至少包含一个哈希槽且能够提供服务的master节点数量. cluster_current_epoch: 集群本地Current Epoch变量的值。这个值在节点故障转移过程时有用,它总是递增和唯一的。 cluster_my_epoch: 当前正在使用的节点的Config Epoch值. 这个是关联在本节点的版本值. cluster_stats_messages_sent: 通过node-to-node二进制总线发送的消息数量. cluster_stats_messages_received: 通过node-to-node二进制总线接收的消息数量.
为了防止一台机器故障整个集群不可用,启用对应从节点保障主节点故障时集群可用
###复制节点时避免本机主复制本机从,要复制其他机器的从节点 ###从节点执行命令,表述自己要复制的节点id redis-cli -h db01 -p 6381 CLUSTER REPLICATE 要复制的主节点id
redis-cli -h db01 -p 6381 CLUSTER REPLICATE 122d0aa0329a16ce11d62460ab57162f777761a4 redis-cli -h db02 -p 6381 CLUSTER REPLICATE 46272fa2df81f4dac2f0c1ac54d68f6727baae69 redis-cli -h db03 -p 6381 CLUSTER REPLICATE d93ddcc484b2bef8462a7163436b95dcdcc20a35
[root@db03 ~]# redis-cli -h db01 -p 6380 set t1 c (error) MOVED 8943 172.16.190.128:6380 #提示要在另外一台机器上插入数据,因为这个分片在168上
集群模式下redis在接收键值前会先计算键值对应的槽,如果是节点本身则处理,不是则恢复moved重定向错误,通知客户端请求正确的节点,这个过程成为mover重定向;
使用-c选项解决redis路由的问题 cat input_key.sh #!/bin/bash for i in $(seq 1 1000) do redis-cli -c -h db01 -p 6380 set k_${i} v_${i} && echo "set k_${i} is ok" done
[root@db03 ~]# redis-cli -h db01 -c -p 6380 db01:6380> get k_1 "v_1" db01:6380> get k_100 -> Redirected to slot [5541] located at 172.16.190.128:6380 #重定向到插槽[*]位于服务器 "v_100" #给了回复但也给了值
通过关闭单节点进程,查看其他节点日志变化及将节点从从节点恢复为主节点;
1、ps -ef|grep redis #查询进程id ,kill掉db03主节点 2、sh redis_shell.sh login 6381 #登录db03从节点,准备查询集群状态 3、172.16.190.130:6381> CLUSTER info cluster_state:ok #杀掉进程后集群显示正常 4、172.16.190.130:6381> CLUSTER nodes 97d428a549976784f0b1e4a18895f611d7bb56d1 172.16.190.128:6381@16381 slave 46272fa2df81f4dac2f0c1ac54d68f6727baae69 0 1642749435000 3 connected #172.16.190.128:6381 46272fa2df81f4dac2f0c1ac54d68f6727baae69 172.16.190.128:6380@16380 master - 0 1642749434000 3 connected 5462-10922 #172.16.190.128:6380 6dcfd0d894189d2c02fbda110e63aeeb85209595 172.16.190.130:6381@16381 myself,master - 0 1642749435000 6 connected 10923-16383 #172.16.190.130:6381(原来的从) d93ddcc484b2bef8462a7163436b95dcdcc20a35 172.16.190.130:6380@16380 slave,fail #状态异常 6dcfd0d894189d2c02fbda110e63aeeb85209595 1642749385493 1642749380000 6 disconnected #172.16.190.130:6380 9f6e5453529bca33ce19b09c0d9771b52801af5d 172.16.190.129:6381@16381 slave 122d0aa0329a16ce11d62460ab57162f777761a4 0 1642749435225 2 connected #172.16.190.129:6381@16381 122d0aa0329a16ce11d62460ab57162f777761a4 172.16.190.129:6380@16380 master - 0 1642749436240 2 connected 0-5461 #172.16.190.129:6380 4、 redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf #启动db02,观察集群状态 5、集群和节点状态正常,将6380切换为主 6、172.16.190.130:6380> CLUSTER FAILOVER OK 172.16.190.130:6380> CLUSTER NODES 6dcfd0d894189d2c02fbda110e63aeeb85209595 172.16.190.130:6381@16381 master - 0 1642753513000 6 connected 10923-16383 d93ddcc484b2bef8462a7163436b95dcdcc20a35 172.16.190.130:6380@16380 myself,slave 6dcfd0d894189d2c02fbda110e63aeeb85209595 0 1642753514000 6 connected …… 172.16.190.130:6380> CLUSTER FAILOVER #想切换谁为主就在谁的节点和端口上执行 OK 172.16.190.130:6380> CLUSTER NODES 6dcfd0d894189d2c02fbda110e63aeeb85209595 172.16.190.130:6381@16381 slave d93ddcc484b2bef8462a7163436b95dcdcc20a35 0 1642753618153 8 connected d93ddcc484b2bef8462a7163436b95dcdcc20a35 172.16.190.130:6380@16380 myself,master - 0 1642753616000 8 connected 10923-16383
故障描述:下班后所有虚拟机全部关闭,第二天通过通过命令启动集群,通过CLUSTER info查看集群状态未fail,节点状态如下:
redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf 172.16.190.128:6380>CLUSTER NODES #节点状态fail d93ddcc484b2bef8462a7163436b95dcdcc20a35 172.16.190.130:6380@16380 master,fail? - 1642729827554 1642729825606 0 connected 10923-16383 9f6e5453529bca33ce19b09c0d9771b52801af5d 172.16.190.129:6381@16381 slave,fail? …… 172.16.190.128:6380> CLUSTER info cluster_state:fail #集群状态未fail cluster_slots_assigned:16384 cluster_slots_ok:5461 cluster_slots_pfail:10923 cluster_slots_fail:0 …… 172.16.190.128:6380> set y1 1 #数据插入显示集群未启动 (error) CLUSTERDOWN The cluster is down #### redis-cli --cluster check 172.16.190.128:6380 #通过集群检查修复命令尝试修复集群 Could not connect to Redis at 172.16.190.129:6381: Connection timed out Could not connect to Redis at 172.16.190.130:6381: No route to host #提示连接失败 Could not connect to Redis at 172.16.190.130:6380: No route to host Could not connect to Redis at 172.16.190.129:6380: No route to host 172.16.190.128:6380 (46272fa2...) -> 327 keys | 5461 slots | 1 slaves. [OK] 327 keys in 1 masters. 0.02 keys per slot on average. >>> Performing Cluster Check (using node 172.16.190.128:6380)#进行集群检查 M: 46272fa2df81f4dac2f0c1ac54d68f6727baae69 172.16.190.128:6380 slots:[5462-10922] (5461 slots) master #主,槽位 additional replica(s) S: 97d428a549976784f0b1e4a18895f611d7bb56d1 172.16.190.128:6381 #从,槽位 slots: (0 slots) slave replicates 46272fa2df81f4dac2f0c1ac54d68f6727baae69 #复制节点数据 [OK] All nodes agree about slots configuration. #所有节点同意 >>> Check for open slots... #检查空槽位 >>> Check slots coverage... #检查槽位覆盖 [ERR] Not all 16384 slots are covered by nodes. #不是所有的槽位都被覆盖 ####通过路由问题想到是防火墙策略未开放,除本机外的集群节点未检查,调整防火墙再次执行命令后集群状态正常 ###不需要所有节点都执行检查命令 单节点执行即可
手动搭建可以了解过程和细节,但配置较多,多节点时工作量较大,官方提供了redis-trib.rb工具,redis-trib.rb 是采用 Ruby 实现的 redis 集群管理工具,内部通过 Cluster 相关命令帮我们简化集群 创建、检查、槽迁移和均衡等常见运维操作,使用前要安装 ruby 依赖环境。
安装命令:
#所有节点执行 yum makecache fast yum install rubygems gem sources --remove https://rubygems.org/ gem sources -a http://mirrors.aliyun.com/rubygems/ gem update –system gem install redis -v 3.3.5 pkill redis rm -rf /data/redis_cluster/redis_6380/* rm -rf /data/redis_cluster/redis_6381/* sh redis_shell.sh start 6380 sh redis_shell.sh start 6381 #db01执行 cd /opt/redis_cluster/redis/src/ redis-cli --cluster create 172.16.190.128:6381 172.16.190.130:6381 172.16.190.129:6381 172.16.190.128:6380 172.16.190.130:6380 172.16.190.129:6380 --cluster-replicas 1 #检查集群完整性 redis-cli --cluster check 172.16.190.129:6380 ##### 请补充主从节点命令
[root@db01 src]# ./redis-trib.rb create --replicas 1 172.16.190.128:6381 172.16.190.130:6381 172.16.190.129:6381 172.16.190.128:6380 172.16.190.130:6380 172.16.190.129:6380 ###该命令已被放弃 WARNING: redis-trib.rb is not longer available! #警告:redis-trib。Rb不再可用! You should use redis-cli instead. 你应该使用redis-cli。 All commands and features belonging to redis-trib.rb have been moved to redis-cli.#为了使用它们,你应该使用——cluster调用redis-cli In order to use them you should call redis-cli with the --cluster option followed by the subcommand name, arguments and options.#选项后面跟着子命令名、参数和选项。 Use the following syntax: #使用以下语法: redis-cli --cluster SUBCOMMAND [ARGUMENTS] [OPTIONS] Example: 举例 redis-cli --cluster create 172.16.190.128:6381 172.16.190.130:6381 172.16.190.129:6381 172.16.190.128:6380 172.16.190.130:6380 172.16.190.129:6380 --cluster-replicas 1 To get help about all subcommands, type: redis-cli --cluster help #帮助命令
Redis 集群的扩容操作可分为以下几个步骤 1)准备新节点 2)加入集群 3)迁移槽和数据
扩容流程图:
db01上新建节点 mkdir -p /opt/redis_cluster/redis_{6390,6391}/{conf,logs,pid} mkdir -p /data/redis_cluster/redis_{6390,6391} cd /opt/redis_cluster/ cp redis_6380/conf/redis_6380.conf redis_6390/conf/redis_6390.conf cp redis_6380/conf/redis_6380.conf redis_6391/conf/redis_6391.conf sed -i 's#6380#6390#g' redis_6390/conf/redis_6390.conf sed -i 's#6380#6391#g' redis_6391/conf/redis_6391.conf
启动节点 bash redis_shell.sh start 6390 bash redis_shell.sh start 6391 发现节点 redis-cli -c -h db01 -p 6380 cluster meet 10.0.0.51 6390 redis-cli -c -h db01 -p 6380 cluster meet 10.0.0.51 6391 在 db01 上使用工具扩容 cd /opt/redis_cluster/redis/src/ ./redis-trib.rb reshard 10.0.0.51:6380
CLUSTER INFO 打印集群的信息 CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。
CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。 CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。 CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。 CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。
CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。 CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。 CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。 CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节 点,那么先让另一个节点删除该槽>,然后再进行指派。 CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。 CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。 CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。
CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。 CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。 CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。
#!/bin/bash USAG(){ echo "sh $0 {start|stop|restart|login|ps|tail} PORT" } if [ "$#" = 1 ] then REDIS_PORT='6379' elif [ "$#" = 2 -a -z "$(echo "$2"|sed 's#[0-9]##g')" ] then REDIS_PORT="$2" else USAG exit 0 fi REDIS_IP=$(hostname -I|awk '{print $1}') PATH_DIR=/opt/redis_cluster/redis_${REDIS_PORT}/ PATH_CONF=/opt/redis_cluster/redis_${REDIS_PORT}/conf/redis_${REDIS_PORT}.conf PATH_LOG=/opt/redis_cluster/redis_${REDIS_PORT}/logs/redis_${REDIS_PORT}.log CMD_START(){ redis-server ${PATH_CONF} } CMD_SHUTDOWN(){ redis-cli -c -h ${REDIS_IP} -p ${REDIS_PORT} shutdown } CMD_LOGIN(){ redis-cli -c -h ${REDIS_IP} -p ${REDIS_PORT} } CMD_PS(){ ps -ef|grep redis } CMD_TAIL(){ tail -f ${PATH_LOG} } case $1 in start) CMD_START CMD_PS ;; stop) CMD_SHUTDOWN CMD_PS ;; restart) CMD_START CMD_SHUTDOWN CMD_PS ;; login) CMD_LOGIN ;; ps) CMD_PS ;; tail) CMD_TAIL ;; *) USAG esac
cd /opt/redis_cluster/ git clone https://github.com/vipshop/redis-migrate-tool.git cd redis-migrate-tool/ autoreconf -fvi ./configure make && make install
[root@db01 ~]# cat redis_6379_to_6380.conf [source] type: single servers: - 10.0.0.51:6379 [target] type: redis cluster servers: - 10.0.0.51:6380 [common] listen: 0.0.0.0:8888 source_safe: true
生成测试数据 [root@db01 ~]# cat input_key.sh #!/bin/bash for i in $(seq 1 1000) do redis-cli -c -h db01 -p 6379 set k_${i} v_${i} && echo "set k_${i} is ok" done
redis-migrate-tool -c redis_6379_to_6380.conf redis-migrate-tool -c redis_6379_to_6380.conf -C redis_check
yum install python-pip gcc python-devel cd /opt/ git clone https://github.com/sripathikrishnan/redis-rdb-tools cd redis-rdb-tools python setup.py install
cd /data/redis_cluster/redis_6380/ rdb -c memory redis_6380.rdb -f redis_6380.rdb.csv
分析rdb并导出 awk -F ',' '{print $4,$2,$3,$1}' redis_6380.rdb.csv |sort > 6380.txt
需求背景 因为开发重复提交,导致电商网站优惠卷过期时间失效 问题分析 如果一个键已经设置了过期时间,这时候在 set 这个键,过期时间就会取消 解决思路 如何在不影响机器性能的前提下批量获取需要监控键过期时间 1.Keys* 查出来匹配的键名。然后循环读取ttl时间 2.scan * 范围查询键名。然后循环读取 ttl 时间 Keys 重操作,会影响服务器性能,除非是不提供服务的从节点 Scan 负担小,但是需要去多次才能取完,需要写脚本
cat 01get_key.sh #!/bin/bash key_num=0 > key_name.log for line in $(cat key_list.txt) do while true do scan_num=$(redis-cli -h 192.168.47.75 -p 6380 SCAN ${key_num} match ${line}\* count 1000|awk 'NR==1{print $0}') key_name=$(redis-cli -h 192.168.47.75 -p 6380 SCAN ${key_num} match ${line}\* count 1000|awk 'NR>1{print $0}') echo ${key_name}|xargs -n 1 >> key_name.log ((key_num=scan_num)) if [ ${key_num} == 0 ] then break fi done done