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

深入拆解消息队列的存储

文章目录

  • 一、消息队列元数据的存储
    • 1.方案一:基于第三方组件
    • 2.方案二:集群内部实现
  • 二、消息数据的存储
    • 1.数据存储结构设计
    • 2.消息数据的分段实现
      • (1)Kafka
      • (2)RocketMQ
      • (3)Pulsar
      • (4)RabbitMQ
      • (5)分段存储的本质
      • (6)总结对比
    • 3.消息数据存储格式
      • (1)消息写入文件的格式
      • (2)消息内容的格式
        • ①Kafka 消息格式
        • ②RocketMQ 消息格式
        • ③Pulsar 消息格式
        • ④RabbitMQ 消息格式
        • ⑤消息格式对比总结
  • 三、消息队列数据清理机制
    • 方案一:ACK删除机制详解
      • 特点分析
    • 方案二:时间/大小删除机制详解
      • 清理策略详解
      • 特点分析
    • 方案三:ACK+过期结合机制详解
      • Group维度ACK机制
      • 特点分析
    • 延时删除机制详解
      • 问题分析
      • 延时删除实现机制
      • 延时删除流程图

存储模块作为消息队列高吞吐、低延时、高可靠特性的基础保证,可以说是最核心的模块。

消息队列中的数据一般分为元数据和消息数据。元数据是指 Topic、Group、User、ACL、Config 等集群维度的资源数据信息,消息数据指客户端写入的用户的业务数据。

一、消息队列元数据的存储

  • 元数据信息的特点:

    • 数据量小:元数据相对于消息数据来说体积较小
    • 读写频率低:不会频繁进行读写操作
    • 强一致性要求:必须保证数据的强一致性和高可靠性
    • 零容错:不允许出现数据丢失
    • 全局通知:需要通知所有Broker节点执行相应操作
  • 两种主要存储方案:

在这里插入图片描述

1.方案一:基于第三方组件

在这里插入图片描述

典型实现:

  • Kafka + ZooKeeper:元数据存储在ZooKeeper中
  • Pulsar + ZooKeeper:同样使用ZooKeeper作为元数据存储
  • RocketMQ + NameServer:元数据存储在Broker+NameServer中

优点:

  • 集成方便:可直接复用第三方组件的成熟功能
  • 开发成本低:无需重新开发一致性存储机制
  • 功能完善:复用已有的Hook机制、高性能读写等能力

缺点:

  • 部署复杂:增加系统部署和运维复杂度
  • 稳定性风险:第三方组件故障会影响整个系统
  • 数据一致性:可能出现第三方组件与Broker间数据不一致

2.方案二:集群内部实现

在这里插入图片描述

典型实现:

  • Kafka (无ZooKeeper版本):基于Raft协议实现内部元数据存储
  • RabbitMQ:使用Mnesia数据库
  • RedPanda:Kafka的C++版本,内置元数据服务

优点:

  • 部署简单:无需额外部署第三方组件
  • 稳定性高:不依赖外部服务,减少故障点
  • 数据一致性:集群内部保证数据一致性

缺点:

  • 开发成本高:需要实现复杂的一致性协议
  • 前期投入大:需要大量开发人力和时间

  • 总结:

当前主流选择:方案一(基于第三方组件)

  • 主要考虑:开发成本和快速上线需求
  • 适用场景:项目初期、资源有限的团队

长期最优选择:方案二(集群内部实现)

  • 主要优势:运维成本低、稳定性高、无外部依赖
  • 适用场景:成熟项目、有充足开发资源的团队

二、消息数据的存储

1.数据存储结构设计

底层数据存储结构当前有两种方案:

  • 第一种方案,每个分区对应一个文件的形式去存储数据。
    • 单个文件读和写都是顺序的,性能最高。
    • 但是当文件很多且都有读写的场景下,硬盘层面就会退化为随机读写,性能会严重下降。

当文件很多且都有读写的场景下,每个文件的读写操作可能会分散在硬盘的不同位置,导致硬盘无法进行连续的顺序读写,而是需要频繁地移动磁头或访问不同的存储区域,从而退化为随机读写。这种随机读写会显著降低性能,因为硬盘在处理随机访问时需要更多的寻道时间和旋转延迟,而顺序读写则可以充分利用硬盘的高速缓存和数据预取机制,提高效率。

  • 第二种方案,每个节点上所有分区的数据都存储在同一个文件中,这种方案需要为每个分区维护一个对应的索引文件,索引文件里会记录每条消息在 File 里面的位置信息。
    • 因为只有一个文件,不存在文件过多的情况,写入层面一直都会是顺序的,性能一直很高。
    • 但是在消费的时候,因为多个分区数据存储在同一个文件中,同一个分区的数据在底层存储上是不连续的,硬盘层面会出现随机读的情况,导致读取的性能降低。

不过随机读带来的性能问题,可以通过给底层配备高性能的硬件来缓解。所以当前比较多的消息队列选用的是第二种方案,但是 Kafka为了保证更高的吞吐性能,选用的是第一种方案。

关于 FD 的占用问题。Linux 上的 FD 数是可以配置的,比如配置几十万个 FD 没问题,所以我们一般不会用完系统的 FD 限制,这一点在实际的落地中不需要太担心。

但是不管是方案一还是方案二,在数据存储的过程中,如果单个文件过大,在文件加载、写入和检索的时候,性能就会有问题,并且消息队列有自动过期机制,如果单个文件过大,数据清理时会很麻烦,效率很低。所以,我们的消息数据都会分段存储

2.消息数据的分段实现

(1)Kafka

  • Kafka 的消息以 Topic 为单位,每个 Topic 可以分为多个 Partition(分区)。
  • 每个 Partition 内部又被分为多个 Segment(段),每个 Segment 是一个实际的日志文件。
  • Producer 发送消息时,消息会根据分区策略(如 key hash)被写入某个 Partition。
  • Partition 内的 Segment 按顺序存储,Segment 达到一定大小或时间后会切换到新 Segment。
Topic
Partition 0
Partition 1
Segment 0
Segment 1
Segment 0
Segment 1

(2)RocketMQ

  • RocketMQ 的消息以 Topic 为单位,每个 Topic 下有多个 MessageQueue(队列,类似分区)。
  • 每个 MessageQueue 内部消息按顺序存储,底层以 CommitLog 文件分段(Segment)存储。
  • CommitLog 文件达到设定大小后会切换到新文件,实现分段。
Topic
MessageQueue 0
MessageQueue 1
CommitLog 0
CommitLog 1
CommitLog 0
CommitLog 1

(3)Pulsar

  • Pulsar 的消息以 Topic 为单位,每个 Topic 可以分为多个 Partition。
  • 每个 Partition 内部由 BookKeeper 管理,BookKeeper 以 Ledger(账本)为单位分段存储消息。
  • Ledger 达到一定大小或时间后会切换到新 Ledger。
Topic
Partition 0
Partition 1
Ledger 0
Ledger 1
Ledger 0
Ledger 1

(4)RabbitMQ

  • RabbitMQ 的消息以 Queue(队列)为单位,通常不做分区。
  • 队列内部消息顺序存储,底层存储可以采用环形缓冲区或磁盘分段(如持久化时)。
  • 分段不是 RabbitMQ 的核心设计,但在持久化时会将消息写入多个磁盘文件(Segment)。
Queue
Segment 0
Segment 1

(5)分段存储的本质

在操作系统层面,分段存储(Segmented Storage)通常指的是将大文件拆分为多个较小的文件(Segment),每个 Segment 在磁盘上对应一个独立的文件。这样做有以下好处:

  • 便于管理:小文件易于删除、备份、迁移。
  • 高效查找:通过索引快速定位到某个 Segment。
  • 顺序写入:顺序写磁盘效率高,减少磁盘碎片。
  • 支持并发:多个 Segment 可并发读写。

文件系统的作用

  • 文件映射:每个 Segment 通常对应一个磁盘文件(如 00000000000000000000.log)。
  • 顺序写入:消息写入时,操作系统通过缓冲区(Page Cache)将数据顺序写入文件,最终刷盘(fsync)保证持久化。
  • 文件切换:当 Segment 文件达到设定大小或时间后,创建新文件,旧文件只读或归档。

以 Kafka 为例

  1. 创建新 Segment 文件
    当当前 Segment 文件大小达到阈值(如1GB),Kafka 调用操作系统 API(如 openwrite)创建新文件。
  2. 顺序写入消息
    Producer 发送的消息被写入当前 Segment 文件,操作系统负责将数据写入 Page Cache,定期刷盘。
  3. Segment 文件只读
    Segment 文件写满后,变为只读,后续只做读取或删除操作。
  4. 文件删除
    消息过期或被消费后,Kafka 调用 unlink 删除 Segment 文件,操作系统回收空间。
文件满/超时
过期/消费
Producer
消息写入缓冲区
顺序写入Segment文件
Page Cache
磁盘
新建Segment文件
删除Segment文件

(6)总结对比

消息队列分段单位分区/分片分段文件/对象
KafkaPartition+Segment支持日志文件(Segment)
RocketMQMessageQueue+CommitLog支持CommitLog 文件
PulsarPartition+Ledger支持Ledger(账本)
RabbitMQQueue+Segment不常用持久化文件(Segment)

3.消息数据存储格式

(1)消息写入文件的格式

消息写入文件的格式指消息是以什么格式写入到文件中的,比如 JSON 字符串或二进制。从性能和空间冗余的角度来看,消息队列中的数据基本都是以二进制的格式写入到文件的。

(2)消息内容的格式

①Kafka 消息格式

Kafka 消息以 Record Batch 为单位存储,每个 Batch 包含多条消息记录。

Record Batch
Batch Header
61 bytes
Record 1
Record 2
Record N
baseOffset: 8 bytes
批次起始偏移量
batchLength: 4 bytes
批次总长度
partitionLeaderEpoch: 4 bytes
分区Leader纪元
magic: 1 byte
版本标识(2)
crc: 4 bytes
CRC32校验和
attributes: 2 bytes
压缩类型等属性
lastOffsetDelta: 4 bytes
最后记录偏移量增量
firstTimestamp: 8 bytes
第一条记录时间戳
maxTimestamp: 8 bytes
最大时间戳
producerId: 8 bytes
生产者ID
producerEpoch: 2 bytes
生产者纪元
baseSequence: 4 bytes
基础序列号
recordsCount: 4 bytes
记录数量
  • Record 格式详解:
Single Record
length: varint
记录长度
attributes: 1 byte
记录属性
timestampDelta: varint
时间戳增量
offsetDelta: varint
偏移量增量
keyLength: varint
键长度
key: byte[]
消息键
valueLength: varint
值长度
value: byte[]
消息体
headersCount: varint
头部数量
headers: Header[]
消息头部
headerKeyLength: varint
headerKey: byte[]
headerValueLength: varint
headerValue: byte[]

字段详细说明:

  • baseOffset: 批次中第一条消息的偏移量
  • batchLength: 整个批次的字节长度(不包括此字段本身)
  • partitionLeaderEpoch: 分区Leader的纪元,用于检测数据一致性
  • magic: 消息格式版本号,当前为2
  • crc: CRC32校验和,从attributes字段开始计算
  • attributes: 位标志,包含压缩类型、时间戳类型等信息
  • producerId: 生产者的唯一标识,用于幂等性和事务
  • producerEpoch: 生产者的纪元,防止僵尸生产者
  • baseSequence: 批次中第一条消息的序列号
②RocketMQ 消息格式
  • CommitLog 文件格式:
CommitLog File
Message 1
Message 2
Message N
TOTALSIZE: 4 bytes
消息总长度
MAGICCODE: 4 bytes
魔数标识
BODYCRC: 4 bytes
消息体CRC
QUEUEID: 4 bytes
队列ID
FLAG: 4 bytes
消息标志
QUEUEOFFSET: 8 bytes
队列偏移量
PHYSICALOFFSET: 8 bytes
物理偏移量
SYSFLAG: 4 bytes
系统标志
BORNTIMESTAMP: 8 bytes
消息产生时间
BORNHOST: 8 bytes
消息产生主机
STORETIMESTAMP: 8 bytes
消息存储时间
STOREHOSTADDRESS: 8 bytes
存储主机地址
RECONSUMETIMES: 4 bytes
重消费次数
Prepared Transaction Offset: 8 bytes
事务偏移量
BODYLEN + BODY: 4+N bytes
消息体长度+内容
TOPICLEN + TOPIC: 1+N bytes
主题长度+名称
PROPERTIESLEN + PROPERTIES: 2+N bytes
属性长度+内容

字段详细说明:

  • TOTALSIZE: 整条消息的总字节数
  • MAGICCODE: 固定魔数,用于验证消息格式
  • BODYCRC: 消息体的CRC32校验值
  • QUEUEID: 消息所属的队列ID
  • FLAG: 消息标志位,包含压缩、事务等信息
  • QUEUEOFFSET: 在MessageQueue中的逻辑偏移量
  • PHYSICALOFFSET: 在CommitLog中的物理偏移量
  • SYSFLAG: 系统标志,标识消息类型(普通、事务、定时等)
  • BORNTIMESTAMP: 消息在生产者端产生的时间戳
  • BORNHOST: 生产者的IP地址和端口
  • STORETIMESTAMP: 消息在Broker端存储的时间戳
  • RECONSUMETIMES: 消息重新消费的次数
③Pulsar 消息格式
  • Message Metadata 格式:
Pulsar Message
Message Metadata
Payload
producer_name: string
生产者名称
sequence_id: uint64
序列ID
publish_time: uint64
发布时间戳
properties: KeyValue[]
用户属性
replicated_from: string
复制来源
partition_key: string
分区键
replicate_to: string[]
复制目标
compression: CompressionType
压缩类型
uncompressed_size: uint32
未压缩大小
num_messages_in_batch: int32
批次消息数量
event_time: uint64
事件时间
encryption_keys: EncryptionKey[]
加密密钥
encryption_algo: string
加密算法
encryption_param: bytes
加密参数
schema_version: bytes
Schema版本
  • Batch Message 格式:
Batch Message
Batch Metadata
Single Message Metadata 1
Payload 1
Single Message Metadata 2
Payload 2
Single Message Metadata N
Payload N
properties: KeyValue[]
消息属性
partition_key: string
分区键
event_time: uint64
事件时间

字段详细说明:

  • producer_name: 生产者的唯一标识名称
  • sequence_id: 生产者维护的消息序列号,用于去重
  • publish_time: 消息发布到Broker的时间戳
  • properties: 用户自定义的键值对属性
  • partition_key: 用于分区路由的键值
  • compression: 压缩算法类型(NONE、LZ4、ZLIB、ZSTD)
  • num_messages_in_batch: 批次中包含的消息数量
  • event_time: 消息的业务时间戳
  • encryption_keys: 用于端到端加密的密钥信息
  • schema_version: Schema演进的版本信息
④RabbitMQ 消息格式
  • AMQP 0-9-1 消息格式:
AMQP Message
Basic Properties
Message Body
content-type: string
内容类型
content-encoding: string
内容编码
headers: table
消息头部
delivery-mode: uint8
投递模式
priority: uint8
消息优先级
correlation-id: string
关联ID
reply-to: string
回复队列
expiration: string
过期时间
message-id: string
消息ID
timestamp: timestamp
时间戳
type: string
消息类型
user-id: string
用户ID
app-id: string
应用ID
cluster-id: string
集群ID
  • 持久化存储格式:
Persistent Message
Message Index
Message Store
Message ID: 16 bytes
消息唯一标识
Queue Name Hash: 4 bytes
队列名哈希
Message Size: 4 bytes
消息大小
Message Offset: 8 bytes
存储偏移量
Message Properties Length: 4 bytes
属性长度
Message Properties: N bytes
序列化属性
Message Body Length: 4 bytes
消息体长度
Message Body: N bytes
消息体内容

字段详细说明:

  • content-type: 消息体的MIME类型
  • content-encoding: 消息体的编码方式
  • headers: 用户自定义的消息头部信息
  • delivery-mode: 投递模式(1=非持久化,2=持久化)
  • priority: 消息优先级(0-255)
  • correlation-id: 用于关联请求和响应的标识符
  • reply-to: 指定回复消息的队列名称
  • expiration: 消息的TTL(生存时间)
  • message-id: 消息的唯一标识符
  • timestamp: 消息的时间戳
  • type: 消息类型标识
  • user-id: 发送消息的用户标识
  • app-id: 发送消息的应用标识

⑤消息格式对比总结
特性KafkaRocketMQPulsarRabbitMQ
批处理支持✅ Record Batch❌ 单条消息✅ Batch Message❌ 单条消息
压缩支持✅ 批次级别✅ 消息级别✅ 消息级别❌ 不支持
事务支持✅ 生产者ID+纪元✅ 事务偏移量✅ 事务ID✅ 事务性队列
Schema演进❌ 应用层❌ 应用层✅ 内置支持❌ 应用层
加密支持❌ 传输层❌ 传输层✅ 端到端加密✅ TLS
消息路由✅ 分区键✅ 队列选择✅ 分区键✅ 路由键

每种消息队列的格式设计都反映了其核心设计理念:

  • Kafka: 高吞吐量,批处理优化
  • RocketMQ: 功能丰富,支持事务和定时消息
  • Pulsar: 云原生,支持多租户和Schema演进
  • RabbitMQ: 标准AMQP协议,功能完整的消息代理

三、消息队列数据清理机制

  • 数据保留周期概览:

在这里插入图片描述

说明:消息队列的数据保留周期根据业务需求而定。短期保留适用于实时性要求高的场景,常规保留是大多数业务的标准配置,长期保留仅在特殊合规或审计需求下使用。保留时间越长,存储成本越高,管理复杂度也随之增加。

  • 三种主要清理机制:
消息数据清理机制
方案1: ACK删除
方案2: 时间/大小删除
方案3: ACK+过期结合
消费完成立即删除
避免重复消费
无法重放消息
代表: RabbitMQ
消息不立即删除
按时间/大小清理
支持消息重放
代表: Kafka/RocketMQ
Group维度ACK
消息可重复订阅
避免Group内重复消费
代表: Pulsar

说明:三种清理机制各有特点。ACK删除机制简单直接,适合任务队列场景(RabbitMQ);时间/大小删除机制支持数据重放,适合流处理场景(kafka、RocketMQ);ACK+过期结合机制兼顾两者优势,适合复杂的多租户场景(Pulsar)。选择哪种机制主要取决于业务对数据重放、存储成本、消费一致性的要求。

方案一:ACK删除机制详解

生产者 消息队列 消费者 死信队列 发送消息 推送消息 处理消息 发送ACK 标记删除消息 消息被删除,无法重放 发送到死信队列 发送ACK 等待后续处理 alt [处理成功] [处理失败] 生产者 消息队列 消费者 死信队列

说明:ACK删除机制的核心是"消费即删除"。当消费者成功处理消息后,发送ACK确认,消息队列立即删除该消息。如果处理失败,消息会被转移到死信队列,避免阻塞后续消息的消费。这种机制保证了消息只被消费一次,但代价是无法进行消息重放。

特点分析

ACK删除机制
优点
缺点
✅ 不会重复消费
✅ 节省存储空间
✅ 消费进度明确
❌ 无法消息重放
❌ 数据丢失风险
❌ 需要死信队列处理异常

说明:ACK删除机制的优点在于逻辑简单、存储效率高、不会出现重复消费问题。但缺点也很明显:一旦消息被删除就无法恢复,存在数据丢失风险;需要额外的死信队列机制来处理异常情况;不适合需要多次处理同一消息的场景。

方案二:时间/大小删除机制详解

生产者 消息队列 消费者 清理线程 发送消息 推送消息 处理消息 提交偏移量 消息保留,仅记录消费进度 检查保留策略 删除过期消息 alt [超过时间/大小限制] loop [定期清理] 生产者 消息队列 消费者 清理线程

说明:时间/大小删除机制将消息的消费和删除解耦。消费者处理完消息后只提交偏移量,表示消费进度,消息本身不会立即删除。系统通过后台清理线程定期检查消息是否超过保留时间或存储大小限制,满足条件才进行删除。这种机制支持消息重放和多消费者组订阅。

清理策略详解

清理策略
基于时间
基于大小
保留7天
保留3天
保留24小时
保留1GB
保留500MB
保留100MB
异步清理线程
定期扫描
检查过期条件
批量删除文件

说明:清理策略通常有两个维度:时间和大小。时间策略根据消息的创建时间或最后访问时间来判断是否过期;大小策略根据分区或主题的总存储大小来决定是否需要清理老数据。异步清理线程定期执行,避免影响正常的消息生产和消费性能。实际应用中,两种策略通常会组合使用。

特点分析

时间/大小删除机制
优点
缺点
✅ 支持消息重放
✅ 多消费者组订阅
✅ 数据容错性好
❌ 可能重复消费
❌ 占用存储空间
❌ 消费进度管理复杂

说明:时间/大小删除机制的最大优势是灵活性强,支持消息重放和多消费者组场景,数据安全性高。但也带来了一些挑战:消费者需要自己管理偏移量,可能出现重复消费;需要更多的存储空间;消费进度的管理和监控相对复杂。

方案三:ACK+过期结合机制详解

生产者 消息队列 消费组A 消费组B 清理线程 发送消息 推送消息 处理消息 Group A ACK 消息保留,Group A标记已消费 推送相同消息 处理消息 Group B ACK 消息保留,Group B标记已消费 检查过期策略 删除过期消息 loop [定期清理] 生产者 消息队列 消费组A 消费组B 清理线程

说明:ACK+过期结合机制是前两种方案的融合。它保留了ACK的概念,但ACK是基于消费者组(Group)的。同一个消费者组内的消息被ACK后不会重复消费,但不同的消费者组可以独立消费同一条消息。消息的最终删除仍然基于时间或大小策略,而不是ACK状态。

Group维度ACK机制

消息
Group A 消费状态
Group B 消费状态
Group C 消费状态
已ACK ✅
未消费 ⏳
消费中 🔄
消息保留策略
所有Group都ACK
达到过期时间
达到大小限制
仍保留消息
删除消息

说明:在Group维度ACK机制中,每个消费者组都有独立的消费状态。即使所有消费者组都已经ACK了某条消息,该消息仍然会保留,直到达到过期时间或大小限制才会被删除。这样既避免了同一消费者组内的重复消费,又支持了多消费者组的独立订阅。

特点分析

ACK+过期结合机制
优点
缺点
✅ Group内避免重复消费
✅ 支持多Group订阅
✅ 支持消息重放
✅ 灵活的清理策略
❌ 实现复杂度高
❌ 需要维护Group状态
❌ 存储开销较大

说明:ACK+过期结合机制集合了前两种方案的优点,但实现复杂度也最高。它需要维护每个消费者组的状态信息,存储开销也比较大。这种机制特别适合云原生和多租户场景,能够很好地平衡数据安全性、消费一致性和系统灵活性。

延时删除机制详解

问题分析

分段文件
消息1 ✅可删除
消息2 ❌不可删除
消息3 ✅可删除
消息4 ❌不可删除
消息5 ✅可删除
立即删除问题
频繁文件操作
随机读写
性能下降
延时删除优势
批量操作
顺序删除
性能提升

说明:在分段存储中,一个文件包含多条消息,这些消息的删除时机可能不同。如果每次ACK都立即删除对应的消息,就需要频繁地修改文件内容,进行随机读写操作,严重影响性能。延时删除机制通过批量操作和顺序删除来解决这个问题。

延时删除实现机制

消费者 消息队列 内存标记 Backlog文件 分段文件 ACK消息3 标记消息3为删除 记录待删除消息 消息3仍在分段文件中 请求消费 检查删除标记 跳过消息3 返回消息 alt [消息已标记删除] [消息未标记删除] 当整个分段都可删除时 删除整个分段文件 消费者 消息队列 内存标记 Backlog文件 分段文件

说明:延时删除机制的核心是"标记删除"。当收到删除请求时,系统不会立即修改文件,而是在内存和Backlog文件中记录删除标记。在后续的消费过程中,系统会检查这些标记,跳过已标记删除的消息。只有当整个分段文件中的所有消息都可以删除时,才会物理删除整个文件。

延时删除流程图

接收删除请求
检查分段状态
整个分段可删除?
直接删除分段文件
标记删除特定消息
内存中记录标记
Backlog文件记录
消费时检查
消息已标记删除?
跳过该消息
正常返回消息
定期检查
分段内所有消息都已标记?
删除整个分段文件
继续等待

说明:延时删除的完整流程包括:接收删除请求后先检查是否可以删除整个分段;如果不能,则进行标记删除;在消费时检查标记并跳过已删除的消息;定期检查分段状态,当整个分段都可删除时执行物理删除。这种机制大大减少了文件操作的频率,提高了系统性能。

http://www.lryc.cn/news/576875.html

相关文章:

  • 信息安全与网络安全---引言
  • <STC32G12K128入门第二十二步>STC32G驱动DS18B20(含代码)
  • Npcap与Pcap4J
  • 学习记录:DAY35
  • vite | vite-plugin-dts 插件生成类型文件 的安装和使用
  • Python爬虫实战:研究untangle库相关技术
  • MYSQL的基础信息如何存放
  • PL-SLAM: Real-Time Monocular Visual SLAM with Points and Lines
  • 实战四:基于PyTorch实现猫狗分类的web应用【2/3】
  • Rust函数与所有权
  • Webpack中的Loader详解
  • SpringBoot医疗用品销售网站源码
  • 什么是P2P 网络(Peer-to-Peer Network)
  • (八)聚类
  • KPL战队近五年热度指数
  • 如何解决大语言模型微调时的模型遗忘问题?
  • MYSQL与PostgreSQL的差异
  • Segment Anything in High Quality之SAM-HQ论文阅读
  • ​扣子Coze飞书多维表插件-创建数据表
  • 机器学习9——决策树
  • MyBatis修改(update)操作
  • 【PaddleOCR】PaddlePaddle 3.0环境安装,及PaddleOCR3.0 快速入门使用
  • 企业级路由器技术全解析:从基础原理到实战开发
  • 学习使用Visual Studio分析.net内存转储文件的基本用法
  • cJSON 使用方法详解
  • 华为云 Flexus+DeepSeek 征文|华为云 Flexus 云服务 Dify-LLM 平台深度部署指南:从基础搭建到高可用实践
  • NLP随机插入
  • 如果将Word里每页的行数设置成50行
  • jenkins启动报错,一直无法启动
  • 高并发电商返利 APP 架构设计:从淘客佣金模型到分布式导购系统的技术落地