科普文:微服务之Spring Cloud Alibaba消息队列组件RocketMQ工作原理

news2024/11/23 15:22:10

概叙

本文探讨 RocketMQ 的事务消息原理,并从源码角度进行分析,以及事务消息适合什么场景,使用事务消息需要注意哪些事项。

同时详细介绍RocketMQ 事务消息的基本流程,并通过源码分析揭示了其内部实现原理,尽管事务消息增加了系统的复杂性,但在需要保证消息一致性的场景中,它仍然是一种非常有效的解决方案,比如资金转账、订单处理、分布式事务、库存管理等场景。

什么是事务消息

事务消息是为了保证分布式系统中消息的一致性而引入的一种消息类型。事务消息允许消息发送方在发送消息后,进行本地事务操作,并根据本地事务的执行结果来决定消息的最终状态(提交或回滚)。

RocketMQ 事务消息的优缺点

优点

  • 保证消息一致性:通过事务消息,RocketMQ 能够保证分布式系统中消息的一致性,避免数据不一致问题。

  • 高性能:RocketMQ 的事务消息性能较高,能够满足高并发场景的需求。

  • 易用性:RocketMQ 提供了简单易用的 API,使得开发者能够方便地使用事务消息。

缺点

  • 复杂性:事务消息的引入增加了系统的复杂性,开发者需要处理事务状态回查等问题。

  • 时延:事务消息的处理涉及half消息、回查等操作,可能会增加消息的时延。

事务消息适用场景

资金转账

在金融系统中,资金转账需要确保资金的一致性和安全性。例如,从账户 A 转账到账户 B,必须确保 A 的金额减少和 B 的金额增加是一个原子操作。使用事务消息可以保证在转账过程中,如果任何一个步骤失败,整个操作都会回滚,确保数据一致性。

订单处理

在电子商务系统中,订单处理通常涉及多个步骤,例如创建订单、扣减库存、生成支付记录等。这些步骤需要保证一致性。使用事务消息可以确保如果某一步操作失败,整个订单处理过程可以回滚,避免数据不一致。

分布式事务

在微服务架构中,分布式事务是一个常见的挑战。多个微服务之间的操作需要协调一致,事务消息可以作为一种分布式事务解决方案,确保各个微服务之间的数据一致性。

库存管理

在库存管理系统中,库存的增减操作需要保证一致性。例如,用户下单后需要扣减库存,使用事务消息可以确保在扣减库存失败时,订单状态不会被错误更新。

事务消息注意事项

确保本地事务的幂等性

在分布式系统中,本地事务操作可能会被多次执行。例如,在事务状态回查时,Broker 可能会多次检查本地事务状态。因此,确保本地事务操作的幂等性非常重要。幂等性可以确保多次执行相同的操作不会产生副作用。

设置合理的超时时间

事务消息的处理涉及half消息、提交或回滚请求以及事务状态回查。设置合理的超时时间可以避免长时间等待,影响系统性能。超时时间应根据实际业务需求和系统性能进行调整。

处理事务状态回查

事务状态回查是事务消息的重要机制。当 Broker 在规定时间内没有收到提交或回滚请求时,会主动发起事务状态回查。开发者需要实现 TransactionCheckListener 接口,并在 checkLocalTransactionState 方法中处理回查逻辑,确保能够正确返回事务状态。

监控和日志

监控和日志是确保事务消息系统稳定运行的重要手段。通过监控,可以及时发现系统中的异常情况,例如事务状态回查失败、消息发送失败等。日志记录可以帮助开发者排查问题,分析系统性能。

资源隔离

在使用事务消息时,确保事务消息与其他普通消息的资源隔离,以避免相互影响。例如,可以为事务消息单独配置 Topic 和队列,确保事务消息的处理不受其他消息影响。

事务消息的重试机制

在某些情况下,事务消息的提交或回滚请求可能会失败。开发者需要考虑实现重试机制,以确保最终能够正确提交或回滚事务消息。重试机制可以通过定时任务或消息队列实现。

性能影响

事务消息的处理涉及多次网络通信和状态检查,可能会对系统性能产生一定影响。在高并发场景中,需要评估事务消息对系统性能的影响,并进行相应的优化。例如,可以通过批量处理、异步处理等方式提高性能。

RocketMQ事务消息(Transactional Message)

RocketMQ事务消息(Transactional Message)是指应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败。RocketMQ的事务消息提供类似 X/Open XA 的分布式事务功能,通过事务消息能达到分布式事务的最终一致。

RocketMQ 事务消息的基本流程

RocketMQ 的事务消息是指在消息发送方发送消息后,需要经过两阶段提交来确保消息的可靠性传递和处理。

RocketMQ 的事务消息流程大致可以分为以下几个阶段:

  • 发送half消息:生产者首先发送一条 "half消息" 到 RocketMQ Broker。half消息是指消息已经发送到 Broker,但此时消息状态不确定,该消息对消费者不可见。

  • 执行本地事务:生产者在发送half消息之后,立即执行本地事务操作。例如,更新数据库、调用外部服务等。

  • 提交或回滚事务消息:本地事务操作完成后,生产者根据本地事务的执行结果,向 Broker 发送 "提交" 或 "回滚" 请求。如果本地事务执行成功,则发送 "提交" 请求,使得half消息变为可消费的正式消息;如果本地事务失败,则发送 "回滚" 请求,Broker 将删除该half消息。

  • 事务状态回查:如果在规定时间内 Broker 没有收到提交或回滚请求,Broker 会主动向消息发送方发起事务状态回查,以确认该消息的最终状态。

Apache RocketMQ在4.3.0版中已经支持分布式事务消息,采用了2PC(两阶段提交)+ 补偿机制(事务状态回查)的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息,如上图所示。

RocketMQ 事务消息的源码分析

下面给出一个完整事务消息发送示例:

public class TransactionProducer {
    public static void main(String[] args) throws Exception {
        // 创建事务消息生产者
        TransactionMQProducer producer = new TransactionMQProducer("TransactionProducerGroup");
        producer.setNamesrvAddr("localhost:9876");

        // 设置事务状态回查监听器
        producer.setTransactionCheckListener(new TransactionCheckListener() {
            @Override
            public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
                // 处理事务状态回查逻辑
                System.out.println("Checking transaction state for message: " + new String(msg.getBody()));
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });

        // 启动生产者
        producer.start();

        // 发送事务消息
        Message msg = new Message("TransactionTopic", "TagA", "Transaction Message".getBytes());
        SendResult sendResult = producer.sendMessageInTransaction(msg, new LocalTransactionExecuter() {
            @Override
            public LocalTransactionState executeLocalTransactionBranch(Message msg, Object arg) {
                // 执行本地事务逻辑
                System.out.println("Executing local transaction for message: " + new String(msg.getBody()));
                // 假设本地事务执行成功,返回 COMMIT_MESSAGE
                // 如果本地事务失败,返回 ROLLBACK_MESSAGE
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        }, null);

        System.out.println("Send result: " + sendResult);

        // 阻塞主线程,防止退出
        System.in.read();

        // 关闭生产者
        producer.shutdown();
    }
}

客户端的事务消息处理

发送half消息

发送half消息的核心代码在 TransactionMQProducer 类中,通过 sendMessageInTransaction 方法实现:

public TransactionSendResult sendMessageInTransaction(Message msg, LocalTransactionExecuter tranExecuter, Object arg) {
    // 1. 发送`half`消息
    SendResult sendResult = this.defaultMQProducerImpl.send(msg);

    // 2. 执行本地事务
    LocalTransactionState localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);
    
    // 3. 根据本地事务状态提交或回滚消息
    this.endTransaction(msg, localTransactionState);
    
    return new TransactionSendResult(sendResult, localTransactionState);
}

在 sendMessageInTransaction 方法中,首先调用 send 方法发送half消息,然后执行本地事务,并根据本地事务的结果调用 endTransaction 方法提交或回滚消息。

执行本地事务

本地事务的执行由 LocalTransactionExecuter 接口的实现类来完成。在实际使用中,用户需要实现该接口,并在 executeLocalTransactionBranch 方法中定义具体的本地事务逻辑。

public interface LocalTransactionExecuter {
    LocalTransactionState executeLocalTransactionBranch(final Message msg, final Object arg);
}

提交或回滚事务消息

提交或回滚事务消息的实现也在 TransactionMQProducer 类中,通过 endTransaction 方法完成:

private void endTransaction(Message msg, LocalTransactionState localTransactionState) {
    // 构建事务结束请求
    EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
    requestHeader.setCommitOrRollback(localTransactionState == LocalTransactionState.COMMIT_MESSAGE ? 0 : 1);
    requestHeader.setTranStateTableOffset(msg.getQueueOffset());
    requestHeader.setCommitLogOffset(msg.getCommitLogOffset());

    // 发送事务结束请求到 Broker
    this.defaultMQProducerImpl.endTransaction(msg, requestHeader);
}

在 endTransaction 方法中,根据本地事务的执行结果构建事务结束请求,并调用 endTransaction 方法将请求发送到 Broker。

事务状态回查

事务状态回查是由 Broker 发起的。当 Broker 在规定时间内没有收到提交或回滚请求时,会主动向消息发送方发起事务状态回查。回查的实现主要在 TransactionCheckListener 接口中:

public interface TransactionCheckListener {
    LocalTransactionState checkLocalTransactionState(final MessageExt msg);
}

消息发送方需要实现 TransactionCheckListener 接口,并在 checkLocalTransactionState 方法中定义如何检查本地事务的状态。

Broker 端的事务消息处理

Broker 端的事务消息处理主要在 TransactionalMessageServiceImpl 类中实现。Broker 负责接收half消息、提交或回滚请求,并在必要时发起事务状态回查。

接收half消息

Broker 接收half消息的逻辑在 TransactionalMessageServiceImpl 类的 prepareMessage 方法中:

public PutMessageResult prepareMessage(MessageExtBrokerInner msgInner) {
    // 存储`half`消息
    return this.store.putMessage(msgInner);
}
提交或回滚消息

Broker 处理提交或回滚请求的逻辑在 TransactionalMessageServiceImpl 类的 commitMessage 和 rollbackMessage 方法中:

public boolean commitMessage(MessageExt msgExt) {
    // 提交消息
    return this.store.commitTransaction(msgExt);
}

public boolean rollbackMessage(MessageExt msgExt) {
    // 回滚消息
    return this.store.rollbackTransaction(msgExt);
}
事务状态回查

Broker 发起事务状态回查的逻辑在 TransactionalMessageServiceImpl 类的 check 方法中:

public void check(long transactionTimeout, int transactionCheckMax, String topic) {
    // 遍历`half`消息队列,发起事务状态回查
    List<MessageExt> halfMessages = this.store.getHalfMessages(topic);
    for (MessageExt msg : halfMessages) {
        // 发起回查请求
        this.brokerController.getBroker2Client().checkProducerTransactionState(msg);
    }
}

RocketMQ分布式事务原理

分布式事务应用场景

随着应用的拆分,从单体架构变成分布式架构,那么每个服务或者模块也会有自己的数据库。一个业务流程的完成需要经过多次的接口调用或者多条MQ消息的发送。
那基于上面的应用场景,应该如何设计发送消息的流程,才能让这两个操作要么都成功,要么都失败呢?其实,可以参照XA两阶段提交的思想,把发送消息分成两步,然后把操作本地数据库也包括在这个流程中。
那么,在介绍原理之前,先科普一下两个新的概念:
1、半消息(Half Message):也就是暂不能投递消费者的消息。发送方已经将消息成功发送到了 MQ
服务端,但是服务端未收到生产者对这条消息的二次确认,这个时候,这条消息会被标记为“暂不能投递”状态。
2、消息回查(Message Status Check):由于网络闪断、生产者应用重启等原因,导致某条事务消息的
二次确认丢失,MQ 服务端通过扫描发现某条消息长期处于“半消息”时,需要主动向消息生产者询问该消息的最终状态,要么是Commit,要么Rollback。
如图所示,一共分为七个步骤

第一步:生产者向 MQ 服务端发送消息。
第二步:MQ 服务端将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息。
第三步:发送方开始执行本地数据库事务逻辑。
第四步:发送方根据本地数据库事务执行结果向 MQ Server 提交二次确认,MQ Server 收到 Commit状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息。
第五步:在断网或者是应用重启的特殊情况下,按步骤4提交的二次确认最终未到达 MQ Server,经过固定时间后 MQ Server 将对该消息发起消息回查。
第六步:发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
第七步:发送方根据检查得到的本地事务的最终状态再次提交二次确认,MQ Server 仍按照步骤4对半消息进行操作(Commit/Rollback)。

RocketMQ事务消息使用限制


使用事务消息,有一些限制条件:

  • 事务消息不支持延时消息和批量消息;
  • 事务性消息可能不止一次被检查或消费,所以消费者端需要做好消费幂等;
为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次(即默认只会回查15次),
我们可以通过 Broker 配置文件的 transactionCheckMax参数来修改此限制。
如果已经检查某条消息超过 N 次的话( N = transactionCheckMax ), 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。
用户可以通过重写AbstractTransactionCheckListener 类来修改这个行为;
事务消息将在 Broker 配置文件中的参数 transactionMsgTimeout 这样的特定时间长度之后被检查。
当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 transactionMsgTimeout 参数;
提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。
它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。

  • 事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。
  • 1.事务消息执行时间限制:RocketMQ 要求事务消息的本地事务执行器(TransactionListener)在规定的时间内完成并返回事务执行结果,否则可能会触发回查机制。
  • 2.回查机制的限制:如果发送方应用程序长时间未返回事务执行结果,RocketMQ 服务端会触发回查机制,这可能会增加系统的负担和网络开销。
  • 3.不支持跨集群事务消息:RocketMQ 不支持跨集群的事务消息,即发送方和消费方需要处于同一个 RocketMQ 集群中。
  • 4.事务消息的可靠性和性能权衡:由于事务消息需要经过两阶段提交,相比普通消息可能存在一定的性能损耗。
  • 5.需要依赖本地事务执行器:发送方应用程序需要自行实现和注册本地事务执行器,确保本地事务的正确执行和结果反馈。

总的来说,RocketMQ 事务消息在确保消息可靠传递的同时,也需要开发者按照一定的规范来设计和实现本地事务执行器,以及处理可能的回查请求,这些都是在使用 RocketMQ 事务消息时需要考虑和遵循的限制。

RocketMQ事务消息怎么实现

在 RocketMQ 中,实现事务消息可以通过使用事务生产者(Transaction Producer)来完成。下面是一个简单的示例代码,演示了如何在 RocketMQ 中实现事务消息:

import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TransactionProducer {
    public static void main(String[] args) throws Exception {
        TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_group");
        producer.setNamesrvAddr("your_namesrv_address");

        // 定义事务监听器
        TransactionListener transactionListener = new TransactionListenerImpl();
        producer.setTransactionListener(transactionListener);

        // 定义线程池来处理事务消息的预备、提交和回查
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });
        producer.setExecutorService(executor);

        producer.start();

        // 发送事务消息
        Message message = new Message("YourTopic", "YourTag", "YourKeys", "YourMsg".getBytes());
        producer.sendMessageInTransaction(message, null);

        // 关闭生产者
        producer.shutdown();
    }
}

class TransactionListenerImpl implements TransactionListener {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // 在此处执行本地事务,根据执行结果返回不同的状态
        return LocalTransactionState.COMMIT_MESSAGE; // or ROLLBACK_MESSAGE or UNKNOW
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 在此处检查本地事务的状态,并返回相应的状态
        return LocalTransactionState.COMMIT_MESSAGE; // or ROLLBACK_MESSAGE or UNKNOW
    }
}

以上代码中,我们创建了一个事务生产者 TransactionMQProducer,并设置了事务监听器 TransactionListener。

在事务监听器的实现中,我们需要实现 executeLocalTransaction 方法来执行本地事务,以及 checkLocalTransaction 方法来检查本地事务的状态。

在 executeLocalTransaction 中,根据本地事务的执行结果返回不同的状态,而在 checkLocalTransaction 中,根据本地事务的状态返回相应的状态。
使用事务消息时,需要确保消息发送的可靠性,以及本地事务的正确执行和状态的正确返回。在实际场景中,还需要根据业务逻辑来合理处理事务消息的执行和状态回查。

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

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

相关文章

【avue+vue2+elementui】删除、rules、页面跳转、列表数据过长、日期dayjs

这里写目录标题 一、删除二、rules三、页面跳转四、列表数据过长截断五、日期 dayjs一、删除 🍃API/*** 删除.* @param {*} data * @returns 返参*/ export const deleteOrder = (data) => {return request({url: /api/Order/deleteOrder,method: post,data}) }HTML🍃左…

常见病症之中医药草一枝黄花

常见病症之中医药草一枝黄花 1. 源由2. 一枝黄花植物描述药用部分主要成分药理作用使用方法注意事项 3. 常用方剂3.1 一枝黄花汤3.2 一枝黄花解毒汤 4. 着凉感冒主要方剂加味处方使用方法注意事项 5. 补充资料 1. 源由 注&#xff1a;仅供参考&#xff0c;建议在中医师指导下使…

Unity【入门】小项目坦克大战

文章目录 1、开始场景1、场景装饰RotateObj 2、开始界面BasePanelBeginPanel 3、设置界面GameDataMgrSettingPanel 4、音效数据逻辑MusicData 5、排行榜界面RankPanel 6、排行榜数据逻辑RankInfo 7、背景音乐BKMusic 2、游戏场景1、游戏界面GamePanel 2、基础场景搭建CubeObjQu…

如何使用极狐GitLab CI/CD Component Catalog?【上】

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

SQL进阶技巧:Hive如何巧解和差计算的递归问题?【应用案例2】

目录 0 问题描述 1 数据准备 2 问题分析 3 小结 0 问题描述 有如下数据:反应了每月的页面浏览量 现需要按照如下规则计算每月的累计阅读量,具体计算规则如下: 最终结果如下: 1 数据准备 with data as( select 2024-01 as month ,2 as pv union all select 2024-02 …

使用MongoDB构建AI:Jina AI将突破性开源嵌入模型变为现实

Jina AI创立于2020年&#xff0c;总部位于德国柏林&#xff0c;主要从事提示工程和嵌入模型业务&#xff0c;已迅速成长为多模态AI领导者。Jina AI积极推动开源和开放研究&#xff0c;致力于弥合先进AI理论与开发者及数据科学家构建的AI驱动型真实世界应用程序之间的差距。目前…

卷积神经网络 - 池化(Pooling)篇

序言 在深度学习的广阔领域中&#xff0c;卷积神经网络&#xff08; CNN \text{CNN} CNN&#xff09;以其卓越的特征提取能力&#xff0c;在图像识别、视频处理及自然语言处理等多个领域展现出非凡的潜力。而池化&#xff08; Pooling \text{Pooling} Pooling&#xff09;作为…

智慧水务项目(四)django(drf)+angular 18 配置REST_FRAMEWORK

一、说明 建立了几个文件 二、一步一步来 1、建立json_response.py 继承了 Response&#xff0c; 一共三个函数&#xff0c;成功、详情&#xff0c;错误 from rest_framework.response import Responseclass SuccessResponse(Response):"""标准响应成功的返回…

springboot共享汽车租赁管理系统-计算机毕业设计源码99204

目 录 第 1 章 引 言 1.1 选题背景及意义 1.2 研究前期调研 1.3 论文结构安排 第 2 章 系统的需求分析 2.1 系统可行性分析 2.1.1 技术方面可行性分析 2.1.2 经济方面可行性分析 2.1.3 法律方面可行性分析 2.1.4 操作方面可行性分析 2.2 系统功能需求分析 2.3 系统…

机械学习—零基础学习日志(高数18——无穷小与无穷大)

零基础为了学人工智能&#xff0c;真的开始复习高数 学习速度加快&#xff01; 无穷小定义 这里可以记住&#xff0c;无穷小有一个特殊&#xff0c;那就是零。 零是最高阶的无穷小&#xff0c;且零是唯一一个常数无穷小。 张宇老师还是使用了超实数概念来讲解无穷小。其实是…

《马拉松名将手记:42.195公里的孤独之旅》大迫杰之舞

《马拉松名将手记&#xff1a;42.195公里的孤独之旅》大迫杰之舞 大迫杰&#xff0c;日本田径长跑选手。2020年3月1日&#xff0c;在东京马拉松赛上&#xff0c;以2小时5分29秒获得日本本土冠军&#xff0c;刷新自己保持的日本国家记录&#xff0c;并拿下东京奥运会马拉松项目入…

UE5 从零开始制作跟随的鸭子

文章目录 二、绑定骨骼三、创建 ControlRig四、创建动画五、创建动画蓝图六、自动寻路七、生成 goose八、碰撞 和 Physics Asset缺点 # 一、下载模型 首先我们需要下载一个静态网格体&#xff0c;这里我们可以从 Sketchfab 中下载&#xff1a;Goose Low Poly - Download Free …

黑暗之魂和艾尔登法环有什么联系吗 黑暗之魂和艾尔登法环哪一个好玩 苹果电脑怎么玩Windows游戏 apple电脑可以玩游戏吗

有不少游戏爱好者对于艾尔登法环与经典游戏黑魂之间是否存在关系产生了疑问。在新旧元素的融合中&#xff0c;艾尔登法环注定成为一场别具匠心的冒险之旅。在实机演示中类魂的玩法以及黑魂相似的画风让不少玩家想要了解本作与黑魂是否有联系&#xff0c;今天&#xff0c;我们将…

SAP生产版本维护以及注意事项

事务代码&#xff1a;C223/MM02&#xff0c;CS01,CA01 步骤&#xff1a; CS01创建BOM CA01创建工艺路线 C223/MM02创建生产版本 选择BOM清单 注意&#xff1a; 1、该生产版本与BOM清单绑定在一起&#xff0c;后续如果BOM有多个&#xff0c;需要添加或修改这个生产版本 2、…

进化版:一个C++模板工厂的编译问题的解决。针对第三方库的构造函数以及追加了的对象构造函数。牵扯到重载、特化等

原始版本在这里 一个C模板工厂的编译问题的解决。针对第三方库的构造函数以及追加了的对象构造函数。牵扯到重载、特化等-CSDN博客 问题 1、关于类型的判断&#xff0c;适应性不强 比如template <typename T>IsFarElementId<>&#xff0c;目前只能判断FarElemen…

达梦数据库的系统视图v$cacheitem

达梦数据库的系统视图v$cacheitem 达梦数据库的系统视图V$CACHEITEM的作用是显示缓存中的项信息&#xff0c;在 ini 参数 USE_PLN_POOL !0 时才统计。这个视图帮助数据库管理员监控和分析缓存的使用情况&#xff0c;优化数据库性能。通过查询V$CACHEITEM视图&#xff0c;可以获…

ai web 1.0靶机漏洞渗透详解

一、导入靶机 解压下载好的靶机&#xff0c;然后打开VMware&#xff0c;点击文件》打开》找到刚刚解压的靶机点击下面的文件》打开 确认是靶机的网络连接模式是NAT模式 二、信息收集 1、主机发现 在本机的命令窗口输入ipconfig查看VMnet8这块网卡&#xff0c;这块网卡就是虚…

案例分享—国外优秀ui设计作品赏析

国外UI设计创意迭出&#xff0c;融合多元文化元素&#xff0c;以极简风搭配动态交互&#xff0c;打造沉浸式体验&#xff0c;色彩运用大胆前卫&#xff0c;引领界面设计新风尚 同时注重用户体验的深度挖掘&#xff0c;通过个性化定制与智能算法结合&#xff0c;让界面不仅美观且…

代码随想录第十八天|动态规划(2)

目录 01背包问题——二维数组 01背包问题——一维数组 LeetCode 416. 分割等和子集 LeetCode 1049. 最后一块石头的重量 II LeetCode 494. 目标和 LeetCode 474. 一和零 总结 01背包问题——二维数组 有n件物品和一个最多能放入重量为w的背包。第i件物品的重量是weight…

web基础之CSS

web基础之CSS 文章目录 web基础之CSS一、CSS简介二、基本用法2、CSS应用方式2.1 行内样式2.2内部样式2.3外部样式 三、选择器1、标签选择器2、类选择器3、ID选择器4、选择器的优先级 四、常见的CSS属性1、字体属性2、文本属性3、背景属性4、表格属性5、盒子模型的属性6、定位 总…