RocketMQ延迟消息机制
两种延迟消息
RocketMQ中提供了两种延迟消息机制
- 指定固定的延迟级别
通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别 - 指定时间点的延迟级别
通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后,RocketMQ会自动发送消息。
在brokerController初始化时对两种延迟消息的处理进行了初始化
public boolean recoverAndInitService() throws CloneNotSupportedException {...if (messageStore != null) {registerMessageStoreHook();//注册一系列的钩子其中有处理指定固定的延迟级别的钩子result = this.messageStore.load();}if (messageStoreConfig.isTimerWheelEnable()) {result = result && this.timerMessageStore.load();//处理指定时间点的延迟消息}...
}
固定延迟级别的延迟消息
这类延迟消息由一个很重要的后台服务scheduleMessageService来管理。 他会在broker启动时也一起加载。
public void start() {if (started.compareAndSet(false, true)) {//cas防止重复启动this.load();this.deliverExecutorService = ThreadUtils.newScheduledThreadPool(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageTimerThread_"));if (this.enableAsyncDeliver) {this.handleExecutorService = ThreadUtils.newScheduledThreadPool(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageExecutorHandleThread_"));}for (Map.Entry<Integer, Long> entry : this.delayLevelTable.entrySet()) {//循环所有级别的延迟来开启任务Integer level = entry.getKey();Long timeDelay = entry.getValue();Long offset = this.offsetTable.get(level);if (null == offset) {offset = 0L;}if (timeDelay != null) {if (this.enableAsyncDeliver) {//是否异步投递默认falsethis.handleExecutorService.schedule(new HandlePutResultTask(level), FIRST_DELAY_TIME, TimeUnit.MILLISECONDS);}//处理固定延迟级别的消息this.deliverExecutorService.schedule(new DeliverDelayedMessageTimerTask(level, offset), FIRST_DELAY_TIME, TimeUnit.MILLISECONDS);}}//延迟消息持久化scheduledPersistService.scheduleAtFixedRate(() -> {try {ScheduleMessageService.this.persist();} catch (Throwable e) {log.error("scheduleAtFixedRate flush exception", e);}}, 10000, this.brokerController.getMessageStoreConfig().getFlushDelayOffsetInterval(), TimeUnit.MILLISECONDS);}}
Broker在处理消息之前,会注册一系列的钩子,类似于过滤器,对消息做一些预处理。其中就会对延迟消息做处理。
HookUtils中有一个方法,就会在Broker处理消息之前对延迟消息做一些特殊处理。
public static PutMessageResult handleScheduleMessage(BrokerController brokerController,final MessageExtBrokerInner msg) {final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE|| tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {if (!isRolledTimerMessage(msg)) {if (checkIfTimerMessage(msg)) {if (!brokerController.getMessageStoreConfig().isTimerWheelEnable()) {//wheel timer is not enabled, reject the messagereturn new PutMessageResult(PutMessageStatus.WHEEL_TIMER_NOT_ENABLE, null);}PutMessageResult transformRes = transformTimerMessage(brokerController, msg);//转移指定时间点的延迟消息if (null != transformRes) {return transformRes;}}}// Delay Deliveryif (msg.getDelayTimeLevel() > 0) {//获取延迟级别大于零代表设置了transformDelayLevelMessage(brokerController, msg);}}return null;}
对于固定延迟级别的延迟消息会转移到系统内置的Topic中。转移到SCHEDULE_TOPIC_XXXX Topic中,对列对应延迟级别。
public static void transformDelayLevelMessage(BrokerController brokerController, MessageExtBrokerInner msg) {if (msg.getDelayTimeLevel() > brokerController.getScheduleMessageService().getMaxDelayLevel()) {msg.setDelayTimeLevel(brokerController.getScheduleMessageService().getMaxDelayLevel());}//保留消息的原始Topic和队列以属性形式来存储// Backup real topic, queueIdMessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));//修改成系统内置的Topic和队列msg.setTopic(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC);msg.setQueueId(ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel()));}
scheduleMessageService.start()延迟启动了任务DeliverDelayedMessageTimerTask在里面再设置下一次延时任务。
public void executeOnTimeUp() {//获取从CommitLog中获取RMQ_SYS_SCHEDULE_TOPIC这个topic下的队列ConsumeQueueInterface cq =ScheduleMessageService.this.brokerController.getMessageStore().getConsumeQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC,delayLevel2QueueId(delayLevel));if (cq == null) {this.scheduleNextTimerTask(this.offset, DELAY_FOR_A_WHILE);return;}ReferredIterator<CqUnit> bufferCQ = cq.iterateFrom(this.offset);if (bufferCQ == null) {long resetOffset;if ((resetOffset = cq.getMinOffsetInQueue()) > this.offset) {log.error("schedule CQ offset invalid. offset={}, cqMinOffset={}, queueId={}",this.offset, resetOffset, cq.getQueueId());} else if ((resetOffset = cq.getMaxOffsetInQueue()) < this.offset) {log.error("schedule CQ offset invalid. offset={}, cqMaxOffset={}, queueId={}",this.offset, resetOffset, cq.getQueueId());} else {resetOffset = this.offset;}this.scheduleNextTimerTask(resetOffset, DELAY_FOR_A_WHILE);return;}long nextOffset = this.offset;try {while (bufferCQ.hasNext() && isStarted()) {CqUnit cqUnit = bufferCQ.next();long offsetPy = cqUnit.getPos();int sizePy = cqUnit.getSize();long tagsCode = cqUnit.getTagsCode();if (!cqUnit.isTagsCodeValid()) {//can't find ext content.So re compute tags code.log.error("[BUG] can't find consume queue extend file content!addr={}, offsetPy={}, sizePy={}",tagsCode, offsetPy, sizePy);long msgStoreTime = ScheduleMessageService.this.brokerController.getMessageStore().getCommitLog().pickupStoreTimestamp(offsetPy, sizePy);tagsCode = computeDeliverTimestamp(delayLevel, msgStoreTime);}long now = System.currentTimeMillis();long deliverTimestamp = this.correctDeliverTimestamp(now, tagsCode);//消息的tagsCode,它在延时消息中被复用为原始消息的投递时间戳long currOffset = cqUnit.getQueueOffset();assert cqUnit.getBatchNum() == 1;nextOffset = currOffset + cqUnit.getBatchNum();long countdown = deliverTimestamp - now;if (countdown > 0) {//如果大于零说明还没时间,直接进入下一轮延迟任务this.scheduleNextTimerTask(currOffset, DELAY_FOR_A_WHILE);ScheduleMessageService.this.updateOffset(this.delayLevel, currOffset);return;}MessageExt msgExt = ScheduleMessageService.this.brokerController.getMessageStore().lookMessageByOffset(offsetPy, sizePy);if (msgExt == null) {continue;}MessageExtBrokerInner msgInner = ScheduleMessageService.this.messageTimeUp(msgExt);if (TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC.equals(msgInner.getTopic())) {//如果是事务消息则不支持延迟,跳过此消息log.error("[BUG] the real topic of schedule msg is {}, discard the msg. msg={}",msgInner.getTopic(), msgInner);continue;}boolean deliverSuc;if (ScheduleMessageService.this.enableAsyncDeliver) {//异步投递,默认falsedeliverSuc = this.asyncDeliver(msgInner, msgExt.getMsgId(), currOffset, offsetPy, sizePy);} else {//同步投递消息,获取当前broker是否为主节点,是则投递到当前节点对应topic的queuedeliverSuc = this.syncDeliver(msgInner, msgExt.getMsgId(), currOffset, offsetPy, sizePy);}if (!deliverSuc) {this.scheduleNextTimerTask(currOffset, DELAY_FOR_A_WHILE);return;}}} catch (Exception e) {log.error("ScheduleMessageService, messageTimeUp execute error, offset = {}", nextOffset, e);} finally {bufferCQ.release();}this.scheduleNextTimerTask(nextOffset, DELAY_FOR_A_WHILE);}
指定时间点的延迟消息
这类延迟消息通过时间轮算法进行定时计算核心类就是TimerMessageStore对象中加载的六个核心线程
public void initService() {//五个核心服务线程enqueueGetService = new TimerEnqueueGetService();//定时从TIMER_TOPIC的消费队列中扫描消息,将延迟消息封装为 TimerRequest 放入enqueuePutQueue(enqueue方法)enqueuePutService = new TimerEnqueuePutService();//从enqueuePutQueue消费TimerRequest,判断消息是否已过,未过期消息写入 TimerLog 和时间轮,过期消息直接转发给消费服务dequeueGetService = new TimerDequeueGetService();//调用 dequeue() 扫描当前时间槽(currReadTimeMs)放入dequeuePutQueue,通过 moveReadTime() 推进时间指针timerFlushService = new TimerFlushService();//定时刷盘 TimerLog 和 TimerWheelint getThreadNum = Math.max(storeConfig.getTimerGetMessageThreadNum(), 1);dequeueGetMessageServices = new TimerDequeueGetMessageService[getThreadNum];for (int i = 0; i < dequeueGetMessageServices.length; i++) {dequeueGetMessageServices[i] = new TimerDequeueGetMessageService();//负责从延迟消息队列中获取消息,触发消息的消费流程。}int putThreadNum = Math.max(storeConfig.getTimerPutMessageThreadNum(), 1);dequeuePutMessageServices = new TimerDequeuePutMessageService[putThreadNum];for (int i = 0; i < dequeuePutMessageServices.length; i++) {dequeuePutMessageServices[i] = new TimerDequeuePutMessageService();//负责将发送的延迟消息从延迟队列(Timer Queue)中取出,然后放入真正的消息队列中,供消费者消费。}}
这五个核心Service会结合TimeMeessageStore中的几个核心队列来进行操作。
protected final BlockingQueue<TimerRequest> enqueuePutQueue;protected final BlockingQueue<List<TimerRequest>> dequeueGetQueue;protected final BlockingQueue<TimerRequest> dequeuePutQueue;private final TimerWheel timerWheel;//时间轮private final TimerLog timerLog;//持久化到磁盘用于崩溃恢复protected volatile long currReadTimeMs;//读指针,TimerDequeueGetService找出到时间的延迟消息并且更新时间protected volatile long currWriteTimeMs;//写指针,在TimerEnqueuePutService把延迟消息放入时间轮的时候更新时间
TimeMeessageStore内部类TimerFlushService就负责每隔1s处理checkpoint和时间轮刷盘
public void run() {...timerLog.getMappedFileQueue().flush(0);//延迟消息的实际数据文件刷盘timerWheel.flush();//时间轮刷盘timerCheckpoint.flush();//checkpoint...waitForRunning(storeConfig.getTimerFlushIntervalMs());//休眠1000...}
他们之间的关系
TimeWheel时间轮组件算法有两个核心:
- 数据按照预设的过期时间,放到对应的slot上(时钟表上的每个秒钟刻度)。
public MessageExtBrokerInner convert(MessageExt messageExt, long enqueueTime, boolean needRoll) {if (enqueueTime != -1) {MessageAccessor.putProperty(messageExt, TIMER_ENQUEUE_MS, enqueueTime + "");}if (needRoll) {if (messageExt.getProperty(TIMER_ROLL_TIMES) != null) {//记录滚动了的次数MessageAccessor.putProperty(messageExt, TIMER_ROLL_TIMES, Integer.parseInt(messageExt.getProperty(TIMER_ROLL_TIMES)) + 1 + "");} else {MessageAccessor.putProperty(messageExt, TIMER_ROLL_TIMES, 1 + "");}}MessageAccessor.putProperty(messageExt, TIMER_DEQUEUE_MS, System.currentTimeMillis() + "");MessageExtBrokerInner message = convertMessage(messageExt, needRoll);return message;}
- 时间轮上设置一个指针变量(钟表上的秒钟),指针会按固定时间前进。指针指向的Slot(指向的刻度),就是当前已经到期的数据。
对于指定时间点的延迟消息也会转移到系统内置的Topic中。转移到TIMER_TOPIC(rmq_sys_wheel_timer) Topic中,队列ID固定为0。
private static PutMessageResult transformTimerMessage(BrokerController brokerController,MessageExtBrokerInner msg) {//do transformint delayLevel = msg.getDelayTimeLevel();long deliverMs;try {if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) {deliverMs = System.currentTimeMillis() + Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)) * 1000;} else if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) != null) {deliverMs = System.currentTimeMillis() + Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS));} else {deliverMs = Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS));}} catch (Exception e) {return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null);}if (deliverMs > System.currentTimeMillis()) {if (delayLevel <= 0 && deliverMs - System.currentTimeMillis() > brokerController.getMessageStoreConfig().getTimerMaxDelaySec() * 1000L) {return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null);}int timerPrecisionMs = brokerController.getMessageStoreConfig().getTimerPrecisionMs();if (deliverMs % timerPrecisionMs == 0) {deliverMs -= timerPrecisionMs;} else {deliverMs = deliverMs / timerPrecisionMs * timerPrecisionMs;}if (brokerController.getTimerMessageStore().isReject(deliverMs)) {return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_FLOW_CONTROL, null);}MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TIMER_OUT_MS, deliverMs + "");MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));msg.setTopic(TimerMessageStore.TIMER_TOPIC);msg.setQueueId(0);//固定0号队列} else if (null != msg.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)) {return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null);}return null;}
数据结构:
- TimerWheel整体是一个数组,工作原理可以理解为一个时钟盘。盘上的每个刻度是一个slot。每个slot记录一条数据的索引。所有具体的消息数据都是放到一个LocalBuffer缓存数组中的。每个Slot就描述一条或多条LocalBuffer上的具体消息数据。Slot存放了firstPos(第一个消息在timerLog的起始地址)和lastPos(最后一个消息在在timerLog的起始地址)
public class Slot {public static final short SIZE = 32;//槽位总大小(字节数),固定为32字节public final long timeMs; //该槽位对应的延迟时间(毫秒级时间戳),表示这个槽位中的消息应该在这个时间点被投递public final long firstPos;//该槽位中第一条消息在TimerLog中的物理偏移量(起始位置)public final long lastPos;//该槽位中最后一条消息在TimerLog中的物理偏移量(结束位置) public final int num;//该槽位中当前包含的消息数量public final int magic; //no use now, just keep it.标志位(目前未使用,保留字段) 可能的用途:标记特殊类型的消息(如需要滚动的消息)...
}
- TimerLog存储了所有指定时间点的延时消息元数据信息,每个延迟消息元数据信息存储了上一个元数据信息的起始地址形成链表,通过元数据信息可以到commitLog获取消息数据
public class TimerLog {...public final static int UNIT_SIZE = 4 //size+ 8 //prev pos上一个元信息的起始地址+ 4 //magic value(消息类型标记,滚动标记和删除标记)+ 8 //curr write time, for trace(写入时间)+ 4 //delayed time, for check(延时时间差)+ 8 //offsetPy(物理偏移量)+ 4 //sizePy(消息大小)+ 4 //hash code of real topic(主题哈希)+ 8; //reserved value, just in case of (保留字段)...
- 在TimerMessageStore中有两个变量currReadTimeMs 和 currReadTimeMs。 这两个指针就类似于时钟上的指针。其中,currWriteTimeMs指向当前正在写入数据的slot。 而currReadTimeMs指向当前正在读取数据的slot。这两个变量不断往前变化,就可以像时钟的指针一样依次读取每一秒上的数据。这时候读到的slot是可以表示当前这一秒的数据 ,还有 时间轮转过多轮后的数据。
相关文章:

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...