​redis cluster集群:

在 redis3.0之前,redis使用的哨兵架构,它借助 sentinel 工具来监控 master 节点的状态;如果 master 节点异常,则会做主从切换,将一台 slave 作为 master。
哨兵模式的缺点:
(1)当master挂掉的时候,sentinel 会选举出来一个 master,选举的时候是没有办法去访问Redis的,会存在访问瞬断的情况;若是在电商网站大促的时候master给挂掉了,几秒钟损失好多订单数据;
(2)哨兵模式,对外只有master节点可以写,slave节点只能用于读。尽管Redis单节点最多支持10W的QPS,但是在电商大促的时候,写数据的压力全部在master上。
(3)Redis的单节点内存不能设置过大,若数据过大在主从同步将会很慢;在节点启动的时候,时间特别长;(从节点上有主节点的所有数据)

Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。
在这里插入图片描述

Redis集群的优点:

(1)Redis集群有多个master,可以减小访问瞬断问题的影响;
  若集群中有一个master挂了,正好需要向这个master写数据,这个操作需要等待一下;但是向其他master节点写数据是不受影响的。
(2)Redis集群有多个master,可以提供更高的并发量; 
(3)Redis集群可以分片存储,这样就可以存储更多的数据;

集群通信:MEET

最开始时,每个Redis实例自己是一个集群,我们通过cluster meet让各个结点互相“握手”。这也是Redis Cluster目前的一个欠缺之处:缺少结点的自动发现功能。

集群中的主从复制

集群中的每个节点都有1个至N个复制品,其中一个为主节点,其余的为从节点,如果主节点下线了,集群就会把这个主节点的一个从节点设置为新的主节点继续工作,这样集群就不会因为一个主节点的下线而无法正常工作。
注意:
1、如果某一个主节点和他所有的从节点都下线的话,redis集群就会停止工作了。redis集群不保证数据的强一致性,在特定的情况下,redis集群会丢失已经被执行过的写命令。
2、使用异步复制(asynchronous replication)是redis 集群可能会丢失写命令的其中一个原因,有时候由于网络原因,如果网络断开时间太长,redis集群就会启用新的主节点,之前发给主节点的数据就会丢失。

集群是如何判断是否有某个节点挂掉

首先要说的是,每一个节点都存有这个集群所有主节点以及从节点的信息。它们之间通过互相的ping-pong判断是否节点可以连接上。如果有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机了,然后去连接它的备用节点。

整个集群挂掉的条件

1.如果集群任意 master 挂掉,且当前 master 没有 slave或者从节点也挂了。此时集群进入 fail 状态,也可以理解成集群的 slot 映射[0-16383]不完整时进入 fail 状态。
2.如果集群超过半数以上 master 挂掉,无论是否有 slave,集群进入 fail 状态

集群挂掉怎么恢复?

重新启动各个节点发现 集群自动恢复了 本来以为要重新使用create 命令
猜测集群是根据node的主从信息文件自己恢复的 利用心跳检测
节点关系的文件node-7001.conf

*新增一个主节点:

新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上,我会在接下来的实践中实验。大致就会变成这样:
节点A覆盖1365-5460
节点B覆盖6827-10922
节点C覆盖12288-16383
节点D覆盖0-1364,5461-6826,10923-12287
同样删除一个节点也是类似,移动完成后就可以删除这个节点了。

删除一个主节点

如果想 移除 节点 1,需要将节点 1 中的 槽 移到其它节点上,然后将 没有任何槽 的节点 1 从集群中 移除 即可。

由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除 或者 改变 某个节点的 哈希槽的数量 都不会造成 集群不可用 的状态.

当主节点挂掉,是如何选举从节点为主节点的

选新主的过程基于Raft协议选举方式来实现的
1)当从节点发现自己的主节点进行已下线状态时,从节点会广播一条
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息,并且具有投票权的主节点向这个从节点投票
2)如果一个主节点具有投票权,并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点
3)每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持
4)如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于集群N/2+1张支持票时,这个从节点就成为新的主节点
5)如果在一个配置纪元没有从能够收集到足够的支持票数,那么集群进入一个新的配置纪元,并再次进行选主,直到选出新的主节点为止
关闭任意一主,会导致部分写操作失败,是由于从节点不能执行写操作,在Slave升级为Master期间会有少量的失败。

搭建:

1.先下载gcc:gcc是linux系统下面用来将代码编译成一个可执行程序的手段。编译出来的是适用于linux系统的可执行二进制文件。可执行程序其实就是一堆的0101二进制机器码。这些机器码代表什么含义只有机器本身能理解。所以你用gcc编译出来的可执行程序只有在linux系统下面可以运行
yum install gcc

2.下载redis-5.0.2.tar.gz(我是在虚拟机上练习的)
在/usr/local下mkdir 一个redis文件夹 然后下载redis到该目录
命令:wget http://download.redis.io/releases/redis-5.0.2.tar.gz
解压: tar xzf redis-5.0.2.tar.gz

redis搭建集群
至少需要三个master节点。这里抢建三个master节点, 在每个节点抢建一个从节点(slave), 共6个redis实例,三组 一主一从。
3. 在redis-5.0.2 目录中执行:make命令。(如果在执行make命令时,出现:gcc命令未找到,说明没安装gcc)

4 新建文件夹用来存放redis6个实例的redis.conf文件

5 在redis 目录下mkdir rediscluster, 然后在rediscluster下mkdir ./node800 {1,2,3,4,5,6},就创建了node8001,node8002,node8003,node8004,node8005,node8006

6.然后 把redis-5.0.2文件夹下的 redis.conf复制到rediscluster里的node8001里

7.vi redis.conf 修改端口号,port 8001
daemonize yes (开启后台运行,就是说启动后把窗口关掉也能运行)
dir /usr/local/redis/rediscluster/node8001/(指定数据文件存放的位置,要指定不同的目录,不然会丢失数据)
protected-mode no(保护模式)
cluster-enabled yes(开启集群) 把注释去掉
cluster-config-file nodes-8001.conf(把注释去掉,并且改成当前端口号,该文件是redis维护的)
cluster-node-timeout 15000((把注释去掉)
appendonly yes(数据同步改成yes)
requirepass 111111(密码设置成你自己 的)
masterauth 111111(改成你设置的)
把#bind 127.0.0.1注释 这样客户端才可以连(我没有注释然后就连不上)

vi 编辑文件时,查找的命令,在不是编辑状态时(如果是编辑状态那就按下Esc键)直接 /你要查找的关键字,例如/port 6379
在这里插入图片描述

8.把配置好的redis.conf复制到其它节点。
cp redis.conf …/node8002
然后到node8002下编辑redis.conf输入命令:%s/原来的/目标的/全局 例 :%s/8001/8002/g
其它的node8003,node8004,node8005,node8006 是一样的

9.启动redis服务。以下都是在(redis/redis-5.0.2)
cd redis/redis-5.0.2 执行./src/redis-server /usr/local/redis/rediscluster/node8001(因为redis-server在原有redis-5.0.2下,node节点下只有redis.conf) ,启动其它的node节点。
启动完成后用ps -ef |grep redis
在这里插入图片描述

注意:忘记了命令可以用redis-cli --cluster help查看
在redis包下 ./src/redis-cli --cluster help

这样创建的话java连接不上(一开始就是这样创建的)
./src/redis-cli -a 111111 --cluster create 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005 127.0.0.1:8006 --cluster-replicas 1

要这们创建才可以。192.168.19.128是我虚拟机的ip
./src/redis-cli -a 111111 --cluster create 192.168.19.128:8001 192.168.19.128:8002 192.168.19.128:8003 192.168.19.128:8004 192.168.19.128:8005 192.168.19.128:8006 --cluster-replicas 1

–cluster-replicas 1意思是几个从这里1个从节点,其中111111是配置文件设置的密码
在这里插入图片描述

集群搭建完成:

  1. 首先集群使用 CRC16 算法对 key 进行计算得到一个hash值,然后对数字进行取余。
    CRC16 : name = 26384
    26384%16384 = 10000
  2. 查找到包含 10000 插槽的节点,比如是 node2,自动跳转到 node2
    实验:连接 8001 执行命令 ./src/redis-cli -a 111111 -p 8001
    set a a
    因为a的hash%16384=15495在第三个节点。应该在8003端口的节点。 在这里插入图片描述

用cluster创建,会自动分配到8003节点 在连接时加 -c,执行./src/redis-cli -a 111111 -c -p 8001
在这里插入图片描述
连接集群 ./src/redis-cli -c -a 111111 -p 8001
用 cluster slots查看所有的主从还有对应的hash值在的集群节点
A: 0-5460
B: 5461-10922
C: 10923-16383
在这里插入图片描述

这里出现bug: 用我虚拟机的地址创建集群报错
在这里插入图片描述

用127.0.0.1创建成功,但是java连不上
在这里插入图片描述

在这里插入图片描述
原因 就是我一开始没有把bind 127.0.0.1注释掉。然后创建集群时ip写成192.169.19.128报错,写成127.0.0.1就创建成功,所以java连192.169.19.128连不到,把redis实例的进程都先kill掉, 把6个实例的redis.conf里的bind127.0.0.1注释掉,然后把appendonly.aof , dump.rdb,node_8001.conf ,这几个文件都要删除,然后重新运行
./src/redis-cli -a 111111 --cluster create 192.168.19.128:8001 192.168.19.128:8002 192.168.19.128:8003 192.168.19.128:8004 192.168.19.128:8005 192.168.19.128:8006 --cluster-replicas 1

javal连接集群:
pom.xml添加jar

  <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
 public static void main(String[] args) {
        Set<HostAndPort> clusterNodes=new HashSet<>();
        clusterNodes.add(new HostAndPort("192.168.19.128", 8001));
        clusterNodes.add(new HostAndPort("192.168.19.128",8002));
        clusterNodes.add(new HostAndPort("192.168.19.128",8003));
        clusterNodes.add(new HostAndPort("192.168.19.128",8004));
        clusterNodes.add(new HostAndPort("192.168.19.128",8005));
        clusterNodes.add(new HostAndPort("192.168.19.128",8006)); 
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setTestOnBorrow(true);        
        JedisCluster jedisCluster=new JedisCluster(clusterNodes,6000,5000,200,"111111",jedisPoolConfig);
        jedisCluster.set("a","a");
        System.out.println(jedisCluster.get("a"));
        jedisCluster.close();
    }

在这里插入图片描述

搭建成功。连接成功
查询集群中所有的key 命令:
./src/redis-cli -c -a 111111 --cluster call 127.0.0.1:8001 keys *

​java连接,以javaconfig的方式:
pom.xml:

  <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

yml:


spring:
  redis:
    cluster:
      nodes: 192.168.19.128:8001,192.168.19.128:8002,192.168.19.128:8003,192.168.19.128:8004,192.168.19.128:8005,192.168.19.128:8006
    password: 111111
    jedis:
      pool:
        min-idle: 0
        max-active: 8
        max-wait: -1
        max-idle: 8

config类:

@Configuration
@Component
public class redisConfig {
    @Value("${spring.redis.cluster.nodes}")
    private String nodes;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWait;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    //redis配置
    @Bean
    public JedisPoolConfig poolConfig(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWait);
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxIdle(maxIdle);
        return jedisPoolConfig;
    }
    //redis集群配置,6个ip以,分割,然后再以:分割
    public RedisClusterConfiguration redisClusterConfiguration(){
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
        String[] cNodes = nodes.split(",");
        Set<RedisNode> hp = new HashSet<>();
        for (String node : cNodes) {
            String[] split = node.split(":");
            hp.add(new RedisNode(split[0].trim(),Integer.valueOf(split[1])));
        }
        redisClusterConfiguration.setPassword(password);
        redisClusterConfiguration.setClusterNodes(hp);
        return redisClusterConfiguration;
    }
    //创建redis连接工厂
    public JedisConnectionFactory jedisConnectionFactory() {
        //集群模式
        JedisConnectionFactory  factory = new JedisConnectionFactory(redisClusterConfiguration(),poolConfig());
        return factory;
    }
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initDomainRedisTemplate(redisTemplate);
        return redisTemplate;
    }
    //初始化redis序列化
    private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        //如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //这个地方有一个问题,这种序列化器会将value序列化成对象存储进redis中,如果
        //你想取出value,然后进行自增的话,这种序列化器是不可以的,因为对象不能自增;
        //需要改成StringRedisSerializer序列化器。
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
    }

controller类:

@Controller
public class redisController {

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/test")
    public String test(){
        redisTemplate.opsForValue().set("name", "lizhao");
        String name = (String) redisTemplate.opsForValue().get("name");
        System.out.println(name);
        return name;
    }
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐