【RocketMQ】源码详解:消息储存服务加载、文件恢复、异常恢复
消息储存服务加载
入口:org.apache.rocketmq.store.DefaultMessageStore#load
在创建brokerContriller时会调用初始化方法初始化brokerController,在初始化方法中会进行消息储存服务的加载
this.messageStore.load();加载方法主要是加载一些必要的参数和数据,如配置文件、消费进度、commitlog文件、consumequeu文件等
加载commitlog等文件,实际上就是创建MappedFile对象,MappedFile会创建与物理文件之间的映射(buffer)与通道(channel)
除此之外, 还会恢复commitlog文件、consumequeue文件, 恢复是指, 检索文件中的每一条消息, 查看是否有效: commitlog中的消息是否符合规范 , consumeQueue中的数据是否符合规范每条是否为20字节。因为消息、数据都是从文件最后开始添加的,故只需文件尾部不符合规范的数据删除掉,即可。
在broker启动的时候会在broker储存目录下创建一个abort文件, 正常退出时则会删除abort文件,broker启动的时候会判断是否存在这个文件来判断是否是正常退出,还是意外宕机。
是否正常退出, 只关系到broker恢复commitlog文件的过程:
若为正常退出,则从倒数第三个文件开始向后检索,若不足三个则从第一个开始向后遍历。找到第一条错误数据,删除其之后的数据,并重新设置刷盘点
若为异常退出,则从最后一个文件开始,向前根据文件魔数等找到一个正常的文件,从该文件开始向后遍历,正常的消息重新发送到consumequeque和indexFile。若为错误数据,则删除其之后的数据,并重新设置刷盘点
同时,两种恢复路径的最后,都会以恢复的commitlog文件的最大有效消息的偏移量为准,来删除consumequeue那边的无效数据
public boolean load() {boolean result = true;try {// 通过判断 abort 文件是否存在,判断是否上次为正常退出。不存在 - 正常boolean lastExitOK = !this.isTempFileExist();log.info("last shutdown {}", lastExitOK ? "normally" : "abnormally");// 加载延时队列,延时消息, 包括延时等级、配置文件if (null != scheduleMessageService) {result = result && this.scheduleMessageService.load();}// load Commit Log// 加载commitlog mappedFile(commitlog文件夹下的)result = result && this.commitLog.load();// load Consume Queue// 加载consumeQueue mappedFile, (store\consumequeue目录下的)result = result && this.loadConsumeQueue();if (result) {this.storeCheckpoint =new StoreCheckpoint(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir()));this.indexService.load(lastExitOK);// 恢复文件this.recover(lastExitOK);log.info("load over, and the max phy offset = {}", this.getMaxPhyOffset());}} catch (Exception e) {log.error("load exception", e);result = false;}if (!result) {this.allocateMappedFileService.shutdown();}return result;
}
加载commitlog文件
加载commitlog文件: org.apache.rocketmq.store.CommitLog#load
public boolean load() {// commitlog文件在代码是一个MappedFile,通过MappedFileQueue进行管理// consumequeue、indexFile文件同样也是MappedFileboolean result = this.mappedFileQueue.load();log.info("load commit log " + (result ? "OK" : "Failed"));return result;
}
public boolean load() {// 遍历文件夹下面的文件File dir = new File(this.storePath);File[] files = dir.listFiles();if (files != null) {// ascending order// 按文件名从大到小排序,rocketmq种的文件都是以数字、文件的最小偏移量命名Arrays.sort(files);for (File file : files) {if (file.length() != this.mappedFileSize) {log.warn(file + "\t" + file.length()+ " length not matched message store config value, please check it manually");return false;}try {// 实例化mappedFileMappedFile mappedFile = new MappedFile(file.getPath(), mappedFileSize);mappedFile.setWrotePosition(this.mappedFileSize);mappedFile.setFlushedPosition(this.mappedFileSize);mappedFile.setCommittedPosition(this.mappedFileSize);this.mappedFiles.add(mappedFile);log.info("load " + file.getPath() + " OK");} catch (IOException e) {log.error("load file " + file + " error", e);return false;}}}return true;
}
构造MappedFile
public MappedFile(final String fileName, final int fileSize) throws IOException {// 根据文件名和文件大小初始化mappedFileinit(fileName, fileSize);
}
javaprivate void init(final String fileName, final int fileSize) throws IOException {this.fileName = fileName;this.fileSize = fileSize;this.file = new File(fileName);this.fileFromOffset = Long.parseLong(this.file.getName());boolean ok = false;ensureDirOK(this.file.getParent());try {// 根据文件创建channelthis.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);// 文件总内存计数增加TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);// 文件总数计数增加TOTAL_MAPPED_FILES.incrementAndGet();ok = true;} catch (FileNotFoundException e) {log.error("Failed to create file " + this.fileName, e);throw e;} catch (IOException e) {log.error("Failed to map file " + this.fileName, e);throw e;} finally {if (!ok && this.fileChannel != null) {this.fileChannel.close();}}
}
恢复文件
恢复文件: org.apache.rocketmq.store.DefaultMessageStore#recover
private void recover(final boolean lastExitOK) {/*** maxPhyOffsetOfConsumeQueue 最大的有效的commitlog消息物理偏移量,就是指的最后一个有效条目中保存的commitlog文件中的物理偏移量,** 文件组自身的最大有效数据偏移量processOffset,就是指的最后一个有效条目在自身文件组中的偏移量* (比如:queueID文件夹下的连续的文件,或commitlog目录下的连续commitlog文件)。*/// 恢复ConsumeQueue文件,并返回consumeQueue最大有效commitlog文件偏移量long maxPhyOffsetOfConsumeQueue = this.recoverConsumeQueue();if (lastExitOK) {// 正常退出,恢复文件this.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue);} else {// 异常退出,恢复文件this.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue);}// 恢复topicQueueTablethis.recoverTopicQueueTable();
}
恢复consumequeue文件
org.apache.rocketmq.store.DefaultMessageStore#recoverConsumeQueue
恢复consumequeue文件,会遍历每个topic,然后遍历topic下的所有queue的文件夹,然后以consumequeue文件夹为维度恢复
从倒数第三个文件开始恢复,依次检查每条数据,是否符合8字节偏移量、4字节消息大小、8字节tag的规则,找到异常数据,则删除其之后的数据。
private long recoverConsumeQueue() {long maxPhysicOffset = -1;// 遍历topicfor (ConcurrentMap<Integer, ConsumeQueue> maps : this.consumeQueueTable.values()) {// 遍历topic下的queuefor (ConsumeQueue logic : maps.values()) {// 恢复这个queueId下的文件logic.recover();if (logic.getMaxPhysicOffset() > maxPhysicOffset) {maxPhysicOffset = logic.getMaxPhysicOffset();}}}// 返回所有queue中消息,在commitLog文件中的最大的偏移量// 也就是,commitLog同步到queue中的最后一条消息的偏移量return maxPhysicOffset;
}
public void recover() {final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();if (!mappedFiles.isEmpty()) {// 从倒数第三个开始,如果少于三个就从第一个开始int index = mappedFiles.size() - 3;if (index < 0)index = 0;int mappedFileSizeLogics = this.mappedFileSize;MappedFile mappedFile = mappedFiles.get(index);ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();long processOffset = mappedFile.getFileFromOffset();long mappedFileOffset = 0;long maxExtAddr = 1;while (true) {// 循环每一条消息索引,大小为 20 字节for (int i = 0; i < mappedFileSizeLogics; i += CQ_STORE_UNIT_SIZE) {// 这条在commitLog文件中的的偏移量long offset = byteBuffer.getLong();// 大小int size = byteBuffer.getInt();long tagsCode = byteBuffer.getLong();// 如果offset和size都大于0则表示当前条目有效if (offset >= 0 && size > 0) {mappedFileOffset = i + CQ_STORE_UNIT_SIZE;this.maxPhysicOffset = offset + size;if (isExtAddr(tagsCode)) {maxExtAddr = tagsCode;}} else {log.info("recover current consume queue file over, " + mappedFile.getFileName() + " "+ offset + " " + size + " " + tagsCode);break;}}// 如果当前ConsumeQueue文件中的有效数据偏移量和文件大小一样,则表示该ConsumeQueue文件的所有条目都是有效的if (mappedFileOffset == mappedFileSizeLogics) {// 下一个文件index++;// 如果是最后一个了,就退出if (index >= mappedFiles.size()) {log.info("recover last consume queue file over, last mapped file "+ mappedFile.getFileName());break;} else {// 获取下一个文件的数据mappedFile = mappedFiles.get(index);byteBuffer = mappedFile.sliceByteBuffer();processOffset = mappedFile.getFileFromOffset();mappedFileOffset = 0;log.info("recover next consume queue file, " + mappedFile.getFileName());}} else {log.info("recover current consume queue queue over " + mappedFile.getFileName() + " "+ (processOffset + mappedFileOffset));break;}}// 文件名偏移量 + 这个文件最大有效的数据的偏移量, 就是这个queue最后一条消息的整体的偏移量processOffset += mappedFileOffset;this.mappedFileQueue.setFlushedWhere(processOffset);this.mappedFileQueue.setCommittedWhere(processOffset);// 删除这个偏移量之后的数据this.mappedFileQueue.truncateDirtyFiles(processOffset);if (isExtReadEnable()) {this.consumeQueueExt.recover();log.info("Truncate consume queue extend file by max {}", maxExtAddr);this.consumeQueueExt.truncateByMaxAddress(maxExtAddr);}}
}
正常恢复commitlog文件
public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();if (!mappedFiles.isEmpty()) {// Began to recover from the last third fileint index = mappedFiles.size() - 3;if (index < 0)index = 0;MappedFile mappedFile = mappedFiles.get(index);ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();long processOffset = mappedFile.getFileFromOffset();long mappedFileOffset = 0;while (true) {// 生成DispatchRequest,验证本条消息是否合法DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);int size = dispatchRequest.getMsgSize();// Normal data// 正常,且size>0if (dispatchRequest.isSuccess() && size > 0) {// 更新mappedFileOffset的值加上本条消息长度mappedFileOffset += size;}// Come the end of the file, switch to the next file Since the// return 0 representatives met last hole,// this can not be included in truncate offset// 正常,size=0,说明到了文件末尾else if (dispatchRequest.isSuccess() && size == 0) {index++;if (index >= mappedFiles.size()) {// Current branch can not happenlog.info("recover last 3 physics file over, last mapped file " + mappedFile.getFileName());break;} else {mappedFile = mappedFiles.get(index);byteBuffer = mappedFile.sliceByteBuffer();processOffset = mappedFile.getFileFromOffset();mappedFileOffset = 0;log.info("recover next physics file, " + mappedFile.getFileName());}}// Intermediate file read error// 消息错误else if (!dispatchRequest.isSuccess()) {log.info("recover physics file end, " + mappedFile.getFileName());break;}}// 即最后一条消息的尾部位置processOffset += mappedFileOffset;// 设置刷盘点this.mappedFileQueue.setFlushedWhere(processOffset);this.mappedFileQueue.setCommittedWhere(processOffset);/** 删除文件最大有效数据偏移量processOffset之后的所有数据*/this.mappedFileQueue.truncateDirtyFiles(processOffset);// Clear ConsumeQueue redundant data// 如果consumequeue那边得到的commitlog最大的有效消息物理偏移量 大于等于 commitlog这边自己得出的自身的最大有效消息物理偏移量// 则以commitlog文件的有效数据为准,再次清除consumequeue文件中的脏数据if (maxPhyOffsetOfConsumeQueue >= processOffset) {log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);}} else {// Commitlog case files are deleted//如果不存在commitlog文件//那么重置刷盘最新位置,提交的最新位置,并且清除所有的consumequeue索引文件log.warn("The commitlog files are deleted, and delete the consume queue files");this.mappedFileQueue.setFlushedWhere(0);this.mappedFileQueue.setCommittedWhere(0);this.defaultMessageStore.destroyLogics();}
}
异常恢复commitlog文件
@Deprecated
public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) {// recover by the minimum time stampboolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();if (!mappedFiles.isEmpty()) {// Looking beginning to recover from which fileint index = mappedFiles.size() - 1;MappedFile mappedFile = null;// 从最后一个文件开始,从后往前检查、恢复for (; index >= 0; index--) {mappedFile = mappedFiles.get(index);// isMappedFileMatchedRecover 检查index对应的mappedFile文件是否正常if (this.isMappedFileMatchedRecover(mappedFile)) {log.info("recover from this mapped file " + mappedFile.getFileName());break;}}if (index < 0) {index = 0;mappedFile = mappedFiles.get(index);}ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();long processOffset = mappedFile.getFileFromOffset();long mappedFileOffset = 0;/*** 从最后一个正常的文件开始遍历*/while (true) {DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);int size = dispatchRequest.getMsgSize();if (dispatchRequest.isSuccess()) {// Normal dataif (size > 0) {mappedFileOffset += size;// 如果消息允许重复复制,默认为 falseif (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) {// 如果 消息物理偏移量 小于 CommitLog的提交指针// 则调用CommitLogDispatcher重新构建当前消息的indexfile索引和consumequeue索引if (dispatchRequest.getCommitLogOffset() < this.defaultMessageStore.getConfirmOffset()) {this.defaultMessageStore.doDispatch(dispatchRequest);}} else {this.defaultMessageStore.doDispatch(dispatchRequest);}}// Come the end of the file, switch to the next file// Since the return 0 representatives met last hole, this can// not be included in truncate offset// 如果消息正常但是size为0,表示到了文件的末尾,则尝试跳到下一个commitlog文件进行检测else if (size == 0) {index++;if (index >= mappedFiles.size()) {// The current branch under normal circumstances should// not happenlog.info("recover physics file over, last mapped file " + mappedFile.getFileName());break;} else {mappedFile = mappedFiles.get(index);byteBuffer = mappedFile.sliceByteBuffer();processOffset = mappedFile.getFileFromOffset();mappedFileOffset = 0;log.info("recover next physics file, " + mappedFile.getFileName());}}} else {log.info("recover physics file end, " + mappedFile.getFileName() + " pos=" + byteBuffer.position());break;}}processOffset += mappedFileOffset;this.mappedFileQueue.setFlushedWhere(processOffset);this.mappedFileQueue.setCommittedWhere(processOffset);/** 删除文件最大有效数据偏移量processOffset之后的所有数据*/this.mappedFileQueue.truncateDirtyFiles(processOffset);// Clear ConsumeQueue redundant data// 如果consumequeue那边得到的commitlog最大的有效消息物理偏移量 大于等于 commitlog这边自己得出的自身的最大有效消息物理偏移量// 则以commitlog文件的有效数据为准,再次清除consumequeue文件中的脏数据if (maxPhyOffsetOfConsumeQueue >= processOffset) {log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);}}// Commitlog case files are deletedelse {log.warn("The commitlog files are deleted, and delete the consume queue files");this.mappedFileQueue.setFlushedWhere(0);this.mappedFileQueue.setCommittedWhere(0);this.defaultMessageStore.destroyLogics();}
}
相关文章:
【RocketMQ】源码详解:消息储存服务加载、文件恢复、异常恢复
消息储存服务加载 入口:org.apache.rocketmq.store.DefaultMessageStore#load 在创建brokerContriller时会调用初始化方法初始化brokerController,在初始化方法中会进行消息储存服务的加载 this.messageStore.load(); 加载方法主要是加载一些必要的参数和数据,如配…...
数字IC设计工程师是做什么的?
随着我国半导体产业的发展,近几年的新入行的从业人员,除了微电子相关专业的,还有就是物理、机械、数学、计算机等专业,很多人对这一高薪行业充满了好奇,那么数字IC设计工程师到底是做什么的? 首先来看看数…...
【040】134. 加油站[简单模拟 + 逻辑转化]
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas 和 cost &am…...
Python用selenium实现自动登录和下单的脚本
前言 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI(用户界面)自动化测试套件之一。Selenium 支持的语言包括C#,Java,Perl,PHP,Python 和 Ruby。目前,Selenium Web 驱动…...
(02)Cartographer源码无死角解析-(55) 2D后端优化→AppendNode()、class MapById、 PoseGraphData、
讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885 文末正下方中心提供了本…...
如何在jmeter中把响应中的数据提取出来并引用
jmeter做接口测试过程中,经常遇到请求需要用到token的时候,我们可以把返回token的接口用后置处理器提取出来,但是在这种情况下,只能适用于当前的线程组,其他线程组无法引用到提取的token变量值,所以必须要生…...
2023环翠区编程挑战赛中学组题解
T1. 出栈序列 题目描述 栈是一种“先进后出”的数据结构,对于一个序列1,2,...,n1,2, ...,n1,2,...,n,其入栈顺序是1,2,...n1,2, ...n1,2,...n,但每个元素出栈的时机可以自由选择。 例如111入栈、111出栈,222入栈、333入栈、333…...
手撸一个Switch开关组件
一、前言 手撸系列又来了,这次咱们来撸一个Switch开关组件,废话不多说,咱们立刻发车。 二、使用效果 三、实现分析 首先我们先不想它的这个交互效果,我们就实现“不合格”时的一个静态页面,静态页面大致如下&#x…...
2023年1月冰箱品牌销量排行:销量环比增长26%,销售额36亿+
鲸参谋电商大数据2023年1月京东平台“冰箱”销售数据出炉! 根据鲸参谋平台电商数据显示,2023年1月份,在京东平台上,冰箱的销量将近130万件,环比增长26%,同比下滑8%;销售额达36亿,环比…...
DSP CCS 开发问题总结及解决办法
文章目录 问题汇总 1. CCS编译器的Project菜单栏工程导入选项丢失,怎么解决! 1.1启动CCS后发现导入工程菜单栏丢失,无法导入工程文件。 1.2方法一 工程选项的导入工程文件丢失,如果要重新获得相应的选项,就需要删除当前…...
Vue3.x+Element Plus仿制Acro Design简洁模式分页器组件
Vue3.xElement Plus仿制Acro Design简洁模式分页器组件 开发中难免会遇到宽度很窄的列表需要使用分页器的情况,这时若使用Element Plus组件的分页器会导致分页器内容超出展示的区域,而Element Plus组件中目前没有Acro Design那样小巧的分页器(…...
经典文献阅读之--VoxelMap(体素激光里程计)
0. 简介 作为激光里程计,常用的方法一般是特征点法或者体素法,最近Mars实验室发表了一篇文章《Efficient and Probabilistic Adaptive Voxel Mapping for Accurate Online LiDAR Odometry》,同时还开源了代码在Github上。文中为雷达里程计提…...
.NET6中使用GRPC详细描述
Supported languages | gRPC,官网。至于原理就不说了,可以百度原理之后,然后再结合代码,事半功倍,就能很好理解GRPC了。 目录 一、简单使用 二、实际应用 一、简单使用 1.使用vs2022创建一个grpc程序,…...
ML@矩阵微积分基础
文章目录矩阵微积分Matrix calculus记法简单Jacobi Matrix分子记法分母记法一般形式的Jacobi MatrixTypes of matrix derivative向量求导向量对标量求导标量对向量求导向量对向量求导矩阵求导矩阵对标量求导(切矩阵)标量对矩阵求导记法向量求导 向量对标量求导标量对向量求导向…...
华为OD机试真题Python实现【优秀学员统计】真题+解题思路+代码(20222023)
优秀学员统计 题目 公司某部门软件教导团正在组织新员工每日打卡学习活动,他们开展这项学习活动已经一个月了,所以想统计下这个月优秀的打卡员工。每个员工会对应一个 id,每天的打卡记录记录当天打卡员工的 id 集合,一共 30 天。 请你实现代码帮助统计出打卡次数 top5 的…...
docsify在线文档支持pdf查看
目录 步骤一:添加插件 步骤二:添加pdf地址 步骤三:成果展示 docsify是一个在github上很好用的文档转换网页的工具,但是大部分情况我们都是使用的markdown文件。最近想把pdf文档也能支持在这上面展示,研究后总结一下…...
ES6中Set类型的基本使用
在ES6之前,存储数据的结构主要有两种:数组、对象。 在ES6中新增了另外两种数据结构(存放数据的方式):Set、Map,以及他们的另外形式WeakSet、WeakMap。 Set的基本使用 Set是一个新增的数据结构,…...
【VUE3.0_CSS功能】
CSS功能组件css作用域深度选择器(标签名空格:deep(标签名))插槽选择器(:soltted(标签名))全局选择器(:global(类名))动态CSS(v-bind)useCSSModule拓展知识:deep的写法组件…...
微机原理复习总结6:汇编语言程序设计
本篇博客主要分享几道汇编语言例题编写一完整的程序,从键盘输入一组字符,直到输入“0”为止,当输入是小写字母时,则修改为大写字母,输入的字符存放在string为首址的存储单元中。data segment ;数据段定义 st…...
计算机网络 部分原理和过程
下面是一台计算机 Ping 和不在同一 IP 网络上的另一台计算机的全过程: 该计算机首先确定要 Ping 的目标 IP 地址,并检查该 IP 地址是否与本地 IP 地址在同一 IP 网络上。如果目标 IP 地址与本地 IP 地址不在同一 IP 网络上,则需要通过路由器…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
