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

news2025/1/9 1:18:50

文章目录

  • 前言
  • 一、架构图和流程图
  • 二、流程说明
    • 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实现高可用架构
源代码下载

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mac电脑php命令如何设置默认的php版本

前提条件:如果mac电脑还没安装多个PHP版本,可以先看这篇安装一下 mac电脑运行多个php版本_mac 同时运行两个php-CSDN博客 第一部分:简单总结 #先解除现在默认的php版本 brew unlink php7.4#再设置的想要设置的php版本 brew link php8.1第二部…

python 多线程 守护线程

daemon线程:守护线程,优先级别最低,一般为其它线程提供服务。通常,daemon线程体是一个无限循环。如果所有的非daemon线程(主线程以及子线程)都结束了,daemon线程自动就会终止。t.daemon 属性,设…

一、MOJO环境部署和安装

以Ubuntu系统为例。 安装mojo-CLI curl https://get.modular.com | MODULAR_AUTHmut_fe303dc5ca504bc4867a1db20d897fd8 sh - 安装mojo SDK modular auth mojo modular auth install mojo 查看mojo版本号 mojo --version 输入mojo指令,进入交互编程窗口

【UE Niagara学习笔记】02 - 制作燃烧的火焰

目录 效果 步骤 一、添加资产 二、制作材质 三、制作粒子 3.1 循环播放 3.2 粒子生成的数量 3.3 粒子的生命周期和初始大小 3.4 火焰高度 3.5 火焰范围 3.6 火焰颜色 效果 步骤 一、添加资产 1. 在虚幻商城中搜索“M5 VFX Vol2. Fire and Flames(Niagara)”…

单片机原理及应用:中断系统结构与控制寄存器

大家好啊,这几天因为考试断更了一段时间,现在放假了也可以恢复正常的更新速度了。今天我们来认识一下单片机的中断系统,这里可以说是我们学习单片机以来第一个核心功能,我们会分几期内容来深入了解中断系统的作用原理和应用方式。…

多模态推荐系统综述:二、特征交互 Fusion

二、Fusion 融合不同的多模态信息,与bridge相比,融合更关注项目之间的多模态内部关系。 它可以灵活地融合不同权重和焦点的多模态信息。 注意机制是应用最为广泛的特征融合。 2.1 粗粒度注意力。 一些模型应用注意力机制在粗粒度级别融合来自多种模式…

Rustdesk本地配置文件存在什么地方?

环境: rustdesk1.1.9 Win10 专业版 问题描述: Rustdesk本地配置文件存在什么地方? 解决方案: RustDesk 是一款功能齐全的远程桌面应用。 支持 Windows、macOS、Linux、iOS、Android、Web 等多个平台。 支持 VP8 / VP9 / AV1 …

e2studio开发三轴加速度计LIS2DW12(1)----轮询获取加速度数据

e2studio开发三轴加速度计LIS2DW12.1--轮询获取加速度数据 概述视频教学样品申请源码下载通信模式管脚定义IIC通信模式速率新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原…

Android studio调试

Android Studio连接手机详细教程(包含遇到的问题集)_android studio 连接手机-CSDN博客 可以创建虚拟机或直连真机或直连模拟器。 无法打开本地终端 Android studio Failed to start [powershell.exe] 利用Android studio的adb命令删除app应用 - 简书 利用ADB工具免root停用A…

MySQL之子查询、连接查询(内外)以及分页查询

一、案例(接上一篇文章) 09)查询学过「张三」老师授课的同学的信息 -- 一共有两种方式 -- 第一种方式: SELECT s.*,c.cname,t.tname,sc.score FROMt_mysql_teacher t,t_mysql_course c,t_mysql_student s,t_mysql_score sc WHERE…

BabylonJS 6.0文档 Deep Dive 摄像机(二):摄像机碰撞

摄像机、网格碰撞和重力 你玩过第一人称射击游戏(FPS)吗?在本教程中,我们将模拟FPS的摄影机移动:摄影机位于地板上,与地面碰撞,并可能与场景中的任何对象碰撞。 如何实现? 为了实现这一功能,我们必须执…

Linux———head命令详解

目录 head 命令是一个用于在命令行中显示文件开头部分内容的常用工具。 head 命令基本语法: 常用选项 示例 显示文件的前 10 行: 显示文件的前 5 行: 显示文件的前 100 个字节: 不显示文件名的标题信息: 显示…

Elasticsearch基本操作之文档操作

本文来说下Elasticsearch基本操作之文档操作 文章目录 文档概述创建文档示例创建文档(生成随机id)创建文档(自定义唯一性标识) 查看文档示例根据主键查看文档查看所有文档 本文小结 文档概述 文档概述 在创建好索引的基础上来创建文档,并添加数据。这里的文档可以类…

Unity 利用UGUI之Slider制作进度条

在Unity中使用Slider和Text组件可以制作简单的进度条。 首先在场景中右键->UI->Slider,新建一个Slider组件: 同样方法新建一个Text组件,最终如图: 创建一个进度模拟脚本,Slider_Progressbar.cs using System.C…

QT qss文件设置样式

方式一 (单个) 方式二 (全局) 所有按钮都会采用这个样式。 方式三 (qss文件) 创建资源文件 创建qss文件(Button.qss) 引用qss文件 QApplication a(argc, argv);QString qss;QFile…

tiktok云手机有用吗?用哪个好?

很多做独立站的跨境卖家都会搭配一些社媒平台给自己引流带货,比如说目前很火的TikTok,这也是目前比较有效的一种引流方式。本文将介绍tiktok运营方法以及如何用tiktok云手机规避运营风险。 TikTok是个不错的风口,不过我们在国内想要运营好Tik…

7 种常见的前端安全攻击

文章目录 七种常见的前端攻击1.跨站脚本(XSS)2.依赖性风险3.跨站请求伪造(CSRF)4.点击劫持5.CDN篡改6. HTTPS 降级7.中间人攻击 随着 Web 应用程序对业务运营变得越来越重要,它们也成为更有吸引力的网络攻击目标。但不…

Vue3-44-Pinia- 安装步骤

介绍 本文介绍 在 vue3 中 安装 Pinia 的步骤 安装步骤 1、npm 安装 npm install pinia》 安装完成后可以看到 package.json 中添加了 pinia 的依赖信息 2、main.ts 中配置 // 引入 vue实例创建方法 import { createApp } from vue// 引入pinia import { createPinia } fro…

10086 shop

中国移动网上商城--个人中心 查看套餐收费流程

安防视频云平台/可视化监控云平台ARM版EasyCVR无法下载录像文件,如何解决?

视频集中存储/云存储/视频监控管理平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,实现视频资源的鉴权管理、按需调阅、全网分发、智能分析等。GB28181视频监控/AI智能大数据视频分析EasyCVR平台已经广泛应用在工地…