基于zookeeper实现服务节点HA主备自动切换

文章目录

  • 前言
  • 一、架构图和流程图
  • 二、流程说明
    • 1.服务启动初始化ZK、注册所有服务节点信息-MasterRegister
    • 2.创建、运行服务节点,并管理服务节点-LeaderSelectorZkClient。
    • 3.典型场景-调度服务单体执行-DigitalEmpTask
  • 总结
  • 参考


前言

Spring Boot 主备切换可以采用数据库的主从同步、Zookeeper选举、Redis Sentinel等技术实现高可用。

其中,数据库的主从同步可以通过配置数据库的主从复制来实现。在主节点出现故障时,从节点可以自动接管并成为新的主节点。这种方式实现简单,但需要手动配置主从复制。

Zookeeper选举可以利用Zookeeper的特性来实现,即在Zookeeper上创建一个临时节点作为选举的标志,节点创建成功的服务就是主节点,其他服务则是备节点。在主节点出现故障时,Zookeeper会重新选举一个新的主节点。这种方式实现相对较为复杂,但具有更好的灵活性和可扩展性。

Redis Sentinel是Redis提供的一种高可用性解决方案,可以自动完成主从切换,同时具有自动故障检测和恢复等功能。Redis Sentinel需要在多个节点上运行,并且可以配置多个从节点来实现数据备份和故障转移。当主节点故障时,Redis Sentinel会自动将其中一个从节点升级为新的主节点,保证服务的高可用性。


一、架构图和流程图

在这里插入图片描述
说明:
  主+备模式中有1个主服务节点、多个备服务节点,由主服务节点向外提供服务,备服务节点监听主机状态,一旦主服务节点宕机,备服务节点速接管主服务继续向外提供服务。
  通过Zookeeper(集群)服务注册/发现特性完成主备切换;

  • 1-工作服务器启动时,各服务节点在ZooKeeper的Servers节点下创建临时节点,并把基本信息写入临时节点,完成注册;
  • 2-各服务节点实时监听Servers节点的子节点列表,并尝试创建Master临时节点,谁创建成功谁就是Master,其他的服务节点就作为Slave
  • 3-所有的服务节点关注Master节点的删除事件,通过监听Master节点的删除事件来体现Master服务器是否宕机(创建临时节点的服务器一旦宕机,它所创建的临时节点即会自动删除)
  • 4-.一旦Master服务器宕机,其它服务节点开始新一轮的Master选举,计算新的Master服务器。

二、流程说明

1.服务启动初始化ZK、注册所有服务节点信息-MasterRegister

代码如下(示例):

package com.merak.hyper.automation.zk;
import com.merak.hyper.automation.util.ZkHelper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * @author harry
 * @version 1.0
 * @ClassName: ZkMasterRegister
 * @description: zk主备MasterRegister:启动时初始化ZK、注册所有服务节点信息
 */
@Component
@Order(1)
public class ZkMasterRegister implements CommandLineRunner {
    public static final Logger log = LoggerFactory.getLogger(ZkMasterRegister.class);
    @Value("${zk_master.status}")
    private String status;
    @Value("${zk_master.serviceurl}")
    private String serviceurl;

    @Override
    public void run(String... args) {
        if( ZkHelper.getInstance().zookeeperOpen(status) ) {
            String[] workServerArr = ZkHelper.getInstance().workServerInfo();
            if (!StringUtils.isBlank(workServerArr[0]) && !StringUtils.isBlank(serviceurl)) {
                LeaderSelectorZkClient.getInstance().initZk(serviceurl, workServerArr[0], workServerArr[1], workServerArr[2], ZkHelper.getInstance().zkMasterPath());
                log.info("程序启动,初始化ZK、注册服务节点等信息!");
            } else {
                log.warn("参数未配置zookeeper服务器的地址[sys.zookeeper.serviceurl],请检查!");//ip 和 name
            }
        }
        else{
            log.warn("当前调度服务为单节点服务,未配置zookeeper服务器");
        }
    }
}

2.创建、运行服务节点,并管理服务节点-LeaderSelectorZkClient。

工作服务器节点的基本信息
每个分布式服务节点基本信息包括:serviceIp、servicePort和name, 确保分布式服务节点的唯一性。

package com.merak.hyper.automation.zk;
import java.io.Serializable;

/**
 * 工作服务器节点的基本信息
 */
public class RunningData implements Serializable {

    private static final long serialVersionUID = 4260577459043203630L;

    private String serviceIp;
    private String servicePort;
    private String name;

    public String getServiceIp() {
        return serviceIp;
    }

    public void setServiceIp(String serviceIp) {
        this.serviceIp = serviceIp;
    }

    public String getServicePort() {
        return servicePort;
    }

    public void setServicePort(String servicePort) {
        this.servicePort = servicePort;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "RunningData{" +
                "serviceIp='" + serviceIp + '\'' +
                ", servicePort='" + servicePort + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

创建、运行服务节点,并管理服务节点
代码如下(示例):

/**
     * @description: ZOOKEEPER_SERVER连接和服务节点管理
     * @param: [zookeeper_server, session_connection_timeout, serviceIp, serviceName] 
     * @return: void
     */
    public  void initZk(String zookeeper_server,String serviceIp, String serviceName, String servicePort,String zkMasterName) {
        try {
            log.info("创建服务器节点["+serviceIp+","+serviceName+"]开始!");
            //创建zkClient
            client = ZkConnect.getInstance().connectZkSever(zookeeper_server);
            //创建serverData
            runningData = new RunningData();
            runningData.setServiceIp(serviceIp);
            runningData.setName(serviceName);
            runningData.setServicePort(servicePort);
            //创建服务
            workServer = new WorkServer(runningData,zkMasterName);
            workServer.setZkClient(client);
            workServer.start();
            log.info("创建服务器节点["+serviceIp+","+serviceName+"]结束!");
        } catch (Exception e) {
            log.error("zookeeper_server init error,msg=" + e.getMessage());
        } finally {
            log.info("zookeeper_server finally ...");
        }
    }

服务节点启动、订阅Master节点删除事件、争抢Master权利成为master节点
代码片断如下:

    //初始化工作服务器WorkServer信息
    public WorkServer(RunningData rd, String zkMasterName) {
        this.serverData = rd; // 记录服务器基本信息
        this.MASTER_PATH = zkMasterName;
        this.dataListener = new IZkDataListener() {
            public void handleDataDeleted(String dataPath) {
                //master切换时需要重置 调度云托管任务表 schedule_status = init
                zkResetScheduleStatus.switchResetScheduleStatus();
                log.info(dataPath + "路径已经删除,开始新一轮Master抢占");
                if (masterData != null && masterData.getName().equals(serverData.getName())
                        && masterData.getServiceIp().equals(serverData.getServiceIp())
                        && masterData.getServicePort().equals(serverData.getServicePort())) {
                    takeMaster();//自己就是上一轮的Master服务器,则直接抢
                } else {
                    //否则延迟5秒后再抢。应对网络抖动给上一轮的Master服务器优先抢占master的权利,避免不必要的数据迁移开销
                    delayExecutor.schedule(new Runnable() {
                        public void run() {
                            log.info("服务器开始抢占Master权利");
                            takeMaster();
                        }
                    }, delayTime, TimeUnit.SECONDS);
                }
            }

            public void handleDataChange(String dataPath, Object data) {
                log.info("IZkDataListener - handleDataChange,dataPath=" + dataPath + ",data=" + data.toString());
            }
        };
    }
    
    .....
    
    // 1 启动服务器
    public void start() throws Exception {
        if (running) {
            throw new Exception("server has startup...");
        }
        running = true;
        // 2 订阅Master节点删除事件
        zkClient.subscribeDataChanges(MASTER_PATH, dataListener);
        // 3 争抢Master权利
        takeMaster();
    }

    .....
    
    // 争抢Master
    private void takeMaster() {
        if (!running)
            return;
        try {
            if (!zkClient.exists(MASTER_PATH)) {
                // 尝试创建Master临时节点
                zkClient.create(MASTER_PATH, serverData, CreateMode.EPHEMERAL);
                masterData = serverData;
                log.info("服务器节点[" + serverData.getServiceIp() + "," + serverData.getName() + "," + serverData.getServicePort() + "]争抢Master成功,成为master[isMaster]!");
            } else {
                // 已被其他服务器创建了,读取Master节点信息
                RunningData runningData = zkClient.readData(MASTER_PATH, true);
                log.info("master已被服务器节点[" + runningData.getServiceIp() + "," + runningData.getName() + "," + runningData.getServicePort() + "]占有,当前节点["
                        + serverData.getServiceIp() + "," + serverData.getName() + "," + serverData.getServicePort() + "]只能读取master节点信息!");
                if (runningData == null) {
                    takeMaster(); // 没读到或读取瞬间Master节点宕机可争抢
                } else {
                    masterData = runningData;
                }
            }
        } catch (ZkNodeExistsException e) {
            log.error("当前节点" + serverData.getServiceIp() + "," + serverData.getName() + "," + serverData.getServicePort() + "]创建Master临时节点异常,msg=" + e.getMessage());
        } catch (Exception e) {
            log.error("当前节点" + serverData.getServiceIp() + "," + serverData.getName() + "," + serverData.getServicePort() + "]争抢Master异常,msg=" + e.getMessage());
        }
    }

3.典型场景-调度服务单体执行-DigitalEmpTask

需求:某个时刻只允许Master节点执行调度服务,其它Slave从节点处于闲置、不执行状态。

package com.merak.hyper.automation.quartz.task;
import com.merak.hyper.automation.util.DateUtils;
import com.merak.hyper.automation.util.ZkHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
 * @author harry
 * @version 1.0
 * @ClassName: BizOrderTask
 * @description: 任务队列服务调度
 */
@Component
public class DigitalEmpTask {
    public static final Logger log = LoggerFactory.getLogger(DigitalEmpTask.class);

    @Value("${zk_master.status}")
    private String status;

    @Scheduled(cron = "0/30 * * * * ?")
    protected void digitalEmpTaskScheduler() {
        //1.判断是否开启zookeeper分布式调度模式
        if( ZkHelper.getInstance().zookeeperOpen(status) ) {
            //2.判断当前工作服务节点为Master节点
            if( ZkHelper.getInstance().checkMaster() ) {
                executeCloudTask();
            }
        }
        else{
            //1.未开启zookeeper分布式调度模式,为单节点部署
            executeCloudTask();
        }
    }

    public void executeCloudTask(){
        log.info("任务开始执行,时间:" + DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
        try {

        } catch (Exception e) {
            log.error("调度失败,原因:" + e.getMessage());
        }
    }

}

总结

1.线上1主2从已运行半年,可达到HA业务需求、自动切换能力
2.前端采取Nginx负载、分流,配置多个工作服务节点

参考

浅析如何基于ZooKeeper实现高可用架构
源代码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/303892.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Go语言学习笔记

go变量和常量-初窥门径-CSDNGo技能树 安装检查go版本 在线运行 在线代码运行 (gotribe.cn) 新建一个文件夹 打开终端执行 go mod init learngo。这将创建一个名为go.mod 新建文件 main.go内容 package mainfunc main() {println("Hello world") } package main…

《BackTrader量化交易图解》 1~7 章

文章目录 1. BackTrader 简介1.1 BackTrader 量化软件特点1.2 模块介绍 2. 数据预处理2.1 数据格式2.2 Lines 内部数据格式 3. 策略编程3.1 SQN 指数&策略评估参数3.2 量化金融指标3.3 策略编程模板 4. Buy 买入策略5. Sell 卖出策略5.1 Position 仓位检查5.2 Smart Stakin…

【Vue3】2-4 : 声明式渲染及响应式数据实现原理

本书目录:点击进入 一、声明式渲染 1.1 什么是JS表达式:能够进行赋值的操作 ▶ 正确 ▶ 错误示例 二、示例:2秒后,页面中 message 由 hello world 变成 hi vue ▶ 效果 三、原理:利用ES6的Proxy对象对底层进…

【方法】如何合并7z分卷压缩文件?

压缩7z文件时,设置分卷压缩可以更方便文件的传输、存储和管理,如果后续不需要分卷了,除了可以将分卷文件解压出来,再压缩成一个文件,还可以利用解压缩软件,直接合并分卷文件。 我们常用的7-Zip和WinRAR都可…

【Linux Shell】9. 流程控制

文章目录 【 1. if else 判断 】1.1 if1.2 if else1.3 if elif else1.4 实例 【 2. case 匹配 】【 3. 循环 】3.1 for 循环3.2 while 循环3.3 until 循环3.4 无限循环3.5 跳出循环3.5.1 break 跳出所有循环3.5.2 continue 仅跳出当前循环 【 1. if else 判断 】 1.1 if fi 是…

Hyperledger Fabric 管理链码 peer lifecycle chaincode 指令使用

链上代码(Chaincode)简称链码,包括系统链码和用户链码。系统链码(System Chaincode)指的是 Fabric Peer 中负责系统配置、查询、背书、验证等平台功能的代码逻辑,运行在 Peer 进程内,将在第 14 …

常见锁策略

目录 乐观锁和悲观锁 重量级锁和轻量级锁 自旋锁和挂起等待锁 互斥锁和读写锁 公平锁和非公平锁 可重入锁和不可重入锁 synchronized内部的工作原理 锁消除 锁粗化 CAS 锁策略,即加锁过程(处理冲突时)时的处理方式 乐观锁和悲观锁…

1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程

本篇文章主要讲解1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程,适合sftpgo webadmin和1panel系统用户配置时使用。 作者:任聪聪 rccblogs.com 日期:2024年1月8日 sftpgo是无法直接直接更改容器内部的网站目录的,但…

酷开科技创新玩法寻找OTT大屏营销新增长

随着技术与数据入局OTT领域,程序化投放、数据追踪、人群定位等等功能也正逐步深入到大屏营销,很大程度上推动了OTT行业的快速发展。围绕OTT大屏营销,品牌的机会点早已脱离了传统营销模式,新营销的价值正在被重构。 消费者在接触品…

小游戏选型(一):游戏化设计助力直播间互动和营收

一、社交直播间小游戏火爆 大家好,作为一个技术宅和游戏迷,今天来聊聊近期爆火的社交直播间小游戏的潮流。喜欢冲浪玩社交产品的小伙伴会发现,近期各大平台都推出了直播间社交小游戏,直播间氛围火爆,小游戏玩法简单&a…

市场上最受欢迎的6 大顶级安卓手机解锁软件

Android 智能手机不断发展,每天都有新的特性和功能加入。然而,密码仍然是一个大问题,因为我们的个人数据存储在这些设备上。因此,忘记密码是令人沮丧的。 不过别担心。有很多可用的 Android 解锁软件可以绕过 Android 设备的锁定…

Prompt提示工程上手指南:基础原理及实践(一)

想象一下,你在装饰房间。你可以选择一套标准的家具,这是快捷且方便的方式,但可能无法完全符合你的个人风格或需求。另一方面,你也可以选择定制家具,选择特定的颜色、材料和设计,以确保每件家具都符合你的喜…

CSS 圆形分割按钮动画 带背景、图片

<template><view class="main"><view class="up"> <!-- 主要部分上 --><button class="card1"><image class="imgA" src="../../static/A.png"></image></button><butt…

基于机器视觉的车牌检测-字符识别

一般步骤 字符识别常用的有以下四类&#xff1a; 第一类&#xff1a;结构识别方法。 第二类&#xff1a;统计识别方法。 第三类&#xff1a;BP神经网络方法。 第四类&#xff1a;模板匹配方法。 模板匹配方法是最常用的方法。 主要内容 模板匹配的车牌识别包括以下几点主…

前端面试题集合一

Canvas是什么&#xff1f;怎样写Canvas&#xff1f; Canvas是HTML5的一个元素&#xff0c;它使用JavaScript在网页上绘制图形。Canvas是一个矩形区域。它的每一个像素都可以由HTML5语言来控制。使用Canvas绘制路径、框、圆、字符和添加图像有几种方法。 如果要在我们的HTML文…

Socket closed 异常解决方案:如何解决 JMeter 压测中的问题

问题描述 JMeter 压测时会报 java.net.SocketException: Socket closed java.net.SocketException: Socket closed at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.ne…

将项目同时提交到GitHub和码云Gitee上面,GitHub与Gitee同步

多个远程仓库同时使用 新建GitHub仓库 创建成功 在终端中创建仓库 如果你想在本地机器上创建Git仓库&#xff0c;或者想添加一个文件夹或文件到已经存在的Git仓库中&#xff0c;你应该在终端中创建你的Git仓库。在你可以通过终端来创建一个Git仓库。以下是在终端中创建Git仓…

如何选择猫粮:哪种主食冻干猫粮比较好

许多铲屎官可能认为&#xff0c;只需给猫咪喂食猫粮就足够了。然而&#xff0c;猫咪实际上是肉食动物&#xff0c;对蛋白质的需求非常高。冻干猫粮采用低温真空干燥处理技术&#xff0c;将鲜肉经过预冻、升华、解析三个过程&#xff0c;去除水分的同时保持蛋白质等营养物质不变…

虚拟机Linux硬盘扩容

扩容前(20G)&#xff1a; 扩容后(60G)&#xff1a; 步骤&#xff1a; 1. 点击 虚拟机 -> 设置 -> 硬件 -> 硬盘(SCSI) -> 扩展(E)... -> 输入想要扩容大大小 -> 扩展(E) 2. 运行虚拟机&#xff0c;查看根目录属于那个文件系统&#xff0c;我的是 /dev/sda1…

FPGA UDP协议栈:基于88E1111,支持RGMII、GMII、SGMII三种模式,提供3套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的以太网方案本协议栈的 1G-UDP版本本协议栈的 10G-UDP版本本协议栈的 25G-UDP版本1G 千兆网 TCP-->服务器 方案1G 千兆网 TCP-->客户端 方案10G 万兆网 TCP-->服务器客户端 方案 3、该UDP协议栈性能4、详细设计方案设…