网站设置默认主页/湖南网络优化服务
在raft协议中, 会初始化三个计时器是和选举有关的:
voteTimer:这个timer负责定期的检查,如果当前的state的状态是候选者(STATE_CANDIDATE),那么就会发起选举
electionTimer:在一定时间内如果leader没有与 Follower 进行通信时,Follower 就可以认为leader已经不能正常担任leader的职责,那么就会进行选举,在选举之前会先发起预投票,如果没有得到半数以上节点的反馈,则候选者就会识趣的放弃参选。所以这个timer负责预投票
stepDownTimer:定时检查是否需要重新选举leader,如果当前的leader没有获得超过半数的Follower响应,那么这个leader就应该下台然后重新选举。
public boolean init(final NodeOptions opts) {....// Init timers//设置投票计时器this.voteTimer = new RepeatedTimer("JRaft-VoteTimer", this.options.getElectionTimeoutMs()) {@Overrideprotected void onTrigger() {//处理投票超时handleVoteTimeout();}@Overrideprotected int adjustTimeout(final int timeoutMs) {//在一定范围内返回一个随机的时间戳return randomTimeout(timeoutMs);}};//设置预投票计时器//当leader在规定的一段时间内没有与 Follower 舰船进行通信时,// Follower 就可以认为leader已经不能正常担任旗舰的职责,则 Follower 可以去尝试接替leader的角色。// 这段通信超时被称为 Election Timeout//候选者在发起投票之前,先发起预投票this.electionTimer = new RepeatedTimer("JRaft-ElectionTimer", this.options.getElectionTimeoutMs()) {@Overrideprotected void onTrigger() {handleElectionTimeout();}@Overrideprotected int adjustTimeout(final int timeoutMs) {//在一定范围内返回一个随机的时间戳//为了避免同时发起选举而导致失败return randomTimeout(timeoutMs);}};//leader下台的计时器//定时检查是否需要重新选举leaderthis.stepDownTimer = new RepeatedTimer("JRaft-StepDownTimer", this.options.getElectionTimeoutMs() >> 1) {@Overrideprotected void onTrigger() {handleStepDownTimeout();}};....if (!this.conf.isEmpty()) {//新启动的node需要重新选举stepDown(this.currTerm, false, new Status());}....
}
如果一个系统存在大量的任务调度,时间轮可以高效的利用线程资源来进行批量化调度。把大批量的调度任务全部都绑定时间轮上,通过时间轮进行所有任务的管理,触发以及运行。能够高效地管理各种延时任务,周期任务,通知任务等。时间轮(TimingWheel)算法应用范围非常广泛,各种操作系统的定时任务调度都有用到,我们熟悉的 Linux Crontab,以及 Java 开发过程中常用的 Dubbo、Netty、Akka、Quartz、ZooKeeper 、Kafka 等,几乎所有和 时间任务调度 都采用了时间轮的思想。
如何实现一个简易的C++时间轮
在介绍这个之前,先介绍linux定时任务的基本实现方式
一般有两个常见的比较有效的方法。一个是用 Linux 内部定时器alarm;另一个是用 sleep 或 usleep 函数让进程睡眠一段时间
如果不要求很精确的话,用 alarm() 和 signal() 就够了
unsigned int alarm(unsigned int seconds)
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
void sig_alarm()
{ exit(0);
}
int main(int argc, char *argv[])
{ signal(SIGALRM, sig_alarm); alarm(10); sleep(15); printf("Hello World!\n"); return 0;
}
所以当main()程序挂起10秒钟时,signal函数调用SIGALRM信号的处理函数sig_alarm,并且sig_alarm执行exit(0)使得程序直接退出。因此,printf(“Hello World!\n”)语句是没有被执行的。
如果你只做一般的定时,到了时间去执行一个任务,sleep是最简单的。但是sleep会让程序挂起,肯定不好。我们要的是可以继续执行下面的任务,时间一到,就出发信号,进行相应任务处理。除非用一个死循环专门用来定时调度。
不过,如果有多个定时任务,就需要对定时器做一些封装了。先介绍一下基于升序时间链表的计时器。
//Time_list.h
#include <netinet/in.h> //sockaddr_in
#include <list>
#include <functional>#define BUFFER_SIZE 64class util_timer; //前向声明//客户端数据
struct client_data {sockaddr_in address; //socket地址int sockfd; //socket文件描述符char buf[BUFFER_SIZE]; //数据缓存区util_timer* timer; //定时器
};//定时器类
class util_timer {
public:time_t expire; //任务超时时间(绝对时间)std::function<void(client_data*)> callBackFunc; //回调函数client_data* userData; //用户数据
};class Timer_list {public:explicit Timer_list();~Timer_list();public:void add_timer(util_timer* timer); //添加定时器void del_timer(util_timer* timer); //删除定时器void adjust_timer(util_timer* timer); //调整定时器void tick(); //处理链表上到期的任务private:std::list<util_timer*> m_timer_list; //定时器链表
};//Timer_list.cpp
#include "Timer_list.h"
#include <time.h>Timer_list::Timer_list() {}Timer_list::~Timer_list() {m_timer_list.clear();
}void Timer_list::add_timer(util_timer* timer) { //将定时器添加到链表if (!timer) return;else {auto item = m_timer_list.begin();while (item != m_timer_list.end()) {if (timer->expire < (*item)->expire) {m_timer_list.insert(item, timer);return;}item++;}m_timer_list.emplace_back(timer);}
}void Timer_list::del_timer(util_timer* timer) { //将定时器从链表删除if (!timer) return;else {auto item = m_timer_list.begin();while (item != m_timer_list.end()) {if (timer == *item) {m_timer_list.erase(item);return;}item++;}}
}void Timer_list::adjust_timer(util_timer *timer) { //调整定时器在链表中的位置del_timer(timer);add_timer(timer);
}void Timer_list::tick() { //SIGALRM信号触发,处理链表上到期的任务if (m_timer_list.empty()) return;time_t cur = time(nullptr);//检测当前定时器链表中到期的任务。while (!m_timer_list.empty()) {util_timer* temp = m_timer_list.front();if (cur < temp->expire) break;temp->callBackFunc(temp->userData);m_timer_list.pop_front();}
}
怎么运用呢服务端?
首先要设置signal(SIGALRM, sig_alarm); alarm(10)这样每10秒钟就会触发信号处理函数,函数里面调用tick处理链表上到期的任务。并且再次发送alarm,周期执行
在主循环不断接受新链接,把新链接的客户端数据绑定到计时器上(计时器包括超时时间,回调函数,用户数据),计时器加入到链表中。
如果要处理不活跃的连接,每次连接有活动,还要更新过期时间=当前时间+10s,调整在链表的位置
//SIGALRM 信号的处理函数
void timer_handler() {
timer_list.tick(); //调用升序定时器链表类的tick() 处理链表上到期的任务
alarm(TIMESLOT); //再次发出 SIGALRM 信号
}
//定时器回调函数 删除socket上注册的事件并关闭此socket
void timer_callback(client_data* user_data) {
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, user_data->sockfd, 0);
if (user_data) {
close(user_data->sockfd);
cout << "close fd : " << user_data->sockfd << endl;
}
}
因为在有序链表插入节点的时间复杂度为O(n),而且是单链表,意味着链表越长,插一个节点所要找到合适位置的时间开销就会越大,这样下来,时间效率是比较低的。
很显然,对于时间轮而言,要提高精度,就要使si的值足够小; 要提高执行效率,则要求N值足够大,使定时器尽可能的分布在不同的槽。
#define BUFFER_SIZE 64
class tw_timer;/* 绑定socket和定时器 */
struct client_data {sockaddr_in address;int sockfd;char buf[BUFFER_SIZE];tw_timer *timer;
};/* 定时器类 */
class tw_timer {
public:tw_timer(int rot, int ts) {next = nullptr;prev = nullptr;rotation = rot;time_slot = ts;}
public:int rotation; /* 记录定时器在时间轮转多少圈后生效 */int time_slot; /* 记录定时器属于时间轮上的哪个槽(对应的链表,下同) */void (*cb_func)(client_data *); /* 定时器回调函数 */client_data *user_data; /* 客户端数据 */tw_timer *next; /* 指向下一个定时器 */tw_timer *prev; /* 指向前一个定时器 */
};
定时器类和之前的不一样了。包含了前驱后驱指针,槽,回调函数。
下面是时间轮代码
**class time_wheel {
public:time_wheel();~time_wheel();/* 根据定时值timeout创建一个定时器,并把它插入到合适的槽中 */tw_timer *add_timer(int timeout);/* 删除目标定时器timer */void del_timer(tw_timer *timer);/* SI时间到后,调用该函数,时间轮向前滚动一个槽的间隔 */void tick();
private:static const int N = 60; /* 时间轮上槽的数量 */static const int SI = 1; /* 每1 s时间轮转动一次,即槽间隔为1 s */tw_timer* slots[N]; /* 时间轮的槽,其中每个元素指向一个定时器链表,链表无序 */int cur_slot; /* 时间轮的当前槽 */
};**
time_wheel::time_wheel() { cur_slot = 0;for (int i = 0; i < N; i++) {slots[i] = nullptr;}
}time_wheel::~time_wheel() {/* 遍历每个槽,并销毁其中的定时器 */for (int i = 0; i < N; i++) {/* 释放链表上的每个节点 */tw_timer *tmp = slots[i];while (tmp) {slots[i] = tmp->next;delete tmp;tmp = slots[i];}}
}tw_timer *time_wheel::add_timer(int timeout) {if (timeout < 0) {return nullptr;}int ticks = 0;/* 下面根据带插入定时器的超时值计算它将在时间轮转动多少个滴答后被触发,并将该滴答* 数存储于变量ticks中。如果待插入定时器的超时值小于时间轮的槽间隔SI,则将ticks* 向上折合为1,否则就将ticks向下折合为timeout/SI */if (timeout < SI) {ticks = 1;} else {ticks = timeout / SI;}/* 计算待插入的定时器在时间轮转动多少圈后被触发 */int rotation = ticks / N;/* 计算待插入的定时器应该被插入到哪个槽中 */int ts = (cur_slot + (ticks % N)) % N;/* 创建新的定时器,它在时间轮转动ratation圈之后被触发,且位于第ts个槽上 */tw_timer *timer = new tw_timer(rotation, ts);/* 如果第ts个槽中无任何定时器,则把新建的定时器插入其中,并将该定时器设置为该槽的头结点 */if (slots[ts] == nullptr) {printf("add timer, rotation is %d,cur_slot is %d\n", rotation, ts, cur_slot);slots[ts] = timer;} else {/* 头插法在链表中插入节点 */timer->next = slots[ts];slots[ts]->prev = timer;slots[ts] = timer;}return timer;
}void time_wheel::del_timer(tw_timer *timer) {if (timer == nullptr) {return;}int ts = timer->time_slot;/* slots[ts]是目标定时器所在槽的头结点。如果目标定时器就是该头结点,则需要* 重置第ts个槽的头结点 */if (timer == slots[ts]) {slots[ts] = slots[ts]->next;if (slots[ts]) {slots[ts]->prev = nullptr;}delete timer;} else {timer->prev->next = timer->next;if (timer->next) {timer->next->prev = timer->prev;}delete timer;}
}void time_wheel::tick() {tw_timer *tmp = slots[cur_slot]; /* 取得时间轮上当前槽的头结点 */printf("current slots is %d\n", cur_slot);while (tmp) {printf("tick the timer once\n");/* 如果定时器的ratation值大于0,则它在这一轮中不起作用 */if (tmp->rotation > 0) {tmp->rotation--;tmp = tmp->next;}/* 否则说明定时器已经到期,于是执行定时任务,然后删除该定时器 */else {tmp->cb_func(tmp->user_data);if (tmp == slots[cur_slot]) {printf("delete header in cur_slot\n");slots[cur_slot] = tmp->next;delete tmp;if (slots[cur_slot]) {slots[cur_slot]->prev = nullptr;}tmp = slots[cur_slot];} else {tmp->prev->next = tmp->next;if (tmp->next) {tmp->next->prev = tmp->prev;}tw_timer *tmp2 = tmp->next;delete tmp;tmp = tmp2;}}}/* 更新时间轮的当前槽,以反映时间轮的转动 */cur_slot = cur_slot + 1;cur_slot = cur_slot % N;
}
时间轮还可以用多个不同精度的时间轮(内存优化),最小堆进行优化。
相关文章:

时间轮来优化定时器
在raft协议中, 会初始化三个计时器是和选举有关的: voteTimer:这个timer负责定期的检查,如果当前的state的状态是候选者(STATE_CANDIDATE),那么就会发起选举 electionTimer:在一定时…...

《和AI交朋友》教学设计——初识人工智能
创新整合点 (1借助编程软件的机器学习扩展,使学生初步体验建立训练模型,让电脑进行学习的过程,进而感受人工智能的核心技术之一。 (2)借助编程软件的人工智能服务, 在编写程序时使用语音交互模块…...

机载雷达的时间简史
从地基起步 蝙蝠,虽然像人一样拥有双眼,但它看起东西来,用到的却不是眼睛。蝙蝠从鼻子里发出的超声波在传输过程中遇到物体后会立刻反弹,根据声波发射和回波接收之间的时间差,蝙蝠就可以轻易地判断出物体的位置。这一工…...

2018年MathorCup数学建模A题矿相特征迁移规律研究解题全过程文档及程序
2018年第八届MathorCup高校数学建模挑战赛 A题 矿相特征迁移规律研究 原题再现: 背景材料: 球团矿具有含铁品位高、粒度均匀、还原性能好、机械强度高、微气孔多等特性, 是高炉炼铁的重要原料之一。近年来国内外普遍认识到球团矿高温状态下冶金性能是评价炉料…...

如何在 Python 中创建对象列表
Python 中要创建对象列表: 声明一个新变量并将其初始化为一个空列表。使用 for 循环迭代范围对象。实例化一个类以在每次迭代时创建一个对象。将每个对象附加到列表中。 class Employee():def __init__(self, id):self.id idlist_of_objects []for i in range(5…...

Canny算法原理和应用
Canny算法的原理使用高斯滤波器滤波使用 Sobel 滤波器滤波获得在 x 和 y 方向上的输出,在此基础上求出梯度的强度和梯度的角度edge为边缘强度,tan为梯度方向上图表示的是中心点的梯度向量、方位角以及边缘方向(任一点的边缘与梯度向量正交&am…...

数据挖掘(2.2)--数据预处理
目录 二、数据描述 1.描述数据中心趋势 1.1平均值和截断均值 1.2加权平均值 1.3中位数(Median)和众数(Mode) 2.描述数据的分散程度 2.1箱线图 2.2方差和标准差 2.3正态分布 3.数据清洗 3.1数据缺失的处理 3.2数据清洗 二、数据描述 描述数…...

JVM堆与堆调优以及出现OOM如何排查
调优的位置——堆 Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。 类加载器读取了类文件后,一般会把什么东西放到堆中?类,方法,常量,变量~,保存我们所有引用类型的真实对象; 堆内存中…...

Springboot——自定义Filter使用测试总结
文章目录前言自定义过滤器并验证关于排除某些请求的方式创建测试接口请求测试验证异常过滤器的执行流程注意事项资料参考前言 在Java-web的开发领域,对于过滤器和拦截器用处还是很多,但两者的概念却极易混淆。 过滤器和拦截器都是采用AOP的核心思想&am…...

软件测试(进阶篇)(1)
一)如何根据需求来设计测试用例? 1)验证功能的正确性,合理性,无二义性,逻辑要正确 2)分析需求,细化需求,从需求中提取出测试项,根据测试项找到测试点,根据测试点具体的来进行设计测试…...

(七十三)大白话深入探索多表关联的SQL语句到底是如何执行的?(1)
今天我们来继续跟大家聊聊多表关联语句是如何执行的这个问题,上次讲了一个最最基础的两个表关联的语句和执行过程,其实今天我们稍微来复习一下,然后接着上次的内容,引入一个“内连接”的概念来。 假设我们有一个员工表࿰…...

SYSU程设c++(第三周) 对象类、类的成员、类与结构体的区别、类的静态成员
对象&类 类用于指定对象的形式,它包含数据的表示方法和用于处理数据的方法。 • 类中的数据和方法称为类的成员。 • 函数在一个类中也被称为类的成员。 定义一个类,其效果是定义一个数据类型的蓝图。它定义了类的对象包括了什么,以及可…...

Redis管道
目录 1、什么是管道? 2、案例演示 3、注意事项 4、面试题 1、什么是管道? 管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完,通过一条响应一次性将结果返回,减少 IO 的次数&…...

conda的共用package[硬链接]@pytorch和tensorflow装在同一个环境里好不好?
文章目录refpackage复用(指定同版本)conda install 比pip install 更可能节省空间pytorch和tensorflow装在同一个环境里?导入依赖导入依赖试验ref python - Can packages be shared across Anaconda environments? - Stack OverflowManaging environments — conda 23.1.0.p…...

「Vue面试题」动态给vue的data添加一个新的属性时会发生什么?怎样去解决的?
一、直接添加属性的问题 我们从一个例子开始 定义一个p标签,通过v-for指令进行遍历 然后给botton标签绑定点击事件,我们预期点击按钮时,数据新增一个属性,界面也 新增一行 <p v-for"(value,key) in item" :key&q…...

Flutter-Scaffold组件
在Flutter开发当中,我们可能会遇到以下的需求:实现页面组合使用,比如说有悬浮按钮、顶部菜单栏、左右抽屉侧边栏、底部导航栏等等效果。Scaffold组件可以帮我们实现上面需求说的效果。这篇博客主要分享容器组件的Scaffold组件的使用ÿ…...

Postman简介及接口测试流程(小菜鸟攻略)
目录 前言 一、常见接口 二、前端和后端 三、什么是接口测试 四、接口组成 1、接口说明 2、调用url 3、请求方法(get\post) 4、请求参数、参数类型、请求参数说明 5、返回参数说明 五、为什么要做接口测试 本章主要介绍如何使用postman做接口…...

kubebuilder注释
标记语法Empty kubebuilder:validation:Optional:空标记像命令行中的布尔标记位-- 仅仅是指定他们来开启某些行为。Anonymous kubebuilder:validation:MaxItems2:匿名标记使用单个值作为参数。Multioption kubebuilder:printcolumn:JSONPath".statu…...

java日志
日志是软件开发的重要组成部分。一个精心编写的日志代码提供快速的调试,维护方便,以及应用程序的运行时信息结构化存储。日志记录确实也有它的缺点。它可以减缓的应用程序Log4jLog4j是Apache的一个开放源代码项目,通过使用Log4j,我…...

研发中台拆分过程的一些心得总结
背景在 21 年,中台拆分在 21 年,以下为中台拆分的过程心得,带有一定的主观,偏向于中小团队中台建设参考(这里的中小团队指 3-100 人的团队),对于大型团队不太适用,毕竟大型团队人中 …...

HTTP介绍
HTTP1、简介HTTP概念:HyperText Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。数据传输的规则指的是请求数据和响应数据需要按照指定的格式进行传输。如果想知道具体的格式,可以打开浏览器…...

10 卷积神经网络及python实现
1 卷积神经网络简介 卷积神经网络(Convolutional Neural Network, CNN)由LeCun在上世纪90年代提出。 LeCun Y., Bottou L., Bengio Y., and Haffner P., Gradient-based learning applied to document recognition, Proceedings of the IEEE, pp. 1-7, 1998. 卷积核和特征图&…...

【立体匹配论文阅读】AANet: Adaptive Aggregation Network for Efficient Stereo Matching
Authors: Haofei Xu, Juyong Zhang Link: https://arxiv.org/abs/2004.09548 Years: 2020 Credit Novelty and Question set up 主流的立体匹配模型的代价聚合操作主要用了3D卷积,这部分操作的算力和内存消耗过大,因此作者提出一种新的模型AANet&#x…...

服务器防入侵攻击,安全加固措施
服务器防入侵攻击,安全加固措施当服务器被入侵,被攻击的时候,很多服务器维护人员不懂得如何去防止入侵,防止黑客的攻击,只能眼巴巴的看着服务器被任意的攻击,给服务器上的网站造成严重的经济损失࿰…...

解读:“出境标准合同”与“出境安全评估”要点与异同
《数据安全法》第四条及《个人信息保护法》第三章对数据出境、个人信息跨境提出明确要求,《数据安全法》与《个人信息保护法》存在互相包含、被包含、衔接、特性、独立性、相互补充等内涵。本文通过上位法互相衔接、关联、特性的思路,去观察《个人信息出…...

python带你成功复刻热门手机游戏——飞翔的小鸟
前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 飞翔的小鸟(游戏英文名:Flappy Bird) 一款由越南独立开发者开发的手机游戏,是之前非常流行的一款手机游戏 小游戏目标:让小鸟穿过管子,不要碰到任何物体…...

YOLOv8初体验:检测、跟踪、模型部署
安装 YOLOv8有两种安装方式,一种是直接用pip命令安装: pip install ultralytics另外一种是通过源码安装: git clone https://github.com/ultralytics/ultralytics cd ultralytics pip install -e .[dev]安装完成后就可以通过yolo命令在命令…...

Vue 监听(watch handler)
普通监听 缺点:不能深度监听(对象属性的改变),刷新或首次加载不能执行。 watch: { carts: function (val, oldVal) { console.log(new: %s, old: %s, val, oldVal) } } 高级监…...

前端代码质量-圈复杂度原理和实践
1. 导读 你们是否也有过下面的想法? 重构一个项目还不如新开发一个项目…这代码是谁写的,我真想… 你们的项目中是否也存在下面的问题? 单个项目也越来越庞大,团队成员代码风格不一致,无法对整体的代码质量做全面的…...

汽车微控制器芯片F280039CPZRQ1、F280039CSPM、F280039CSPN规格参数
F280039CPZRQ1、F280039CSPM、F280039CSPN是C2000实时微控制器系列中的一款器件。C2000微控制器是可扩展、超低延迟器件,旨在提高电力电子设备的效率,包括但不限于:高功率密度、高开关频率,并支持使用 GaN和SiC技术。F280039CPZRQ…...