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

【RocketMQ NameServer】- NettyEventExecutor 处理 Netty 事件

文章目录

  • 1. 前言
  • 2. NettyEventExecutor 线程
  • 3. NettyEvent 是怎么来的
  • 4. NettyEventExecutor 线程处理不同事件的逻辑
    • 4.1 IDLE\CLOSE\EXCEPTION - onChannelIdle
    • 4.2 CONNECT - onChannelConnect
  • 5. 小结


本文章基于 RocketMQ 4.9.3

1. 前言

  • 【RocketMQ】- 源码系列目录

上一篇文章《【RocketMQ NameServer】- NameServer 启动源码》我们介绍了 NameServer 启动的核心源码,最后也留下了一个问题,就是 NettyEventExecutor 是如何处理 Netty 事件的,这篇文章就来看下。


2. NettyEventExecutor 线程

NettyEventExecutor 本质上是一个线程,上一篇文章中我们说过了就是如果 BrokerHousekeepingService 不为空,就启动 NettyEventExecutor 线程去处理里面的 Netty 事件。由于这个类代码量比较少,所以直接给出全部的代码。

class NettyEventExecutor extends ServiceThread {private final LinkedBlockingQueue<NettyEvent> eventQueue = new LinkedBlockingQueue<NettyEvent>();private final int maxSize = 10000;/*** 将 Netty 事件添加到 eventQueue 中* @param event*/public void putNettyEvent(final NettyEvent event) {int currentSize = this.eventQueue.size();if (currentSize <= maxSize) {this.eventQueue.add(event);} else {log.warn("event queue size [{}] over the limit [{}], so drop this event {}", currentSize, maxSize, event.toString());}}@Overridepublic void run() {log.info(this.getServiceName() + " service started");// 获取事件监听器, 这个监听器就是 BrokerHousekeepingServicefinal ChannelEventListener listener = NettyRemotingAbstract.this.getChannelEventListener();while (!this.isStopped()) {try {// 从 eventQueue 阻塞队列中拉取 Netty 事件NettyEvent event = this.eventQueue.poll(3000, TimeUnit.MILLISECONDS);if (event != null && listener != null) {// 判断事件类型switch (event.getType()) {case IDLE:listener.onChannelIdle(event.getRemoteAddr(), event.getChannel());break;case CLOSE:listener.onChannelClose(event.getRemoteAddr(), event.getChannel());break;case CONNECT:listener.onChannelConnect(event.getRemoteAddr(), event.getChannel());break;case EXCEPTION:listener.onChannelException(event.getRemoteAddr(), event.getChannel());break;default:break;}}} catch (Exception e) {log.warn(this.getServiceName() + " service has exception. ", e);}}log.info(this.getServiceName() + " service end");}@Overridepublic String getServiceName() {return NettyEventExecutor.class.getSimpleName();}
}

其实这个方法比较简单,就是线程不断轮循 eventQueue,从里面取出 NettyEvent,然后根据不同的状态走不通的处理逻辑。不够要注意一下,listener 是在创建 NettyRemotingServer 的时候设置的。
在这里插入图片描述
在这里插入图片描述
而创建 NettyRemotingServer 则是在初始化 NamesrvController 的时候设置进去的,可以看到设置进去的就是 BrokerHousekeepingService


3. NettyEvent 是怎么来的

NettyEvent 是在 NettyConnectManageHandler 处理入站出站消息的时候产生的,NettyConnectManageHandler 这个类在前一篇文章我们也说过在启动 NameServer 的时候(调用 start() 方法)会调用 this.remotingServer.start() 去启动 Netty 服务端,同时也把 NettyConnectManageHandler 设置到了 NioSocketChannel 的 Pipeline 中。
在这里插入图片描述
当 Netty 服务端接受到消息或者发送消息的时候都会经过这个处理器,并且针对不同事件会创建不同的 NettyEvent 加入到集合 eventQueue 中。

@ChannelHandler.Sharable
class NettyConnectManageHandler extends ChannelDuplexHandler {/*** NioSocketChannel 注册到 NioEventLoop* @param ctx* @throws Exception*/@Overridepublic void channelRegistered(ChannelHandlerContext ctx) throws Exception {final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());log.info("NETTY SERVER PIPELINE: channelRegistered {}", remoteAddress);super.channelRegistered(ctx);}/*** NioSocketChannel 从 NioEventLoop 中销毁* @param ctx* @throws Exception*/@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) throws Exception {final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());log.info("NETTY SERVER PIPELINE: channelUnregistered, the channel[{}]", remoteAddress);super.channelUnregistered(ctx);}/*** NioSocketChannel 连接成功连接到远程地址或者已经接收了一个新的连接* @param ctx* @throws Exception*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());log.info("NETTY SERVER PIPELINE: channelActive, the channel[{}]", remoteAddress);super.channelActive(ctx);if (NettyRemotingServer.this.channelEventListener != null) {NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CONNECT, remoteAddress, ctx.channel()));}}/*** 连接已经关闭或者断开* @param ctx* @throws Exception*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());log.info("NETTY SERVER PIPELINE: channelInactive, the channel[{}]", remoteAddress);super.channelInactive(ctx);if (NettyRemotingServer.this.channelEventListener != null) {NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel()));}}/*** 用户自定义事件* @param ctx* @throws Exception*/@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleStateEvent event = (IdleStateEvent) evt;if (event.state().equals(IdleState.ALL_IDLE)) {final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());log.warn("NETTY SERVER PIPELINE: IDLE exception [{}]", remoteAddress);RemotingUtil.closeChannel(ctx.channel());if (NettyRemotingServer.this.channelEventListener != null) {NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.IDLE, remoteAddress, ctx.channel()));}}}ctx.fireUserEventTriggered(evt);}/*** 连接事件处理过程中发生异常* @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());log.warn("NETTY SERVER PIPELINE: exceptionCaught {}", remoteAddress);log.warn("NETTY SERVER PIPELINE: exceptionCaught exception.", cause);if (NettyRemotingServer.this.channelEventListener != null) {NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.EXCEPTION, remoteAddress, ctx.channel()));}RemotingUtil.closeChannel(ctx.channel());}
}

可以看到在 NettyConnectManageHandler 中会针对不同的事件来生成不同的 NettyEvent,比如当有连接建立的时候会生成一个 NettyEventType.CONNECT 类型的事件,连接已经关闭或者断开会生成一个 NettyEvent(NettyEventType.CLOSE 类型的事件,用户自定义事件被触发就会生成一个 NettyEventType.IDLE 事件,当 Channel 在处理过程中发生异常会触发 NettyEventType.EXCEPTION 事件,这里大家就先看下这些概念,因为已经很久没看过 Netty 这块的源码了,到时候看完了再回来详细补充下这些事件的触发机制。


4. NettyEventExecutor 线程处理不同事件的逻辑

既然知道 NetyEvent 从哪来,那么下面就继续回到第二节的内容,看看 NettyEventExecutor 线程里面是怎么处理这些事件的。

4.1 IDLE\CLOSE\EXCEPTION - onChannelIdle

IDLE 事件类型的通过 listener.onChannelIdle(event.getRemoteAddr(), event.getChannel()) 去处理,这个类型的事件是触发了用户自定义的事件,下面来看下里面的逻辑。

@Override
public void onChannelIdle(String remoteAddr, Channel channel) {// 连接销毁事件this.namesrvController.getRouteInfoManager().onChannelDestroy(remoteAddr, channel);
}

可以看到这个方法走的就是连接销毁的方法,这个方法在 RouteInfoManager 里面,意思是如果触发了用户自定义的事件就是空闲连接需要销毁?

/*** 要销毁的连接的地址* @param remoteAddr* @param channel*/
public void onChannelDestroy(String remoteAddr, Channel channel) {String brokerAddrFound = null;if (channel != null) {try {try {// 加读锁this.lock.readLock().lockInterruptibly();Iterator<Entry<String, BrokerLiveInfo>> itBrokerLiveTable =this.brokerLiveTable.entrySet().iterator();while (itBrokerLiveTable.hasNext()) {Entry<String, BrokerLiveInfo> entry = itBrokerLiveTable.next();if (entry.getValue().getChannel() == channel) {// 是否找到对应的 broker 连接通道brokerAddrFound = entry.getKey();break;}}} finally {// 解除读锁this.lock.readLock().unlock();}} catch (Exception e) {log.error("onChannelDestroy Exception", e);}}// 如果没找到就赋值为 remoteAddrif (null == brokerAddrFound) {brokerAddrFound = remoteAddr;} else {log.info("the broker's channel destroyed, {}, clean it's data structure at once", brokerAddrFound);}if (brokerAddrFound != null && brokerAddrFound.length() > 0) {try {try {// 加写锁this.lock.writeLock().lockInterruptibly();// 从 brokerLiveTable 集合删掉这个 brokerthis.brokerLiveTable.remove(brokerAddrFound);this.filterServerTable.remove(brokerAddrFound);String brokerNameFound = null;boolean removeBrokerName = false;// 然后再遍历 brokerAddrTable, 也要维护这个 broker 集群集合的地址消息Iterator<Entry<String, BrokerData>> itBrokerAddrTable =this.brokerAddrTable.entrySet().iterator();/*** 1. 维护 brokerAddrTable*/while (itBrokerAddrTable.hasNext() && (null == brokerNameFound)) {// 获取这个集群下面的 broker 信息BrokerData brokerData = itBrokerAddrTable.next().getValue();// 遍历这个集群下面的主从节点Iterator<Entry<Long, String>> it = brokerData.getBrokerAddrs().entrySet().iterator();while (it.hasNext()) {Entry<Long, String> entry = it.next();Long brokerId = entry.getKey();String brokerAddr = entry.getValue();// 如果找到了要删除的if (brokerAddr.equals(brokerAddrFound)) {// 记录下要删除的 brokerNamebrokerNameFound = brokerData.getBrokerName();// 删掉it.remove();log.info("remove brokerAddr[{}, {}] from brokerAddrTable, because channel destroyed",brokerId, brokerAddr);break;}}if (brokerData.getBrokerAddrs().isEmpty()) {// 如果说删除了这个 broker 信息之后这个集群下面的 broker 地址为空了 removeBrokerName = true;// 那么就将这个 brokerName -> brokerData 的映射也删掉, 表明此时这个 broker 集群已经没有节点了itBrokerAddrTable.remove();log.info("remove brokerName[{}] from brokerAddrTable, because channel destroyed",brokerData.getBrokerName());}}/*** 2. 维护 clusterAddrTable*/if (brokerNameFound != null && removeBrokerName) {// 然后如果说删除了一个 brokerName, 也要维护 clusterAddrTable 集合, 这个集合记录了 clusterName -> Set(brokerName) 的集合, // 这里要说下 clusterAddrTable 这个集合, broker 配置文件可以指定 brokerClusterName 为集群名称, 同时这个集群里面可以包含多个 broker// 主从集群, 这些主从 broker 集群的 brokerName 是一样的, 依靠 brokerId 区分主节点还是从节点, 所以这个 table 的 value 是 Set 集合Iterator<Entry<String, Set<String>>> it = this.clusterAddrTable.entrySet().iterator();while (it.hasNext()) {Entry<String, Set<String>> entry = it.next();// 集群名称String clusterName = entry.getKey();// 集群下面的 broker 主从集群的 brokerName 集合Set<String> brokerNames = entry.getValue();// 从集合中删掉boolean removed = brokerNames.remove(brokerNameFound);if (removed) {// 如果删掉了就打印日志log.info("remove brokerName[{}], clusterName[{}] from clusterAddrTable, because channel destroyed",brokerNameFound, clusterName);// 如果删掉这个 brokerName 之后, 集群下面没有 broker 集群了, 就删掉这个 clusterif (brokerNames.isEmpty()) {log.info("remove the clusterName[{}] from clusterAddrTable, because channel destroyed and no broker in this cluster",clusterName);it.remove();}break;}}}/*** 3. 维护 topicQueueTable*/if (removeBrokerName) {// 遍历 topicQueueTable 下面的 topic 信息Iterator<Entry<String, List<QueueData>>> itTopicQueueTable =this.topicQueueTable.entrySet().iterator();while (itTopicQueueTable.hasNext()) {Entry<String, List<QueueData>> entry = itTopicQueueTable.next();// 遍历所有 topicString topic = entry.getKey();// 遍历 topic 的队列配置信息List<QueueData> queueDataList = entry.getValue();Iterator<QueueData> itQueueData = queueDataList.iterator();while (itQueueData.hasNext()) {// 因为 topic 下面的队列可以分配到不同 broker 上面, 所以需要把分配到这个 brokerName 集群上面的队列也// 移除掉QueueData queueData = itQueueData.next();if (queueData.getBrokerName().equals(brokerNameFound)) {// 从集合中移除itQueueData.remove();log.info("remove topic[{} {}], from topicQueueTable, because channel destroyed",topic, queueData);}}if (queueDataList.isEmpty()) {// 如果移除之后已经没有队列了, 那么这个 topic 队列配置信息也要删掉了itTopicQueueTable.remove();log.info("remove topic[{}] all queue, from topicQueueTable, because channel destroyed",topic);}}}} finally {// 解除写锁this.lock.writeLock().unlock();}} catch (Exception e) {log.error("onChannelDestroy Exception", e);}}
}

上面这个方法就是 IDLE 事件的销毁逻辑,broker 向 NameServer 注册信息的时候包括创建 topic 信息的时候都会将一些配置信息存到不同的集合中,上面的方法就是处理这些集合:

  • brokerLiveTable: broker 地址 -> broker 活跃信息,里面维持了已建立连接的 broker 的连接通道。
  • filterServerTable: broker 地址 -> 这个 broker 的过滤服务器地址,过滤服务器在当前这个版本已经看不到了,所以可以忽略。
  • brokerAddrTable: brokerName -> broker 信息的集合(注意主从集群的 brokerName 是一样的,这样才能够存储一个集群下面的所有 broker 地址)。
  • topicQueueTable: topic -> 队列信息,因为一个 topic 可以存储到多个 broker 下面,所以是一个 List。
  • clusterAddrTable: 集群名称 -> 集群下面的所有 broker 主从集群的 broker 名称(主从 broker 的 brokerName 一般是一样的)

比如说将这个通道对应的地址删掉之后,需要判断下这个地址是在哪个 brokerName 下面的,然后判断如果删掉之后这个 brokerName 集群下面还有没有其他的地址,如果没有说明这个 broker 集群可以从 brokerAddrTable 中删掉了,接着再去处理剩余的几个队列。

下面就到了 clusterAddrTable,处理逻辑是一样的,找出哪个 cluster 集群下面存在这个 broker 地址,然后删掉,接着判断删掉之后这个 cluster 集群下面还有没有 broker 地址,如果没有了,就直接把这个集群也从集合中删掉。

然后维护 topicQueueTable,一个 topic 的队列可以分配到不同的 broker 上,所以如果 brokerName 被删掉了,就需要将分配在这个 brokerName 上面的队列信息也给删掉,然后同理判断下这个 topic 是否还存在队列信息,如果不存在,那么这个 topic 也可以从这个集合中删掉了。

但是大家要注意一点就是,维护 topicQueueTable 的时候是需要判断 brokerName 是否被删掉的,也就是说当这个连接通道对应的 broker 地址被删掉之后,brokerName 这个集群下面可能还有其他 broker 地址,可以是从节点,这种情况下 brokerName 就不会从 brokerAddrTable 中删掉,也就不会维护 topicQueueTable 的地址了。

当然这些 broker 信息、topic 信息是什么时候存到这些集合里面的,这些等到后面介绍 broker 启动或者创建 topic 的时候再详细说。


4.2 CONNECT - onChannelConnect

@Override
public void onChannelConnect(String remoteAddr, Channel channel) {
}

这个方法就是一个空方法,实际上也是,当连接建立的时候 broker 会通过定时任务上报信息给 NameServer,所以理论上是不需要在这里实现的。


5. 小结

好了,最后来总结一下,NettyEventExecutor 就是专门处理 NettyConnectManageHandler 这个处理器在处理入站出站数据的时候添加的 NettyEvent 事件,对于 IDLE\CLOSE\EXCEPTION 这三个类型的 NettyEvent,最终会调用 onChannelIdle 去删除 broker 连接,同时处理 brokerLiveTable、filterServerTable、brokerAddrTable、topicQueueTable、clusterAddrTable 这几个集合,而对于连接事件则是通过 onChannelConnect 去处理,但是这个方法是一个空方法。





如有错误,欢迎指出!!!!

相关文章:

【RocketMQ NameServer】- NettyEventExecutor 处理 Netty 事件

文章目录 1. 前言2. NettyEventExecutor 线程3. NettyEvent 是怎么来的4. NettyEventExecutor 线程处理不同事件的逻辑4.1 IDLE\CLOSE\EXCEPTION - onChannelIdle4.2 CONNECT - onChannelConnect 5. 小结 本文章基于 RocketMQ 4.9.3 1. 前言 【RocketMQ】- 源码系列目录 上一…...

JAVA刷题记录: 递归,搜索与回溯

专题一 递归 面试题 08.06. 汉诺塔问题 - 力扣&#xff08;LeetCode&#xff09; class Solution {public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {dfs(A, B, C, A.size());}public void dfs(List<Integer> a, List<In…...

【进阶】C# 委托(Delegate)知识点总结归纳

1. 委托的基本概念 定义&#xff1a;委托是一种类型安全的函数指针&#xff0c;用于封装方法&#xff08;静态方法或实例方法&#xff09;。 核心作用&#xff1a;允许将方法作为参数传递&#xff0c;实现回调机制和事件处理。 类型安全&#xff1a;委托在编译时会检查方法签…...

推理能力:五一模型大放送

--->更多内容&#xff0c;请移步“鲁班秘笈”&#xff01;&#xff01;<--- 近日人工智能领域迎来了一波密集的模型发布潮&#xff0c;多家科技巨头和研究机构相继推出了具有突破性特点的AI模型。这些新模型在参数规模、计算效率、多模态能力以及推理能力等方面都展现出…...

数据库=====

创建数据库 1.直接创建数据库 语法&#xff1a;CREATE DATABASE [IF NOT EXISTS] 数据库名 ——[]表示内部内容可省略 2.指定字符集和排序规则方式创建数据库 语法&#xff1a;CREATE DATABASE[IF NOT EXISTS] 数据库名 CHARACTER SET 字符集 COLLATE 排序规则 示例&#xff1a…...

VITA STANDARDS LIST,VITA 标准清单下载

VITA STANDARDS LIST&#xff0c;VITA 标准清单下载 DesignationTitleAbstractStatusVMEbus Handbook, 4th EditionA users guide to the VME, VME64 and VME64x bus specifications - features over 70 product photos and over 160 circuit diagrams, tables and graphs. The…...

npm pnpm yarn 设置国内镜像

国内镜像 常用的国内镜像&#xff1a; 淘宝镜像 https://registry.npmmirror.com 腾讯云镜像​​ https://mirrors.cloud.tencent.com/npm/ 华为云镜像​​ https://repo.huaweicloud.com/repository/npm/ CNPM&#xff08;阿里系&#xff09; ​​ https://r.cnpmjs.org/ 清华…...

互联网大厂Java面试:从Spring到微服务的技术探讨

场景&#xff1a;互联网大厂Java求职者面试 在一家知名的互联网大厂面试中&#xff0c;面试官王严肃正在面试一位名叫谢飞机的程序员。谢飞机以其独特的幽默感而闻名&#xff0c;但在技术面前&#xff0c;他的能力能否得到认可呢&#xff1f; 第一轮提问&#xff1a;核心技术…...

[machine learning] Transformer - Attention (二)

本文介绍带训练参数的self-attention&#xff0c;即在transformer中使用的self-attention。 首先引入三个可训练的参数矩阵Wq, Wk, Wv&#xff0c;这三个矩阵用来将词向量投射(project)到query, key, value三个向量上。下面我们再定义几个变量&#xff1a; import torch inpu…...

Java多语言DApp质押挖矿盗U源码(前端UniApp纯源码+后端Java)

内容&#xff1a; 这款Java多语言DApp质押挖矿盗U源码提供了完整的前端与后端开发框架&#xff0c;适用于区块链应用开发。系统包括&#xff1a; 前端源码&#xff08;UniApp&#xff09;&#xff1a;采用UniApp开发&#xff0c;跨平台支持iOS、Android及H5。界面简洁&#xf…...

如何解决 403 错误:请求被拒绝,无法连接到服务器

解决 403 错误&#xff1a;请求被拒绝&#xff0c;无法连接到服务器 当您在浏览网站或应用时&#xff0c;遇到 403 错误&#xff0c;通常会显示类似的消息&#xff1a; The request could not be satisfied. Request blocked. We can’t connect to the server for this app o…...

CGI(Common Gateway Interface)协议详解

CGI&#xff08;通用网关接口&#xff09;是一种标准化的协议&#xff0c;定义了 Web服务器 与 外部程序&#xff08;如脚本或可执行文件&#xff09;之间的数据交互方式。它允许服务器动态生成网页内容&#xff0c;而不仅仅是返回静态文件。 1. CGI 的核心作用 动态内容生成&a…...

HybridCLR 详解:Unity 全平台原生 C# 热更新方案

HybridCLR&#xff08;原 Huatuo&#xff09;是 Unity 平台革命性的热更新解决方案&#xff0c;它通过扩展 Unity 的 IL2CPP 运行时&#xff0c;实现了基于原生 C# 的完整热更新能力。下面从原理到实践全面解析这一技术。 一、核心原理剖析 1. 技术架构 原始 IL2CPP 流程&am…...

电脑RGB888P转换为JPEG方案 ,K230的RGB888P转换为JPEG方案

K230开发板本身具备将RGB888P转换为JPEG的能力&#xff0c;但需要正确调用硬件或软件接口。以下是具体分析及解决方案&#xff1a; 一、K230原生支持性分析 1. 硬件支持 K230的NPU&#xff08;神经网络处理器&#xff09;和图像处理单元&#xff08;ISP&#xff09;理论上支持…...

基于SpringBoot+Vue实现的电影推荐平台功能三

一、前言介绍&#xff1a; 1.1 项目摘要 2023年全球流媒体用户突破15亿&#xff0c;用户面临海量内容选择困难&#xff0c;传统推荐方式存在信息过载、推荐精准度低等问题。传统推荐系统存在响应延迟高&#xff08;平均>2s&#xff09;。随着互联网的快速发展&#xff0c;…...

NHANES指标推荐:triglyceride levels

文章题目&#xff1a;Association between triglyceride levels and rheumatoid arthritis prevalence in women: a cross-sectional study of NHANES (1999-2018) DOI&#xff1a;10.1186/s12905-025-03645-y 中文标题&#xff1a;女性甘油三酯水平与类风湿性关节炎患病率之间…...

打印Activity的调用者

有时候我们会发现自己应用中的某个Activity被陌名奇妙的打开了&#xff0c;但是不知道是哪里的代码打开的&#xff0c;此时可以打印Activity的调用堆栈&#xff0c;在Activity的onCreate函数中添加如下代码&#xff1a; Arrays.stream(Thread.currentThread().getStackTrace()…...

深入解析 SqlSugar 与泛型封装:实现通用数据访问层

在现代软件开发中&#xff0c;ORM&#xff08;对象关系映射&#xff09;框架的使用已经成为不可或缺的部分&#xff0c;SqlSugar 是一款非常流行且强大的 ORM框架。它不仅提供了简单易用的数据库操作&#xff0c;还具备了高效的性能和灵活的配置方式。为了进一步提升数据库操作…...

普通 html 项目引入 tailwindcss

项目根目录安装依赖 npm install -D tailwindcss3 postcss autoprefixer 初始化生成tailwind.config.js npx tailwindcss init 修改tailwind.config.js /** type {import(tailwindcss).Config} */ module.exports {content: ["./index.html"], //根据自己的项目…...

Go小技巧易错点100例(二十七)

本期分享&#xff1a; 1. Go语言中的Scan函数 2. debug.Stack()打印堆栈信息 3. Go条件编译 正文&#xff1a; Go语言中的Scan函数 在Go语言中&#xff0c;Scan函数是一个强大的工具&#xff0c;它主要用于从输入源&#xff08;如标准输入、文件或网络连接&#xff09;读取…...

单细胞测序数据分析流程的最佳实践

单细胞测试数据分析流程是整个论文数据分析过程中相对固定的部分&#xff0c;有一定的标准流程&#xff0c;以下整理了发表论文的相关内容供简要了解&#xff0c;详细内容可以参照2019年发表的综述&#xff1a;Luecken MD, Theis FJ. Current best practices in single-cell RN…...

Elasticsearch:RAG 和 grounding 的价值

作者&#xff1a;来自 Elastic Toms Mura 了解 RAG、grounding&#xff0c;以及如何通过将 LLM 连接到你的文档来减少幻觉。 更多阅读&#xff1a;Elasticsearch&#xff1a;在 Elastic 中玩转 DeepSeek R1 来实现 RAG 应用 想获得 Elastic 认证吗&#xff1f;查看下一期 Elast…...

经典算法 求解台阶问题

求解台阶问题 题目描述 实现一个算法求解台阶问题。介绍如下&#xff1a; 对于高度为 n 的台阶&#xff0c;从下往上走&#xff0c;每一步的阶数为 1、2 或 3 中的一个。问要走到顶部一共有多少种走法。 输入描述 输入一个数字 N&#xff1a; 1 ≤ N ≤ 35表示台阶的高度 …...

伊甸园之东: 农业革命与暴力的复杂性

农业革命的开始 农业革命是人类历史上的第一次重大经济和社会变革&#xff0c;标志着人们从狩猎采集转向农耕。 该变革虽然进展缓慢&#xff0c;却彻底改变了人类的生活方式和社会结构。狩猎采集社会的特征 狩猎采集者生活在小规模、低密度的部落中&#xff0c;依赖于不稳定的自…...

MCP多智能体消息传递机制(Message Passing Between Agents)

目录 &#x1f680; MCP多智能体消息传递机制&#xff08;Message Passing Between Agents&#xff09; &#x1f31f; 为什么要引入消息传递机制&#xff1f; &#x1f3d7;️ 核心设计&#xff1a;Agent间消息传递模型 &#x1f6e0;️ 1. 定义标准消息格式 &#x1f6e…...

Deformable DETR模型解读(附源码+论文)

Deformable DETR 论文链接&#xff1a;Deformable DETR: Deformable Transformers for End-to-End Object Detection 官方链接&#xff1a;Deformable-DETR(这个需要在linux上运行&#xff0c;所以我是用的是mmdetection里面的Deformable DERT&#xff0c;看了一下源码基本是…...

游戏引擎学习第255天:构建配置树

为今天的内容设定背景 今天的任务是构建性能分析&#xff08;profiling&#xff09;视图。 目前来看&#xff0c;展示性能分析图形本身并不复杂&#xff0c;大部分相关功能在昨天已经实现。图形显示部分应该相对直接&#xff0c;工作量不大。 真正需要解决的问题&#xff0c;是…...

JavaScript性能优化实战之调试与性能检测工具

在进行 JavaScript 性能优化时,了解和使用正确的调试与性能检测工具至关重要。它们能够帮助我们识别性能瓶颈,精确定位问题,并做出有针对性的优化措施。本文将介绍一些常见的调试和性能检测工具,帮助你更好地分析和优化你的 JavaScript 代码。 1️⃣ Chrome DevTools Chro…...

C#VisionMaster算子二次开发(非方案版)

前言 在网上VisionMaster的教程通常都是按照方案执行的形式&#xff0c;当然海康官方也是推荐使用整体方案的形式进行开发。但是由于我是做标准设备的&#xff0c;为了适配原有的软件框架和数据结构&#xff0c;就需要将特定需要使用的算子进行二次封装。最直接的好处是&#…...

计算机总线系统入门:理解数据传输的核心

一、总线系统简介&#xff1a;计算机内部的交通网络 在计算机系统中&#xff0c;总线是指连接各个组件的一组共享信号线或传输通道&#xff0c;用于在系统内不同的硬件模块之间传递数据、地址、控制信号等信息。它类似于交通系统中的道路&#xff0c;帮助计算机各个部件&#…...

【Linux】Petalinux驱动开发基础

基于Petalinux做Linux驱动开发。 部分图片和经验来源于网络,若有侵权麻烦联系我删除,主要是做笔记的时候忘记写来源了,做完笔记很久才写博客。 专栏目录:记录自己的嵌入式学习之路-CSDN博客 目录 1 一个完整的Linux系统(针对Zynq) 1.1 PS部分 1.2 PL部分(若…...

提升办公效率的PDF转图片实用工具

软件介绍 这款专注于PDF文档处理的工具功能单一但实用&#xff0c;能够将PDF文件内容智能提取并自动拼接成长图&#xff0c;为用户提供便捷的图片化文档处理方案&#xff0c;无需复杂设置即可轻松上手。 简洁直观的用户界面 软件界面设计简洁清爽&#xff0c;没有任何多余…...

动态库与ELF加载

目录 动态库 ELF格式 ELF和后缀的区别 什么是目标文件 ELF文件中的地址--虚拟地址 动静态库和可执行文件 动态库ELF加载 为什么编译时静态库需要指定库?而运行时不需要指定库的&#xff0c;但是动态库需要呢&#xff1f; 总结: 动态库 动态库制作需要的.o文件需要使…...

算法每日一题 | 入门-顺序结构-数字反转

数字反转 题目描述 输入一个不小于 且小于 &#xff0c;同时包括小数点后一位的一个浮点数&#xff0c;例如 &#xff0c;要求把这个数字翻转过来&#xff0c;变成 并输出。 输入格式 一行一个浮点数 输出格式 一行一个浮点数 输入输出样例 #1 输入 #1 123.4输出 #1 …...

ROS2学习笔记|实现订阅消息并朗读的详细步骤

本教程将详细介绍如何使用 ROS 2 实现一个节点订阅另一个节点发布的消息&#xff0c;并将接收到的消息通过 espeakng 库进行朗读的完整流程。以下步骤假设你已经安装好了 ROS 2 环境&#xff08;以 ROS 2 Humble 为例&#xff09;&#xff0c;并熟悉基本的 Linux 操作。 注意&…...

【Hot 100】 146. LRU 缓存

目录 引言LRU 缓存官方解题LRU实现&#x1f4cc; 实现步骤分解步骤 1&#xff1a;定义双向链表节点步骤 2&#xff1a;创建伪头尾节点&#xff08;关键设计&#xff09;步骤 3&#xff1a;实现链表基础操作操作 1&#xff1a;添加节点到头部操作 2&#xff1a;移除任意节点 步骤…...

web应用开发说明文档

工程目录结构 FACTORY--bin #网络流可执行程序 参考后文1.1部分文字说明webrtc-streamer--deployment #部署相关的配置--mysql #参考1.3 mysql数据库详细说明--conf #存放mysql的配置文件--data #存放pem加密…...

快速搜索与管理PDF文档的专业工具

软件介绍 在处理大量PDF文档时&#xff0c;专业的文档管理工具能显著提升工作效率。这款工具能够帮助用户快速检索PDF内容&#xff0c;并提供了便捷的合并与拆分功能&#xff0c;让复杂的PDF操作变得简单高效。 多文件内容检索能力 不同于传统PDF阅读器的单文件搜索局…...

在GPU集群上使用Megatron-LM进行高效的大规模语言模型训练

摘要 大型语言模型在多个任务中已取得了最先进的准确率。然而,训练这些模型的效率仍然面临挑战,原因有二:a) GPU内存容量有限,即使在多GPU服务器上也无法容纳大型模型;b) 所需的计算操作数量可能导致不现实的训练时间。因此,提出了新的模型并行方法,如张量并行和流水线…...

NocoDB:开源的 Airtable 替代方案

NocoDB&#xff1a;开源的 Airtable 替代方案 什么是 NocoDB&#xff1f;NocoDB 的主要特点丰富的电子表格界面工作流自动化应用商店程序化访问 NocoDB 的应用场景使用 Docker 部署 NocoDB1. 创建数据目录2. 运行 Docker 容器3. 访问 NocoDB 注意事项总结 什么是 NocoDB&#x…...

关于Python:7. Python数据库操作

一、sqlite3&#xff08;轻量级本地数据库&#xff09; sqlite3 是 Python 内置的模块&#xff0c;用于操作 SQLite 数据库。 SQLite 是一个轻量级、零配置的关系型数据库系统&#xff0c;整个数据库保存在一个文件中&#xff0c;适合小型项目和本地存储。 SQLite 不需要安装…...

修改ollama.service都可以实现什么?

通过修改 ollama.service 系统服务单元文件,可以实现以下核心配置变更: 一、网络与访问控制 监听地址与端口 通过 Environment="OLLAMA_HOST=0.0.0.0:11434" 修改服务绑定的 IP 和端口: 0.0.0.0 允许所有网络接口访问(默认仅限本地 127.0.0.1)。示例:改为 0.0.…...

k8s笔记——kubebuilder工作流程

kubebuilder工作流程 Kubebuilder 工作流程详解 Kubebuilder 是 Kubernetes 官方推荐的 Operator 开发框架&#xff0c;用于构建基于 Custom Resource Definitions (CRD) 的控制器。以下是其核心工作流程的完整说明&#xff1a; 1. 初始化项目 # 创建项目目录 mkdir my-opera…...

长江学者答辩ppt美化_特聘教授_校企联聘学者_青年长江学者PPT案例模板

WordinPPT / 持续为双一流高校、科研院所、企业等提供PPT制作系统服务。 长江学者特聘教授 “长江学者奖励计划”中的一类&#xff0c;是高层次人才计划的重要组成部分&#xff0c;旨在吸引和培养具有国际领先水平的学科带头人。特聘教授需全职在国内高校工作&#xff0c;是高…...

Vscode/Code-Server 安装中文包——CI/CD

前言 啊好多人问我怎么还不更新&#xff0c;其实本月是已经写了一篇测评的&#xff0c;但是鉴于过于超前会给产品带来不好的影响&#xff0c;所以就没有公开。那么既然这样本月就再更新一篇。 首先 声明 一点&#xff0c;安装中文包的初衷不是看不懂英文&#xff0c;也不是对…...

【信息系统项目管理师-论文真题】2012上半年论文详解(包括解题思路和写作要点)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 试题1:论信息系统工程的风险管理1、写作要点2、解题思路对项目风险的认识和项目风险管理的基本过程、主要方法、工具信息系统项目最主要的风险是什么试题2:论信息系统工程项目可行性研究1、写作要点2、解题思…...

PowerPC架构详解:定义、应用及特点

一、PowerPC架构的定义 PowerPC&#xff08;Performance Optimization With Enhanced RISC – Performance Computing&#xff09; 是一种由IBM、摩托罗拉&#xff08;现NXP&#xff09;和苹果于1991年联合开发的精简指令集&#xff08;RISC&#xff09;处理器架构&#xff0c…...

IP伪装、代理池与分布式爬虫

一、动态代理IP应用&#xff1a;代理池的获取、选择与使用 代理池技术的核心是通过动态切换IP地址&#xff0c;让爬虫看起来像不同用户在访问网站&#xff0c;从而规避封禁。 &#xff08;一&#xff09;代理池的获取途径 1. 免费代理&#xff1a;低成本但高风险 免费代理可…...

【Arthas】火焰图优化应用CPU(问题原因:获取调用栈)

优化场景总结归纳 1. 问题背景 现象&#xff1a;在公共搜索功能中&#xff0c;火焰图分析发现 获取Java调用栈&#xff08;StackTrace&#xff09; 占用了约 6%的CPU&#xff08;日常流量下&#xff09;&#xff0c;系统高负载时占比更高。原因&#xff1a; 每次外部API调用时…...

回溯算法详解(Java实现):从组合到排列的全面解析

引言 回溯算法是一种强大的算法思想&#xff0c;广泛应用于解决各种组合优化问题。它通过系统性地尝试所有可能的解&#xff0c;并在发现当前路径无法得到解时立即回溯&#xff0c;从而高效地找到问题的解。在本文中&#xff0c;我们将深入探讨回溯算法的核心思想、三要素、通…...