RocketMQ 发送事务消息

news2025/2/27 18:29:36

文章目录

  • 事务的相关理论
    • 事务ACID特性
    • CAP 理论
    • BASE 理论
  • 事务消息应用场景
    • MQ 事务消息处理处理逻辑
  • RocketMQ 事务消息处理流程
    • 官网事务消息流程图
  • rocketmq-client-java 示例(gRPC 协议)
    • 创建事务主题
    • 生产者
    • 消费者
  • rocketmq-client 示例(Remoting 协议)
    • 生产者
  • 消费者

事务的相关理论

事务ACID特性

  • Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复到事务开始前的状态,就像这个事务从来没有执行过一样。
  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。完整性包括外键约束、应用定义的等约束不会被破坏。
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

CAP 理论

CAP 定理(也称为 Brewer 定理),指的是在分布式系统环境下,有3个核心的需求:

  1. 一致性(Consistency):在分布式系统中,所有实例节点同一时间看到是相同的数据
  2. 可用性(Availability):不管是否成功,确保每一个请求都能接收到响应
  3. 分区容错性(Partition Tolerance):系统任意分区后,在网络故障、应用掉线时,仍能正常操作

分布式系统不可能同时满足上面三种,最多同时满足其中两种,也就是CA、CP、AP

  • CA:放弃分区容错性。保证数据的强一致性,在分布式系统环境下,分区容错性一般是必要保障,否则任何一个服务或应用出文档,都将导致系统不可用。所以我们不会选择 CA,意味着我们必选 P。
  • CP:放弃可用性。意味着,不能确保每个请求都能收到响应,意味着用户操作会出现长时间无反馈或者无正确反馈的问题。
  • AP:放弃一致性。准确的说是放弃强一致性,在用户操作之后一定时间内,各个节点数据可能有不一致的情况,但最终会一致。例如:支付宝向银行转账,支付宝转账成功后。过一段时间在银行账户上才到账。在实际应用中,AP 是最常采用的方案。MQ 即是实现该方案的常用中间件。

BASE 理论

  • Basically Available(基本可用):分布式系统在出现不可预知的故障时,允许损失部分可用性。
  • Soft state(软状态):在保证系统基本可用的前提下,允许数据到部分不一致的状态。
  • Eventually consistent(最终一致性):数据不一致的状态进过一定的时间,最终都会一致。

事务消息应用场景

顾名思义,事务消息主要用于分布式应用中,解决分布式事务的问题,且是采用的最终一致的方案。

此处我们采用官网案例:用户支付订单操作,此业务操作的处理分支包括:

  • 主分支订单系统状态更新:由未支付变更为支付成功。
  • 物流系统状态新增:新增待发货物流记录,创建订单物流记录。
  • 积分系统状态变更:变更用户积分,更新用户积分表。
  • 购物车系统状态变更:清空购物车,更新用户购物车记录。

此操作涉及这么多的下游系统,如果采用强一致性事务来实现,首先会导致事务控制时间太长,事务控制的范围太大,进一步导致系统并发效率低下,系统性能也低。

MQ 事务消息处理处理逻辑

在这里插入图片描述

此图我们还是按照用户订单支付操作的例子来说明,订单支付操作是订单系统的操作,其对应一个本地事务(branch 2),其余下游系统都是在这个支付事务之后需要执行的事务(branch 2.1、branch 2.2、branch 2.3)。

RocketMQ 事务消息,保证的是 branch 2 的事务如果成功,则 MQ 服务端就一定会有一条对应的半事务消息。如果 branch 2 的事务回滚,则 MQ 服务端也会回滚对应的半事务消息,此需要生产者来保证。

branch 2.1、branch 2.2、branch 2.3 是由各个子系统中对应的事务消息的消费者来实现的,只要 branch 2 成功,那么对应的 2.1 、2.2 、2.3 都必须要执行成功,此需要消费者来保证。

RocketMQ 事务消息处理流程

在这里插入图片描述

图中黄色线条为特殊情况下的状态回查流程。

  1. 生产者将消息发送至Apache RocketMQ服务端。

  2. Apache RocketMQ服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息被标记为"暂不能投递",这种状态下的消息即为半事务消息。

  3. 生产者开始执行本地事务逻辑。

  4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:

    • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
    • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  5. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。 说明 服务端回查的间隔时间和最大回查次数,请参见参数限制。

  6. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。

  7. 生产者根据检查到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。

官网事务消息流程图

在这里插入图片描述

rocketmq-client-java 示例(gRPC 协议)

创建事务主题

$> ./mqadmin updatetopic -n localhost:9876 -c DefaultCluster -t MY_TRANSACTION_TOPIC -a +message.type=TRANSACTION

生产者

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.commons.collections.MapUtils;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.message.Message;
import org.apache.rocketmq.client.apis.producer.Producer;
import org.apache.rocketmq.client.apis.producer.Transaction;
import org.apache.rocketmq.client.apis.producer.TransactionResolution;

import java.util.Map;

public class TransactionProducerDemo {

    public static void main(String[] args) {

        // 用于提供:生产者、消费者、消息对应的构建类 Builder
        ClientServiceProvider provider = ClientServiceProvider.loadService();

        // 构建配置类(包含端点位置、认证以及连接超时等的配置)
        ClientConfiguration configuration = ClientConfiguration.newBuilder()
                // endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
                .setEndpoints(MyMQProperties.ENDPOINTS)
                .build();

        // 构建生产者
        Producer producer = null;
        try {
            producer = provider.newProducerBuilder()
                    // Topics 列表:生产者和主题是多对多的关系,同一个生产者可以向多个主题发送消息
                    .setTopics("MY_TRANSACTION_TOPIC")
                    .setClientConfiguration(configuration)
                    // 设置回查对象 TransactionChecker(注意:此方法回查的是订单系统本地事务是否成功,以决定当前消息事务是否提交或回滚)
                    .setTransactionChecker(messageView -> {
                        Map<String,String> p = messageView.getProperties();

                        if(MapUtils.isEmpty(p)){
                            // 说明回查的消息有误,直接回滚(此处是回滚的消息事务,半事务消息将不会投递)
                            return TransactionResolution.ROLLBACK;
                        }

                        String orderId = p.get("orderId");
                        String status = p.get("status");
                        // 验证订单系统本地数据库事务是否成功
                        return checkOrderStatus(orderId,status) ? TransactionResolution.COMMIT : TransactionResolution.ROLLBACK;
                    })
                    // 构建生产者,此方法会抛出 ClientException 异常
                    .build();
        } catch (ClientException e) {
            throw new RuntimeException(e);
        }

        // 开启消息事务
        final Transaction transaction = beginTransaction(producer);

        // 定义消息
        Message message = provider.newMessageBuilder()
                // 设置消息发送到的主题
                .setTopic("MY_TRANSACTION_TOPIC")
                // 设置消息索引键,可根据关键字精确查找某条消息。其一般为业务上的唯一值。如:订单id
                .setKeys("order_id_10")
                // 设置消息Tag,当前为订单支付
                .setTag("ORDER_PAY")
                // 添加回查校验需要的信息
                .addProperty("order_id","10").addProperty("status","paid")
                // 消息体,单条消息的传输负载不宜过大。所以此处的字节大小最好有个限制
                .setBody(("{\"success\":true,\"msg\": 事务消息发送成功}").getBytes())
                .build();

        try {
            // 发送半事务消息(此处不要使用异步发送,因为执行的顺序即为半事务消息发送后执行本地事务逻辑)
            producer.send(message,transaction);
            // 执行本地数据库事务
            doLocalTransaction();

            // 本地事务执行成功,提交发送消息事务
            commitTransaction(transaction);
        } catch (ClientException e) {
            e.printStackTrace();
            // 半事务消息发送失败或者本地数据库事务执行失败,都回滚消息事务
            rollbackTransaction(transaction);
            throw new RuntimeException(e);
        } catch (Exception e){
            e.printStackTrace();
            rollbackTransaction(transaction);
            throw new RuntimeException(e);
        }
    }

    /**
     * 验证订单系统当前的订单状态
     * @param orderId 订单id
     * @param status 当前对应的状态
     * @return
     */
    public static final boolean checkOrderStatus(String orderId,String status){
        // 通过 sql 或代码进行业务状态验证,检查订单系统本地数据库事务是否成功

        // 比如数据库信息如下:
        String dbOrderId = "10";
        String dbStatus = "paid";

        if(dbOrderId.equals(orderId) && dbStatus.equals(status)){
            return true;
        }

        return false;

    }

    public static final void rollbackTransaction(Transaction transaction){
        try {
            transaction.rollback();
        } catch (ClientException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public static final void commitTransaction(Transaction transaction){
        try {
            transaction.commit();
        } catch (ClientException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    public static final Transaction beginTransaction(Producer producer){
        try {
            return producer.beginTransaction();
        } catch (ClientException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 本地事务方法
     * 实际应用中,此方法应该是定义在 Service 中
     * 且进行本地事务控制,一般情况下出现异常回滚事务,正常情况提交事务
     */
    public static final void doLocalTransaction(){
        // 本地订单系统业务相关操作代码
        // throw new RuntimeException("本地事务失败");
    }

}

如果 doLocalTransaction 发生异常,则半事务消息会回滚。

消费者

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.PushConsumer;

import java.nio.ByteBuffer;
import java.util.Collections;

public class TranscationConsumerDemo {

    public static void main(String[] args) throws ClientException {

        // 用于提供:生产者、消费者、消息对应的构建类 Builder
        ClientServiceProvider provider = ClientServiceProvider.loadService();

        // 构建配置类(包含端点位置、认证以及连接超时等的配置)
        ClientConfiguration configuration = ClientConfiguration.newBuilder()
                // endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
                .setEndpoints(MyMQProperties.ENDPOINTS)
                .build();


        // 设置过滤条件(这里为使用 tag 进行过滤)
        String tag = "ORDER_PAY";
        FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);

        // 构建消费者
        PushConsumer pushConsumer = provider.newPushConsumerBuilder()
                .setClientConfiguration(configuration)
                // 设置消费者分组
                .setConsumerGroup("MY_TRANSACTION_GROUP")
                // 设置主题与消费者之间的订阅关系
                .setSubscriptionExpressions(Collections.singletonMap("MY_TRANSACTION_TOPIC", filterExpression))
                .setMessageListener(messageView -> {
                    System.out.println(messageView);
                    System.out.println(messageView.getProperties());
                    ByteBuffer rs = messageView.getBody();
                    byte[] rsByte = new byte[rs.limit()];
                    rs.get(rsByte);

                    System.out.println("Message body:" + new String(rsByte));
                    // 处理消息并返回消费结果。
                    System.out.println("Consume message successfully, messageId=" + messageView.getMessageId());
                    return ConsumeResult.SUCCESS;
                }).build();


        // 如果不需要再使用 PushConsumer,可关闭该实例。
        // pushConsumer.close();

    }

}

我们的消费者代码和普通消息的消费者是一样的,无需特殊的处理。只不过,我们的下游有多个子系统,就需要多个消费者,生产者和消费者之间是一对多的订阅关系,我们在入门介绍一章中已经介绍,为消费者定义不同的消费分组即可。

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.PushConsumer;

import java.nio.ByteBuffer;
import java.util.Collections;

public class TranscationConsumerDemo {

    public static void main(String[] args) throws ClientException {

        // 用于提供:生产者、消费者、消息对应的构建类 Builder
        ClientServiceProvider provider = ClientServiceProvider.loadService();

        // 构建配置类(包含端点位置、认证以及连接超时等的配置)
        ClientConfiguration configuration = ClientConfiguration.newBuilder()
                // endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
                .setEndpoints(MyMQProperties.ENDPOINTS)
                .build();


        // 设置过滤条件(这里为使用 tag 进行过滤)
        String tag = "ORDER_PAY";
        FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);

        // 模拟物流子系统消费者
        provider.newPushConsumerBuilder()
                .setClientConfiguration(configuration)
                // 设置消费者分组
                .setConsumerGroup("MY_TRANSACTION_WMS_GROUP")
                // 设置主题与消费者之间的订阅关系
                .setSubscriptionExpressions(Collections.singletonMap("MY_TRANSACTION_TOPIC", filterExpression))
                .setMessageListener(messageView -> {
                    System.out.println(messageView);
                    System.out.println(messageView.getProperties());
                    ByteBuffer rs = messageView.getBody();
                    byte[] rsByte = new byte[rs.limit()];
                    rs.get(rsByte);

                    System.out.println("物流子系统:Message body:" + new String(rsByte));
                    // 处理消息并返回消费结果。
                    System.out.println("物流子系统:Consume message successfully, messageId=" + messageView.getMessageId());
                    return ConsumeResult.SUCCESS;
                }).build();


        // 模拟积分子系统消费者
        provider.newPushConsumerBuilder()
                .setClientConfiguration(configuration)
                // 设置消费者分组
                .setConsumerGroup("MY_TRANSACTION_UPS_GROUP")
                // 设置主题与消费者之间的订阅关系
                .setSubscriptionExpressions(Collections.singletonMap("MY_TRANSACTION_TOPIC", filterExpression))
                .setMessageListener(messageView -> {
                    System.out.println(messageView);
                    System.out.println(messageView.getProperties());
                    ByteBuffer rs = messageView.getBody();
                    byte[] rsByte = new byte[rs.limit()];
                    rs.get(rsByte);

                    System.out.println("积分子系统:Message body:" + new String(rsByte));
                    // 处理消息并返回消费结果。
                    System.out.println("积分子系统:Consume message successfully, messageId=" + messageView.getMessageId());
                    return ConsumeResult.SUCCESS;
                }).build();


        // 如果不需要再使用 PushConsumer,可关闭该实例。
        // pushConsumer.close();

    }

}

rocketmq-client 示例(Remoting 协议)

生产者

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

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

public class TransactionProducerDemo {
    /**
     * 生产者分组
     */
    private static final String PRODUCER_GROUP = "TRANSCATION_PRODUCT_GROUP";

    /**
     * 主题
     */
    private static final String TOPIC = "MY_TRANSCATION_TOPIC";
    public static void main(String[] args) {

        // 注意:事务消息使用 TransactionMQProducer
        TransactionMQProducer producer = new TransactionMQProducer(PRODUCER_GROUP);

       // 设置 事务回查的线程池
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), r -> {
            Thread thread = new Thread(r);
            thread.setName("client-transaction-msg-check-thread");
            return thread;
        });

        producer.setExecutorService(executorService);
        // 设置监听
        producer.setTransactionListener(new TransactionListener(){

            /**
             * 半事务消息发送成功后,执行本地事务的方法
             * @param msg 半事务消息
             * @param arg 执行本地事务需要的业务参数
             * @return
             */
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                // 模拟本地数据库事务执行
                try {
                    System.out.println("执行本地事务:" + msg);
                    System.out.println("执行本地事务:" + arg);
                    doLocalTransaction();
                }catch (Exception e){
                    e.printStackTrace();
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                }

                return LocalTransactionState.COMMIT_MESSAGE;
            }

            /**
             * MQ 服务端未收到消息提交或回滚的确认,二次检查本地事务是否执行成功的方法
             * @param msg 要回查的消息
             * @return
             */
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
               String orderId = msg.getProperty("orderId");
               String status = msg.getProperty("status");

                if(StringUtils.isEmpty(orderId) || StringUtils.isEmpty(status)){
                    // 说明回查的消息有误,直接回滚(此处是回滚的消息事务,半事务消息将不会投递)
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                }

                // 通过 sql 或代码进行业务状态验证,检查订单系统本地数据库事务是否成功

                // 比如数据库信息如下:
                String dbOrderId = "10";
                String dbStatus = "paid";

                if(dbOrderId.equals(orderId) &&
                        dbStatus.equals(status)){
                    return LocalTransactionState.COMMIT_MESSAGE;
                }

                return LocalTransactionState.ROLLBACK_MESSAGE;
            }
        });

        /*
         * NamesrvAddr 的地址,多个用分号隔开。如:xxx:9876;xxx:9876
         */
        producer.setNamesrvAddr(MyMQProperties.NAMESRV_ADDR);

        /*
         * 发送消息超时时间,默认即为 3000
         */
        producer.setSendMsgTimeout(3000);
        try {
            producer.start();
        } catch (MQClientException e) {
            throw new RuntimeException(e);
        }


        // 发送事务消息
        Message msg = new Message();
        msg.setTopic(TOPIC);
        // 设置消息索引键,可根据关键字精确查找某条消息。
        msg.setKeys("order_id_10");
        // 设置消息Tag,用于消费端根据指定Tag过滤消息。
        msg.setTags("ORDER_PAY");
        // 设置消息体
        msg.setBody(("{\"success\":true,\"msg\": Remoting 协议事务消息发送成功}").getBytes());

        // 添加 Properties
        msg.putUserProperty("orderId","10");
        msg.putUserProperty("status","paid");

        try {
            // 发送事务消息
            producer.sendMessageInTransaction(msg,"业务参数对象");
        } catch (MQClientException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 本地事务方法
     * 实际应用中,此方法应该是定义在 Service 中
     * 且进行本地事务控制,一般情况下出现异常回滚事务,正常情况提交事务
     */
    public static final void doLocalTransaction(){
        // 本地订单系统业务相关操作代码
        // throw new RuntimeException("本地事务失败");
    }

}

消费者

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;

public class TransactionConsumerDemo {

    /**
     * 设置消费者分组
     */
    public static final String CONSUMER_GROUP = "TRANSCATION_CONSUMER_GROUP";
    /**
     * 主题
     */
    public static final String TOPIC = "MY_TRANSCATION_TOPIC";


    public static void main(String[] args) throws MQClientException {

        /*
         * 通过消费者分组,创建消费者
         */
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);

        /*
         * NamesrvAddr 的地址,多个用分号隔开。如:xxx:9876;xxx:9876
         */
        consumer.setNamesrvAddr(MyMQProperties.NAMESRV_ADDR);

        /*
         * 指定从哪一个消费位点开始消费 CONSUME_FROM_FIRST_OFFSET 表示从第一个开始
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        /*
         * 消费者订阅的主题,和过滤条件
         * 我们这里使用 * 表示,消费者消费主题下的所有消息,多个tag 使用 || 隔开
         */
        consumer.subscribe(TOPIC, "ORDER_PAY");

        /*
         * 注册消费监听
         */
        consumer.registerMessageListener((MessageListenerConcurrently) (msg, context) -> {
            System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        /*
         * 启动消费者.
         */
        consumer.start();

        System.out.printf("Consumer Started.%n");

        // 如果消费者不再使用,关闭
        // consumer.shutdown();

    }

}

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

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

相关文章

pycharm中恢复原始界面布局_常用快捷键_常用设置

文章目录 1 恢复默认布局1 .1直接点击file→Manage IDE Settings→Restore Default Settings&#xff08;如下图所示&#xff09;&#xff1a;1.2 直接点击Restore and Restart&#xff0c; 然后Pycharm就会自动重启&#xff0c;重启之后的界面就是最原始的界面了 2 改变主题2.…

(NDK编译)详解使用Android.mk编译的C/C++程序过程

想要在Android设备上运行C/C程序可执行文件&#xff0c;可采用一个方法就是使用NDK编译&#xff0c;很多时候要比gcc编译更适合&#xff0c;这里我采用的是imx6q开发板上面装载了自己编写的Android6.0.1镜像&#xff0c;在Ubuntu64位系统上采用NDK编译。 目录 1.准备文件 2.…

任意文件的上传和下载

1.任意文件下载&#xff08;高危&#xff09; 定义 一些网站由于业务需求&#xff0c;往往需要提供文件查看或文件下载功能&#xff0c;但若对用户查看或下载的文件不做限制&#xff0c;则恶意用户就能够查看或下载任意敏感文件&#xff0c;这就是文件查看与下载漏洞。 可以下载…

BCC介绍

本文参考以下博文&#xff1a; bcc/ebpf使用介绍一文看懂eBPF、eBPF的使用&#xff08;超详细&#xff09; ebpf之bcc程序入门 特此致谢&#xff01; BCC&#xff08;有时也写作bcc&#xff09;的全称是BPF Compiler Collection&#xff0c;即BPF编译器集合。BCC是一个开源项…

QT实现钟表

1、 头文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QPaintEvent> //绘制事件类 #include <QDebug> //信息调试类 #include <QPainter> //画家类 #include <QTimerEve…

vue3+vite3项目打包优化

vue3vite3项目打包优化 1、前言2、视图分析工具rollup-plugin-visualizer3、路由懒加载4、第三方库CDN引入5、使用 gzip 压缩6、按需引入第三方库7、使用 Tree Shaking8、剔除console和debugger9、分包策略10、图片压缩 1、前言 Vue项目开发完毕后&#xff0c;对项目进行打包发…

数据预处理方式合集

删除空行 #del all None value data_all.dropna(axis1, howall, inplaceTrue) 删除空列 #del all None value data_all.dropna(axis0, howall, inplaceTrue) 缺失值处理 观测缺失值 观测数据缺失值有一个比较好用的工具包——missingno&#xff0c;直接传入DataFrame&…

windows 查看端口被占用

1. 使用netstat -ano | findstr 8181 命令查看被占用的端口所在的pid 2. 查看是被哪个应用占用tasklist|findstr "pid" 3.杀死他 taskkill -pid 19200 -f

30.链表练习题(1)(王道2023数据结构2.3.7节1-15题)

【前面使用的所有链表的定义在第29节】 试题1&#xff1a; 设计一个递归算法&#xff0c;删除不带头结点的单链表L中所有值为x的结点。 首先来看非递归算法&#xff0c;暴力遍历&#xff1a; int Del(LinkList &L,ElemType x){ //此函数实现删除链表中为x的元素LNode *…

详细介绍下VLAN隔离与VLAN之间互联

什么VLAN&#xff1f; VLAN代表虚拟局域网&#xff08;Virtual Local Area Network&#xff09;&#xff0c;它是一种在物理网络基础上创建逻辑上独立的虚拟网络的技术。VLAN允许将一个局域网划分为多个虚拟的逻辑网络&#xff0c;这些虚拟网络在逻辑上相互隔离&#xff0c;就…

2023年项目管理工具使用趋势分析及预测

随着技术的不断进步以及工作和领导态度的演变&#xff0c;各个行业都在经历着深刻的变革。项目管理领域同样如此&#xff0c;团队项目的技术和人员管理风格及策略正在不断地调整与优化&#xff0c;以适应新冠疫情后所呈现出的新的工作场所格局。在此背景下&#xff0c;以下是我…

win部署CRM

win部署crm&#xff09; 1.phpstudy2.composer3.代码4.其他配置 周末锴哥让我帮他部署了一个CRM&#xff0c;写个教程&#xff0c;方便之后他用。锴哥用的是 NxCrm&#xff0c;先把代码下下来。 1.phpstudy 1.首先是下载小皮面板&#xff0c;配置php的环境。这里面下载了php8…

【Linux】【网络】传输层协议:UDP

文章目录 UDP 协议1. 面向数据报2. UDP 协议端格式3. UDP 的封装和解包4. UDP 的缓冲区 UDP 协议 UDP传输的过程类似于寄信。 无连接&#xff1a;知道对端的IP和端口号就直接进行传输&#xff0c;不需要建立连接。不可靠&#xff1a;没有确认机制&#xff0c;没有重传机制&am…

MacOS上的Pip和Python升级指南

在MacOS系统上&#xff0c;保持Pip和Python版本的最新状态对于顺利进行Python开发至关重要。通过升级Pip和Python&#xff0c;你可以享受到最新的功能、修复的bug以及提升的开发效率。本文将为你提供在MacOS上升级Pip和Python的详细指南&#xff0c;助你打造更强大的开发环境。…

软件测试-BUG

软件测试-BUG 1.如何合理创建一个BUG 创建bug的要素&#xff1a; 软件的版本发现问题的环境发现问题的步骤预期结果实际结果 Bug报告&#xff1a; 软件版本&#xff1a;Google Chrome浏览器&#xff08;具体版本号&#xff09; 发现问题环境&#xff1a;在Windows 10操作系统…

Learn Prompt-Prompt 高级技巧:Agents 组件详解

在以LLM驱动的Agent系统中&#xff0c;LLM扮演着Agent的大脑角色&#xff0c;并辅以几个关键组件&#xff1a; 规划&#xff1a;LLM能够进行全面的规划&#xff0c;不仅仅是简单的任务拆分。它可以评估不同的路径和策略&#xff0c;制定最佳的行动计划&#xff0c;以实现用户给…

qml Combobox用法介绍与代码演示

ComboBox 是 QML 中的一个组件,用于在下拉列表中显示一组项供用户选择。它是 Qt Quick Controls 2 模块中的一个组件,经常用于创建用户界面。 下面是 ComboBox 的一些基本用法: 1. 基本使用: import QtQuick 2.15 import QtQuick.Controls 2.15ApplicationWindow {visib…

解决qml编译时出现错误ninja: build stopped: subcommand failed.

qml编译时出现错误ninja: build stopped: subcommand failed. 如下图&#xff1a; 解决这个编译错误其实很简单&#xff0c;我把Window写错了&#xff0c;写成了window, 如果有类似的报错&#xff0c;可以检查一下qml代码是否有问题。当然在Qt Creator里也没有错误提示&#x…

二叉树的操作大全

文章目录 1.通过前序遍历数组"ABD##E#H##CF##G##"构建二叉树2.前序遍历3.中序遍历4.后序遍历5.层序遍历6.二叉树结点个数及第k层结点个数7.查找为x的结点8.叶子结点个数9.销毁二叉树(二级指针)10.判断是否为完全二叉树测试代码及运行结果 1.通过前序遍历数组"AB…

LeetCode【37.解数独】

世间多少痴儿女&#xff0c;情到深处无怨尤 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#…