5、Redis 高可用集群模式
一、Redis高可用集群搭建
1.1、在第一台机器的/usr/local下创建文件夹redis‐cluster,然后在其下面分别创建2个文件夾如下
mkdir ‐p /usr/local/redis‐cluster mkdir 8001 8004
1.2、把之前的redis.conf配置文件copy到8001下,修改如下内容:
daemonize yes port 8001 # 分别对每个机器的端口号进行设置 pidfile /var/run/redis_8001.pid # 把pid进程号写入pidfile配置的文件 dir /usr/local/redis‐cluster/8001/ # 指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据 cluster‐enabled yes # 启动集群模式 cluster‐config‐file nodes‐8001.conf # 集群节点信息文件,这里800x最好和端口号对应上 cluster‐node‐timeout 10000 # 设置超时时间 protected‐mode no # 关闭保护模式 appendonly yes # 开启aof # 如果要设置密码需要增加如下配置 requirepass zhuge # 设置redis访问密码 masterauth zhuge # 设置集群节点间访问密码,跟上面一致
1.3、把修改后的配置文件,copy到8004,修改上一步中第2、3、4、6行的端口号为8004,另外两台机器也需要做上面几步操作,第二台机器用8002和8005,第三台机器用8003和8006
/usr/local/redis‐5.0.3/src/redis‐server /usr/local/redis‐cluster/8001/redis.conf /usr/local/redis‐5.0.3/src/redis‐server /usr/local/redis‐cluster/8002/redis.conf /usr/local/redis‐5.0.3/src/redis‐server /usr/local/redis‐cluster/8003/redis.conf /usr/local/redis‐5.0.3/src/redis‐server /usr/local/redis‐cluster/8004/redis.conf /usr/local/redis‐5.0.3/src/redis‐server /usr/local/redis‐cluster/8005/redis.conf /usr/local/redis‐5.0.3/src/redis‐server /usr/local/redis‐cluster/8006/redis.conf ps ‐ef | grep redis # 查看是否启动成功
1.4、分别启动6个redis实例,然后检查是否启动成功
1.5、用redis‐cli创建整个redis集群
/usr/local/redis‐5.0.3/src/redis‐cli ‐a zhuge ‐‐cluster create ‐‐cluster‐replicas 1 192.168.0.61:8001 192.168.0.62:8002 192.168.0.63:8003 192.168.0.61:8004 192.168.0.62:8005 192.168.0.63:8006
systemctl stop firewalld # 临时关闭防火墙 systemctl disable firewalld # 禁止开机启动
1.7、验证集群
/usr/local/redis‐5.0.3/src/redis‐cli ‐a zhuge ‐c ‐h 192.168.0.61 ‐p 8001 # 连接任意一个客户端即可 cluster info # 查看集群信息 cluster nodes # 查看节点列表 # 关闭集群则需要逐个进行关闭,使用命令 /usr/local/redis‐5.0.3/src/redis‐cli ‐a zhuge ‐c ‐h 192.168.0.60 ‐p 8001 shutdown
1.8、增加一组主从一主(8007)一从(8008)
mkdir 8007 8008 cd 8001 cp redis.conf /usr/local/redis-cluster/8007/ cp redis.conf /usr/local/redis-cluster/8008/ # 修改8007文件夹下的redis.conf配置文件 vim /usr/local/redis-cluster/8007/redis.conf # 修改如下内容: port:8007 dir /usr/local/redis-cluster/8007/ cluster-config-file nodes-8007.conf # 修改8008文件夹下的redis.conf配置文件 vim /usr/local/redis-cluster/8008/redis.conf 修改内容如下: port:8008 dir /usr/local/redis-cluster/8008/ cluster-config-file nodes-8008.conf # 启动8007和8008俩个服务并查看服务状态 /usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/8007/redis.conf /usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/8008/redis.conf ps -el | grep redis
配置8007为集群主节点,使用add-node命令新增一个主节点8007(master),前面的ip:port为新增节点,后面的ip:port为已知存在节点,看到日志最后有"[OK] New node added correctly"提示代表新节点加入成功
/usr/local/redis-5.0.3/src/redis-cli -a zhuge --cluster add-node 192.168.0.61:8007 192.168.0.61:8001
查看集群状态
usr/local/redis-5.0.3/src/redis-cli -a zhuge -c -h 192.168.0.61 -p 8001 192.168.0.61:8001> cluster nodes
/usr/local/redis-5.0.3/src/redis-cli -a zhuge --cluster reshard 192.168.0.61:8001
How many slots do you want to move (from 1 to 16384)? 600 # ps:需要多少个槽移动到新的节点上,自己设置,比如600个hash槽 What is the receiving node ID? 2728a594a0498e98e4b83a537e19f9a0a3790f38 # ps:把这600个hash槽移动到哪个节点上去,需要指定节点id Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node 1:all # ps:输入all为从所有主节点(8001,8002,8003)中分别抽取相应的槽数指定到新节点中,抽取的总槽数为600个 ... ... Do you want to proceed with the proposed reshard plan (yes/no)? yes # ps:输入yes确认开始执行分片任务
查看下最新的集群状态
/usr/local/redis-5.0.3/src/redis-cli -a zhuge -c -h 192.168.0.61 -p 8001 192.168.0.61:8001> cluster nodes
配置8008为8007的从节点
/usr/local/redis-5.0.3/src/redis-cli -a zhuge -c -h 192.168.0.61 -p 8008 192.168.0.61:8008> cluster replicate 2728a594a0498e98e4b83a537e19f9a0a3790f38 #后面这串id为8007的节点id
1.8、删除主从一主(8007)一从(8008)
/usr/local/redis-5.0.3/src/redis-cli -a zhuge --cluster del-node 192.168.0.61:8008 a1cfe35722d151cf70585cee21275565393c0956
再次查看集群状态,如下图所示,8008这个slave节点已经移除,并且该节点的redis服务也已被停止
最后,我们尝试删除之前加入的主节点8007,这个步骤相对比较麻烦一些,因为主节点的里面是有分配了hash槽的,所以我们这里必须先把8007里的hash槽放入到其他的可用主节点中去,然后再进行移除节点操作,不然会出现数据丢失问题(目前只能把master的数据迁移到一个节点上,暂时做不了平均分配功能),执行命令如下:
/usr/local/redis-5.0.3/src/redis-cli -a zhuge --cluster reshard 192.168.0.61:8007
How many slots do you want to move (from 1 to 16384)? 600 What is the receiving node ID? dfca1388f124dec92f394a7cc85cf98cfa02f86f # ps:这里是需要把数据移动到哪?8001的主节点id Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node 1:2728a594a0498e98e4b83a537e19f9a0a3790f38 # ps:这里是需要数据源,也就是我们的8007节点id Source node 2:done # ps:这里直接输入done 开始生成迁移计划 ... ... Do you want to proceed with the proposed reshard plan (yes/no)? Yes
至此,我们已经成功的把8007主节点的数据迁移到8001上去了,我们可以看一下现在的集群状态如下图,你会发现8007下面已经没有任何hash槽了,证明迁移成功!
最后我们直接使用del-node命令删除8007主节点即可
/usr/local/redis-5.0.3/src/redis-cli -a zhuge --cluster del-node 192.168.0.61:8007 2728a594a0498e98e4b83a537e19f9a0a3790f38
查看集群状态,一切还原为最初始状态
二、Java操作集群
2.1、jedis
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
public class JedisClusterTest {
public static void main(String[] args) throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.0.61", 8001));
jedisClusterNode.add(new HostAndPort("192.168.0.62", 8002));
jedisClusterNode.add(new HostAndPort("192.168.0.63", 8003));
jedisClusterNode.add(new HostAndPort("192.168.0.61", 8004));
jedisClusterNode.add(new HostAndPort("192.168.0.62", 8005));
jedisClusterNode.add(new HostAndPort("192.168.0.63", 8006));
JedisCluster jedisCluster = null;
try {
//connectionTimeout:指的是连接一个url的连接等待时间
//soTimeout:指的是连接上一个url,获取response的返回等待时间
jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "zhuge", config);
System.out.println(jedisCluster.set("cluster", "zhuge"));
System.out.println(jedisCluster.get("cluster"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedisCluster != null)
jedisCluster.close();
}
}
}2.2、Spring Boot
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
springboot项目核心配置
server: port: 8080 spring: redis: database: 0 timeout: 3000 password: zhuge cluster: #集群模式 nodes: 192.168.0.61:8001,192.168.0.62:8002,192.168.0.63:8003,192.168.0.61:8004,192.168.0.62:8005,192.168.0.63:8006 lettuce: pool: max-idle: 50 min-idle: 10 max-active: 100 max-wait: 1000
@RestController
public class IndexController {
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/test_cluster")
public void testCluster() throws InterruptedException {
stringRedisTemplate.opsForValue().set("zhuge", "666");
System.out.println(stringRedisTemplate.opsForValue().get("zhuge"));
}
}三、集群原理分析
3.1、槽位定位算法
3.2、跳转重定位
3.3、Redis集群节点间的通信机制
维护集群的元数据(集群节点信息,主从角色,节点数量,各节点共享的数据等)有两种方式:集中式和gossip
3.3.1、集中式
3.3.2、gossip
gossip通信的10000端口
四、集群选举原理分析
当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
slave发现自己的master变为FAIL
将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息
其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)
slave广播Pong消息通知其他集群节点。
延迟计算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。
4.1、哨兵leader选举流程
五、集群脑裂数据丢失问题
min-replicas-to-write 1 # 写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数
六、集群是否完整才能对外提供服务
七、Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
八、Redis集群对批量操作命令的支持
对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XX},这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去,示例如下:
mset {user1}:1:name zhuge {user1}:1:age 18假设name和age计算的hash slot值不一样,但是这条命令在集群下执行,redis只会用大括号里的 user1 做hash slot计算,所以算出来的slot值肯定相同,最后都能落在同一slot。
版权声明
非特殊说明,本文由Zender原创或收集发布,欢迎转载。
ZENDER











发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。