手写简易redis哨兵demo

  redis常用主从读写分离模式,为了保证redis服务的高可用性,这时候可以通过哨兵服务来监控redis服务的状态,哨兵通过某种方式来确定redis当前Master节点是否可用,若当前节点不可用,则通过一定的规则在从服务器中选取一个节点提升为Master。
  哨兵负责监控所有的服务节点,当节点不可用时,将节点剔除,等节点恢复可用性后,再将节点添加到从节点。
  假设初始时,有server1、2、3三个服务器,server1为master,2、3为slave,然后sentinel负责监控:
  这里写图片描述
  然后sentinel检查到server1挂掉以后,将server1剔除,选取server2作为master:
  这里写图片描述
  后来sentinel发现server1又恢复正常了,就把server1添加到集群中,但是此时server1只能作为slave而不能直接回到老大位置了:
  这里写图片描述
  这就是哨兵的大致原理,具体哨兵如何判断服务是否可用,因为哨兵实际应该是分布式系统,所以可以采用各个哨兵服务进行投票的方式来选取,具体选取那个slave作为master也可以采用一定的算法。
  这里只是实现一个demo,判断服务可用只是简单的ping一下redis,选取slave提升为master也是直接选取一个可用节点进行提权。
  思路如下:维护master节点值,维护从节点列表和崩溃节点列表,然后开启定时任务,对每个节点进行扫描,然后按上述规则进行相应的状态变更。采用的redis连接池为lettuce5
  代码实现如下:
  

public class MyRedisSentinel {
    //节点存储格式  host:port | host:port:password  ---> 127.0.0.1:6379 | 127.0.0.1:6379:12345678
    //主节点
    private volatile String master;
    //从节点
    private Vector<String> slaveRedisServers = new Vector<>();
    //崩溃节点
    private Vector<String> badRedisServers = new Vector<>();

    public MyRedisSentinel(String[] addresses) {
        master = addresses[0];

        slaveRedisServers.addAll(Arrays.asList(addresses).subList(1, addresses.length));

        //初始化从节点,使其slaveof 主节点
        initSlaves();

        //开启定时任务  定时检查主节点 从节点 崩溃节点的状态
        new ScheduledThreadPoolExecutor(1).scheduleAtFixedRate(
                () -> {
                    updateBadServers();
                    updateSlaveServers();
                    updateMaster();
                }, 0, 2000, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取当前可用主节点连接
     *
     * @return StatefulRedisConnection
     */
    public StatefulRedisConnection<String, String> getNowMaster() {
        try {
            return getClient(master.split(":"));
        }catch (Exception e){
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }
        return getNowMaster();
    }

    /**
     * 根据uri获取连接
     *
     * @param params uri(host:port | host:port:password)
     * @return StatefulRedisConnection
     */
    private StatefulRedisConnection<String, String> getClient(String[] params) {
        RedisURI redisURI = RedisURI.Builder.redis(params[0], Integer.parseInt(params[1])).build();

        RedisClient redisClient = RedisClient.create(redisURI);

        StatefulRedisConnection<String, String> statefulRedisConnection = redisClient.connect();
        if (params.length > 2) {
            statefulRedisConnection.sync().auth(params[2]);
        }

        return statefulRedisConnection;
    }

    /**
     * 初始化从节点 使其slaveof 主节点
     * 如果从节点崩溃  将其加入到崩溃节点
     */
    private void initSlaves() {
        String[] masterParams = master.split(":");
        for (String address : slaveRedisServers) {
            String[] params = address.split(":");
            try (StatefulRedisConnection client = getClient(params)) {
                client.sync().slaveof(masterParams[0], Integer.parseInt(masterParams[1]));
            }
        }

    }

    /**
     * 检查主节点状态 如果主节点崩溃  从从节点中选取一个可用节点当主节点
     */
    private void updateMaster() {
        if (!checkNode(master)) {
            while (!checkNode(master)) {
                badRedisServers.add(master);
                master = slaveRedisServers.firstElement();
                slaveRedisServers.remove(master);
            }
            System.out.println("--------切换master为 : " + master);
            try(StatefulRedisConnection connection = getClient(master.split(":"))) {
                connection.sync().slaveofNoOne();
                initSlaves();
            }
        }

        System.out.println("当前主节点:");
        System.out.println(master);
    }

    /**
     * 检查崩溃节点的状态 如果恢复正常  加入到从节点
     */
    private void updateBadServers() {
        Iterator<String> iterable = badRedisServers.iterator();

        String[] masterParams = master.split(":");

        while (iterable.hasNext()) {
            String uri = iterable.next();
            if (checkNode(uri)) {
                try(StatefulRedisConnection<String, String> connection = getClient(uri.split(":"));) {
                    connection .sync().slaveof(masterParams[0], Integer.parseInt(masterParams[1]));
                    slaveRedisServers.add(uri);
                    iterable.remove();
                    System.out.println("------节点 : " + uri + "   恢复正常");
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }

        System.out.println("当前崩溃节点:");
        badRedisServers.forEach(System.out::println);
    }

    /**
     * 检查从节点状态  如果从节点崩溃  剔除并添加崩溃节点
     */
    private void updateSlaveServers() {
        Iterator<String> iterator = slaveRedisServers.iterator();

        while (iterator.hasNext()) {
            String uri = iterator.next();
            if (!checkNode(uri)) {
                badRedisServers.add(uri);
                iterator.remove();
                System.out.println("------节点 : " + uri + "   节点崩溃");
            }
        }

        System.out.println("当前从节点:");
        slaveRedisServers.forEach(System.out::println);
    }

    /**
     * 检查节点是否正常
     *
     * @param uri host:port | host:port:password
     * @return boolean
     */
    private boolean checkNode(String uri) {
        String[] params = uri.split(":");

        boolean rs = false;
        try(StatefulRedisConnection client = getClient(params)) {
            client.sync().ping();
            rs = true;
        } catch (Exception e) {
            System.err.println("节点 " + uri + "  崩溃了");
        }
        return rs;
    }
}

测试:
启动redis主从服务,启动方法参考redis主从服务配置

启动redis服务后,运行以下测试代码:

@Test
    public void testRedisSentinel(){
        MyRedisSentinel myRedisSentinel = new MyRedisSentinel(new String[]{"127.0.0.1:6379:123456",
                "127.0.0.1:6380:123456","127.0.0.1:6381:123456","127.0.0.1:6382:123456"
        });

        int count = 1;
        while (count < 10000){
            StatefulRedisConnection<String, String> connection = myRedisSentinel.getNowMaster();
            connection.sync().set("key" + count, "" + count);
            connection.close();
            count++;
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

然后关闭master节点服务,查看redis状态,
再开启关闭的节点服务,查看redis状态。

猜你喜欢

转载自blog.csdn.net/qq_36666651/article/details/80681770
今日推荐