java 实现简易基于Dledger 的选举
java 实现简易基于Dledger 的选举
1. 定义 Dledger 节点类,包含节点的状态、日志存储、选举和日志复制逻辑
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;// Dledger 节点类,包含节点的状态、日志存储、选举和日志复制逻辑
class DledgerNode {// 选举超时时间的最小值(毫秒)private static final int ELECTION_TIMEOUT_MIN = 150;// 选举超时时间的最大值(毫秒)private static final int ELECTION_TIMEOUT_MAX = 300;// 心跳间隔时间(毫秒)private static final int HEARTBEAT_INTERVAL = 50;// 当前节点所处的任期号private long currentTerm;// 当前节点投票给的节点 ID,-1 表示未投票private int votedFor;// 存储日志条目的列表private List<LogEntry> log;// 节点的状态,0 表示追随者,1 表示候选人,2 表示领导者private int state;// 当前节点的 IDprivate int selfId;// 集群中其他节点的 ID 列表private List<Integer> peers;// 用于定时任务的调度器private ScheduledExecutorService scheduler;// 选举超时时间(毫秒)private long electionTimeout;// 候选人收到的投票数private int votesReceived;// 构造函数,用于初始化节点的相关信息public DledgerNode(int selfId, List<Integer> peers) {// 初始任期号为 0this.currentTerm = 0;// 初始未投票this.votedFor = -1;// 初始化日志列表this.log = new ArrayList<>();// 初始状态为追随者this.state = 0;// 设置当前节点的 IDthis.selfId = selfId;// 设置集群中其他节点的 ID 列表this.peers = peers;// 创建一个单线程的调度器this.scheduler = Executors.newScheduledThreadPool(1);// 重置选举超时时间resetElectionTimeout();}// 重置选举超时时间的方法private void resetElectionTimeout() {// 创建一个随机数生成器Random random = new Random();// 生成一个介于 ELECTION_TIMEOUT_MIN 和 ELECTION_TIMEOUT_MAX 之间的随机数作为选举超时时间this.electionTimeout = random.nextInt(ELECTION_TIMEOUT_MAX - ELECTION_TIMEOUT_MIN + 1) + ELECTION_TIMEOUT_MIN;// 调度一个定时任务,在选举超时时间后启动选举scheduler.schedule(this::startElection, electionTimeout, TimeUnit.MILLISECONDS);}// 启动选举的方法private void startElection() {// 将节点状态设置为候选人state = 1;// 任期号加 1currentTerm++;// 投票给自己votedFor = selfId;// 初始收到的投票数为 1(自己的一票)votesReceived = 1;// 创建一个投票请求对象VoteRequest request = new VoteRequest(currentTerm, selfId, getLastLogIndex(), getLastLogTerm());// 向集群中的每个节点发送投票请求for (int peer : peers) {sendVoteRequest(peer, request);}}// 发送投票请求的方法private void sendVoteRequest(int peer, VoteRequest request) {// 模拟网络通信,实际中需要使用网络库// 调用 handleVoteRequest 方法处理投票请求并获取响应VoteResponse response = handleVoteRequest(peer, request);// 处理投票响应handleVoteResponse(response);}// 处理投票请求的方法private VoteResponse handleVoteRequest(int peer, VoteRequest request) {// 如果请求的任期号小于当前节点的任期号,拒绝投票if (request.term < currentTerm) {return new VoteResponse(currentTerm, false);}// 如果请求的任期号大于当前节点的任期号,更新当前节点的任期号,将状态设置为追随者,重置投票信息if (request.term > currentTerm) {currentTerm = request.term;state = 0;votedFor = -1;}// 如果当前节点未投票或者已经投票给该候选人,并且候选人的日志至少和自己一样新,则授予投票if ((votedFor == -1 || votedFor == request.candidateId) &&(request.lastLogTerm > getLastLogTerm() ||(request.lastLogTerm == getLastLogTerm() && request.lastLogIndex >= getLastLogIndex()))) {votedFor = request.candidateId;// 重置选举超时时间resetElectionTimeout();return new VoteResponse(currentTerm, true);}// 否则拒绝投票return new VoteResponse(currentTerm, false);}// 处理投票响应的方法private void handleVoteResponse(VoteResponse response) {// 如果响应的任期号大于当前节点的任期号,更新当前节点的任期号,将状态设置为追随者,重置投票信息if (response.term > currentTerm) {currentTerm = response.term;state = 0;votedFor = -1;// 重置选举超时时间resetElectionTimeout();}// 如果当前节点是候选人,并且收到了投票,则增加投票数if (state == 1 && response.voteGranted) {votesReceived++;// 如果收到的投票数超过集群节点数的一半,则当选为领导者if (votesReceived > peers.size() / 2) {state = 2;System.out.println("Node " + selfId + " has been elected as the leader in term " + currentTerm);// 开始发送心跳startSendingHeartbeats();}}}// 开始发送心跳的方法private void startSendingHeartbeats() {// 调度一个定时任务,每隔 HEARTBEAT_INTERVAL 毫秒发送一次心跳scheduler.scheduleAtFixedRate(() -> {// 创建一个追加日志请求对象,entries 为空表示这是一个心跳请求AppendEntriesRequest request = new AppendEntriesRequest(currentTerm, selfId, getLastLogIndex(), getLastLogTerm(), new LogEntry[0], getCommitIndex());// 向集群中的每个节点发送追加日志请求for (int peer : peers) {sendAppendEntriesRequest(peer, request);}}, 0, HEARTBEAT_INTERVAL, TimeUnit.MILLISECONDS);}// 发送追加日志请求的方法private void sendAppendEntriesRequest(int peer, AppendEntriesRequest request) {// 模拟网络通信,实际中需要使用网络库// 调用 handleAppendEntriesRequest 方法处理追加日志请求并获取响应AppendEntriesResponse response = handleAppendEntriesRequest(peer, request);// 处理追加日志响应handleAppendEntriesResponse(response);}// 处理追加日志请求的方法private AppendEntriesResponse handleAppendEntriesRequest(int peer, AppendEntriesRequest request) {// 如果请求的任期号小于当前节点的任期号,拒绝请求if (request.term < currentTerm) {return new AppendEntriesResponse(currentTerm, false);}// 如果请求的任期号大于当前节点的任期号,更新当前节点的任期号,将状态设置为追随者,重置投票信息if (request.term > currentTerm) {currentTerm = request.term;state = 0;votedFor = -1;}// 重置选举超时时间resetElectionTimeout();// 简单处理日志追加逻辑// 如果前一条日志的索引超出了日志列表的范围,或者前一条日志的任期号不匹配,则拒绝请求if (request.prevLogIndex >= 0 && (request.prevLogIndex >= log.size() || log.get((int) request.prevLogIndex).term != request.prevLogTerm)) {return new AppendEntriesResponse(currentTerm, false);}// 遍历要追加的日志条目for (LogEntry entry : request.entries) {// 如果要追加的日志位置已经存在日志,则替换该位置的日志if (request.prevLogIndex + 1 + log.indexOf(entry) < log.size()) {log.set((int) (request.prevLogIndex + 1 + log.indexOf(entry)), entry);} else {// 否则将日志追加到日志列表的末尾log.add(entry);}}// 如果领导者的提交索引大于当前节点的提交索引,更新当前节点的提交索引if (request.leaderCommit > getCommitIndex()) {// 更新提交索引}// 返回追加成功的响应return new AppendEntriesResponse(currentTerm, true);}// 处理追加日志响应的方法private void handleAppendEntriesResponse(AppendEntriesResponse response) {// 如果响应的任期号大于当前节点的任期号,更新当前节点的任期号,将状态设置为追随者,重置投票信息if (response.term > currentTerm) {currentTerm = response.term;state = 0;votedFor = -1;// 重置选举超时时间resetElectionTimeout();}}// 获取最后一条日志索引的方法private long getLastLogIndex() {return log.size() - 1;}// 获取最后一条日志任期号的方法private long getLastLogTerm() {// 如果日志列表为空,返回 0if (log.isEmpty()) {return 0;}// 否则返回最后一条日志的任期号return log.get(log.size() - 1).term;}// 获取提交索引的方法,这里简单返回最后一条日志的索引private long getCommitIndex() {return getLastLogIndex();}@Overridepublic String toString() {return "DledgerNode{" +"currentTerm=" + currentTerm +", votedFor=" + votedFor +", log=" + log +", state=" + state +", selfId=" + selfId +", peers=" + peers +", scheduler=" + scheduler +", electionTimeout=" + electionTimeout +", votesReceived=" + votesReceived +'}';}
}
2. 定义日志条目类
// 日志条目类,用于存储日志的任期号和具体数据
class LogEntry {// 该日志条目的任期号,用于标识日志的先后顺序long term;// 日志条目的具体数据内容String data;// 构造函数,用于初始化日志条目的任期号和数据public LogEntry(long term, String data) {this.term = term;this.data = data;}
}// 投票请求类,用于在选举过程中向其他节点请求投票
class VoteRequest {// 候选人当前所处的任期号long term;// 候选人的节点 IDint candidateId;// 候选人最后一条日志的索引long lastLogIndex;// 候选人最后一条日志的任期号long lastLogTerm;// 构造函数,用于初始化投票请求的相关信息public VoteRequest(long term, int candidateId, long lastLogIndex, long lastLogTerm) {this.term = term;this.candidateId = candidateId;this.lastLogIndex = lastLogIndex;this.lastLogTerm = lastLogTerm;}
}// 投票响应类,用于对投票请求进行回应
class VoteResponse {// 当前节点的任期号long term;// 是否授予投票的标志,true 表示授予,false 表示拒绝boolean voteGranted;// 构造函数,用于初始化投票响应的相关信息public VoteResponse(long term, boolean voteGranted) {this.term = term;this.voteGranted = voteGranted;}
}// 追加日志请求类,用于领导者向追随者发送日志追加请求
class AppendEntriesRequest {// 领导者当前所处的任期号long term;// 领导者的节点 IDint leaderId;// 要追加日志的前一条日志的索引long prevLogIndex;// 要追加日志的前一条日志的任期号long prevLogTerm;// 要追加的日志条目数组LogEntry[] entries;// 领导者当前的提交索引long leaderCommit;// 构造函数,用于初始化追加日志请求的相关信息public AppendEntriesRequest(long term, int leaderId, long prevLogIndex, long prevLogTerm, LogEntry[] entries, long leaderCommit) {this.term = term;this.leaderId = leaderId;this.prevLogIndex = prevLogIndex;this.prevLogTerm = prevLogTerm;this.entries = entries;this.leaderCommit = leaderCommit;}
}// 追加日志响应类,用于追随者对追加日志请求进行回应
class AppendEntriesResponse {// 当前节点的任期号long term;// 日志追加是否成功的标志,true 表示成功,false 表示失败boolean success;// 构造函数,用于初始化追加日志响应的相关信息public AppendEntriesResponse(long term, boolean success) {this.term = term;this.success = success;}
}
3. Dledger 的测试用例
import java.util.Arrays;
import java.util.List;public class DledgerTest {public static void main(String[] args) {// 定义集群中节点的 ID 列表List<Integer> peers = Arrays.asList(1, 2, 3);// 创建节点 1DledgerNode node1 = new DledgerNode(1, peers);
// System.out.println(node1);// 创建节点 2DledgerNode node2 = new DledgerNode(2, peers);
// System.out.println(node2);// 创建节点 3DledgerNode node3 = new DledgerNode(3, peers);
// System.out.println(node3);try {// 让主线程休眠 5 秒,以便观察节点的选举和日志复制过程Thread.sleep(5000);} catch (InterruptedException e) {// 处理线程中断异常e.printStackTrace();}}
}
相关文章:
java 实现简易基于Dledger 的选举
java 实现简易基于Dledger 的选举 1. 定义 Dledger 节点类,包含节点的状态、日志存储、选举和日志复制逻辑 import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.S…...

大数据“调味“ ,智慧“添香“,看永洪科技助力绝味食品数字化新征程
近年来,随着国家数字化政策不断出台、新兴技术不断进步、企业内生需求持续释放,数字化转型逐步成为企业实现高质量发展的必由之路,成为企业实现可持续发展乃至弯道超车的重要途径。 在全国数字化浪潮驱动下,以人工智能、互联网、…...

【嵌入式】MQTT
MQTT 文章目录 MQTT安装简介MQTT客户端代码 安装 安装Paho MQTT C库: sudo apt-get install libpaho-mqtt3-dev头文件包含: #include "MQTTClient.h"编译选项: gcc -o $ $^ -lpaho-mqtt3c简介 MQTT协议全称是(Message Queuing…...
vue原理面试题
以下是一些关于Vue原理的面试题: 一、虚拟DOM与响应式系统 Vue中的虚拟DOM是如何工作的? 答案: 当Vue组件的数据发生变化时,Vue首先会在虚拟DOM中构建一个新的虚拟DOM树来表示更新后的组件结构。然后,Vue会将新的虚拟DOM树与旧的虚拟DOM树进行比较(这个过程称为Diff算法…...

office集成deepseek插件,office集成deepseek教程(附安装包)
文章目录 前言一、下载与安装OfficeAI 助手二、获取 DeepSeek 的 API key三、在 OfficeAI 助手中配置 DeepSeek API key四、使用 OfficeAI 助手功能 前言 本教程将为你详细讲解 Office 集成 DeepSeek 的安装步骤和使用方法,助你轻松拥抱智能办公新时代,…...

行业洞察|安踏、迪桑特、始祖鸟、昂跑、lululemon等运动户外品牌的「营销创新和会员运营」对比解读
商派助力国际知名鞋品牌OMS系统全面升级,拓展业务类型和营销玩法! 一、业务模式创新:打破传统边界,构建多维竞争力 近年来,户外运动品牌在业务模式上的革新呈现三大趋势:DTC模式深化、多品牌矩阵重构、技术…...

小鹏汽车申请注册“P7 Ultra”商标 或为P7车型升级版铺路
大湾区经济网品牌工程频道报道,据企查查APP显示,广东小鹏汽车科技有限公司近日提交“P7 Ultra”商标注册申请,国际分类为运输工具,当前状态为“注册申请中”。业内推测,此举或为小鹏P7车型高端版本量产上市做准备。 作…...
数列极限入门习题
数列极限入门习题 lim n → ∞ ( 1 1 2 1 3 ⋯ 1 n ) 1 n \lim\limits_{n\rightarrow\infty}(1 \frac{1}{2}\frac{1}{3}\cdots\frac{1}{n})^{\frac{1}{n}} n→∞lim(12131⋯n1)n1 lim n → ∞ ( 1 n 1 1 n 2 ⋯ 1 n n ) \lim\limits_{n\rightarrow\…...

ubuntu部署gitlab-ce及数据迁移
ubuntu部署gitlab-ce及数据迁移 进行前梳理: 在esxi7.0 Update 3 基础上使用 ubuntu22.04.5-server系统对 gitlab-ce 16.10进行部署,以及将gitlab-ee 16.9 数据进行迁移到gitlab-ce 16.10 进行后总结: 起初安装了极狐17.8.3-jh 版本(不支持全局中文,就没用了) …...

批量设置 Word 样式,如字体信息、段落距离、行距、页边距等信息
在 Word 文档中,我们可以做各种样式的处理。比如设置 Word 文档的字体样式、设置 Word 文档的段落样式以及设置 Word 文档的页面样式。我们通常可以在 Office 中完成这些操作,相信绝大部分场景我们也是这样完成的。但是如果我们手上有 1000 个 Word 文档…...
【论文分析】语义驱动+迁移强化学习:无人机自主视觉导航的高效解决方案(语义驱动的无人机自主视觉导航)
论文阅读:《Semantic-Driven Autonomous Visual Navigation for Unmanned Aerial Vehicles》语义驱动的无人机自主视觉导航 1. 引言 这篇论文《Semantic-Driven Autonomous Visual Navigation for Unmanned Aerial Vehicles》发表在《IEEE Transactions on Indust…...

JDK官网安装教程 Windows
文章目录 概要整体架构流程 概要 JDK 是 Java 开发的基础,无论是开发桌面应用、Web 应用、移动应用,还是大数据、云计算相关项目,都需要先安装 JDK 整体架构流程 第一步,进入官网 Java Downloads | Oracle 中国 ①可以直接复…...

MR30系列分布式I/O:高稳定与高精准赋能锂电池覆膜工艺革新
在新能源行业高速发展的背景下,锂电池生产工艺对自动化控制的精准性和可靠性提出了更高要求。作为锂电池生产中的关键环节,覆膜工艺直接关系到电池的绝缘性能、安全性及使用寿命。面对复杂的工艺控制需求,明达技术MR30系列分布式I/O模块凭借其…...

android 横竖屏适配工作总结
1、创建一个横屏文件夹,复制一份竖屏的布局。然后修改适配横屏。只要布局id都有,其他想怎么改就怎么修改。 2、最好使用kotlin语言编写和使用viewBinding绑定控件,可以使用?.判空控件是否存在,不至于缺少这个控件时候直接崩溃。 …...

离散傅里叶变换(Discrete Fourier Transform, DFT)及其在图像处理中的应用
离散傅里叶变换(DFT)及其在图像处理中的应用 什么是离散傅里叶变换? 离散傅里叶变换(Discrete Fourier Transform, DFT)是一种强大的数学工具,用于将离散信号从时域(或空间域)转换…...
两周学习安排
日常安排 白天 看 MySQL实战45讲,每日一讲 看 图解设计模式 每天1-2道力扣算法题(难度中等以上) 每天复习昨天的单词,记20个单词,写一篇阅读 晚上 写服创项目 每日产出 MySQL实战45讲 读书笔记 设计模式 读书笔…...

vscode通过ssh远程连接(linux系统)不能跳转问题
1.问题描述 unbantu中的vscode能够通过函数跳转到函数定义,而windows通过ssh连接unbantu的vscode却无法跳转 2.原因: 主要原因是这里缺少插件,这里是unbantu给主机的服务器,与ubantu本地vscode插件相互独立,能否跳转…...

eMMC存储器详解(存储区域结构、EXT_CSD[179]、各分区介绍、主要引脚、命令格式与类型等)
读本篇博文所需要的先行知识 关于芯片内部的ROM的作用、工作原理的介绍,链接如下: https://blog.csdn.net/wenhao_ir/article/details/145969584 eMMC的物理结构、特点、用途 这个标题的相关内容见我的另一篇博文,博文链接如下:…...
洛谷 P11830 省选联考2025 幸运数字 题解
题意 小 X 有 n n n 个正整数二元组 ( a i , b i ) ( 1 ≤ i ≤ n ) (a_i, b_i) (1 \leq i \leq n) (ai,bi)(1≤i≤n)。他将会维护初始为空的可重集 S S S,并对其进行 n n n 轮操作。第 i ( 1 ≤ i ≤ n ) i (1 \leq i \leq n) i(1≤i≤n) 轮操作中&#…...
win11编译pytorchaudio cuda128版本流程
1. 前置条件 本篇续接自 win11编译pytorch cuda128版本流程,阅读前请先参考上一篇配置环境。 访问https://kkgithub.com/pytorch/audio/archive/refs/tags/v2.6.0.tar.gz下载源码,下载后解压; 2. 编译 在visual studio 2022安装目录下查找…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...