目录

channel和connection的区别

自动装配RabbitAutoConfiguration

消息发送流程

获取connection对象

获取channel对象

AMQConnection读取frame帧并回调publishconfirm和publishreturn

MainLoop线程监听

执行回调


channel和connection的区别

Spring AMQP 是 Spring 框架对 AMQP(高级消息队列协议)的支持,提供了一个高级抽象层,使得在 Spring 项目中使用消息队列变得更加方便。

在源码中会出现Channel和Connecttion的概念,我先来解释一下

TCP连接:TCP连接是传输层面上的连接,通常是通过IP地址和端口号建立的,RabbitMQ使用TCP协议进行网络通信,所有的消息传递都是在TCP连接上进行的。

RabbitMQ的连接:RabbitMQ的连接是指通过TCP建立的连接,通常是指Connection对,RabbitMQ在一个TCP连接上可以创建多个逻辑连接(即Channel)。

        RabbitMQ的设计理念是尽量减少TCP连接的数量,推荐使用一个TCP连接来承载多个Channel,这种设计可以减少网络开销,提高性能,同时也简化了连接管理。

自动装配RabbitAutoConfiguration

@Configuration(proxyBeanMethods = false
)
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration {@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean({RabbitOperations.class})
public RabbitTemplate rabbitTemplate(RabbitProperties properties, ObjectProvider<MessageConverter> messageConverter, ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers, ConnectionFactory connectionFactory) {PropertyMapper map = PropertyMapper.get();RabbitTemplate template = new RabbitTemplate(connectionFactory);messageConverter.ifUnique(template::setMessageConverter);template.setMandatory(this.determineMandatoryFlag(properties));RabbitProperties.Template templateProperties = properties.getTemplate();if (templateProperties.getRetry().isEnabled()) {template.setRetryTemplate((new RetryTemplateFactory((List)retryTemplateCustomizers.orderedStream().collect(Collectors.toList()))).createRetryTemplate(templateProperties.getRetry(), Target.SENDER));}templateProperties.getClass();map.from(templateProperties::getReceiveTimeout).whenNonNull().as(Duration::toMillis).to(template::setReceiveTimeout);templateProperties.getClass();map.from(templateProperties::getReplyTimeout).whenNonNull().as(Duration::toMillis).to(template::setReplyTimeout);templateProperties.getClass();map.from(templateProperties::getExchange).to(template::setExchange);templateProperties.getClass();map.from(templateProperties::getRoutingKey).to(template::setRoutingKey);templateProperties.getClass();map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);return template;
}@Bean
public CachingConnectionFactory rabbitConnectionFactory(RabbitProperties properties, ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) throws Exception {PropertyMapper map = PropertyMapper.get();CachingConnectionFactory factory = new CachingConnectionFactory((com.rabbitmq.client.ConnectionFactory)this.getRabbitConnectionFactoryBean(properties).getObject());properties.getClass();map.from(properties::determineAddresses).to(factory::setAddresses);properties.getClass();map.from(properties::isPublisherReturns).to(factory::setPublisherReturns);properties.getClass();map.from(properties::getPublisherConfirmType).whenNonNull().to(factory::setPublisherConfirmType);RabbitProperties.Cache.Channel channel = properties.getCache().getChannel();channel.getClass();map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);channel.getClass();map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis).to(factory::setChannelCheckoutTimeout);RabbitProperties.Cache.Connection connection = properties.getCache().getConnection();connection.getClass();map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);connection.getClass();map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);connectionNameStrategy.getClass();map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);return factory;
}}

@ConfigurationProperties(prefix = "spring.rabbitmq"
)
public class RabbitProperties {private String host = "localhost";private int port = 5672;private String username = "guest";private String password = "guest";private final Ssl ssl = new Ssl();private String virtualHost;private String addresses;@DurationUnit(ChronoUnit.SECONDS)private Duration requestedHeartbeat;private boolean publisherReturns;private CachingConnectionFactory.ConfirmType publisherConfirmType;private Duration connectionTimeout;private final Cache cache = new Cache();private final Listener listener = new Listener();private final Template template = new Template();private List<Address> parsedAddresses;}

这里就是spring自动装配的流程,其完整流程就是SpringBoot启动->@SpringBootApplication->@EnableAutoConfiguration->AutoConfigurationImportSelector扫描META-INF/spring.factories->加载RabbitAutoConfiguration->创建RabbitMQ相关Bean

RabbitProperties类加了@ConfigurationProperties会去读取配置文件中的参数,否则就提供类属性里面的默认配置,RabbitAutoConfiguration用@Bean注解通过方法将RabbitTemplate,CachingConnectionFactory都会注册成bean,在方法里面会注入RabbitProperties的bean给他们设置参数。

消息发送流程

protected void sendToRabbit(Channel channel, String exchange, String routingKey, boolean mandatory, Message message) throws IOException {AMQP.BasicProperties convertedMessageProperties = this.messagePropertiesConverter.fromMessageProperties(message.getMessageProperties(), this.encoding);channel.basicPublish(exchange, routingKey, mandatory, convertedMessageProperties, message.getBody());
}
public class Message implements Serializable {private static final long serialVersionUID = -7177590352110605597L;private static final String DEFAULT_ENCODING = Charset.defaultCharset().name();private static final Set<String> whiteListPatterns = new LinkedHashSet(Arrays.asList("java.util.*", "java.lang.*"));private static String bodyEncoding;private final MessageProperties messageProperties;private final byte[] body;public Message(byte[] body, MessageProperties messageProperties) {this.body = body;this.messageProperties = messageProperties;}}

在通过RabbitTemplate的convertAndSend()方法发送消息的时候,先判断是否定义了RetryTemplate,在RetryTemplate对象里面会定义重试的次数,间隔,RetryTemplate对象是否为null来判断是否要重试,如果开启了重试就调调用RetryTemplate的doexecute方法,并传入RecoveryCallback,用于处理所有重试失败后的逻辑,如果没有开启重试直接调用doExecute进行处理。

doExecute会通过CachingConnectionFactory来获取channel,最后通过dosend()方法发送消息。dosend方法先进行setupConfirm()方法再调用sendToRabbit发消息,RabbitTemplate内部会将消息包装成Message对象,通过Channel.basicPublish()方法发送消息,Message内部有一个byte字节数组封装要发送的消息,还有MessageProperties封装一些属性,像消息id,做一个防止重复消费消息

获取connection对象

public final Connection createConnection() throws AmqpException {if (this.stopped) {throw new AmqpApplicationContextClosedException("The ApplicationContext is closed and the ConnectionFactory can no longer create connections.");} else {synchronized(this.connectionMonitor) {if (this.cacheMode == CachingConnectionFactory.CacheMode.CHANNEL) {if (this.connection.target == null) {this.connection.target = super.createBareConnection();if (!this.checkoutPermits.containsKey(this.connection)) {this.checkoutPermits.put(this.connection, new Semaphore(this.channelCacheSize));}this.connection.closeNotified.set(false);this.getConnectionListener().onCreate(this.connection);}return this.connection;} else {return this.cacheMode == CachingConnectionFactory.CacheMode.CONNECTION ? this.connectionFromCache() : null;}}}
}

CachingConnectionFactory的createConnection方法创建连接,CachingConnectionFactory内部定义了一个Object对象作为锁connectionMonitor,在获取连接的时候会进行一个上锁的操作,判断采用的策略

获取channel对象

private Channel getChannel(ChannelCachingConnectionProxy connection, boolean transactional) {Semaphore permits = null;if (this.channelCheckoutTimeout > 0L) {permits = this.obtainPermits(connection);}LinkedList<ChannelProxy> channelList = this.determineChannelList(connection, transactional);ChannelProxy channel = null;if (connection.isOpen()) {channel = this.findOpenChannel(channelList, channel);if (channel != null && this.logger.isTraceEnabled()) {this.logger.trace("Found cached Rabbit Channel: " + channel.toString());}}if (channel == null) {try {channel = this.getCachedChannelProxy(connection, channelList, transactional);} catch (RuntimeException var7) {if (permits != null) {permits.release();if (this.logger.isDebugEnabled()) {this.logger.debug("Could not get channel; released permit for " + connection + ", remaining:" + permits.availablePermits());}}throw var7;}}return channel;
}
private ChannelProxy findOpenChannel(LinkedList<ChannelProxy> channelList, ChannelProxy channelArg) {ChannelProxy channel = channelArg;synchronized(channelList) {while(!channelList.isEmpty()) {channel = (ChannelProxy)channelList.removeFirst();if (this.logger.isTraceEnabled()) {this.logger.trace(channel + " retrieved from cache");}if (channel.isOpen()) {break;}this.cleanUpClosedChannel(channel);channel = null;}return channel;}
}
private ChannelProxy getCachedChannelProxy(ChannelCachingConnectionProxy connection, LinkedList<ChannelProxy> channelList, boolean transactional) {Channel targetChannel = this.createBareChannel(connection, transactional);if (this.logger.isDebugEnabled()) {this.logger.debug("Creating cached Rabbit Channel from " + targetChannel);}this.getChannelListener().onCreate(targetChannel, transactional);Class[] interfaces;if (!CachingConnectionFactory.ConfirmType.CORRELATED.equals(this.confirmType) && !this.publisherReturns) {interfaces = new Class[]{ChannelProxy.class};} else {interfaces = new Class[]{ChannelProxy.class, PublisherCallbackChannel.class};}return (ChannelProxy)Proxy.newProxyInstance(ChannelProxy.class.getClassLoader(), interfaces, new CachedChannelInvocationHandler(connection, targetChannel, channelList, transactional));
}
private Channel createBareChannel(ChannelCachingConnectionProxy connection, boolean transactional) {if (this.cacheMode == CachingConnectionFactory.CacheMode.CHANNEL) {// 检查连接是否断开if (!this.connection.isOpen()) {synchronized(this.connectionMonitor) {// 双重检查,确保在获取锁后连接仍然是关闭状态if (!this.connection.isOpen()) {// 通知连接关闭事件this.connection.notifyCloseIfNecessary();}// 再次检查并重建连接if (!this.connection.isOpen()) {// 清除旧连接this.connection.target = null;// 创建新连接this.createConnection();}}}
}return this.doCreateBareChannel(this.connection, transactional);} else if (this.cacheMode == CachingConnectionFactory.CacheMode.CONNECTION) {if (!connection.isOpen()) {synchronized(this.connectionMonitor) {((LinkedList)this.allocatedConnectionNonTransactionalChannels.get(connection)).clear();((LinkedList)this.allocatedConnectionTransactionalChannels.get(connection)).clear();connection.notifyCloseIfNecessary();this.refreshProxyConnection(connection);}}return this.doCreateBareChannel(connection, transactional);} else {return null;}
}
private Channel doCreateBareChannel(ChannelCachingConnectionProxy conn, boolean transactional) {Channel channel = conn.createBareChannel(transactional);if (!CachingConnectionFactory.ConfirmType.NONE.equals(this.confirmType)) {try {((Channel)channel).confirmSelect();} catch (IOException var5) {this.logger.error("Could not configure the channel to receive publisher confirms", var5);}}if ((CachingConnectionFactory.ConfirmType.CORRELATED.equals(this.confirmType) || this.publisherReturns) && !(channel instanceof PublisherCallbackChannelImpl)) {channel = this.publisherChannelFactory.createChannel((Channel)channel, this.getChannelsExecutor());}if (channel != null) {((Channel)channel).addShutdownListener(this);}return (Channel)channel;
}
public class ChannelN extends AMQChannel implements Channel {private static final String UNSPECIFIED_OUT_OF_BAND = "";private static final Logger LOGGER = LoggerFactory.getLogger(ChannelN.class);private final Map<String, Consumer> _consumers;private final Collection<ReturnListener> returnListeners;private final Collection<ConfirmListener> confirmListeners;}

CachingConnectionFactory调用getChannel()获取channel,会用一个map存储Connection,和一个 Semaphore来保证每个Connection的channel数,会再获取到存储channel的LinkList,实现Channel的复用。

获取LinkList中的连接会先上个锁,防止并发下产生问题。

如果从LinkList中获取的channel为null则通过getCachedChannelProxy()方法去获取channel的一个代理对象

getCachedChannelProxy()方法先通过createBareChannel方法获取一个targetchannel,会通过Jdk代理生成ChannelProxy对象,会判断有没有开启retrun机制,开启了代理实现相应的接口,CachingConnectionFactory内部定义了一个CachedChannelInvocationHandler

createBareChannel方法会先判断有没有创建connection对象,若是connection对象为null则采用了双重检验的方法,判断加锁再判断来防止多线程创建的问题,最后调用doCreateBareChannel方法创建channel,存在直接调用doCreateBareChannel。

doCreateBareChannel通过connection创建channel,再判断是否开启confirmType,publisherReturns,在channel接口的实现类会有returnListeners,confirmListeners存储。

AMQConnection读取frame帧并回调publishconfirm和publishreturn

MainLoop线程监听

public class AMQConnection {private final MainLoop mainLoop;private volatile ChannelManager _channelManager;// 连接启动时会启动MainLoop线程public void startMainLoop() {MainLoop loop = new MainLoop();String name = "AMQP Connection " + this.getHostAddress() + ":" + this.getPort();this.mainLoopThread = Environment.newThread(this.threadFactory, loop, name);this.mainLoopThread.start();
}
public Channel createChannel(int channelNumber) throws IOException {this.ensureIsOpen();ChannelManager cm = this._channelManager;if (cm == null) {return null;} else {Channel channel = cm.createChannel(this, channelNumber);this.metricsCollector.newChannel(channel);return channel;}
}// MainLoop是一个独立线程,负责从socket读取数据// 从socket读取AMQP帧private class MainLoop implements Runnable {private MainLoop() {}public void run() {boolean shouldDoFinalShutdown = true;try {while(AMQConnection.this._running) {Frame frame = AMQConnection.this._frameHandler.readFrame();AMQConnection.this.readFrame(frame);}} catch (Throwable var6) {if (var6 instanceof InterruptedException) {shouldDoFinalShutdown = false;} else {AMQConnection.this.handleFailure(var6);}} finally {if (shouldDoFinalShutdown) {AMQConnection.this.doFinalShutdown();}}}
}
private void readFrame(Frame frame) throws IOException {if (frame != null) {this._missedHeartbeats = 0;if (frame.type != 8) {if (frame.channel == 0) {this._channel0.handleFrame(frame);} else if (this.isOpen()) {ChannelManager cm = this._channelManager;if (cm != null) {ChannelN channel;try {channel = cm.getChannel(frame.channel);} catch (UnknownChannelException var5) {LOGGER.info("Received a frame on an unknown channel, ignoring it");return;}channel.handleFrame(frame);}}}} else {this.handleSocketTimeout();}}
}

public class ChannelManager {
private final Map<Integer, ChannelN> _channelMap;private ChannelN addNewChannel(AMQConnection connection, int channelNumber) {if (this._channelMap.containsKey(channelNumber)) {throw new IllegalStateException("We have attempted to create a channel with a number that is already in use. This should never happen. Please report this as a bug.");} else {ChannelN ch = this.instantiateChannel(connection, channelNumber, this.workService);this._channelMap.put(ch.getChannelNumber(), ch);return ch;}
}
}

AMQConnection是一些对frame帧,连接启动时会启动MainLoop线程,MainLoop是一个独立线程,负责从socket读取数据, 从socket读取AMQP帧,根据channel号找到对应的channel并处理帧,根据channel号找到对应的channel并处理帧,在AMQConnection 有个ChannelManager类型的对象,依靠他来管理创建的channel,保存在一个map里面,key为序列号,value是channel。

执行回调

public class PublisherCallbackChannelImpl implements PublisherCallbackChannel, ConfirmListener, ReturnListener, ShutdownListener {private static final MessagePropertiesConverter CONVERTER = new DefaultMessagePropertiesConverter();private static final long RETURN_CALLBACK_TIMEOUT = 60L;private final Log logger = LogFactory.getLog(this.getClass());private final Channel delegate;private final ConcurrentMap<String, PublisherCallbackChannel.Listener> listeners = new ConcurrentHashMap();private final Map<PublisherCallbackChannel.Listener, SortedMap<Long, PendingConfirm>> pendingConfirms = new ConcurrentHashMap();private final Map<String, PendingConfirm> pendingReturns = new ConcurrentHashMap();private final SortedMap<Long, PublisherCallbackChannel.Listener> listenerForSeq = new ConcurrentSkipListMap();}
public void handleAck(long seq, boolean multiple) {if (this.logger.isDebugEnabled()) {this.logger.debug(this.toString() + " PC:Ack:" + seq + ":" + multiple);}this.processAck(seq, true, multiple, true);
}public void handleNack(long seq, boolean multiple) {if (this.logger.isDebugEnabled()) {this.logger.debug(this.toString() + " PC:Nack:" + seq + ":" + multiple);}this.processAck(seq, false, multiple, true);
}
public synchronized void addPendingConfirm(PublisherCallbackChannel.Listener listener, long seq, PendingConfirm pendingConfirm) {SortedMap<Long, PendingConfirm> pendingConfirmsForListener = (SortedMap)this.pendingConfirms.get(listener);Assert.notNull(pendingConfirmsForListener, "Listener not registered: " + listener + " " + this.pendingConfirms.keySet());pendingConfirmsForListener.put(seq, pendingConfirm);this.listenerForSeq.put(seq, listener);if (pendingConfirm.getCorrelationData() != null) {String returnCorrelation = pendingConfirm.getCorrelationData().getId();if (StringUtils.hasText(returnCorrelation)) {this.pendingReturns.put(returnCorrelation, pendingConfirm);}}}

PublisherCallbackChannelImpl是channel的实现类,若是传递了correlationData会转化成PendingConfirm放到map里面,Listener是key,PendingConfirm是value的key。当broker确认消息后,会触发channel的confirm监听器,找到对应的CorrelationData,执行回调。

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

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

相关文章

Linux系统安装PaddleDetection

一、安装cuda 1. 查看设备 先输入nvidia-smi&#xff0c;查看设备支持的最大cuda版本&#xff0c;选择官网中支持的cuda版本 https://www.paddlepaddle.org.cn/install/quick?docurl/documentation/docs/zh/install/conda/linux-conda.html 2. 下载CUDA并安装 使用快捷键…

Linux系统中的时间同步服务

1.时间同步&#xff1a;多主机协作工作&#xff0c;时间应该保持一致&#xff0c;如加密协议、日志、集群等&#xff0c;利用NTP&#xff08;Network Time Protocol&#xff09;协议使得各个主机时间达到同步。 ntp:将系统时钟和世界协调时UTC同步&#xff0c;精度在局域网内可…

【Linux笔记】系统的延迟任务、定时任务极其相关命令(at、crontab极其黑白名单等)

一、延时任务 1、概念 延时任务&#xff08;Delayed Jobs&#xff09;通常指在指定时间或特定条件满足后执行的任务。常见的实现方式包括 at 和 batch 命令&#xff0c;以及结合 cron 的调度功能。 2、命令 延时任务的命令最常用的是at命令&#xff0c;第二大节会详细介绍。…

软考 系统架构设计师系列知识点 —— 黑盒测试与白盒测试(1)

本文内容参考&#xff1a; 黑盒测试和白盒测试详解-CSDN博客 软件测试中的各种覆盖&#xff08;Coverage&#xff09;详解-CSDN博客 特此致谢&#xff01; 零、概述 黑盒测试又名为功能测试&#xff0c;主要目的是发现软件设计的需求或者是软件设计规格说明书中的错误缺陷。…

yolov11 epoch100轮 训练笔记5 kaggle comet

Football Players Detection using YOLOV11 | Kaggle !pip install comet_ml import comet_mlcomet_ml.login(project_name"c") Comet - Build Better Models Faster yolov11训练 100轮一眨眼训练完了 然而comet接不到yolo的sdk 优秀 训练17轮map就0.99了 v5训练100…

Ubuntu K8S(1.28.2) 节点/etc/kubernetes/manifests 不存在

Ubuntu K8S(1.28.2) 节点/etc/kubernetes/manifests 不存在 在查看日志&#xff08;journalctl -xefu kubelet&#xff09;时发现各节点/etc/kubernetes/manifests 不存在&#xff0c;但主节点没有异常 21080 file.go:104] "Unable to read config path" err"…

neo4j基础操作:命令行增删改查

目录 一&#xff0c;Neo4j的增 1.1.新增节点 1.2.新增关系 1.2.1创建节点时&#xff0c;创建关系 1.2.2在已有的节点上&#xff0c;创建关系 二&#xff0c;Neo4j的删除 2.1删除节点 2.1.1无关系的节点删除 2.1.2 有关系的节点删除 三&#xff0c;节点修改 3.1 给节点…

rollout 是什么:机器学习(强化学习)领域

rollout 是什么:机器学习(强化学习)领域 指从特定初始状态开始,按照某个策略或模型进行一系列动作和状态转移,直到达到终止状态或预定时间步数 。比如: 迷宫任务:强化学习代理在迷宫中,从起始点出发,按某策略(如随机选方向走)进行移动,直到找到出口或达到最大移动…

stm32之TIM定时中断详解

目录 1.引入1.1 简介1.2 类型1.2.1 基本定时器1.2.2 通用定时器1. 触发控制单元 (Trigger Control Unit)2. 输入捕获单元 (Input Capture Unit)3. 输出比较单元 (Output Compare Unit)4. CNT 计数器5. 自动重装载寄存器 (ARR)6. 预分频器 (PSC)7. 中断与 DMA 事件8. 刹车功能 (…

centos8源码安装openssl

前言&#xff1a; 在使用python3.11部署运行FastAPI时&#xff0c;由于其uvicorn需要使用openssl模块&#xff0c;导致没有安装openssl的服务器项目运行不起来. 【第一步】 我的网盘下载openssl-1.1.1n.tar.gz 提取码: vay9 【第二步】 上传到服务器解压 tar -zxvf opens…

vue3 动态修改系统title

vue3 动态修改系统title 修改前 修改后 1、封装 useTitle 工具函数 创建组合式 API&#xff0c;通过 watchEffect 监听标题变化&#xff1a; // composables/useTitle.js import { ref, watchEffect } from vue;export function useTitle(initialTitle) {const title r…

比较两种判断相同二叉树的方法:递归与遍历序列对比

在二叉树操作中&#xff0c;判断两棵树是否相同是一个常见的问题。本文将对比两种不同的解决方案&#xff1a;递归法和遍历序列对比法&#xff0c;分析它们的优缺点&#xff0c;并探讨为何递归法是更优的选择。 问题描述 给定两棵二叉树的根节点 p 和 q&#xff0c;判断它们是…

从0开始学习大模型--Day01--大模型是什么

初识大模型 在平时遇到问题时&#xff0c;我们总是习惯性地去运用各种搜索引擎如百度、知乎、CSDN等平台去搜索答案&#xff0c;但由于搜索到的内容质量参差不齐&#xff0c;检索到的内容只是单纯地根据关键字给出内容&#xff0c;往往看了几个网页都找不到答案&#xff1b;而…

【AI大模型】SpringBoot整合Spring AI 核心组件使用详解

目录 一、前言 二、Spring AI介绍 2.1 Spring AI介绍 2.2 Spring AI主要特点 2.3 Spring AI核心组件 2.4 Spring AI应用场景 2.5 Spring AI优势 2.5.1 与 Spring 生态无缝集成 2.5.2 模块化设计 2.5.3 简化 AI 集成 2.5.4 支持云原生和分布式计算 2.5.5 安全性保障…

洛谷 P9007 [入门赛 #9] 最澄澈的空与海 (Hard Version)

这道题可不入门。 [Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription] 给定 n n n&#xff0c;求有多少组 ( x , y , z ) (x,y,z) (x,y,z) 满足&#xff1a; x − y z n ! x-\dfrac{y}{z}n! x−zy​n! x − y z n ! n \dfrac{x-y…

PostgreSQL 的 pg_stat_file 函数

PostgreSQL 的 pg_stat_file 函数 pg_stat_file 是 PostgreSQL 提供的一个系统管理函数&#xff0c;用于获取文件系统上文件的元数据信息。这个函数对于数据库管理员进行文件级别的监控和诊断非常有用。 一 函数基本语法 pg_stat_file(filename text [, missing_ok boolean …

关于麒麟服务器实现docker-compose服务开机自启

我本地服务器环境是麒麟V10版本&#xff1a; 首先确定docker-compose服务绝对路径命令&#xff1a; which docker-compose我这里输出是&#xff1a;/usr/bin/docker-compose 编辑服务文件&#xff1a; sudo vim /etc/systemd/system/docker-compose-webup.service[Unit] Desc…

基于 jQuery 实现复选框全选与选中项查询功能

在 Web 开发中&#xff0c;复选框是常见的交互元素&#xff0c;尤其是在涉及批量操作、数据筛选等场景时&#xff0c;全选功能和选中项查询功能显得尤为重要。本文将介绍如何使用 HTML、CSS 和 jQuery 实现一个具备全选、反选以及选中项查询功能的复选框组&#xff0c;帮助开发…

AfuseKt2.4.2 | 支持阿里云盘、Alist等平台视频播放,具备自动海报墙刮削功能的强大播放器

AfuseKt是一款功能强大的安卓端在线视频播放器&#xff0c;支持播放阿里云盘、Alist、WebDAV等平台的视频内容。它具备自动海报墙刮削功能&#xff0c;能自动生成影片信息和海报墙&#xff0c;提供良好的视觉体验。此外&#xff0c;它还支持倍速播放、字幕、音轨切换等多种实用…

Netlink在SONiC中的应用

Netlink在SONiC中的应用 Netlink介绍 Netlink 是 Linux 内核态程序与用户空间程序之间进行通信的机制之一&#xff0c;原本是用于传递网络协议栈中的各种控制消息。它采用和套接字&#xff08;socket&#xff09;编程接口相同的形式&#xff0c;常用于配置内核网络子系统&…