接口 V2 完善:基于责任链模式、Canal 监听 Binlog 实现数据库、缓存的库存最终一致性

news2025/1/23 5:26:31

🎯 本文介绍了一种使用Canal监听MySQL Binlog实现数据库与缓存最终一致性的方案。文章首先讲解了如何修改Canal配置以适应订单表和时间段表的变化,然后详细描述了通过责任链模式优化消息处理逻辑的方法,确保能够灵活应对不同数据表的更新需求。最后,展示了如何利用RocketMQ消费Canal消息并通过责任链处理器同步更新缓存,从而保证数据的一致性。此方法有效提升了系统的可扩展性和维护效率。
🏠️ HelloDam/场快订(场馆预定 SaaS 平台)

文章目录

  • 前言
  • 修改Canal配置
  • 修改 Canal 消息消费逻辑
    • 识别修改哪个数据表
    • 如何实现根据表执行相应操作
      • 责任链模式框架
      • 常量
      • 具体处理器
      • MQ 消费者调用责任链

前言

在上一篇文章中,使用令牌限流方式来实现时间段的预定。时间段令牌和时间段库存缓存是分离的,因此需要额外对库存缓存进行更新,如何实现数据库与缓存的数据一致性是一个常见问题。本文使用 Canal 监听 Binlog 实现数据库、缓存的库存最终一致性。为什么使用这种方案?不了解的读者可以先阅读文章:https://zhuanlan.zhihu.com/p/408515044

不了解 MySQL Binlog 开启和 Canal 安装与配置的朋友请先阅读小白手把手教程:https://hellodam.blog.csdn.net/article/details/144483823

修改Canal配置

修改instance.properties的过滤规则为canal.instance.filter.regex=^(venue-reservation)\\.(time_period_order_([0-9]|1[0-9]|2[0-9]|3[0-1])|time_period_([0-9]|1[0-5]))$。现在不仅需要考虑订单表time_period_order,还要考虑时间段表time_period,因为现在要保证时间段库存和空闲场号的数据库、缓存一致性

在这里插入图片描述

修改 Canal 消息消费逻辑

之前的实现如下

import cn.hutool.core.util.ObjectUtil;
import com.vrs.annotation.Idempotent;
import com.vrs.constant.OrderStatusConstant;
import com.vrs.constant.RocketMqConstant;
import com.vrs.domain.dto.mq.CanalBinlogDTO;
import com.vrs.domain.dto.req.TimePeriodStockRestoreReqDTO;
import com.vrs.enums.IdempotentSceneEnum;
import com.vrs.service.TimePeriodService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Author dam
 * @create 2024/9/20 21:30
 */
@Slf4j(topic = RocketMqConstant.CANAL_TOPIC)
@Component
@RocketMQMessageListener(topic = RocketMqConstant.CANAL_TOPIC,
        consumerGroup = RocketMqConstant.CANAL_CONSUMER_GROUP,
        messageModel = MessageModel.CLUSTERING
)
@RequiredArgsConstructor
public class CanalBinlogCommonListener implements RocketMQListener<CanalBinlogDTO> {

    private final TimePeriodService timePeriodService;

    /**
     * 消费消息的方法
     * 方法报错就会拒收消息
     *
     * @param CanalBinlogDTO 消息内容,类型和上面的泛型一致。如果泛型指定了固定的类型,消息体就是我们的参数
     */
    @Idempotent(
            uniqueKeyPrefix = "canal_binlog_common:",
            key = "#canalBinlogDTO.getId()+''",
            scene = IdempotentSceneEnum.MQ,
            keyTimeout = 3600L
    )
    @SneakyThrows
    @Override
    public void onMessage(CanalBinlogDTO canalBinlogDTO) {
        if (canalBinlogDTO.getOld() == null) {
            return;
        }
        Map<String, Object> alterDataMap = canalBinlogDTO.getData().get(0);
        Map<String, Object> oldDataMap = canalBinlogDTO.getOld().get(0);
        if (ObjectUtil.equal(canalBinlogDTO.getType(), "UPDATE") && oldDataMap.containsKey("order_status")) {
            log.info("[消费者] 消费canal的消息,恢复时间段的库存和空闲场号,时间段ID:{}", alterDataMap.get("time_period_id"));
            Long userId = Long.parseLong(alterDataMap.get("user_id").toString());
            Long timePeriodId = Long.parseLong(alterDataMap.get("time_period_id").toString());
            Long partitionId = Long.parseLong(alterDataMap.get("partition_id").toString());
            Long courtIndex;
            if (alterDataMap.containsKey("partition_index")) {
                courtIndex = Long.parseLong(alterDataMap.get("partition_index").toString());
            } else {
                courtIndex = Long.parseLong(alterDataMap.get("court_index").toString());
            }
            Integer orderStatus = Integer.parseInt(alterDataMap.get("order_status").toString());
            Integer oldOrderStatus = Integer.parseInt(oldDataMap.get("order_status").toString());
            if (orderStatus.equals(OrderStatusConstant.CANCEL) && oldOrderStatus.equals(OrderStatusConstant.UN_PAID)) {
                // 恢复库存
                timePeriodService.restoreStockAndBookedSlots(TimePeriodStockRestoreReqDTO.builder()
                        .userId(userId)
                        .courtIndex(courtIndex)
                        .timePeriodId(timePeriodId)
                        .partitionId(partitionId)
                        .build());
            }
        }
    }
}

因为之前只需要处理订单表即可,现在还需要处理时间段表的更改。所以需要做两件事:

  • 识别是哪个数据表更新了
  • 根据所识别的表,执行相应的业务逻辑

识别修改哪个数据表

首先需要了解 Canal 发送的消息内容格式,从下图可以看到有数据库名,但没有表名。因此为了识别数据表,只能通过表的独有字段名来识别了

在这里插入图片描述

如何实现根据表执行相应操作

一种方式是,直接在onMessage方法中,识别完数据表类型之后,调用相应的方法来处理。这种方式实现简单,但后续如果要处理新的表,需要修改代码,违反了开闭原则。

为了解决这个问题,本文使用责任链模式,即封装多个处理器到责任链上,每个处理器负责识别一个表,并进行相应的业务逻辑。后续使用时就依次调用链上的处理器,如果处理器发现是自己的职责,就执行逻辑,否则直接返回,调用下一个处理器。

责任链模式框架

【抽象处理器】

package com.vrs.chain_of_responsibility;

/**
 * @Author dam
 * @create 2024/12/11 19:18
 */
public interface AbstractChainHandler<T> {
    /**
     * 由实现类来实现具体的处理方法
     */
    boolean handle(T param);

    /**
     * 名称,用来区分不同的责任链
     */
    String name();

    /**
     * 处理器的排序
     */
    int order();
}
  • handler:由具体的处理器来实现,用来实现业务逻辑
  • name:用来标识责任链,返回相同名字的处理器被归到一个责任链中
  • order:用来给同一责任链的处理器排序

【责任链上下文】

该类用来管理不同的责任链。

  • 当Spring启动时,执行run方法,通过获取AbstractChainHandler的实现类来初始化所有责任链,即将处理器按照name划分到不同的责任链中,后面可以通过容器chainContainer来获取。最后对同一链上的处理器按照sort升序排序。
  • 当调用handler方法时,会根据name获取责任链,然后依次调用链上的处理器来进行业务处理,若有任意处理器的handle方法返回 true ,责任链就会中断。如果想要依次执行所有处理器,那所有处理器都返回 false 即可
package com.vrs.chain_of_responsibility;

import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * @Author dam
 * @create 2024/12/11 19:20
 */
@Component
public class ChainContext<T> implements ApplicationContextAware, CommandLineRunner {
    /**
     * 通过 Spring IOC 获取 Bean 实例
     */
    private ApplicationContext applicationContext;
    /**
     * key:责任链名称
     * value:责任链
     */
    private final Map<String, List<AbstractChainHandler>> chainContainer = new HashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void run(String... args) {
        // 从 Spring IOC 容器中获取接口对应的 Spring Bean 集合
        Map<String, AbstractChainHandler> chainFilterMap = applicationContext.getBeansOfType(AbstractChainHandler.class);
        chainFilterMap.forEach((beanName, bean) -> {
            // 判断 name 是否已经存在抽象责任链容器中
            // 如果已经存在直接向集合新增
            // 如果不存在,创建对应的集合
            List<AbstractChainHandler> abstractChainHandlers = chainContainer.getOrDefault(bean.name(), new ArrayList<>());
            abstractChainHandlers.add(bean);
            chainContainer.put(bean.name(), abstractChainHandlers);
        });
        chainContainer.forEach((mark, unsortedChainHandlers) -> {
            // 对每个责任链的实现类根据order升序排序
            Collections.sort(unsortedChainHandlers, ((o1, o2) -> {
                return Integer.compare(o1.order(), o2.order());
            }));
        });
    }

    /**
     * 责任链组件执行
     *
     * @param name         责任链组件标识
     * @param requestParam 请求参数
     */
    public void handler(String name, T requestParam) {
        // 根据 name 从责任链容器中获取对应的责任链
        List<AbstractChainHandler> abstractChainHandlers = chainContainer.get(name);
        if (CollectionUtils.isEmpty(abstractChainHandlers)) {
            throw new RuntimeException(name + "对应的责任链不存在");
        }

        // 遍历责任链处理器
        for (AbstractChainHandler handler : abstractChainHandlers) {
            if (handler.handle(requestParam)) {
                // --if-- 如果处理器返回 true,表示已经处理完成,退出责任链
                return;
            }
        }
    }
}

常量

package com.vrs.constant;

/**
 * Redis缓存Key常量类
 */
public class ChainConstant {

    public static final String RESERVE_CHAIN_NAME = "reserve_chain";

    public static final String CANAL_CHAIN_NAME = "canal_chain";

}

具体处理器

由于修改的要么是订单表,要么是时间段表,因此责任链上面只要有任一处理器成功处理,即返回 true ,就无须调用余下的其他处理器

【时间段库存修改处理器】

当数据库中的库存修改之后,同步修改缓存中的库存

package com.vrs.service.chainHander.canal;

import cn.hutool.core.util.ObjectUtil;
import com.vrs.chain_of_responsibility.AbstractChainHandler;
import com.vrs.constant.ChainConstant;
import com.vrs.constant.RedisCacheConstant;
import com.vrs.domain.dto.mq.CanalBinlogDTO;
import com.vrs.service.PartitionService;
import com.vrs.service.TimePeriodService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 订单超时关闭处理逻辑
 *
 * @Author dam
 * @create 2024/12/11 19:43
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class TimePeriodStockChangeHandler implements AbstractChainHandler<CanalBinlogDTO> {

    private final StringRedisTemplate redisTemplate;
    private final TimePeriodService timePeriodService;
    private final PartitionService partitionService;

    @Override
    public boolean handle(CanalBinlogDTO canalBinlogDTO) {
        Map<String, Object> alterDataMap = canalBinlogDTO.getData().get(0);
        Map<String, Object> oldDataMap = canalBinlogDTO.getOld().get(0);
        if (ObjectUtil.equal(canalBinlogDTO.getType(), "UPDATE") && oldDataMap.containsKey("stock")) {
            // --if-- 如果是修改操作,且修改了stock
            log.info("[消费者] 消费canal的消息,时间段库存修改,同步修改缓存的库存,时间段ID:{}", alterDataMap.get("id"));
            Long timePeriodId = Long.parseLong(alterDataMap.get("id").toString());
            Long partitionId = Long.parseLong(alterDataMap.get("partition_id").toString());
            Integer stock = Integer.parseInt(alterDataMap.get("stock").toString());
            Long bookedSlots = Long.parseLong(alterDataMap.get("booked_slots").toString());
            // 更新库存
            redisTemplate.opsForValue().set(String.format(RedisCacheConstant.VENUE_TIME_PERIOD_STOCK_KEY, timePeriodId), stock.toString());
            // 更新位图
            timePeriodService.initializeFreeIndexBitmap(
                    String.format(RedisCacheConstant.VENUE_TIME_PERIOD_FREE_INDEX_BIT_MAP_KEY, timePeriodId),
                    partitionService.getPartitionDOById(partitionId).getNum(),
                    bookedSlots,
                    24 * 3600);
            return true;
        }
        return false;
    }

    @Override
    public String name() {
        return ChainConstant.CANAL_CHAIN_NAME;
    }

    @Override
    public int order() {
        return 10;
    }
}

【订单超时关闭处理器】

package com.vrs.service.chainHander.canal;

import cn.hutool.core.util.ObjectUtil;
import com.vrs.chain_of_responsibility.AbstractChainHandler;
import com.vrs.constant.ChainConstant;
import com.vrs.constant.OrderStatusConstant;
import com.vrs.domain.dto.mq.CanalBinlogDTO;
import com.vrs.domain.dto.req.TimePeriodStockRestoreReqDTO;
import com.vrs.service.TimePeriodService;
import lombok.extern.slf4j.Slf4j;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 订单超时关闭处理逻辑
 *
 * @Author dam
 * @create 2024/12/11 19:43
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class OrderCloseHandler implements AbstractChainHandler<CanalBinlogDTO> {

    private final TimePeriodService timePeriodService;

    @Override
    public boolean handle(CanalBinlogDTO canalBinlogDTO) {
        Map<String, Object> alterDataMap = canalBinlogDTO.getData().get(0);
        Map<String, Object> oldDataMap = canalBinlogDTO.getOld().get(0);
        if (ObjectUtil.equal(canalBinlogDTO.getType(), "UPDATE") && oldDataMap.containsKey("order_status")) {
            log.info("[消费者] 消费canal的消息,订单超时关闭,恢复时间段的库存和空闲场号,时间段ID:{}", alterDataMap.get("time_period_id"));
            Long userId = Long.parseLong(alterDataMap.get("user_id").toString());
            Long timePeriodId = Long.parseLong(alterDataMap.get("time_period_id").toString());
            Long partitionId = Long.parseLong(alterDataMap.get("partition_id").toString());
            Long courtIndex;
            if (alterDataMap.containsKey("partition_index")) {
                courtIndex = Long.parseLong(alterDataMap.get("partition_index").toString());
            } else {
                courtIndex = Long.parseLong(alterDataMap.get("court_index").toString());
            }
            Integer orderStatus = Integer.parseInt(alterDataMap.get("order_status").toString());
            Integer oldOrderStatus = Integer.parseInt(oldDataMap.get("order_status").toString());
            if (orderStatus.equals(OrderStatusConstant.CANCEL) && oldOrderStatus.equals(OrderStatusConstant.UN_PAID)) {
                // 恢复库存
                timePeriodService.restoreStockAndBookedSlots(TimePeriodStockRestoreReqDTO.builder()
                        .userId(userId)
                        .courtIndex(courtIndex)
                        .timePeriodId(timePeriodId)
                        .partitionId(partitionId)
                        .build());
            }
            return true;
        }
        return false;
    }

    @Override
    public String name() {
        return ChainConstant.CANAL_CHAIN_NAME;
    }

    @Override
    public int order() {
        return 0;
    }
}

MQ 消费者调用责任链

使用非常简单,直接调用chainContext.handler(ChainConstant.CANAL_CHAIN_NAME, canalBinlogDTO);即可

package com.vrs.rocketMq.listener;

import com.vrs.annotation.Idempotent;
import com.vrs.chain_of_responsibility.ChainContext;
import com.vrs.constant.ChainConstant;
import com.vrs.constant.RocketMqConstant;
import com.vrs.domain.dto.mq.CanalBinlogDTO;
import com.vrs.enums.IdempotentSceneEnum;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * @Author dam
 * @create 2024/9/20 21:30
 */
@Slf4j(topic = RocketMqConstant.CANAL_TOPIC)
@Component
@RocketMQMessageListener(topic = RocketMqConstant.CANAL_TOPIC,
        consumerGroup = RocketMqConstant.CANAL_CONSUMER_GROUP,
        messageModel = MessageModel.CLUSTERING
)
@RequiredArgsConstructor
public class CanalBinlogCommonListener implements RocketMQListener<CanalBinlogDTO> {

    private final ChainContext chainContext;

    /**
     * 消费消息的方法
     * 方法报错就会拒收消息
     *
     * @param CanalBinlogDTO 消息内容,类型和上面的泛型一致。如果泛型指定了固定的类型,消息体就是我们的参数
     */
    @Idempotent(
            uniqueKeyPrefix = "canal_binlog_common:",
            key = "#canalBinlogDTO.getId()+''",
            scene = IdempotentSceneEnum.MQ,
            keyTimeout = 3600L
    )
    @SneakyThrows
    @Override
    public void onMessage(CanalBinlogDTO canalBinlogDTO) {
        if (canalBinlogDTO.getOld() == null) {
            // --if-- 如果不是修改数据,快速退出,因为我们现在的业务逻辑都是识别出数据修改才有下面的操作
            return;
        }
        // 调用责任链来消费 canal 消息
        chainContext.handler(ChainConstant.CANAL_CHAIN_NAME, canalBinlogDTO);
    }
}

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

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

相关文章

内容中台实施最佳实践解析与应用指南

内容概要 内容中台是一个旨在提升企业内容管理与分发能力的战略性平台&#xff0c;其实施最佳实践对于企业在数字化转型中尤为重要。内容中台的建设&#xff0c;不仅涉及技术层面的架构设计&#xff0c;还需结合组织变革、业务流程优化等多个方面&#xff0c;以实现高效、灵活…

顺序表和链表(详解)

线性表 线性表&#xff08; linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线。…

TCP全连接队列

1. 理解 int listen(int sockfd, int backlog) 第二个参数的作用 backlog&#xff1a;表示tcp全连接队列的连接个数1。 如果连接个数等于backlog1&#xff0c;后续连接就会失败&#xff0c;假设tcp连接个数为0&#xff0c;最大连接个数就为1&#xff0c;并且不accept获取连接…

C语言程序环境与预处理—从源文件到执行程序,这里面有怎么的工序?绝对0基础!

正文开始前&#xff0c;我们简单聊上一聊&#xff01; 众所周知&#xff01;编译器的功能非常强大的&#xff0c;我们在编译软件上敲的每一行代码&#xff0c;点击执行&#xff0c;就会输出结果&#xff0c;从代码-->输出结果&#xff0c;这中间经历了怎样的一个过程&#…

第35天:安全开发-JavaEE应用原生反序列化重写方法链条分析触发类类加载

时间轴&#xff1a; 序列化与反序列化图解&#xff1a; 演示案例&#xff1a; Java-原生使用-序列化&反序列化 Java-安全问题-重写方法&触发方法 Java-安全问题-可控其他类重写方法 Java-原生使用-序列化&反序列化 1.为什么进行序列化和反序列化&#xff1…

硬件作品3----STM32F103RCT6最小系统板MCU配置

参考文章&#xff1a;对stm32F103RCT6原理图解析&#xff08;详细&#xff09;-CSDN博客 本想绘制稍微复杂一些的电路&#xff0c;但是出现很多问题&#xff0c;因此先绘制一块最小系统板进行原理、绘制方法的验证。 设计难度&#xff1a;★ 适合人群&#xff1a;初学者 一、…

SparkSQL数据源与数据存储综合实践

文章目录 1. 打开项目2. 查看数据集2.1 查看JSON格式数据2.2 查看CSV格式数据2.3 查看TXT格式数据 3. 添加单元测试依赖4. 创建数据加载与保存对象4.1 创建Spark会话对象4.2 创建加载JSON数据方法4.3 创建加载CSV数据方法4.4 创建加载Text数据方法4.5 创建加载JSON数据扩展方法…

【回忆迷宫——处理方法+DFS】

题目 代码 #include <bits/stdc.h> using namespace std; const int N 250; int g[N][N]; bool vis[N][N]; int dx[4] {0, 0, -1, 1}; int dy[4] {-1, 1, 0, 0}; int nx 999, ny 999, mx, my; int x 101, y 101; //0墙 (1空地 2远方) bool jud(int x, int y) {if…

项目中使用的是 FastJSON(com.alibaba:fastjson)JSON库

从你的 pom.xml 文件中可以看到&#xff0c;项目明确依赖了以下 JSON 库&#xff1a; FastJSON&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version> </depende…

高效安全文件传输新选择!群晖NAS如何实现无公网IP下的SFTP远程连接

文章目录 前言1. 开启群晖SFTP连接2. 群晖安装Cpolar工具3. 创建SFTP公网地址4. 群晖SFTP远程连接5. 固定SFTP公网地址6. SFTP固定地址连接 前言 随着远程办公和数据共享成为新常态&#xff0c;如何高效且安全地管理和传输文件成为了许多人的痛点。如果你正在寻找一个解决方案…

Windows第一次上手鸿蒙周边

端云一体所需装备 很重要&#xff1a;C/D/E/F盘要有二三十G的可用空间&#xff01; 硬件&#xff1a;华为鸿蒙实验箱&#xff08;基础版&#xff09;》飞机板核心板环境监测板 软件&#xff1a;Visual Studio Code写代码 终端编译 Hiburn烧录到开发板 MobaXterm &#xff08…

使用AI生成金融时间序列数据:解决股市场的数据稀缺问题并提升信噪比

“GENERATIVE MODELS FOR FINANCIAL TIME SERIES DATA: ENHANCING SIGNAL-TO-NOISE RATIO AND ADDRESSING DATA SCARCITY IN A-SHARE MARKET” 论文地址&#xff1a;https://arxiv.org/pdf/2501.00063 摘要 金融领域面临的数据稀缺与低信噪比问题&#xff0c;限制了深度学习在…

【Qt】05-菜单栏

做菜单 前言一、创建文件二、菜单栏 QMenuBar2.1 示例代码2.2 运行结果 三、工具栏 QToolBar3.1 运行代码3.2 结果分析 四、状态栏 QStatusBar4.1 运行代码4.2 运行结果 五、文本编辑框 QTextEdit5.1 运行代码5.2 运行结果 六、浮动窗口 addDockWidget6.1 运行代码6.2 运行结果…

细说STM32F407单片机电源低功耗StandbyMode待机模式及应用示例

目录 一、待机模式基础知识 1、进入待机模式 2、待机模式的状态 3、退出待机模式 二、待机模式应用示例 1、示例功能和CubeMX项目设置 &#xff08;1&#xff09; 时钟 &#xff08;2&#xff09; DEBUG、LED1、KeyRight、USART6、CodeGenerator &#xff08;3&#x…

中国综合算力指数(2024年)报告汇总PDF洞察(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p39061 在全球算力因数字化技术发展而竞争加剧&#xff0c;我国积极推进算力发展并将综合算力作为数字经济核心驱动力的背景下&#xff0c;该报告对我国综合算力进行研究。 中国算力大会发布的《中国综合算力指数&#xff08;2024年…

w-form-select.vue(自定义下拉框组件)(与后端字段直接相关性)

文章目录 1、w-form-select.vue 组件中每个属性的含义2、实例3、源代码 1、w-form-select.vue 组件中每个属性的含义 好的&#xff0c;我们来详细解释 w-form-select.vue 组件中每个属性的含义&#xff0c;并用表格列出它们是否与后端字段直接相关&#xff1a; 属性解释表格&…

前沿技术趋势洞察:2024年技术的崭新篇章与未来走向!

引言 时光飞逝&#xff0c;2024年已经来临&#xff0c;回顾过去一年&#xff0c;科技的迅猛进步简直让人目不暇接。 在人工智能&#xff08;AI&#xff09;越来越强大的今天&#xff0c;我们不再停留在幻想阶段&#xff0c;量子计算的雏形开始展示它的无穷潜力&#xff0c;Web …

消息队列篇--原理篇--RabbmitMQ(Exchange,消息转换器、docker部署,绑定和确认机制等)

RabbitMQ是一个基于AMQP协议的消息队列系统&#xff0c;支持多种消息传递模式&#xff0c;包括点对点&#xff08;P2P&#xff09;、发布/订阅&#xff08;Pub/Sub&#xff09;和路由模式。RabbitMQ 的设计目标是提供高可用性、可扩展性和可靠性&#xff0c;适用于企业级应用集…

C++入门 详细版

欢迎来到干货小仓库&#xff01;&#xff01; 一分耕耘一分收获&#xff0c;离自己的目标越来越近。 passion&#xff01;passion&#xff01;&#xff01;passion&#xff01;&#xff01;&#xff01; 1.命名空间 由于C语言无法避免名字或者函数重复等问题&#xff0c;当有多…

一文大白话讲清楚webpack基本使用——9——预加载之prefetch和preload以及webpackChunkName的使用

文章目录 一文大白话讲清楚webpack基本使用——9——预加载之prefetch和preload1. 建议按文章顺序从头看&#xff0c;一看到底&#xff0c;豁然开朗2. preload和prefetch的区别2. prefetch的使用3. preload的使用4. webpackChunkName 一文大白话讲清楚webpack基本使用——9——…