当前位置: 首页 > article >正文

服务端定时器的学习(一)

一、定时器

1、定时器是什么?

定时器不仅存在于硬件领域,在软件层面(客户端、网页和服务端)也普遍应用,核心功能都是高效管理大量延时任务。不同应用场景下,其实现方式和使用方法有所差异。

2、定时器解决了什么问题?

可以定期清理缓存,定时备份数据,在服务器负载较大时,自动执行一些重要的功能,提高服务器的效率和稳定性。

3、是怎么解决的?

  • 组织大量延时任务的数据结构(容器)
  • 触发最近将超时的任务的机制

4、实现方式:

  • 对任务按触发时间进行排序:红黑树(map,set,multimap,multiset)–nginx,最小堆–libevent,libev,go,应用在单线程场景下
    • 1、触发时刻作为key,任务作为val
    • 2、快速找到最近要超时的任务
    • 3、触发后要删除该任务且支持随时删除任务
    • 4、允许相同时刻触发任务
  • 对执行顺序进行组织:时间轮,针对当前时间指针做偏移。–netty,skynet,kafka,应用在多线程场景下

5、有哪些常用触发机制?

  • I/O多路复用的最后一个超时参数
  • 将定时器转化为io处理,timerfd

6、使用场景

  • 与网络模块协同处理
  • 基于事件驱动业务开展
  • 除了协同网络处理,复用系统调用

二、具体实现

1、采用红黑树,对任务按触发时间进行排序

  • map<key, value>: 以key存触发时间,value存任务,那么可能存在多个同一时刻的任务,不选
  • multimap<key, value>:可能存储重复的值,然后操作起来比较麻烦,不选
  • set: 不可能出现重复的,可以
    那么以一个自定义结构作为key值进行存储,并且按触发时间进行排序
typedef struct TimerNode_S{time_t expire;//过期时间uint64_t id;    //由于可能存在多个定时器在同一时间过期,所以需要一个唯一标识}TimerNode_S;bool operator < (const TimerNode_S& lhd, const TimerNode_S& rhd)
{if(lhd.expire < rhd.expire){return true;}else if(lhd.expire > rhd.expire){return false;}else{      //如果相等,谁先插入,谁就先执行return lhd.id < rhd.id;}
}set<TimerNode, less<>> timeouts;

2、计算最近触发的定时任务离当前还有多久?

time_t TimeOut() 
{auto iter = timeouts.begin();if (iter == timeouts.end()) {return -1;}time_t t = iter->expire - GetTick();return t > 0 ? t : 0;
}

3、获取当前时间

/**
* @brief 获取当前时间的时间戳(以毫秒为单位)
*
* 使用 std::chrono::steady_clock 获取从系统启动到当前的时间
* std::chrono::system_clock,受系统时间影响,可能会被修改
*
* @return 返回当前时间的时间戳(以毫秒为单位)
*/
static inline time_t GetTick()                  
{return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();   
}

4、添加定时器

/**
* @brief 添加定时器
*
* 将一个定时器节点添加到定时器列表中,并返回该节点的标识。
*
* @param msec 定时器超时时间,单位为毫秒
* @param cb 定时器超时时执行的回调函数
*
* @return 返回定时器的标识,类型为 TimerNode_S
*/
TimerNode_S AddTimer(int msec, TimerNode::Callback cb)
{time_t expire = GetTick() + msec;       //过期时间if(timeouts.empty() || expire <= timeouts.crbegin()->expire){auto pairs = timeouts.emplace(GetID(), expire, move(cb));return static_cast<TimerNode_S>(*pairs.first);}auto ele = timeouts.emplace_hint(timeouts.crbegin().base(), GetID(), expire, move(cb));return static_cast<TimerNode_S>(*ele);
}

5、删除定时器

/**
* @brief 删除定时器节点
*
* 从定时器集合中删除指定的定时器节点。
*
* @param node 需要删除的定时器节点
*/
void DelTimer(TimerNode_S& node)
{auto iter = timeouts.find(node);if(iter != timeouts.end()){timeouts.erase(iter);}
}

6、处理定时器任务

/**
* @brief 处理超时事件
*
* 该函数遍历超时事件列表,对于已超时的每个事件,调用其回调函数进行处理,并从列表中移除该事件。
*
* @param now 当前时间戳
*/
void HandleTimeout(time_t now)
{auto iter = timeouts.begin();while(iter != timeouts.end() && iter->expire <= now){iter->cb(*iter);iter = timeouts.erase(iter);}
}struct epoll_event evs[64] = {0};
while(true){int n = epoll_wait(epfd, evs, 64, timer->TimeOut());time_t now = CTimer::GetTick();for(int j = 0; j < n; ++j){cout<<"epoll_wait:"<<endl;}timer->HandleTimeout(now);
}

在这里插入图片描述

以上是采用I/O多路复用的最后一个超时参数,接下来更换成timerfd

7、主要调用的函数

/*
*功能:创建定时器
*clockfd: CLOCK_REALTIME-系统实时时钟,与系统时间同步,受用户手动修改时间影响。CLOCK_MONOTONIC-单调递增时钟,自系统启动以来的时间,不受系统时间调整的影响
*flags:TFD_NONBLOCK(非阻塞模式)和 TFD_CLOEXEC(在 exec 调用时自动关闭文件描述符)          
*
*/
timerfd_create(int clockfd, int flags);/*
* 功能:用于设置定时器的初始超时时间和后续周期时间
* fd:timerfd
* flags: 控制定时器行为的标志:
*       0-------绝对时间
*       TFD_TIMER_ABSTIME------表示相对时间
* new_value: 指定了定时器的初始超时时间和(可选的)后续周期时间
* old_value: 用于恢复定时器到之前的状态,常设为nullptr
*/
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

8、将之前的计算触发时间的方式改成timerfd

void UpdateTimerfd(const int fd) {struct timespec abstime;auto iter = timeouts.begin();if (iter != timeouts.end()) {abstime.tv_sec = iter->expire / 1000;abstime.tv_nsec = (iter->expire % 1000) * 1000000;} else {abstime.tv_sec = 0;abstime.tv_nsec = 0;}struct itimerspec its = {.it_interval = {},.it_value = abstime};timerfd_settime(fd, TFD_TIMER_ABSTIME, &its, nullptr);
}int tfd = timerfd_create(CLOCK_MONOTONIC, 0);struct epoll_event evs[64] = {0};
while(true){timer->UpdateTimerfd(tfd);int n = epoll_wait(epfd, evs, 64, -1);time_t now = CTimer::GetTick();for(int j = 0; j < n; ++j){cout<<"epoll_wait:"<<endl;}timer->HandleTimeout(now);
}

在这里插入图片描述

三、总结

  • 定时器在程序中无处不在,无论是硬件,还是网页,客户端,服务端等。
  • 在服务端上合理地使用定时器,能提高服务器的效率和稳定性,如定时清理缓存,在服务器高负载情况下,自动执行一些重要的任务等。
  • 定时器的数据结构多种多样,有根据触发时间排序的红黑树,最小堆,也有根据执行顺序的时间轮。
  • 服务端常与网络模块协同处理
  • 服务端常基于事件驱动业务开展
  • 服务端除了协同网络处理,复用系统调用

代码:
Code
0voice·Github

相关文章:

服务端定时器的学习(一)

一、定时器 1、定时器是什么&#xff1f; 定时器不仅存在于硬件领域&#xff0c;在软件层面&#xff08;客户端、网页和服务端&#xff09;也普遍应用&#xff0c;核心功能都是高效管理大量延时任务。不同应用场景下&#xff0c;其实现方式和使用方法有所差异。 2、定时器解…...

【前端】vue 防抖和节流

在 Vue.js 中&#xff0c;防抖&#xff08;Debounce&#xff09; 和 节流&#xff08;Throttle&#xff09; 是优化高频事件&#xff08;如输入、滚动、点击&#xff09;的核心技术&#xff0c;可显著提升性能与用户体验。以下是具体实现方法和最佳实践&#xff1a; ⏳ 一、防抖…...

Modbus转EtherNET IP网关开启节能改造新范式

在现代工业生产和能源管理中&#xff0c;无锡耐特森Modbus转EtherNET IP网关MCN-EN3001发挥着至关重要的作用。通过将传统的串行通信协议Modbus转换为基于以太网的EtherNET IP协议&#xff0c;这种网关设备不仅提高了数据传输的效率&#xff0c;而且为能源管理和控制系统的现代…...

Android高级开发第四篇 - JNI性能优化技巧和高级调试方法

文章目录 Android高级开发第四篇 - JNI性能优化技巧和高级调试方法引言为什么JNI性能优化如此重要&#xff1f;第一部分&#xff1a;JNI性能基础知识JNI调用的性能开销何时使用JNI才有意义&#xff1f; 第二部分&#xff1a;核心性能优化技巧1. 减少JNI调用频率2. 高效的数组操…...

【PCB工艺】绘制原理图 + PCB设计大纲:最小核心板STM32F103ZET6

绘制原理图和PCB布线之间的联系,在绘制原理图的时候,考虑到后续的PCB设计+嵌入式软件代码的业务逻辑,需要在绘制原理图之初涉及到 硬件设计流程的前期规划。在嵌入式系统开发中,原理图设计是整个项目的基础,直接影响到后续的: PCB 布线效率和质量 ☆☆☆重点嵌入式软件的…...

C#入门学习笔记 #7(传值/引用/输出/数组/具名/可选参数、扩展方法(this参数))

欢迎进入这篇文章,文章内容为学习C#过程中做的笔记,可能有些内容的逻辑衔接不是很连贯,但还是决定分享出来,由衷的希望可以帮助到你。 笔记内容会持续更新~~ 本篇介绍各种参数,参数本质上属于方法的一部分,所以本篇算是对方法更深度的学习。本章难度较大... 传值参数 …...

【DeepSeek】【Dify】:用 Dify 对话流+标题关键词注入,让 RAG 准确率飞跃

1 构建对话流处理数据 初始准备 文章大纲摘要 数据标注和清洗 代码执行 特别注解 2 对话流测试 准备工作 大纲生成 清洗片段 整合分段 3 构建知识库 构建 召回测试 4 实战应用测试 关键词提取 智能总结 测试 1 构建对话流处理数据 初始准备 构建对话变量 用…...

DELETE 与 TRUNCATE、DROP 的区别

DELETE 与 TRUNCATE、DROP 的区别 1. 基本概念 1.1 DELETE DELETE 是标准的 DML(数据操作语言) 命令,用于从表中删除特定行或所有行数据,但保留表结构。 go专栏:https://duoke360.com/tutorial/path/golang 1.2 TRUNCATE TRUNCATE 是 DDL(数据定义语言) 命令,用于快速…...

yFiles:专业级图可视化终极解决方案

以下是对yFiles的详细介绍,结合其定义、功能、技术特点、应用场景及行业评价等多维度分析: 一、yFiles的定义与核心定位 yFiles是由德国公司yWorks GmbH开发的 动态图与网络可视化软件开发工具包(SDK) ,专注于帮助用户将复杂数据转化为交互式图表。其核心价值在于提供跨平…...

VSCode 工作区配置文件通用模板创建脚本

下面是分别使用 Python 和 Shell&#xff08;Bash&#xff09;脚本 自动生成 .vscode 文件夹及其三个核心配置文件&#xff08;settings.json、tasks.json、launch.json&#xff09;的完整示例。 你可以选择你熟悉的语言版本来使用&#xff0c;非常适合自动化项目初始化流程。…...

echarts显示/隐藏标签的同时,始终显示饼图中间文字

显示标签的同时&#xff0c;始终显示饼图中间文字 let _data this.chartData.slice(1).map((item) > ({name: item.productName,value: Number(item.stock), })); this.chart.setOption({tooltip: {trigger: item,},graphic: { // 重点在这里&#xff08;显示饼图中间文字&…...

【Spring AI】调用 DeepSeek 实现问答聊天

文章目录 一、基础概念解析1.Spring AI 框架2.DeepSeek 模型 二、开发环境搭建1.JDK 环境准备2.开发工具选择 三、项目创建与依赖添加1.创建 Spring Boot 项目2.DeepSeek API 四、代码编写实现调用1.创建问答服务类2.创建 Controller 类3.前端调用示例 五、项目运行与调试 在人…...

Java消息队列与安全实战:谢飞机的烧饼摊故事

Java消息队列与安全实战&#xff1a;谢飞机的烧饼摊故事 第一轮&#xff1a;消息队列与缓存 面试官&#xff1a;谢飞机&#xff0c;Kafka和RabbitMQ在电商场景如何选型&#xff1f; 谢飞机&#xff1a;&#xff08;摸出烧饼&#xff09;Kafka适合订单日志处理&#xff0c;像…...

parquet :开源的列式存储文件格式

1. Parquet文件定义与核心概念 Parquet是一种开源的列式存储文件格式,由Twitter和Cloudera合作开发,2015年成为Apache顶级项目。其设计目标是为大数据分析提供高效存储和查询,主要特点包括: 列式存储:数据按列而非按行组织,相同数据类型集中存储,显著提升分析查询效率(…...

SpringBoot关于文件上传超出大小限制--设置了全局异常但是没有正常捕获的情况+捕获后没有正常响应返给前端

项目背景 一个档案管理系统&#xff0c;在上传比较大的文件时由于系统设置的文件大小受限导致文件上传不了&#xff0c;这时候设置的异常捕捉未能正常报错导致前端页面一直在转圈&#xff0c;实际上后端早已校验完成。 全局异常类设置的捕捉 添加了ControllerAdvice以及RestCon…...

【Go语言】Ebiten游戏库开发者文档 (v2.8.8)

1. 简介 欢迎来到 Ebiten (现已更名为 Ebitengine) 的世界&#xff01;Ebiten 是一个使用 Go 语言编写的开源、极其简洁的 2D 游戏库&#xff08;或称为游戏引擎&#xff09;。它由 Hajime Hoshi 发起并主要维护&#xff0c;旨在提供一套简单直观的 API&#xff0c;让开发者能…...

Spring Boot应用开发实战

Spring Boot应用开发实战&#xff1a;从零到生产级项目的深度指南 在当今Java生态中&#xff0c;Spring Boot已占据绝对主导地位——据统计&#xff0c;超过75%的新Java项目选择Spring Boot作为开发框架。本文将带您从零开始&#xff0c;深入探索Spring Boot的核心精髓&#xf…...

实验设计与分析(第6版,Montgomery著,傅珏生译) 第9章三水平和混合水平析因设计与分式析因设计9.5节思考题9.1 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第9章三水平和混合水平析因设计与分式析因设计9.5节思考题9.1 R语言解题。主要涉及方差分析。 YieldDesign <-expand.grid(A gl(3, 1, labels c("-", "0","…...

Pycharm 配置解释器

今天更新了一版pycharm&#xff0c;因为很久没有配置解释器了&#xff0c;发现一直失败。经过来回试了几次终于成功了&#xff0c;记录一下过程。 Step 1 Step 2 这里第二步一定要注意类型要选择python 而不是conda。 虽然我的解释器是conda 里面建立的一个环境。挺有意思的...

learn react course

从零开始构建 React 应用 – React 中文文档 弃用 Create React App 虽然 Create React App 让入门变得简单&#xff0c;但其存在的若干限制 使得构建高性能的生产级应用颇具挑战。理论上&#xff0c;我们可以通过将其逐步发展为 框架 的方式来解决这些问题。 然而&#xff…...

SQL进阶之旅 Day 11:复杂JOIN查询优化

【SQL进阶之旅 Day 11】复杂JOIN查询优化 在数据处理日益复杂的今天&#xff0c;JOIN操作作为SQL中最强大的功能之一&#xff0c;常常成为系统性能瓶颈。今天我们进入"SQL进阶之旅"系列的第11天&#xff0c;将深入探讨复杂JOIN查询的优化策略。通过本文学习&#xf…...

web第八次课后作业--分层解耦

一、分层 Controller&#xff1a;控制层。接收前端发送的请求&#xff0c;对请求进行处理&#xff0c;并响应数据。Service&#xff1a;业务逻辑层。处理具体的业务逻辑。Dao&#xff1a;数据访问层(Data Access Object)&#xff0c;也称为持久层。负责数据访问操作&#xff0…...

MySQL 事务深度解析:面试核心知识点与实战

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Java 中 MySQL 事务深度解析&#xff1a;面试…...

使用Redis作为缓存,提高MongoDB的读写速度

在现代Web应用中,随着数据量和访问量的增长,数据库性能常常成为系统瓶颈。MongoDB作为NoSQL数据库,虽然具备高扩展性和灵活性,但在某些读密集型场景下仍可能遇到性能问题。 本文将介绍如何使用Redis作为缓存层来显著提升MongoDB的读写性能,包括架构设计、详细设计、Pytho…...

【图片自动识别改名】识别图片中的文字并批量改名的工具,根据文字对图片批量改名,基于QT和腾讯OCR识别的实现方案

现在的工作单位经常搞一些意义不明的绩效工作&#xff0c;每个月都搞来一万多张图片让我们挨个打开对应图片上的名字进行改名操作以方便公司领导进行检查和搜索调阅&#xff0c;图片上面的内容有数字和文字&#xff0c;数字没有特殊意义不做识别&#xff0c;文字有手写的和手机…...

Kafka消息队列笔记

一、Kafka 核心架构 四大组件 Producer&#xff1a;发布消息到指定 Topic。 Consumer&#xff1a;订阅 Topic 并消费消息&#xff08;支持消费者组并行&#xff09;。 Broker&#xff1a;Kafka 服务器节点&#xff0c;存储消息&#xff0c;处理读写请求。 ZooKeeper/KRaft&a…...

机器人变量类型与配置

机器人变量类型与配置 机器人变量类型与配置知识 1. 变量类型 1.1 按创建位置分类 程序变量&#xff1a; 仅适用于当前运行程序程序停止后变量值丢失可在赋值程序节点中直接创建 配置变量&#xff1a; 可用于多个程序变量名和值在机器人安装期间持续存在需预先在配置变量界面…...

nssm配置springboot项目环境,注册为windows服务

NSSM 的官方下载地址是&#xff1a;NSSM - the Non-Sucking Service Manager1 使用powershell输入命令,java项目需要手动配置和依赖nacos .\nssm.exe install cyMinio "D:\minio\启动命令.bat" .\nssm.exe install cyNacos "D:\IdeaProject\capacity\nacos-s…...

20-项目部署(Docker)

在昨天的课程中&#xff0c;我们学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目。大家想一想自己最大的感受是什么&#xff1f; 我相信&#xff0c;除了个别天赋异禀的同学以外&#xff0c;大多数同学都会有相同的…...

Python学习(6) ----- Python2和Python3的区别

Python2 和 Python3 是两个主要版本的 Python 编程语言&#xff0c;它们之间有许多重要的区别。Python3 是对 Python2 的一次重大升级&#xff0c;不完全兼容旧版本。以下是它们的主要区别&#xff1a; &#x1f9f5; 基本语法差异 1. 打印语法 Python2&#xff1a;print 是一…...