Cluster架构

Redis哨兵与Cluster集群模式对比

1. 哨兵模式

Redis3.0之前一般是通过哨兵工具来监测master节点的状态,若master节点宕机,则哨兵集群会进行主从切换,从其他slave节点中选举出新的master节点。
相较于高可用集群模式而言,哨兵模式有如下不足:

  • 哨兵模式的配置相对较复杂,且性能与高可用方面较一般
  • 主从切换的过程中会出现访问瞬断的问题
  • 哨兵模式中只有一个master节点对外提供读写服务,不能支持很高的并发;并且单节点的内存不宜过大,否则持久化文件过大影响主从同步的效率
2. 高可用Cluster集群模式

Redis高可用Cluster模式是由多个主从节点群组成的分布式服务器群,具有复制,高可用和分片特性;
相较于哨兵模式,其配置简单并且在性能和高可用方面更有一筹,不需要哨兵也能实现主从切换、故障转移功能,并且每个节点支持水平扩展(官方最大扩展1000个节点)。

Redis Cluster工作原理

Redis Cluster将数据划分为16384个槽位,每个节点负责其中一部分槽位,这部分槽位信息保存在对应负责的节点中。
当Client端连接集群时会得到一份集群的槽位配置信息缓存在客户端本地,在client查找某个key时可以根据缓存的槽位配置直接定位到对应的集群目标节点,另外当client端和server端的槽位信息不一致时,通过槽位纠正机制实现槽位的校验调整。

  • 槽位定位算法: HASH_SLOT = CRC16(key) % 16384
  • 跳转重定位:当Client端向一个“错误”节点(指令key所在的哈希slot不是这个节点时)发出指令,这个节点发现指令key对应的槽位不属于自己负责的,则会向client端发送一个携带目标节点的跳转指令告诉client端去这个目标节点获取数据,client端接收到指令后会跳转到指定的目标节点并更新本地缓存的槽位信息。
  • 集群节点间通信:Redis Cluster集群节点间采用gossip协议通信
  • 网络抖动:Redis Cluster提供cluster-­node-­timeout配置,当某个节点持续cluster-­node-­timeout时间失联会被认为节点故障则会进行主从切换
  • 批量操作的支持:redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XX},这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一个slot中。如:mset {user}:1:name Jeffrey {user}:1:age 18
  • 脑裂问题:redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点,只可能会导致大量数据丢失。Redis Cluster提供min‐replicas‐to‐write 1配置最大程度上规避脑裂问题(该配置表示写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数)
Redis Cluster集群选举原理

当slave发现自己的master变为FAIL状态时,slave期望成为新的master会尝试进行Failover,由于挂掉的master
可能会有多个slave,从而存在多个slave竞争成为master节点的过程。 其过程如下:

  1. slave发现自己的master变为FAIL
  2. 将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
  3. 其他节点收到该信息,只有master响应,master会判断请求合法性,并发送FAILOVER_AUTH_ACK,对每一个
    epoch只发送一次ack
  4. 尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
  5. slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两
    个,当其中一个挂了,只剩一个主节点是不能选举成功的)
  6. slave广播Pong消息通知其他集群节点

从节点并不是一发现主节点FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
•延迟计算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。该方式理论上持有最新数据的slave将会先发起选举

搭建部署

前提:已编译安装完毕Redis(安装目录为 /usr/local/redis-5.0.3)

Redis Cluster集群至少要有3个master节点,本次搭建部署三个主从小集群即三个一主一从6个节点作为演示;
一般情况下,这三个主从小集群分别部署到三个不同的服务器节点,每台机器一主一从,但由于资源有限,本次演示的6个Redis节点均部署到同一台虚拟机。

部署步骤:

  1. 在目录/usr/local/下新建目录 redis-cluster并进入该目录,在该目录下新建三个子目录作为三个不同的服务器节点node1, node2, node3,然后分别在这三个子目录下新建两个子目录作为主从节点的配置目录,其中node1下新建子目录8001, 8004,node2下新建子目录8002, 8005,node3下新建子目录8003, 8006,再创建6个节点数据存放目录用于保存集群节点配置文件以及持久化数据文件

mkdir -p /usr/local/redis-cluster/node1/8001
mkdir -p /usr/local/redis-cluster/node1/8004
mkdir -p /usr/local/redis-cluster/node2/8002
mkdir -p /usr/local/redis-cluster/node2/8005
mkdir -p /usr/local/redis-cluster/node3/8003
mkdir -p /usr/local/redis-cluster/node3/8006
mkdir -p /usr/local/redis-cluster/8001
mkdir -p /usr/local/redis-cluster/8002
mkdir -p /usr/local/redis-cluster/8003
mkdir -p /usr/local/redis-cluster/8004
mkdir -p /usr/local/redis-cluster/8005
mkdir -p /usr/local/redis-cluster/8006
cd /usr/local/redis-cluster && ll

  1. 将Redis安装目录下的redis.conf配置文件拷贝到node1中的8001目录下

cp /usr/local/redis-5.0.3/redis.conf ./node1/8001/redis.conf

  1. 分别对8001~8006下的redis.conf配置文件进行修改(下面以node1下的8001节点配置为例,其他节点配置类似,只需替换对应端口号即可)
  • 修改8001节点配置

vi node1/8001/redis.conf

#设置端口
port 8001
#设置后台启动
daemonize yes
#将PID进程号写入pidfile文件
pidfile “/var/run/redis_8001.pid”
#指定数据存放目录
dir “/usr/local/redis-cluster/8001”
#启动集群模式
cluster-enabled yes
#设置集群节点配置文件
cluster-config-file nodes-8001.conf
#设置集群节点超时时间(ms)
cluster-node-timeout 10000
#注释bind
#bind 127.0.0.1
#关闭保护模式
protected-mode no
#开启AOF
appendonly yes
#密码设置如下
#设置redis访问密码
requirepass admin
#设置集群节点间访问密码
masterauth admin(与访问密码一致即可)

:wq

  • 修改node1下的8004节点配置(将修改后的8001下的redis.conf复制到8004下,使用批量替换进行修改 :%s/源字符串/目标字符串/g)

cp node1/8001/redis.conf node1/8004/redis.conf
vi node1/8004/redis.conf
:%s/8001/8004/g
:wq

  • 同上,依次修改node2下的8002,8005和node3下的8003,8006目录下的redis.conf
  1. 分别启动8001~8006节点并查看是否启动成功

/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node1/8001/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node1/8004/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node2/8002/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node2/8005/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node3/8003/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node3/8006/redis.conf

ps -ef|grep redis

  1. 创建集群:通过redis-cli创建(redis5之前是通过ruby脚本的redis‐trib.rb实现)
    注意:由于执行下面命令需要确认三台机器之间的redis实例要能相互访问,如果这三个主从小集群是分别部署在三个不同服务器节点上,则可以关闭所有机器防火墙或打开所有机器redis服务端口和集群节点gossip通信端口16379(默认是在redis端口号上加10000)
    命令 --cluster-replicas 1 表示为集群中每个主节点创建一个从节点
    说明:本次演示是在同一台虚拟机上部署,采取直接关闭防火墙的方式(临时关闭 systemctl stop firewalld,开机禁止启动 systemctl disable firewalld)

/usr/local/redis-5.0.3/src/redis-cli -a admin --cluster create --cluster-replicas 1 192.168.126.130:8001 192.168.126.130:8002 192.168.126.130:8003 192.168.126.130:8004 192.168.126.130:8005 192.168.126.130:8006

  1. 验证集群:redis-cli连接任意一个集群节点即可

#连接集群节点8001
/usr/local/redis-5.0.3/src/redis-cli -a admin -c -h 192.168.126.130 -p 8001
#查看集群信息
cluster info

#查看集群节点列表
cluster nodes

数据操作验证:

SpringBoot整合Redis Cluster集群

引入依赖

<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>

修改application.yml

server:port: 8080#redis cluster config
spring:application:name: redis-demoredis:database: 0timeout: 3000password: admincluster:nodes: 192.168.126.130:8001,192.168.126.130:8002,192.168.126.130:8003,192.168.126.130:8004,192.168.126.130:8005,192.168.126.130:8006lettuce:pool:max-idle: 50min-idle: 10max-active: 100max-wait: 1000

启动类App

package com.itjeffrey.redis.test;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableScheduling;/*** @From: Jeffrey* @Date: 2022/11/12*/
@EnableScheduling
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}
}

RedisConfig

package com.itjeffrey.redis.test.service;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;
import java.util.HashMap;
import java.util.Map;/*** Redis配置* @From: Jeffrey*/
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {//string serializerprivate RedisSerializer<String> redisSerializer = new StringRedisSerializer();//object serializerprivate Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);public RedisConfig(){setObjectMapper(jackson2JsonRedisSerializer);}//LettuceConnectionFactory实例化过程中会自动从spring.cache.redis中读取配置信息@Autowiredprivate LettuceConnectionFactory lettuceConnectionFactory;/*** config redisTemplate---manually add caches* @return RedisTemplate*/@Beanpublic RedisTemplate<String, Object> redisTemplate() {RedisTemplate<String, Object> template = new RedisTemplate<>();// 设置ObjectMapper(解决查询缓存转换异常问题)setObjectMapper(jackson2JsonRedisSerializer);// 设置连接工厂template.setConnectionFactory(lettuceConnectionFactory);// 配置key, value, hashValue序列化template.setKeySerializer(redisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}/*** 解决查询缓存转换异常问题*/private void setObjectMapper(Jackson2JsonRedisSerializer jackson2JsonRedisSerializer){ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);}
}

RedisUtil

package com.itjeffrey.redis.test.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;/*** Redis操作工具类* @From: Jeffrey*/
@Component
public class RedisUtil {@Autowiredprivate RedisTemplate redisTemplate;/*** 写入缓存** @param key* @param value* @return*/public boolean set(final String key, Object value) {boolean result = false;try {ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 写入缓存设置时效时间** @param key* @param value* @return*/public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) {boolean result = false;try {ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);redisTemplate.expire(key, expireTime, timeUnit);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 批量删除对应的value** @param keys*/public void remove(final String... keys) {for (String key : keys) {remove(key);}}/*** 批量删除key** @param pattern*/public void removePattern(final String pattern) {Set<Serializable> keys = redisTemplate.keys(pattern);if (keys.size() > 0) {redisTemplate.delete(keys);}}/*** 删除对应的value** @param key*/public void remove(final String key) {if (exists(key)) {redisTemplate.delete(key);}}/*** 判断缓存中是否有对应的value** @param key* @return*/public boolean exists(final String key) {return redisTemplate.hasKey(key);}/*** 读取缓存** @param key* @return*/public Object get(final String key) {Object result = null;ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();result = operations.get(key);return result;}/*** 哈希 添加** @param key* @param hashKey* @param value*/public void hmSet(String key, Object hashKey, Object value) {HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();hash.put(key, hashKey, value);}/*** 哈希获取数据** @param key* @param hashKey* @return*/public Object hmGet(String key, Object hashKey) {HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();return hash.get(key, hashKey);}/*** 列表添加** @param k* @param v*/public void lPush(String k, Object v) {ListOperations<String, Object> list = redisTemplate.opsForList();list.rightPush(k, v);}/*** 列表获取** @param k* @param l* @param l1* @return*/public List<Object> lRange(String k, long l, long l1) {ListOperations<String, Object> list = redisTemplate.opsForList();return list.range(k, l, l1);}/*** 集合添加** @param key* @param value*/public void add(String key, Object value) {SetOperations<String, Object> set = redisTemplate.opsForSet();set.add(key, value);}/*** 集合获取** @param key* @return*/public Set<Object> setMembers(String key) {SetOperations<String, Object> set = redisTemplate.opsForSet();return set.members(key);}/*** 有序集合添加** @param key* @param value* @param scoure*/public void zAdd(String key, Object value, double scoure) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();zset.add(key, value, scoure);}/*** 有序集合获取** @param key* @param scoure* @param scoure1* @return*/public Set<Object> rangeByScore(String key, double scoure, double scoure1) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();return zset.rangeByScore(key, scoure, scoure1);}
}

测试

package com.itjeffrey.redis.test.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;/*** @From: Jeffrey* @Date: 2022/11/12*/
@Service
public class TestService {private int count;@Autowiredprivate RedisUtil redisUtil;@Scheduled(cron = "0 0/2 * * * ?")public void test(){String key = "user" + count;redisUtil.set(key, "jeffrey-" + count);System.out.println("set redis cache, key:" + key + " - value:" + redisUtil.get(key));count++;}
}

结果

控制台打印:

client连接8001节点并查看数据

分布式Redis集群--Cluster架构相关推荐

  1. 分布式和集群的架构套路总结

    本文成于2020年3月14日 参考:原文 文章目录 分布式和集群名词解释 使用分布式的心路历程 常见的分布式集群架构 1. 纯负载均衡形式(集群方向) 2. 领导选举型(分布式方向) 3. 区块链型( ...

  2. Redis集群Cluster部署

    这篇Redis 集群部署笔记参考的书籍资料是: <Redis入门指南>第二版,作者:李子骅 第8章 <Redis开发与运维> 作者:付磊 第10章 以下是学习笔记,记录了Red ...

  3. Redis集群技术架构

    原文链接:http://blog.csdn.net/xyang81/article/details/51898465 1.无中心化 Redis Cluster采用无中心架构,每个节点都保存数据和整个集 ...

  4. Redis集群cluster环境(快速搭建过程10分钟)

    安装环境CentOS Linux release 7.5.1804 (Core) 如果服务器没有连接网络,请按步骤自行百度其离线方式 话不多说,开整!!! 一.安装redis #下载至/home/in ...

  5. 【Redis】集群(cluster)

    文章目录 Redis集群(cluster) 定义 官网 作用 集群算法(分片->槽位) 分片(每台Redis实例) 槽位 slot槽位映射 哈希取余分区 一致性哈希算法分区 哈希槽分区 搭建三主 ...

  6. Redis 集群介绍

    开源键值对存储数据库Redis在4月1日发布了3.0.0版.主要新特性包括:Redis Cluster,Redis子集的分布式实现:新的"嵌套字符串"对象编码减少缓存遗漏,大幅提高 ...

  7. Redis集群~StackExchange.redis连接Twemproxy代理服务器

    本文是Redis集群系列的一篇文章,主要介绍使用StackExchange.Redis进行Twemproxy(文中简称TW)代理服务的连接过程,事务上,对于TW来说,我们需要理解一下它的物理架构,它类 ...

  8. redis 集群 实操 (史上最全、5w字长文)

    文章很长,建议收藏起来慢慢读! 总目录 博客园版 为大家准备了更多的好文章!!!! 推荐:尼恩Java面试宝典(持续更新 + 史上最全 + 面试必备)具体详情,请点击此链接 尼恩Java面试宝典,34 ...

  9. Redis集群功能概述

    在单机Redis中介绍过Redis的复制特性以及Redis Sentinel和twemproxy,其中: 复制:可以创建指定服务器的复制品,这些复制品可以用户扩展系统处理读请求的能力: Redis S ...

最新文章

  1. 长文综述:从大数据中寻找复杂系统的核心变量
  2. 圣诞夜,让你的代码都变成圣诞树吧!
  3. qt中显示文件夹下的目录及文件的过滤
  4. hdu5444(2015长春网络赛H题)
  5. linux 移除python_第16 p,PYthon中的用户交互,Python GUI编程
  6. python初始化方法对应的变量是全局变量嘛_在Python中初始化全局变量的正确方法...
  7. spring cloud 总结
  8. 为什么我们总是忍不住要刷微信?
  9. jzoj1758-过河【dp】
  10. R7-6 A-B (20 分)
  11. IS-IS详解(五)——IS-IS 三次握手与两次握手
  12. 《程序员健康指南》读书笔记
  13. git 本地被覆盖如何恢复
  14. 不值一提的计算机基础教程-0-前言
  15. tpc服务器做系统,tpc-c对应服务器配置
  16. maven命令错误:-Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HOME
  17. FPGA产生相位编码基带信号
  18. echart简单应用
  19. 【重磅新课上线】一建通信与广电实务(老杨)
  20. 最详细的Hadoop+Hbase+Hive完全分布式环境搭建教程(二)

热门文章

  1. 彻底搞懂Java普通类以及集合List浅克隆和深克隆
  2. 基于JSP的房产中介系统的设计与实现免费源代码+LW
  3. matlab蒙特卡洛方法求积分,matlab-蒙特卡洛法估计积分值
  4. 嵌入式Linux开发-网络设备驱动
  5. c语言程序中函数类型,C语言中的函数分别有什么?
  6. 从零开始制作一款游戏
  7. 导入带隐藏列的Excel发生错位
  8. 紫外线UV杀菌灯的工作原理
  9. spark聚类算法的聚类效果评估指标“轮廓系数”细节
  10. dilation conv 和 deconv