【服务的主从切换实现原理】

news2025/2/27 22:52:38

文章目录

  • 主从架构介绍
  • zookeeper
  • 利用ZK实现主从架构

主从架构介绍

主从服务架构是一种常见的分布式系统设计模式,常用于提高系统的性能、可用性和扩展性。在这种架构中,系统中的节点被分为两类:主节点(Master)和从节点(Slave)。
在这里插入图片描述

zookeeper

zookeeper有以下几个特点:
1.集群部署:一般是3~5台机器组成一个集群,每台机器都在内存保存了zk的全部数据,机器之间互相通信同步数据,客户端连接任何一台机器都可以。
2.顺序一致性:所有的写请求都是有序的;集群中只有leader机器可以写,所有机器都可以读,所有写请求都会分配一个zk集群全局的唯一递增编号:zxid,用来保证各种客户端发起的写请求都是有顺序的。
3.原子性:要么全部机器成功,要么全部机器都不成功。
4.数据一致性:无论客户端连接到哪台节点,读取到的数据都是一致的;leader收到了写请求之后都会同步给其他机器,保证数据的强一致,你连接到任何一台zk机器看到的数据都是一致的。
5.高可用:如果某台机器宕机,会保证数据不丢失。集群中挂掉不超过一半的机器,都能保证集群可用。比如3台机器可以挂1台,5台机器可以挂2台。
6.实时性:一旦数据发生变更,其他节点会实时感知到。
7.高性能:每台zk机器都在内存维护数据,所以zk集群绝对是高并发高性能的,如果将zk部署在高配置物理机上,一个3台机器的zk集群抗下每秒几万请求是没有问题的。
8.高并发:高性能决定的,主要是基于纯内存数据结构来处理,并发能力是很高的,只有一台机器进行写,但是高配置的物理机,比如16核32G,可以支撑几万的写入QPS。所有机器都可以读,选用3台高配机器的话,可以支撑十万+的QPS。

利用ZK实现主从架构

导入依赖

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<zookeeper.version>3.4.14</zookeeper.version>
		<curator-framework.version>2.12.0</curator-framework.version>
		<curator-recipes.version>2.12.0</curator-recipes.version>
		<ssdb.version>9.4</ssdb.version>
		<jodatime.version>2.10</jodatime.version>
		<binlog.version>0.21.0</binlog.version>
		<disruptor.version>3.4.2</disruptor.version>
	</properties>

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
		<exclusions>
			<!-- 去除旧log依赖 -->
			<exclusion>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-logging</artifactId>
			</exclusion>
		</exclusions>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-quartz</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>

	<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-log4j2</artifactId>
	</dependency>

	<!-- log4j2异步日志需要加载disruptor-3.0.0.jar或者更高的版本 -->
	<dependency>
		<groupId>com.lmax</groupId>
		<artifactId>disruptor</artifactId>
		<version>3.4.2</version>
	</dependency>

	<!-- zookeeper -->
	<dependency>
		<groupId>org.apache.zookeeper</groupId>
		<artifactId>zookeeper</artifactId>
		<version>${zookeeper.version}</version>
	</dependency>
	<dependency>
		<groupId>org.apache.curator</groupId>
		<artifactId>curator-framework</artifactId>
		<version>${curator-framework.version}</version>
	</dependency>
	<dependency>
		<groupId>org.apache.curator</groupId>
		<artifactId>curator-recipes</artifactId>
		<version>${curator-recipes.version}</version>
	</dependency>

zk客户端:

public class ZookeeperClient {

    /**
     * 客户端
     */
    private CuratorFramework client;

    /**
     * Leader选举
     */
    private LeaderLatch leader;

    public ZookeeperClient(LeaderLatch leader,CuratorFramework client){
        this.client = client;
        this.leader = leader;
    }

    /**
     * 启动客户端
     * @throws Exception
     */
    public void startZKClient() throws Exception {
        client.start();
        leader.start();
    }

    /**
     * 关闭客户端
     * @throws Exception
     */
    public void closeZKClient() throws Exception {
        leader.close();
        client.close();
    }

    /**
     * 判断是否变为领导者
     * @return
     */
    public boolean hasLeadership(){
        return leader.hasLeadership();
    }

    public CuratorFramework getClient() {
        return client;
    }

    public void setClient(CuratorFramework client) {
        this.client = client;
    }

    public LeaderLatch getLeader() {
        return leader;
    }

    public void setLeader(LeaderLatch leader) {
        this.leader = leader;
    }

public class ZookeeperClientInfo {

    /**
     * 是否是leader 默认为false
     */
    public static boolean isLeader = false;

    /**
     * 客户端ID
     */
    private String id;

    /**
     * 连接信息字符串
     */
    private String connectString;

    /**
     * 节点路径
     */
    private String path;

    /**
     * 连接超时时间
     */
    private Integer connectTimeOut;

    /**
     * 最大连接次数
     */
    private Integer maxRetries;

    /**
     * 重连休眠时间
     */
    private Integer retrySleepTime;

    public static boolean isLeader() {
        return isLeader;
    }

    public static void setIsLeader(boolean isLeader) {
        ZookeeperClientInfo.isLeader = isLeader;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getConnectString() {
        return connectString == null ? null : connectString.replaceAll("//s+", "");
    }

    public void setConnectString(String connectString) {
        this.connectString = connectString;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Integer getConnectTimeOut() {
        return connectTimeOut;
    }

    public void setConnectTimeOut(Integer connectTimeOut) {
        this.connectTimeOut = connectTimeOut;
    }

    public Integer getMaxRetries() {
        return maxRetries;
    }

    public void setMaxRetries(Integer maxRetries) {
        this.maxRetries = maxRetries;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("ZookeeperClientInfo{ ").append("id=").append(id)
                .append(",isLeader=").append(isLeader).append(", connectString=").append(connectString)
                .append(", path=").append(path).append(",connectTimeOut=").append(connectTimeOut)
                .append(", maxRetries=").append(maxRetries).append(", retrySleepTime=").append(retrySleepTime).append(" }");
        return sb.toString();
    }
}

监听器,来监听主节点变更,实现主要是继承LeaderLatchListener类

@Component
public class ZookeeperClientListener implements LeaderLatchListener {

    private final static Logger log = LoggerFactory.getLogger(ZookeeperClientListener.class);

    @Autowired
    private ChangeLeaderService changeLeaderService;

    /**
     *  将本服务达成jar包,部署到2台服务器上。启动两个服务。
     *  1、第一台服务(机器1)启动后抢到leader,会进入到该方法中。另外一台服务(机器2)会进入到notLeader()中。
     *  2、当机器1宕机后,连接断开后zookeeper会删除临时节点。机器2根据选举会成为leader,成为leader后会进入到isLeader()中
     *     然后在changeLeaderService.taskExecut() 再次将定时任务做补偿处理。
     */
    @Override
    public void isLeader() {
        log.error("{},当前服务已变为leader,将从事业务消费======>>>>", JodaDateUtil.date2String(new Date()));
        ZookeeperClientInfo.isLeader = true;

        // 切换机器后,继续执行上一个机器未完成的定时任务。
        changeLeaderService.taskExecut();
    }

    @Override
    public void notLeader() {
        log.error("{},当前服务已退出leader,不再从事消费业务=====>>>", JodaDateUtil.date2String(new Date()));
        ZookeeperClientInfo.isLeader = false;
    }
}

当服务主从切换时的补偿措施:

public interface ChangeLeaderService {

    /**
     * 主从服务切换时,手动触发分配到leader机器上的定时任务中的业务逻辑
     */
    void taskExecut();
}
@Service
public class ChangeLeaderServiceImpl implements ChangeLeaderService {

    private final static Logger log = LoggerFactory.getLogger(ChangeLeaderServiceImpl.class);
;
    @Autowired
    private SSDB ssdb;

    @Override
    public void taskExecut() {
        //TODO 从ssdb 中查询出定时任务的标识,是否正常执行完成,未完成的话,在这里再触发执行。
        log.info("===ChangeLeaderServiceImpl===taskExecut()===");
        /*Response response = ssdb.get(Constant.TEST_STATE);
        if (response.ok() && response.datas.size() > 0) {
            int tenantState = byteArrayToInt(response.datas.get(0));
            if (Constant.TASK_EXECUTING == tenantState) { //当前任务未完成,接着完成
                // service.do(); 伪代码
                log.info("service.do();");
                ssdb.set(Constant.TEST_STATE, Constant.TASK_END);
            }
        }*/
    }

    /**
     * byte数组转int
     * @param b
     * @return
     */
    private int byteArrayToInt(byte[] b){
        String str = byteArrayToString(b);
        return StringToInt(str);
    }

    /**
     * byte数组转string
     * @param b
     * @return
     */
    private String byteArrayToString(byte[] b) {
        if (null == b || b.length == 0) {
            return "";
        }
        return new String(b);
    }

    /**
     * string转int
     * @param str
     * @return
     */
    private int StringToInt(String str){
        if (StringUtils.isEmpty(str)){
            return 0;
        }
        return Integer.parseInt(str);
    }
}

zk的配置信息

@Component
public class ZookeeperConfig {

    /**
     * zk 地址
     */
    @Value("${spring.slaveof.zk.addr}")
    private String addr;

    /**
     * 重试策略----最大重试次数
     */
    @Value("${spring.slaveof.zk.max}")
    private int max;

    /**
     * 重试策略-----sleepTime
     */
    @Value("${spring.slaveof.zk.sleep}")
    private int sleepTime;

    /**
     * 连接超时时间
     */
    @Value("${spring.slaveof.zk.connection}")
    private int connectionTime;

    /**
     * 会话超时时间
     */
    @Value("${spring.slaveof.zk.session}")
    private int sessionTime;

    public String getAddr() {
        return addr;
    }

    public int getMax() {
        return max;
    }

    public int getSleepTime() {
        return sleepTime;
    }

    public int getConnectionTime() {
        return connectionTime;
    }

    public int getSessionTime() {
        return sessionTime;
    }
}

服务启动限制性的类,了解ApplicationRunner,SpringBoot项目启动时,若想在启动之后直接执行某一段代码,就可以用 ApplicationRunner这个接口,并实现接口里面的run(ApplicationArguments args)方法,方法中写上自己的代码逻辑。也就是:spring容器启动完成之后,就会紧接着执行这个接口实现类的run方法。

@Component
public class ZkDemoApplicationRunner implements ApplicationRunner {

    private final static Logger log = LoggerFactory.getLogger(ZkDemoApplicationRunner.class);

    @Autowired
    private ZookeeperClientListener zkClientListener;

    @Autowired
    private ZookeeperConfig zookeeperConfig;

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        log.info("====================>>>>>>>>启动执行zk>>>>>>>>==================");
        log.error("===>>>>>>>>zookeeper: addr:{}, sleepTime:{}, max:{}, connectionTime:{}=====", zookeeperConfig.getAddr(), zookeeperConfig.getSleepTime(), zookeeperConfig.getMax(), zookeeperConfig.getConnectionTime());
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString(zookeeperConfig.getAddr())
                .retryPolicy(new ExponentialBackoffRetry(zookeeperConfig.getSleepTime(), zookeeperConfig.getMax()))
                .connectionTimeoutMs(zookeeperConfig.getConnectionTime()).build();
        LeaderLatch leaderLatch = new LeaderLatch(client, "/diaoliwei", "client1", LeaderLatch.CloseMode.NOTIFY_LEADER);
        if (zkClientListener == null) {
            log.error("==================>>>>>>>>>>>>>>>>zkClientListener is null=====>>>>>>>>>>>");
        }
        leaderLatch.addListener(zkClientListener);
        ZookeeperClient zkClient = new ZookeeperClient(leaderLatch, client);
        try {
            zkClient.startZKClient();
        } catch (Exception e) {
            log.error("======>>>>>>zk客户端连接失败<<<<<=====error:{}===", e);
            return;
        }
        CuratorFrameworkState state = client.getState();
        if (CuratorFrameworkState.STOPPED == state) {
            log.error("zk客户端已关闭");
            return;
        }

        /*while (true) {  // 测试日志用
            try {
                if(!zkClient.hasLeadership()){
                    log.info("2当前服务不是leader");
                    Thread.sleep(2000);
                    log.error("error:::::::Test02 do it...>>>>>>> ");
                    continue;
                }  else {
                    log.info("2当前服务是leader");
                }
                log.info("Test02 do it... ");
                log.error("Test02 do it...>>>>>>> ");
            } catch (Exception e) {
                log.error("Exception=====>>>>>>>>>>>>eeee:", e);
            }
        }*/

        //log.info("======>>>>>zk客户端连接成功<<<<<<=======");
    }
}

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

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

相关文章

JVM 类加载器的工作原理

JVM 类加载器的工作原理 类加载器&#xff08;ClassLoader&#xff09;是一个用于加载类文件的子系统&#xff0c;负责将字节码文件&#xff08;.class 文件&#xff09;加载到 JVM 中。Java 类加载器允许 Java 应用程序在运行时动态地加载、链接和初始化类。 2. 类加载器的工…

Dom案例——计算器,二级联动,表单验证(附完整代码)

Dom案例——计算器&#xff0c;二级联动&#xff0c;表单验证&#xff08;附完整代码&#xff09; 文章目录 Dom案例——计算器&#xff0c;二级联动&#xff0c;表单验证&#xff08;附完整代码&#xff09;一、计算器案例二、二级联动三、表单验证 一、计算器案例 1、实现计…

vivado HW_VIO

描述 虚拟输入/输出&#xff08;VIO&#xff09;调试核心hw_VIO可以监视和驱动内部 在编程的XilinxFPGA上实时显示信号。在没有物理访问的情况下 目标硬件&#xff0c;可以使用此调试功能来驱动和监视 存在于物理设备上。 VIO核心具有硬件探测器hw_probe对象&#xff0c;用于监…

vulhub之httpd篇

Apache 换行解析漏洞&#xff08;CVE-2017-15715&#xff09; Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞&#xff0c;在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过一…

用于工业网络状态监测的Softing plantPerfect Monitor产品亮相2024 ACHEMA

Softing工业在2024年阿赫玛博览会&#xff08;ACHEMA 2024&#xff09;上展示了“plantPerfect Monitor”产品。该产品是一种用于对工业网络和现有工业系统进行全面状况监测的创新解决方案。 &#xff08;“plantPerfect Monitor”提供全面的工业通信网络状态监控&#xff09; …

单触控单输出触摸开关芯片PT2052A

1.产品概述 PT2052封装和丝印 PT2052A 是一款单通道触摸检测芯片。该芯片内建稳压电路&#xff0c;提供稳定电压给触摸感应电路使用&#xff0c;同时内部集成高效完善的触摸检测算法&#xff0c;使得芯片具有稳定的触摸检测效果。该芯片专为取代传统按键而设计&#xff0c;具有…

Hexapod C-887使用手册 -- 4,5,6

4 - 拆包 小心拆包C-887 根据合同和发货注意比较发货范围的内容&#xff1a; 检查危险符号的内容。如果任何零件损坏或缺失&#xff0c;立即联系客服部门。 保存所有包装材料&#xff0c;以防产品需要返厂。 5 - 安装 本章中 安装一般注意 安装PC软件 确保通风 接地C-…

一键取票,YonSuite商旅费控助力企业“消灭报销”

在数字化与智能化并行的时代&#xff0c;企业商旅管理正经历着前所未有的变革。面对传统商旅出行管理中流程复杂、费用不透明等问题&#xff0c;YonSuite商旅费控以其独特的数智化商旅管理平台&#xff0c;为企业提供了一站式的解决方案&#xff0c;特别是其“一键取票”功能&a…

数据中心布线管理:预标记线缆与移动扫描技术的融合

随着信息技术的飞速发展&#xff0c;数据中心布线管理面临着前所未有的挑战。传统的布线管理方式已无法满足现代数据中心高效、准确和可靠的需求。在这样一个背景下&#xff0c;预标记线缆与移动扫描技术的结合&#xff0c;为数据中心布线管理带来了革命性的解决方案。 布线管理…

Python 五子棋游戏(人人对战人机对战)【含Python源码 MX_006期】

系统简介&#xff1a; 五子棋是一种双人对弈的策略棋类游戏&#xff0c;玩家轮流在棋盘上落子&#xff0c;目标是通过在水平、垂直或对角线上连成一条直线的方式&#xff0c;最先在棋盘上形成连续的五颗棋子。五子棋的规则相对简单&#xff0c;但是需要玩家在落子过程中进行深思…

C++数据结构02 队列及其应用

目录 队列及其特点 利用数组模拟队列的基本操作 创建队列 空队条件 元素入队 元素出队 模拟超市收银问题 队列操作 初始化 入队操作 出队操作 取出队首元素 STL模板中队列的基本使用 训练&#xff1a;约瑟夫问题 参考程序 队列及其特点 队列是一种特殊的线性表&am…

学习笔记——网络管理与运维——SNMP(概述)

一、SNMP概述 1、SNMP背景 SNMP的基本思想&#xff1a;为不同种类的设备、不同厂家生产的设备、不同型号的设备&#xff0c;定义为一个统一的接口和协议&#xff0c;使得管理员可以是使用统一的外观面对这些需要管理的网络设备进行管理。 通过网络&#xff0c;管理员可以管理…

一键实现电脑投屏到电视机,轻松享受更大画面

在数字化的今天&#xff0c;我们常常希望在更大的屏幕上分享电脑上的内容&#xff0c;观看视频、展示演示文稿&#xff0c;或者与家人一同欣赏照片。而实现电脑屏幕投射到电视机上&#xff0c;成为了许多人追求的方便而实用的功能。本文将为您详细介绍电脑投屏到电视机的方法&a…

Qt creator day2练习

使用手动连接&#xff0c;将登录框中的取消按钮使用第二种方式&#xff0c;右击转到槽&#xff0c;在该函数中&#xff0c;调用关闭函数&#xff0c;将登录按钮使用Qt4版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为“admin”&#xff0c;密…

实现搜索功能中搜索内容高亮效果,本文通过fuzzysort库方案实现

目录 一&#xff1a;fuzzysort1.fuzzysort 介绍&#xff1a;2.需求所用方法介绍:gohighlight 3.效果实现 一&#xff1a;fuzzysort 1.fuzzysort 介绍&#xff1a; fuzzysort 是一个 JavaScript 库&#xff0c;用于对字符串数组进行模糊搜索和排序。它特别适用于自动补全&#…

只要往前走,至少能到达自己所能做到的部分

很多说自己力不足的人&#xff0c;往往是中道而废&#xff0c;在通往目标的途中就失败了。 无论怎么力不足&#xff0c;只要往前走&#xff0c;至少也能到达自己所能做到的部分。 《刻意练习》有个原则&#xff1a;如果有人能做到一件事&#xff0c;其他人就都能做到。 &…

【最新鸿蒙应用开发】——ArkWeb1——arkts加载h5页面

1. Web组件概述 Web组件用于在应用程序中显示Web页面内容&#xff0c;为开发者提供页面加载、页面交互、页面调试等能力。 页面加载&#xff1a;Web组件提供基础的前端页面加载的能力&#xff0c;包括&#xff1a;加载网络页面、本地页面、html格式文本数据。 页面交互&#…

测试记录4:在windows wsl2上配置ubuntu20.04

1.下载ubuntu20.04 (1) 在microsoft store中下载ubuntu20.04 (2) 在powershell中检查ubuntu20.04 wsl --listwsl -l -v安装成功 2.安装界面 见测试记录3 3.安装必要的功能包 sudo apt install zip sudo apt install gedit 出现问题&#xff1a; Command: xacro /home/buaa…

HCIA 15 AC+FIT AP结构WLAN基础网络

本例配置AC+FIT,即瘦AP+AC组网。生活中家庭上网路由器是胖AP,相当于AC+FIT二合一集成到一个设备上。 1.实验介绍及拓扑 某企业网络需要用户通过 WLAN 接入网络,以满足移动办公的最基本需求。 1. AC 采用旁挂核心组网方式,AC 与AP 处于同一个二层网络。 2. AC 作为DHCP …

【QT5】<重点> IMX6ULL开发板运行QT

目录 1. 安装交叉编译器 2. 命令行交叉编译QT项目 3. 运行该可执行程序 4. 开发板上运行UDP程序与Ubuntu通信 1. 安装交叉编译器 第一步&#xff1a;进入正点原子论坛找到IMX6ULL开发板的资料&#xff0c;下载“开发工具”&#xff0c;将“交叉编译工具”中的fsl-imx-x11-…