一、Redis常用的三种集群模式
主从(单体架构项目)
通过持久化功能,Redis保证了即使在服务器重启的情况下也不会丢失(或少量丢失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。 但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。
为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此,Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。
主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。
而从数据库一般是只读的,并接受主数据库同步过来的数据。
一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
哨兵(单体架构项目)
第一种主从同步/复制的模式,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。
其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
cluster
Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存
所以在redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容。
官方推荐的集群模式,水平扩容更方便
二、Redis Cluster(Redis集群)简介
redis3.0版本之前只支持单例模式,在3.0版本及以后才支持集群
redis集群采用P2P模式,是完全去中心化的,不存在中心节点或者代理节点;
redis集群是没有统一的入口的,客户端(client)连接集群的时候连接集群中的任意节点(node)即可,集群内部的节点是相互通信的(PING-PONG机制),每个节点都是一个redis实例;
为了实现集群的高可用,即判断节点是否健康(能否正常使用),redis-cluster有这么一个投票容错机制:如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法;
那么如何判断集群是否挂了呢? -> 如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法;
那么为什么任意一个节点挂了(没有从节点)这个集群就挂了呢? -> 因为集群内置了16384个slot(哈希槽),并且把所有的物理节点映射到了这16384[0-16383]个slot上,或者说把这些slot均等的分配给了各个节点。
1、当需要在Redis集群存放一个数据(key-value)时,redis会先对这个key进行crc16算法,然后得到一个结果。
2、再把这个结果对16384进行求余,这个余数会对应[0-16383]其中一个槽,进而决定key-value存储到哪个节点中。
3、所以一旦某个节点挂了,该节点对应的slot就无法使用,那么就会导致集群无法正常工作。
1、集群搭建需要的环境
1.1 Redis集群至少需要3个节点
因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以2个节点无法构成集群。
奇数个节点
1.2 Redis集群至少需要6台服务器
因为要保证集群的高可用,需要每个节点都有从节点(备份节点)
我没有那么多服务器,而且启动那么多虚拟机会很卡,所在这里搭建的是伪分布式集群
一台服务器虚拟运行6个redis实例,修改端口号为(7001-7006),当然实际生产环境的Redis集群搭建是一样的
1.3 卸载
之前redis改的东西太多,在搭建集群之前,最好还原redis最纯净的环境
1、停止redis服务器
#等同于 ps -ef | grep redis [root@D bin]# ps aux|grep redis # 用于通过命令行连接 Redis 服务器并执行操作 [root@D bin]# redis-cli -p 6379 shutdown2、删除make的时候生成的redis相关文件
[root@D local]# cd /usr/local/bin/ [root@D local]# rm -f /usr/local/bin/redis*3、删除掉解压后的文件目录及所有文件
[root@D local]# cd /opt [root@D opt]# rm -rf redis-5.0.42、集群搭建具体步骤如下(注意要关闭防火墙)
重新安装一遍reids
#tar.gz,先打包再压缩,适用于linux平台 [root@D opt]# wget http://download.redis.io/releases/redis-5.0.4.tar.gz # 解压到 /opt 目录下(需确保目录存在) tar -zxvf filename.tar.gz -C /opt # 解压名为 filename.tar.gz 的压缩包到当前目录,有v会显示过程, [root@D opt]# tar -zxvf redis-5.0.4.tar.gz [root@D opt]# cd redis-5.0.4 # 安装编译依赖 [root@D opt]# apt install -y build-essential gcc g++ libc6-dev tcl # 修改sds.h文件作用域为外部声明,37行左右关键字改为:extern const char *SDS_NOINIT; [root@D redis-5.0.4]# nano /opt/redis-5.0.4/src/sds.h # 根据 Makefile 中的规则,将源码编译成可执行文件(二进制程序)。 [root@D redis-5.0.4]# make #将编译好的二进制文件、配置文件、手册等复制到系统默认或自定义的安装目录,完成软件的 “安装”。 [root@D redis-5.0.4]# make install2.1 在redis的安装目录下将redis.conf复制6份
redis7001.conf,redis7002.conf,redis7003.conf...
[root@D local]# cd /opt/redis-5.0.4 [root@D redis-5.0.4]# cp redis.conf redis7001.conf [root@D redis-5.0.4]# vim redis7001.conf2.2 修改配置文件
port 7001 //端口7001,7002,7003 bind 0.0.0.0 //默认ip为127.0.0.1 需要改为其他节点机器可访问的ip 否则创建集群时无法访问对应的端口,无法创建集群 daemonize yes //redis后台运行 pidfile /var/run/redis_7001.pid //pidfile文件对应7001,7002,7003 logfile "redis7001.log" dbfilename dump7001.rdb appendonly yes //aof日志开启 有需要就开启,它会每次写操作都记录一条日志 appendfilename "appendonly7001.aof" cluster-enabled yes //开启集群 把注释#去掉 cluster-config-file nodes_7001.conf //集群的配置 配置文件首次启动自动生成 7001,7002,7003 把注释#去掉 cluster-node-timeout 15000 //请求超时 默认15秒,可自行设置 把注释#去掉在/opt/redis-5.0.4/目录下创建脚本,将redis7001.conf中所有7001替换成7002之后,并生成新文件redis7002.conf~redis7006.conf
[root@D redis-5.0.4]# nano run.shrun.sh内容如下:
#!/bin/bash # 批量生成Redis集群节点配置文件(7002-7006),基于redis7001.conf模板 # 检查模板文件是否存在 if [ ! -f "redis7001.conf" ]; then echo "错误:模板文件 redis7001.conf 不存在,请确保在 /opt/redis-5.0.4/ 目录执行" exit 1 fi # 循环生成7002-7006的配置文件 for port in {7002..7006} do # 替换端口和对应的集群配置文件(nodes-端口.conf) sed "s/7001/$port/g" redis7001.conf > redis$port.conf # 验证文件是否生成成功 if [ -f "redis$port.conf" ]; then echo "成功生成:redis$port.conf" else echo "失败:未生成 redis$port.conf" fi done echo "=== 所有配置文件生成完成 ==="给run.sh赋执行权限:
[root@D redis-5.0.4]# chmod +x generate_redis_configs.sh执行脚本:已经执行过,下一次就不必执行
./run.sh2.3 创建启动监本
一个一个启动太麻烦,写一个sh脚本启动
[root@D local]# cd /usr/local/bin/ [root@D bin]# vim start-all-redis.sh脚本内容如下:
#! /bin/bash #启动 Redis 服务器,并指定使用 /opt/redis-5.0.4/redis7001.conf 作为配置文件 redis-server /opt/redis-5.0.4/redis7001.conf redis-server /opt/redis-5.0.4/redis7002.conf redis-server /opt/redis-5.0.4/redis7003.conf redis-server /opt/redis-5.0.4/redis7004.conf redis-server /opt/redis-5.0.4/redis7005.conf redis-server /opt/redis-5.0.4/redis7006.conf [root@D bin]# chmod +x start-all-redis.sh [root@D bin]# ./start-all-redis.sh [root@D bin]# ps aux|grep redis创建停止脚本
nano stop.sh内容
#!/bin/bash # 定义Redis集群节点端口范围(根据实际集群修改) START_PORT=7001 END_PORT=7006 # 关闭指定端口的Redis节点 shutdown_node() { local port=$1 echo "正在关闭节点 $port ..." # 执行关闭命令(若有密码,添加 -a 密码参数,如:redis-cli -p $port -a your_password shutdown) redis-cli -p $port shutdown # 检查是否关闭成功 if [ $? -eq 0 ]; then echo "节点 $port 关闭成功" else echo "警告:节点 $port 关闭失败(可能已关闭或连接异常)" fi } # 遍历端口,逐个关闭节点 for (( port=START_PORT; port<=END_PORT; port++ )); do shutdown_node $port done # 最终检查进程是否残留 echo -e "\n检查是否有残留Redis进程..." remaining=$(ps -ef | grep redis-server | grep -v grep | wc -l) if [ $remaining -eq 0 ]; then echo "所有Redis集群节点已成功关闭" else echo "警告:仍有 $remaining 个Redis进程残留,需手动检查" ps -ef | grep redis-server | grep -v grep fi赋执行权限
chmod +x stop.sh运行脚本
./stop.sh
2.4 创建集群
原生的集群步骤:
1、配置并开启集群的节点
cluster-enabled yes
cluster-config-file nodes_7001.conf
2、meet
cluster meet ip port
3、指派槽位
通过命令计算key的槽位:cluster keyslot key
3主:16384/3,每个主机分配的槽位范围 0-5461 、 5462-10923 、 10923-16383
4主:16384/4,每个主机分配的槽位4096
cluster addslots slot(槽位下标)
4、分配主从
cluster replicate node-id
以上步骤全部继承到命令 redis-trib.rb 这个工具中,但是目前已经废弃,而5.0以后直接使用redis-cli
先查看下集群命令的帮助参数
--cluster-replicas 1
数字是分配主从节点数量的比例
1:1主1从
2:1主2从
#redis-cli 是 Redis 自带的命令行客户端工具,用于与 Redis 服务器交互(发送命令、管理数据、操作服务等),是使用 Redis 最基础也最常用的工具。 [root@D bin]# redis-cli --cluster help # 使用 redis-cli 的集群模式创建一个 Redis 集群 # --cluster create:redis-cli 的集群管理命令,用于初始化新集群 # --cluster-replicas 1:指定每个主节点(master)配备 1 个从节点(slave,改变数字1就会改变主节点的从节点数量。 # 后面的 IP:端口 是参与集群的 6 个 Redis 实例(需提前启动并开启集群模式) #第一次时初始化集群使用,创建一个 Redis 集群(Redis Cluster),通过 redis-cli 的集群管理功能,将多个 Redis 实例组成一个分布式集群。(这里换成自己的IP) [root@D bin]# redis-cli --cluster create --cluster-replicas 1 192.168.145.128:7001 192.168.145.128:7002 192.168.145.128:7003 192.168.145.128:7004 192.168.145.128:7005 192.168.145.128:7006数据库中可能有数据,所以无法集群
#redis-cli:Redis 命令行客户端工具。 #-p 7001:指定连接的 Redis 节点端口为 7001(需确保该端口是集群中的一个节点)。 #-c:开启 集群模式(cluster mode) [root@D bin]# redis-cli -p 7001 -c 127.0.0.1:7001> keys * 1) "allStudent" 127.0.0.1:7001> flushall OK 127.0.0.1:7001> exit集群成功(如果出错,一定要查看/usr/local/bin下的log日志)
3、测试集群
测试存取值:客户端连接集群redis-cli需要带上 -c(以集群的身份登录) ,redis-cli -c -p 端口号,如下:
[root@D bin]# redis-cli -c -p 7001 127.0.0.1:7001> set name laosun -> Redirected to slot [5798] located at 192.168.204.144:7002 OK 192.168.204.144:7002> get name "laosun" 192.168.204.144:7002>根据redis-cluster的key值分配,name应该分配到节点7002[5461-10922]上,上面显示redis cluster自动从7001跳转到了7002节点。
对name进行CRC16算法求哈希值,123%16384=5798,这个值分配到具体的槽位上,槽位的范围就是具体的7002服务器
我们可以测试一下7006从节点获取name值
192.168.204.144:7002> exit [root@D bin]# ./redis-cli -c -p 7006 127.0.0.1:7006> get name -> Redirected to slot [5798] located at 192.168.204.144:7002 "laosun" 192.168.204.144:7002>自动跳转至7002获取值,这也是redis cluster的特点,它是去中心化,每个节点都是对等的,连接哪个节点都可以获取和设置数据
4、集群的选举
集群节点信息
[root@D bin]# redis-cli -p 7001 127.0.0.1:7001> cluster nodes从上面集群的情况可以看出,4跟3,6跟2,5跟1
如果将7003主节点挂掉,按照redis-cluster原理会选举会将 7003的从节点7004选举为主节点
[root@D bin]# redis-cli -p 7003 shutdown给redis一点开会选举的时间
如果7003再次回归
[root@D bin]# redis-server /opt/redis-5.0.4/redis7003.conf4已经成为老大,3只好从小弟重新干起
三、扩容
sed:Linux 下的流编辑器,用于对文本进行批量替换、删除等操作。's/7001/7007/g':send的替换指令,含义是:s:表示 “替换” 操作(substitute)。7001:被替换的原始字符串(这里是原配置文件中的端口号)。7007:替换后的新字符串(新配置文件要使用的端口号)。g:全局替换(global),即替换文件中所有出现7001的地方,而非仅第一个。
/opt/redis-5.0.4/redis7001.conf:源配置文件路径(以 7001 端口的配置为模板)。> /opt/redis-5.0.4/redis7007.conf:输出重定向,将替换后的内容写入新文件redis7007.conf(若文件不存在则创建,存在则覆盖)。加入7007和7008两台机器
[root@D bin]# sed 's/7001/7007/g' /opt/redis-5.0.4/redis7001.conf > /opt/redis-5.0.4/redis7007.conf [root@D bin]# sed 's/7001/7008/g' /opt/redis-5.0.4/redis7001.conf > /opt/redis-5.0.4/redis7008.conf [root@D bin]# redis-server /opt/redis-5.0.4/redis7007.conf [root@D bin]# redis-server /opt/redis-5.0.4/redis7008.conf [root@D bin]# ps aux|grep redis集群加入节点(默认为主节点)
#redis-cli --cluster add-node:Redis 集群管理的添加节点命令,用于将新节点加入集群。 #192.168.145.128:7007:新节点的地址(IP: 端口),需提前启动且配置文件中开启集群模式(cluster-enabled yes)。 #192.168.145.128:7001:集群中已存在的任意节点地址,用于让新节点通过它获取整个集群的信息(如其他节点地址、槽位分配等)。 [root@D bin]# redis-cli --cluster add-node 192.168.145.128:7007 192.168.145.128:7001 #连接任意节点(如 7001),查看 7007 是否已加入集群: redis-cli -p 7001 -c cluster nodes | grep 7007集群加入节点(以从节点身份加入),唯一标识码6ba9是7007的
[root@D bin]# redis-cli --cluster add-node 192.168.145.128:7008 192.168.145.128:7001 --cluster-slave --cluster-master-id 6ba982a4367d7e4ab1d888502270e586b15c76167008跟随7007,但此时这个集群虽然成立了,但是并没有分配槽位,所以无法保存数据,必须要分配槽位
从集群中分片出多少个槽位
[root@D bin]# redis-cli --cluster reshard 192.168.145.128:7001参数:
输入需要分配的槽数量(例如从现有主节点各迁移部分,如 1000 个槽)。
输入接收槽位的主节点 ID(7007 的节点 ID,从
cluster nodes中获取)。输入源节点 ID(指定从哪些主节点迁移,或输入
all从所有主节点分配)。输入
yes确认执行。1000个槽位是7001和7002共同 提供的
如果有问题
# 完整命令格式:redis-cli --cluster reshard 主节点IP:端口 --cluster-to 接收节点ID --cluster-slots 迁移槽数 --cluster-from 源节点ID --cluster-yes redis-cli --cluster reshard 192.168.145.128:7001 --cluster-to 47b6738f3bd538c44c4043307280a2ca1804e02c --cluster-slots 1000 --cluster-from all --cluster-yes槽位就分配过来了,注意:槽位分配过来,槽位上的数据也跟随过来了
四、缩容
redis-cli --cluster reshard 192.168.145.128:7001
redis-cli --cluster reshard 192.168.145.128:7001 \ # 1. 集群入口节点(任意存活节点的IP:端口,用于连接集群) --cluster-from 47b6738f3bd538c44c4043307280a2ca1804e02c \ # 2. 源节点的nodeid(要迁出槽位的节点ID,这里是7007的ID) --cluster-to e9e238e8e3a88c8408894ca70dc92c2aa016cca4 \ # 3. 目标节点的nodeid(要接收槽位的节点ID) --cluster-slots 336 \ # 4. 要迁移的槽位数量(本次迁移336个槽位) --cluster-yes # 5. 自动确认迁移(无需交互式输入"yes",直接执行) # redis-cli --cluster reshard 集群任意节点 --cluster-from 7007的nodeid --cluster-to 7001的nodeid --cluster-slots 槽位数 [root@D bin]# redis-cli --cluster reshard 192.168.145.128:7001 --cluster-from 47b6738f3bd538c44c4043307280a2ca1804e02c --cluster-to e9e238e8e3a88c8408894ca70dc92c2aa016cca4 --cluster-slots 336 --cluster-yes从集群中移除7007和7008
# 先停止 [root@D bin]# redis-cli -p 7007 shutdown [root@D bin]# redis-cli -p 7008 shutdown #移除(redis-cli --cluster del-node host:port node_id)连接其他在运行的节点,比如7001,进行删除 [root@D bin]# redis-cli --cluster del-node 192.168.145.128:7001 ae13225645268253446511d53526882fbb19e41d [root@D bin]# redis-cli --cluster del-node 192.168.145.128:7001 47b6738f3bd538c44c4043307280a2ca1804e02c #如果停止节点后不能移除,随便进入一个在线的节点,进入交互模式,forget手动移除(CLUSTER FORGET 节点ID) [root@D bin]# redis-cli -p 7001 -c # 查看集群所有节点信息 127.0.0.1:7001> cluster nodes # 查看槽位分配情况 127.0.0.1:7001> cluster slots移除成功
五、FAQ
1、如果一组主从宕机,集群是否可用 ?
之前说一个节点挂掉(没有从节点),那么集群不可用(默认为yes)
如果修改下面的配置属性,则宕机的1组不可用,而其他组的机器可用
往1组保存数据,失败,导致数据丢失
往2组或3组保存数据,正产运行
## 表示当负责一个槽位的主机下线且没有相应的从机进行故障修复,是否整个集群不可用 ### yes:整个集群不可用, ### no:别的主机可以正常使用。 # cluster-require-full-coverage yes
主机和从机一定部署在不同的机器上,所以同时挂掉的几率很小
万分之一的几率如果同时挂掉,高可用则被打破,所以不存在100%的高可用,只存在99%
2、搭建集群时候--cluster-replicas 1 的意思是什么?
主从比例为1,即1台主1台从,所以集群的主机数量最好是3以上的奇数,而集群全部的机器最好是主机数量的倍数(2倍,3倍都可以)
如果想让1号机带3台从机,让2号机带2台从机,该如何编写命令?
一键构建集群的自动化命令无法达到要求,而且就算是形成集群后,也可以通过原生的命令随意更改。
如果有上述要求,可以使用原生的命令进行集群的搭建。虽然麻烦,但非常灵活,随意搭配
3、主机挂掉,从机会自动升为主机,是哨兵吗 ?
redis-cluster集群,从机升主机确实具备哨兵的功能,但不是哨兵,因为并没有依赖哨兵的节点。
而是本身的晋级,不是哨兵让其晋级(全自动晋级机制)
4、jedis提供的CRC16算法
在 Redis 集群中,
CRC16是哈希算法,用于计算键对应的槽位:槽位 = CRC16(key) % 16384(先对key得到hash值,然后再和16384取余,得到存储槽位)
先对键(key)计算 CRC16 哈希值,得到一个 16 位整数(范围 0~65535); 再将这个哈希值与 16384 取余数(%),最终得到 0~16383 之间的一个整数,即该键对应的槽位(slot)。作用:将键映射到 0~16383 中的某个槽位,而槽位绑定到具体主节点,实现数据分片存储。
特殊:键中
{}内的字符串会被单独计算(如a{user}b仅用user计算),确保关联键同槽。验证:用
cluster keyslot key可直接查看键对应的槽位。
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency> System.out.println( JedisClusterCRC16.getCRC16("hello") );