「布道师系列文章」众安保险王凯解析 Kafka 网络通信

作者|众安保险基础平台 Java 开发专家王凯

引言

今天给大家带来的是 Kafka 网路通信主要流程的解析(基于 Apache Kafka 3.7[2])。同时引申分析了业界当前较火的AutoMQ基于Kafka在网络通信层面的优化和提升。

01

如何构建一个基本的请求和处理响应

一个消息队列涉及的网络通信主要有两块:

  • 消息生产者与消息队列服务器之间(Kafka 中是生产者向队列「推」消息)

  • 消息消费者与消息队列服务器之间(Kafka 中是消费者向队列「拉」消息)

图上就是一个从发送消息到收到响应主要经过的流程。Client:1.KafkaProducer 初始化 Sender 线程2.Sender 线程从 RecordAccumulator 中获取攒批好的数据(这里详细的客户端发送可以看 https://mp.weixin.qq.com/s/J2\_O1l81duknfdFvHuBWxw)3.Sender 线程调用 NetworkClient 检查连接(未 ready 需要 initiateConnect)4.Sender 线程调用 NetworkClient 的 doSend 方法将数据写入 KafkaChannel5.Sender 线程调用 NetworkClient 的 poll 进行实际的发送Server:1.KafkaServer 初始化 SocketServer、dataPlaneRequestProcessor(KafkaApis)、dataPlaneRequestHandlerPool2.SocketServer 初始化 RequestChannel、dataPlaneAcceptor3.dataPlaneAcceptor 负责获取连接并分配处理任务给对应的 Processor4.Processor 线程从 newConnections 的 queue 中取出任务进行处理5.Processor 线程处理准备好的 IO 事件

  • configureNewConnections() :创建新连接

  • processNewResponses():发送 Response,并将 Response 放入到 inflightResponses 临时队列

  • poll():执行 NIO poll,获取对应 SocketChannel 上准备就绪的 I/O 操作

  • processCompletedReceives():将接收到的 Request 放入 RequestChannel 队列

  • processCompletedSends():为临时 Response 队列中的 Response 执行回调逻辑

  • processDisconnected():处理因发送失败而导致的连接断开

  • closeExcessConnections():关闭超过配额限制部分的连接

6.KafkaRequestHandler 从 RequestChannel 中获取准备好的事件根据 apiKey 分配给对应的 KafkaApi 进行处理7.KafkaApi 处理完成后,把 response 放入 RequestChannel8.Processor 线程将 response 响应给 client以上就是一个完整的 kafka 发送消息,客户端和服务端的处理流程。

02

Kafka 中的网络通信
1. 服务端通信线程模型

不同于 rocketmq 中通过 netty 实现了高效的网络通信,Kafka 中的相当于通过 java NIO 实现了一个主从 Reactor 模式的网路通信(不熟悉的可以关注 https://jenkov.com/tutorials/java-nio/overview.html)。

DataPlanAcceptor 和 ControlPlanAcceptor 都是 Acceptor 的一个子类,Acceptor 又是一个实现了 Runnable 接口的线程类,Acceptor 的主要目的是监听并且接收 Client 和 Broker 之间的请求,同时建立传输通道(SocketChannel),通过轮询的方式交给一个 Processor 处理。这里还有一个 RequestChannel(ArrayBlockingQueue),用于建立 Processor 和 Handler 的连接,MainReactor(Acceptor)只负责监听 OP_ACCEPT 事件, 监听到之后把 SocketChannel 传递给 SubReactor(Processor), 每个 Processor 都有自己的 Selector,SubReactor 会监听并处理其他的事件,并最终把具体的请求传递给 KafkaRequestHandlerPool。

2. 线程模型中主要组件的初始化

从图上可以看出在 broker 启动时会调用 KafkaServer 的 startup 方法(这里我们默认还是基于 zookeeper 的模式)。startup 方法中主要创建:1.KafkaApis 的处理类:dataPlaneRequestProcessor 和 controlPlaneRequestProcessor 的创建2.KafkaRequestHandlePool:dataPlaneRequestHandlerPool 和 controlPlaneRequestHandlerPool 创建3.socketServer 的初始化4.controlPlaneAcceptorAndProcessor 和 dataPlaneAcceptorAndProcessor 的创建其实这里还有一步图上没有但是也在 startup 方法中非常重要的方法,线程启动:enableRequestProcessing 是通过初始化完成的 socketServer 进行。

3. Processor 的添加和销毁

1.添加

  • broker 启动时添加

  • 主动调整 num.network.threads 处理线程数量

2.启动

  • broker 启动 accetor 时启动 processor

  • 主动调整时启动未启动的新的处理线程

3.移除队列并销毁

  • broker 关闭

  • 主动调整 num.network.threads 处理线程数量时移除多余的线程并关闭

4.KafkaRequestHandlePool 和 KafkaRequestHandler

1.KafkaRequestHandlerPool

真正处理 Kafka 请求的地方,它是一个请求处理线程池,主要负责创建、维护、管理和销毁下辖的请求处理线程。

2.KafkaRequestHandler

真正的业务请求处理线程类,每个请求处理线程实例,负责从 SocketServer 的 RequestChannel 的请求队列中获取请求对象,并进行处理。如下是 KafkaRequestHandler 的线程处理的方法体:

 def run(): Unit = {threadRequestChannel.set(requestChannel)while (!stopped) {// We use a single meter for aggregate idle percentage for the thread pool.// Since meter is calculated as total_recorded_value / time_window and// time_window is independent of the number of threads, each recorded idle// time should be discounted by # threads.val startSelectTime = time.nanoseconds// 从请求队列中获取下一个待处理的请求val req = requestChannel.receiveRequest(300)val endTime = time.nanosecondsval idleTime = endTime - startSelectTimeaggregateIdleMeter.mark(idleTime / totalHandlerThreads.get)req match {case RequestChannel.ShutdownRequest =>debug(s"Kafka request handler $id on broker $brokerId received shut down command")completeShutdown()returncase callback: RequestChannel.CallbackRequest =>val originalRequest = callback.originalRequesttry {// If we've already executed a callback for this request, reset the times and subtract the callback time from the // new dequeue time. This will allow calculation of multiple callback times.// Otherwise, set dequeue time to now.if (originalRequest.callbackRequestDequeueTimeNanos.isDefined) {val prevCallbacksTimeNanos = originalRequest.callbackRequestCompleteTimeNanos.getOrElse(0L) - originalRequest.callbackRequestDequeueTimeNanos.getOrElse(0L)originalRequest.callbackRequestCompleteTimeNanos = NoneoriginalRequest.callbackRequestDequeueTimeNanos = Some(time.nanoseconds() - prevCallbacksTimeNanos)} else {originalRequest.callbackRequestDequeueTimeNanos = Some(time.nanoseconds())}threadCurrentRequest.set(originalRequest)callback.fun(requestLocal)} catch {case e: FatalExitError =>completeShutdown()Exit.exit(e.statusCode)case e: Throwable => error("Exception when handling request", e)} finally {// When handling requests, we try to complete actions after, so we should try to do so here as well.apis.tryCompleteActions()if (originalRequest.callbackRequestCompleteTimeNanos.isEmpty)originalRequest.callbackRequestCompleteTimeNanos = Some(time.nanoseconds())threadCurrentRequest.remove()}// 普通情况由KafkaApis.handle方法执行相应处理逻辑case request: RequestChannel.Request =>try {request.requestDequeueTimeNanos = endTimetrace(s"Kafka request handler $id on broker $brokerId handling request $request")threadCurrentRequest.set(request)apis.handle(request, requestLocal)} catch {case e: FatalExitError =>completeShutdown()Exit.exit(e.statusCode)case e: Throwable => error("Exception when handling request", e)} finally {threadCurrentRequest.remove()request.releaseBuffer()}case RequestChannel.WakeupRequest => // We should handle this in receiveRequest by polling callbackQueue.warn("Received a wakeup request outside of typical usage.")case null => // continue}}completeShutdown()
}

这里的 56 行会将任务最重分配给 KafkaApis 的 handle 进行处理。

03

统一的请求处理转发

Kafka 中主要的业务处理方法类其实是 KafkaApis,上面的所有的通信,线程处理类,最终都是为了更好的来到 KafkaApis 的 handle。

 override def handle(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = {def handleError(e: Throwable): Unit = {error(s"Unexpected error handling request ${request.requestDesc(true)} " +s"with context ${request.context}", e)requestHelper.handleError(request, e)}try {trace(s"Handling request:${request.requestDesc(true)} from connection ${request.context.connectionId};" +s"securityProtocol:${request.context.securityProtocol},principal:${request.context.principal}")if (!apiVersionManager.isApiEnabled(request.header.apiKey, request.header.apiVersion)) {// The socket server will reject APIs which are not exposed in this scope and close the connection// before handing them to the request handler, so this path should not be exercised in practicethrow new IllegalStateException(s"API ${request.header.apiKey} with version ${request.header.apiVersion} is not enabled")}request.header.apiKey match {case ApiKeys.PRODUCE => handleProduceRequest(request, requestLocal)case ApiKeys.FETCH => handleFetchRequest(request)case ApiKeys.LIST_OFFSETS => handleListOffsetRequest(request)case ApiKeys.METADATA => handleTopicMetadataRequest(request)case ApiKeys.LEADER_AND_ISR => handleLeaderAndIsrRequest(request)case ApiKeys.STOP_REPLICA => handleStopReplicaRequest(request)case ApiKeys.UPDATE_METADATA => handleUpdateMetadataRequest(request, requestLocal)case ApiKeys.CONTROLLED_SHUTDOWN => handleControlledShutdownRequest(request)case ApiKeys.OFFSET_COMMIT => handleOffsetCommitRequest(request, requestLocal).exceptionally(handleError)case ApiKeys.OFFSET_FETCH => handleOffsetFetchRequest(request).exceptionally(handleError)case ApiKeys.FIND_COORDINATOR => handleFindCoordinatorRequest(request)case ApiKeys.JOIN_GROUP => handleJoinGroupRequest(request, requestLocal).exceptionally(handleError)case ApiKeys.HEARTBEAT => handleHeartbeatRequest(request).exceptionally(handleError)case ApiKeys.LEAVE_GROUP => handleLeaveGroupRequest(request).exceptionally(handleError)case ApiKeys.SYNC_GROUP => handleSyncGroupRequest(request, requestLocal).exceptionally(handleError)case ApiKeys.DESCRIBE_GROUPS => handleDescribeGroupsRequest(request).exceptionally(handleError)case ApiKeys.LIST_GROUPS => handleListGroupsRequest(request).exceptionally(handleError)case ApiKeys.SASL_HANDSHAKE => handleSaslHandshakeRequest(request)case ApiKeys.API_VERSIONS => handleApiVersionsRequest(request)case ApiKeys.CREATE_TOPICS => maybeForwardToController(request, handleCreateTopicsRequest)case ApiKeys.DELETE_TOPICS => maybeForwardToController(request, handleDeleteTopicsRequest)case ApiKeys.DELETE_RECORDS => handleDeleteRecordsRequest(request)case ApiKeys.INIT_PRODUCER_ID => handleInitProducerIdRequest(request, requestLocal)case ApiKeys.OFFSET_FOR_LEADER_EPOCH => handleOffsetForLeaderEpochRequest(request)case ApiKeys.ADD_PARTITIONS_TO_TXN => handleAddPartitionsToTxnRequest(request, requestLocal)case ApiKeys.ADD_OFFSETS_TO_TXN => handleAddOffsetsToTxnRequest(request, requestLocal)case ApiKeys.END_TXN => handleEndTxnRequest(request, requestLocal)case ApiKeys.WRITE_TXN_MARKERS => handleWriteTxnMarkersRequest(request, requestLocal)case ApiKeys.TXN_OFFSET_COMMIT => handleTxnOffsetCommitRequest(request, requestLocal).exceptionally(handleError)case ApiKeys.DESCRIBE_ACLS => handleDescribeAcls(request)case ApiKeys.CREATE_ACLS => maybeForwardToController(request, handleCreateAcls)case ApiKeys.DELETE_ACLS => maybeForwardToController(request, handleDeleteAcls)case ApiKeys.ALTER_CONFIGS => handleAlterConfigsRequest(request)case ApiKeys.DESCRIBE_CONFIGS => handleDescribeConfigsRequest(request)case ApiKeys.ALTER_REPLICA_LOG_DIRS => handleAlterReplicaLogDirsRequest(request)case ApiKeys.DESCRIBE_LOG_DIRS => handleDescribeLogDirsRequest(request)case ApiKeys.SASL_AUTHENTICATE => handleSaslAuthenticateRequest(request)case ApiKeys.CREATE_PARTITIONS => maybeForwardToController(request, handleCreatePartitionsRequest)// Create, renew and expire DelegationTokens must first validate that the connection// itself is not authenticated with a delegation token before maybeForwardToController.case ApiKeys.CREATE_DELEGATION_TOKEN => handleCreateTokenRequest(request)case ApiKeys.RENEW_DELEGATION_TOKEN => handleRenewTokenRequest(request)case ApiKeys.EXPIRE_DELEGATION_TOKEN => handleExpireTokenRequest(request)case ApiKeys.DESCRIBE_DELEGATION_TOKEN => handleDescribeTokensRequest(request)case ApiKeys.DELETE_GROUPS => handleDeleteGroupsRequest(request, requestLocal).exceptionally(handleError)case ApiKeys.ELECT_LEADERS => maybeForwardToController(request, handleElectLeaders)case ApiKeys.INCREMENTAL_ALTER_CONFIGS => handleIncrementalAlterConfigsRequest(request)case ApiKeys.ALTER_PARTITION_REASSIGNMENTS => maybeForwardToController(request, handleAlterPartitionReassignmentsRequest)case ApiKeys.LIST_PARTITION_REASSIGNMENTS => maybeForwardToController(request, handleListPartitionReassignmentsRequest)case ApiKeys.OFFSET_DELETE => handleOffsetDeleteRequest(request, requestLocal).exceptionally(handleError)case ApiKeys.DESCRIBE_CLIENT_QUOTAS => handleDescribeClientQuotasRequest(request)case ApiKeys.ALTER_CLIENT_QUOTAS => maybeForwardToController(request, handleAlterClientQuotasRequest)case ApiKeys.DESCRIBE_USER_SCRAM_CREDENTIALS => handleDescribeUserScramCredentialsRequest(request)case ApiKeys.ALTER_USER_SCRAM_CREDENTIALS => maybeForwardToController(request, handleAlterUserScramCredentialsRequest)case ApiKeys.ALTER_PARTITION => handleAlterPartitionRequest(request)case ApiKeys.UPDATE_FEATURES => maybeForwardToController(request, handleUpdateFeatures)case ApiKeys.ENVELOPE => handleEnvelope(request, requestLocal)case ApiKeys.DESCRIBE_CLUSTER => handleDescribeCluster(request)case ApiKeys.DESCRIBE_PRODUCERS => handleDescribeProducersRequest(request)case ApiKeys.UNREGISTER_BROKER => forwardToControllerOrFail(request)case ApiKeys.DESCRIBE_TRANSACTIONS => handleDescribeTransactionsRequest(request)case ApiKeys.LIST_TRANSACTIONS => handleListTransactionsRequest(request)case ApiKeys.ALLOCATE_PRODUCER_IDS => handleAllocateProducerIdsRequest(request)case ApiKeys.DESCRIBE_QUORUM => forwardToControllerOrFail(request)case ApiKeys.CONSUMER_GROUP_HEARTBEAT => handleConsumerGroupHeartbeat(request).exceptionally(handleError)case ApiKeys.CONSUMER_GROUP_DESCRIBE => handleConsumerGroupDescribe(request).exceptionally(handleError)case ApiKeys.GET_TELEMETRY_SUBSCRIPTIONS => handleGetTelemetrySubscriptionsRequest(request)case ApiKeys.PUSH_TELEMETRY => handlePushTelemetryRequest(request)case ApiKeys.LIST_CLIENT_METRICS_RESOURCES => handleListClientMetricsResources(request)case _ => throw new IllegalStateException(s"No handler for request api key ${request.header.apiKey}")}} catch {case e: FatalExitError => throw ecase e: Throwable => handleError(e)} finally {// try to complete delayed action. In order to avoid conflicting locking, the actions to complete delayed requests// are kept in a queue. We add the logic to check the ReplicaManager queue at the end of KafkaApis.handle() and the// expiration thread for certain delayed operations (e.g. DelayedJoin)// Delayed fetches are also completed by ReplicaFetcherThread.replicaManager.tryCompleteActions()// The local completion time may be set while processing the request. Only record it if it's unset.if (request.apiLocalCompleteTimeNanos < 0)request.apiLocalCompleteTimeNanos = time.nanoseconds}
}

从上面代码可以看到负责副本管理的 ReplicaManager、维护消费者组的 GroupCoordinator ,操作 Controller 组件的 KafkaController,还有我们最常用到的 KafkaProducer.send(发送消息)和 KafkaConcumser.consume(消费消息)。

04

AutoMQ 中线程模型

1. 处理线程的优化

AutoMQ 参照 CPU 的流水线将 Kafka 的处理模型优化成流水线模式,兼顾了顺序性和高效两方面。

  • 顺序性:TCP 连接与线程绑定,对于同一个 TCP 连接有且只有一个网络线程在解析请求,并且有且只有一个 RequestHandler 线程在进行业务逻辑处理;

  • 高效:不同阶段流水线化,网络线程解析完 MSG1 后就可以立马解析 MSG2,无需等待 MSG1 持久化完成。同理 RequestHandler 对 MSG1 进行完校验 & 定序后,立马就可以开始处理 MSG2;同时为了进一步提高持久化的效率,AutoMQ 还会将数据攒批进行刷盘持久化。

2. 通道 RequestChannel 的优化

AutoMQ 将 RequestChannel 进行了多队列改造,通过多队列模式,可以做到对于相同连接的请求都被放入相同一个队列,并且只被特定的 KafkaRequestHandler 进行业务逻辑处理,保障了检验 & 定序阶段内部的顺序处理。

  • 队列和 KafkaRequestHandler 一一映射,数量保持一致;

  • Processor 解析完请求后,根据 hash(channelId) % N 来决定路由到特定的队列。

参考资料 [1]AutoMQ:https://github.com/AutoMQ/automq
[2]Kafka3.7:https://github.com/apache/kafka/releases/tag/3.7.0
[3]JAVANIO:https://jenkov.com/tutorials/java-nio/overview.html
[4]AutoMQ 线程优化:https://mp.weixin.qq.com/s/kDZJgUnMoc5K8jTuV08OJw

END

关于我们

我们是来自 Apache RocketMQ 和 Linux LVS 项目的核心团队,曾经见证并应对过消息队列基础设施在大型互联网公司和云计算公司的挑战。现在我们基于对象存储优先、存算分离、多云原生等技术理念,重新设计并实现了 Apache Kafka 和 Apache RocketMQ,带来高达 10 倍的成本优势和百倍的弹性效率提升。

🌟 GitHub 地址:https://github.com/AutoMQ/automq
💻 官网:https://www.automq.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/21608.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记(一)——Langchain基本操作与函数

学习笔记(一)——Langchain基本操作与函数 目录 学习笔记(一)——Langchain基本操作与函数基本初始化配置LangsmithLanguage Models 基础指令传递信息OutputParsers 输出解析器chain 链Prompt Templates 提示模板Message History 消息历史记录Managing Conversation History 管…

XL7005A SOP-8 0.4A1.25-20V 150KHz降压直流转换器芯片

XL7005A作为一款高性能的降压型电源管理芯片&#xff0c;在智能家居中有着广泛的应用。以下是一些具体的案例&#xff1a; 1. 智能灯具&#xff1a;XL7005A可用于控制LED灯的电源&#xff0c;提供稳定高效的电源支持&#xff0c;确保灯具亮度稳定且无频闪&#xff0c;提高用户体…

springboot从2.7.2 升级到 3.3.0

文章目录 概要准备报错调整小结后记 概要 时代在进步&#xff0c;springboot已经来到了3.3.0 , 于是我们也打算升级下sbvadmin到3.3&#xff0c; jdk使用21的版本&#xff0c;下面是升级过程中碰到的一些问题&#xff0c;问题不大。 2.7.2 -> 3.3.0 准备 下载jdk21&#…

windows的软件修改图标

要修改一个可执行文件&#xff08;.exe&#xff09;的图标&#xff0c;你可以使用 Resource Hacker 这样的工具。Resource Hacker 是一个免费的资源编辑器&#xff0c;可以用于修改和编辑 Windows 可执行文件中的资源。 以下是一个简单的步骤来修改一个 .exe 文件的图标&#x…

shell脚本 字符串拼接变量赋值失效

问题现象&#xff1a; 代码如下&#xff1a; 执行结果&#xff1a; 可以看到data_dir属性是有值的&#xff0c;但是做字符串拼接变量赋值失效了很奇怪 怀疑赋值哪里写错了 问题分析&#xff1a; 1. 还是觉得赋值没有问题&#xff0c;手动显式赋值再执行下 执行结果&#…

职场如同“染缸”,老板只是给你个平台,染的好坏,全凭运气!

无论哪个单位&#xff0c;在职场大染缸里总有那么一拨同事是你喜欢的&#xff0c;也有那么一拨同事是不痛不痒的&#xff0c;还有那么一拨同事却是你怎么看都觉得不顺眼的。“不顺眼”的定义很宽泛&#xff0c;可能是他曾经的一些言论触及了你的道德底线&#xff0c;可能是他的…

C++数据结构之:堆Heap

摘要&#xff1a; it人员无论是使用哪种高级语言开发东东&#xff0c;想要更高效有层次的开发程序的话都躲不开三件套&#xff1a;数据结构&#xff0c;算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合&#xff0c;即带“结构”的数据元素的集合&am…

【Qt】win10,QTableWidget表头下无分隔线的问题

1. 现象 2. 原因 win10系统的UI样式默认是这样的。 3. 解决 - 方法1 //横向表头ui->table->horizontalHeader()->setStyleSheet("QHeaderView::section{""border-top:0px solid #E5E5E5;""border-left:0px solid #E5E5E5;""bord…

Matlab|【重磅】配电网故障重构/孤岛划分

目录 1 主要内容 1.1 背景 1.2 流程图 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序主要复现《基于GA_BFGS算法的配电网故障恢复性重构研究_郑海广》&#xff0c;采用matlab编程软件实现&#xff0c;依据网络结构和DG供电方式对配电网进行孤岛划分&#xff0c;将含…

【算法训练记录——Day24】

Day24——回溯算法Ⅰ 77.组合 今日内容&#xff1a; ● 理论基础 ● 77. 组合 理论&#xff1a;代码随想录 77.组合 思路&#xff1a;k层for循环&#xff0c;不会 回溯&#xff0c;将组合问题抽象成n叉树&#xff0c;for循环控制宽度&#xff0c;递归的深度控制二叉树的深度 …

CSS学习笔记之高级教程(五)

23、CSS 媒体查询 - 实例 /* 如果屏幕尺寸超过 600 像素&#xff0c;把 <div> 的字体大小设置为 80 像素 */ media screen and (min-width: 600px) {div.example {font-size: 80px;} }/* 如果屏幕大小为 600px 或更小&#xff0c;把 <div> 的字体大小设置为 30px …

程序员的五大职业素养,你知道吗?

程序员职业生涯的挑战与机遇 在当今这个科技日新月异的时代&#xff0c;程序员作为技术行业的中坚力量&#xff0c;其职业生涯无疑充满了无数挑战与机遇。技术的快速迭代要求他们必须不断学习新知识、掌握新技能&#xff0c;以跟上时代的步伐。同时&#xff0c;云计算、人工智…

学习经验分享篇(1)——怎样将示波器数据(.CSV数据)导入Matlab/Simulink中并进行FFT分析(电机控制/电力电子方向必备技能)

最近比较忙&#xff0c;没怎么更新&#xff0c;后续打算不断出一些学习贴。 1.为什么要出这篇文章&#xff1f; &#xff08;1&#xff09;我当时第一次导示波器数据进入Matlab里面的时候&#xff0c;一直疯狂报错&#xff0c;搞了好久。 &#xff08;2&#xff09;好多同学现…

一点连接千家银行,YonSuite让“银行回单”一键获取

在当今日益复杂多变的商业环境中&#xff0c;企业的资金管理变得尤为重要。传统的银行回单管理方式&#xff0c;如手动登录网银、逐一下载回单、核对信息等&#xff0c;不仅效率低下&#xff0c;而且容易出错&#xff0c;给企业的财务管理带来了极大的挑战。 然而&#xff0c;…

【Pytorch】深入Pytorch模型的训练、log、可视化

文章目录 模型训练的模板综合案例-Pytorch 官网demo优化记录日志解析日志增加tensorboard数据记录保存训练曲线模型参数可视化增加wandb数据记录模型训练的模板 综合案例-Pytorch 官网demo pytorch 官网tutorial-quickstart https://blog.csdn.net/weixin_39107270/article/de…

安装 Android Studio 2024.1.1.6(Koala SDK35)和过程问题解决

记录更新Android Studio版本及适配Android V应用配置的一些过程问题。 安装包&#xff1a;android-studio-2024.1.1.6-windows.exe原版本&#xff1a;Android Studio23.2.1.23 Koala 安装过程 Uninstall old version 不会删除原本配置&#xff08;左下角提示&#xff09; Un…

YOLOv5改进 | 主干网络 | 将主干网络替换为轻量化的ShuffleNetv2【原理 + 完整代码】

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 目标检测是计算机视觉中一个重要的下游任务。对于边缘盒子的计算平台来说&#xff0c;一个大型模型很难实现实时检测的要求。基于一系列消融…

MQ之初识kafka

1. MQ简介 1.1 MQ的诞生背景 以前网络上的计算机&#xff08;或者说不同的进程&#xff09;传递数据&#xff0c;通信都是点对点的&#xff0c;而且要实现相同的协议&#xff08;HTTP、 TCP、WebService&#xff09;。1983 年的时候&#xff0c;有个在 MIT 工作的印度小伙突发…

前端开发记录

一.Node相关 镜像&#xff1a;registry "https://registry.npmmirror.com" 二.Eslint格式检查 关闭语法格式检查

【MySQL03】【 Buffer Pool】

文章目录 一、前言二、缓冲池&#xff08;Buffer Pool &#xff09;1. 缓冲池的概念2. LRU List、Free List 和 Flush List2.1 Free 链表2.1.1 缓冲页的哈希处理 2.2 Flush 链表2.3 LRU 链表2.3.1 简单 LRU 链表2.3.2 优化后的 LRU 列表2.3.3 更进一步的优化 3. 脏页的刷新4. 多…