news 2026/10/22 9:26:31

微服务IM消息存储子服务重构实践:分表、幂等与压测全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微服务IM消息存储子服务重构实践:分表、幂等与压测全解析

1. 项目背景与核心需求拆解

前阵子我把一套微服务即时通讯系统里的消息存储子服务完整重构了一遍,顺带把功能测试和性能压测也重新理了一轮。这个子服务看着不起眼,但在整个IM架构里属于那种“平时没人夸、出故障全组遭殃”的基础设施。这篇文章就把我当时踩过的坑、做过的选型权衡、落库链路的实现细节,以及测试方案的完整思路整理出来。正在做IM系统、或者打算把消息模块从单体里拆出去的同学,可以参考一下,能少走不少弯路。

先说清楚这个子服务要解决什么问题。即时通讯系统里,用户最关心的就两件事:消息能不能秒达、历史记录能不能随时翻。消息发送和推送靠长连接网关和实时消息服务,但“历史记录”这一步必须落到持久化存储里。如果让消息发送服务直接写数据库,短期内能用,一旦消息量上来,会出现几个绕不开的问题:存储压力跟着在线人数线性上涨、历史消息查询和实时写入互相争抢数据库资源、单库单表在亿级消息下索引和写入性能迅速恶化。消息存储子服务就是把“消息读写”从核心业务链路中隔离出来,独立部署、独立扩容、独立优化,让即时通讯系统的其他模块不需要关心消息到底存在哪、怎么存、多久清理,只需要调用存储接口。

说得直白一点,消息存储子服务就是一个高度定制化的消息持久化网关。消息先经过它落库,确认写入成功后再返回给业务层,后续历史消息查询也统一走它的读取接口。它的核心职责有三块:写入可靠、读取高效、数据可管理。写入可靠意味着消息不能因为磁盘故障、进程重启、缓存淘汰等意外静默丢失;读取高效意味着翻历史聊天记录不能越翻越慢、越翻越卡;数据可管理意味着要对消息的保存周期、存储成本、冷热生命周期有明确策略。

这个子服务的适用场景也不止是IM。直播间弹幕回放、客服工单对话记录、社区评论归档,本质都是“海量小消息 + 频繁读历史 + 要求不丢不重”。做完这个项目之后我最大的感受是:存储服务的技术难点不在“写进去”这一步,而在“长时间稳定地写进去、还能很快查出来”,以及“量上来之后依然扛得住”。

1.1 消息存储子服务在IM架构中的位置

在微服务即时通讯系统里,消息路径通常是这样的:客户端A发消息 → 网关服务做连接与协议解析 → 消息服务做业务逻辑(校验、敏感词、会话判断) → 消息存储服务落库 → 消息服务推给在线端/离线消息中转 → 客户端B拉取新消息。我在设计的时候,把消息存储服务放在消息服务下游,二者通过内部RPC接口通信,不直接暴露到公网。

这样拆有两个直接好处。一是消息服务可以专注处理实时性逻辑,不用关心SQL拼接、分库分表、慢查询优化这些事;二是存储服务的部署规模可以和消息服务解耦,比如大促或运营活动期间在线人数暴涨,只扩容网关和消息服务,存储服务CPU和连接池压力不会跟着全线拉爆。当时我们线上有一次因为活动导致消息量翻了近三倍,就是因为存储服务独立,后端数据库除了连接数升高,整体负载还在可控范围内。

1.2 存储子服务的三大核心问题

我梳理需求时,把问题收敛成三个维度,后面所有技术选型都围绕这三个维度展开。

第一个是数据可靠性。消息发出去了,用户看到“已发送”状态,这时候消息是绝不能丢的。数据库节点故障、网络分区、进程OOM,这些故障在长时间运行的服务里一定会发生,只是时间问题。所以从写入路径设计上就要保证:业务确认成功之前,消息必须已经落在可靠存储介质里。

第二个是读写性能。IM消息不像电商订单那样有明显的读写比例,它是典型的写多读少、但读有突发性。用户点进一个群聊,一口气可能翻几百条历史消息;但平时挂机在线时几乎不产生读请求。消息存储子服务要能扛住写入洪峰,同时拉历史消息时第一屏要快,翻页也不能有明显的阶梯式延迟。

第三个是数据管理成本。消息攒着不删,再大的存储也不够用。但用户哪天忽然想起半年前的一句话,又必须能找回来。所以要有分级存储和过期清理机制,在成本和体验之间找平衡点。这些需求不是说简单建个表就完事,而是需要在表结构、索引、分片、归档策略上做一套组合设计。

2. 存储模型与核心实现设计

先交代一下我当时做选型的背景。团队已有基础设施是 MySQL 集群加 Redis,消息中间件最早用的 RabbitMQ,后来逐步在向 Kafka 迁移。考虑到运维成本和团队技术惯性,我们没有因为“微服务”三个字就盲目引入 TiDB、Cassandra 这类分布式存储,而是先把 MySQL 的水平扩展能力用够。事实证明,在消息量到一定规模之前,MySQL 合理分片完全顶得住,而且排障和备份都要简单得多。

2.1 消息表模型设计:从单表到分表

消息表的设计是整个存储子服务的地基。第一版我按“最常规”的做法建了一张单表,字段包括消息ID、会话ID、发送者ID、消息类型、消息内容、客户端消息ID、创建时间、修改时间等。这个表在数据量到几百万条的时候一切正常,但到了千万级之后,索引体积增大、写入页分裂加剧,最明显的是翻页查询开始出现明显的延迟尖刺。

后面改成按会话维度分表。核心思路是用会话ID做哈希取模,将消息数据分散到多张物理表中。当时我们按 64 张表来分,会话ID哈希后对 64 取模。这样同一个会话的消息一定落在同一张表里,查询单会话历史消息时只需要在一个分片上操作,避免跨库跨表聚合。分表之后,单表数据量被限制在一个合理范围,索引维护成本也降下来了。

表结构大概是这样,实际字段比这多一些,但核心就这些:

CREATE TABLE message_0 ( id BIGINT NOT NULL COMMENT '全局消息ID', session_id BIGINT NOT NULL COMMENT '会话ID', sender_id BIGINT NOT NULL COMMENT '发送者ID', msg_type TINYINT NOT NULL COMMENT '1文本 2图片 3语音 4文件', content MEDIUMBLOB NOT NULL COMMENT '消息内容(加密后)', client_msg_id VARCHAR(64) NOT NULL COMMENT '客户端生成的消息ID,用于幂等', create_time DATETIME(3) NOT NULL COMMENT '消息产生时间', PRIMARY KEY (id), UNIQUE KEY uk_session_msg (session_id, client_msg_id), KEY idx_session_create (session_id, create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有两个细节容易踩坑。一是content字段类型根据实际内容灵活选择,纯文本场景用TEXT够用,但IM系统往往要兼容图片URL、语音文件地址、自定义消息体,统一用MEDIUMBLOB或者独立的message_content扩展表更稳妥。二是client_msg_id必须有唯一索引,这是消息幂等的关键保障,后面展开讲。

2.2 消息ID生成与顺序性设计

消息ID是存储服务的核心数据之一,不能随便用数据库自增ID——分库分表场景下自增ID根本没法全局唯一,而且消息ID还承担着排序功能。我当时采用的方案是:全局发号服务统一发放趋势递增的分布式ID,底层用类似雪花算法的实现,64位整数中包含时间戳、机器ID、序列号三部分。

为什么要趋势递增?因为IM历史消息的查询几乎总是按时间顺序来展示,如果ID递增趋势与时间保持一致,那么“按ID排序”就等价于“按消息产生时间排序”,可以减少一次额外的排序开销。同时,建索引时ID作为主键,顺序写入对B+树友好,避免随机写导致的页分裂。

这里说一个容易忽略的点:消息ID的时间有序性并不需要做到严格全局时间序,只需要做到“同一会话内”基本有序即可。因为用户感知的消息顺序是会话维度的,不同会话之间的先后关系没有那么敏感。所以发号器在跨机器存在轻微时钟漂移时,只要漂移控制在合理范围(例如几十毫秒内),对业务是无感知的。

2.3 数据分片策略与选型依据

分片有两种常见方式:按会话ID哈希和按时间分片。我当时选了按会话ID哈希,原因是IM历史消息查询几乎全是“某个会话拉取一段”,按会话维度切分能让一次查询只落到一个分片。如果按时间分片,比如按月分表,单会话的历史消息会横跨很多表,翻历史记录就需要跨表合并再排序,查询复杂度会明显上升。

但会话哈希分片也有代价,最典型的两个。一是热点会话问题:某些活跃群聊的消息量远超普通会话,哈希会让这些热点消息集中在特定几个分片上,形成局部热点。二是会话数量少的时候分片利用率不均匀。针对热点,我当时做了两级处理:先把热点会话单独识别出来,路由到独立的高性能存储分片;同时对单表容量设置阈值,当某个分片接近水位线时,对该分片内的会话做冷热迁移。这里不展开细说,想强调的是:分片策略没有“绝对最佳”,只有“适不适合你自己业务”。

提示:做会话哈希分片时,分片数量尽量选2的幂次或者素数,取模结果更均匀。另外分片数量要留出未来2-3年的增长余量,否则后期扩容要迁移数据,成本很高。

3. 写入链路:消息如何安全落库

落库链路是整个消息存储子服务最核心的部分。我先描述一个完整消息从业务层到磁盘的路径,然后重点讲三个环节:异步削峰、幂等去重、可靠性兜底。

3.1 消息写入的完整调用链

业务方(消息服务)调用存储服务时,我设计的是同步接口确认加异步批量落库的组合方式。调用方先把消息数据提交到存储服务,存储服务将消息写入本地的内存队列并立刻返回“已接收”,后台批量线程再攒批写入数据库。很多人会担心:既然落库是异步的,那如果写库前服务崩溃了,消息不就丢了吗?

这里要区分“已接收”和“已确认”两个概念。我实际做的时候并没有让业务层把“已接收”当成最终成功。完整链路是这样的:

  1. 消息服务接收入站消息后,调用存储服务接口,传入消息内容。
  2. 存储服务先将消息写入本地WAL日志(基于磁盘的顺序追加写),保证进程崩溃后可以重放。
  3. 写入WAL成功后,将消息推入内存队列,同时应答存储服务调用方“写日志成功”。
  4. 后台消费者从内存队列批量取出消息,攒批写入MySQL。
  5. MySQL写入成功后更新WAL中该消息的状态为已完成,并向消息服务发送异步确认。

这个设计的关键在于:消息服务不会因为收到“成功”应答就立刻向用户展示“已发送”。它要等存储子服务异步确认“MySQL落库完成”之后,才更新消息状态。所以对业务层来说,最终成功是以MySQL落库为准;WAL只解决“进程崩溃后不丢”的中间状态问题。

我用生活化的类比解释一下:这就好比你去银行存钱,柜台先把你的单据扫描进系统(WAL),告诉你“业务已受理”,但你会听到“存款成功”的语音提示,其实是在核心系统记账完成后才发生的。中间那段时间系统就算断电,也能凭单据重新入账。

3.2 消息幂等与去重设计

网络超时重试、消息服务重复投递、客户端断线重连后重发消息,这些场景在IM里太常见了。如果没有幂等机制,同一条消息重复落库,用户就会看到两遍内容,几乎是最容易遭投诉的BUG类型。

我们的做法是双保险。第一层是客户端生成全局唯一的client_msg_id,客户端在构造消息时生成,重试同一消息时保持不变。第二层是存储服务写入时利用uk_session_msg (session_id, client_msg_id)唯一索引做约束,插入时使用INSERT ... ON DUPLICATE KEY UPDATE或者先查后插。在并发写入场景下,唯一索引直接由数据库保证,比应用层加锁要可靠得多。

这里有一个细节值得说:如果消息被重复投递,但客户端同一时刻只发了一次,为什么不能只在客户端做去重?原因在于服务端不确定客户端是否会重试。客户端A发送消息后网络闪断,用户以为没发出去又点了一次,但第一次的消息实际已经到了服务端,只是ACK没回到客户端。这种场景只有服务端靠消息ID做最终去重,才能保证用户端和存储端一致。所以客户端ID去重不是可选项,而是必选项。

3.3 可靠性与一致性权衡

消息存储服务不是数据库本身,它比数据库更靠近业务,因此还要考虑一个远端问题:MySQL主从切换、网络分区、存储节点不可用时,存储服务怎么表现?

我当时给存储服务设定的行为是:故障期间拒绝写入,而不是降级为只写缓存。原因很简单,缓存写成功但数据库没写成功,会给业务层造成“消息已存”的错觉,一旦缓存因淘汰或重启丢失,消息就永久丢了。所以宁可短暂不可用,也不能在可靠性上打折扣。这个取舍很重要,我建议所有做IM存储的同学都记下这句话:消息系统里,“不丢”永远优先于“可用”。

写入MySQL时,我也做了分级策略。普通消息用异步批量写入,每条消息延迟在百毫秒级,吞吐量高;而针对一些需要立即生效的消息(比如撤回消息、重要系统通知),则走同步写路径,确保写入数据库成功后才返回。两条路径并存,避免一刀切让所有消息都变慢。

4. 读取链路:会话消息拉取与分页优化

写入做扎实了,读取就是用户体验的胜负手。IM历史记录最典型的行为是:进会话先拉最近一页,然后往上翻更早的消息。这个行为和分页密切相关,选错分页方案,前几十页没事,越翻到后面越卡。

4.1 分页为什么必须用游标

如果看过一些后端开发者的老代码,会发现常规后台管理列表都爱用LIMIT offset, size做分页。这种方案在消息存储服务里必须避雷。为什么?因为LIMIT offset是先查出来前面所有数据再丢掉,消息量越大,翻得越深,数据库扫描的行数越多。当用户翻到第几百页的时候,一次分页查询可能扫描几百万行,延迟直接秒级。就算加了索引,MySQL也没法直接“跳过”offset行。

我采用游标分页方案。所谓游标分页,就是每次查询传入上一页最后一条消息的ID,SQL 写成WHERE session_id = ? AND id < ? ORDER BY id DESC LIMIT 20。由于主键ID的有序性,数据库可以直接定位到游标位置继续向后扫描,每页查询消耗的资源和页数无关。

这里有个功夫在诗外的点:游标分页依赖ID有序,所以消息ID的“趋势递增”设计在读取侧又体现了一次价值。ID有序 + 主键聚簇索引,查询天然高效。如果当初ID用随机字符串或者UUID,存储层的分页优化就很难做。

4.2 会话维度读取与缓存设计

消息存储服务不是只查数据库就完了。对IM来说,用户反复查看的往往是最新消息和最活跃的几个会话。如果每次都穿透到数据库,那是浪费。我当时引入了一层本地缓存加一层Redis缓存,组成两级缓存。

  • 第一级:进程内本地缓存,存储热点会话最近的前200条消息,读请求先查本地,命中直接返回。
  • 第二级:Redis缓存,存储最近N天活跃会话的消息摘要和最近页数据,用于多实例间的共享缓存。
  • 未命中前两级,才走数据库查询,并回填缓存。

缓存最怕的是缓存穿透、击穿、雪崩。对消息场景,穿透多发生在恶意构造不存在的会话ID,解决办法是缓存空结果并且设置较短过期时间;击穿容易发生在冷门会话第一次被大量拉取,解决办法是加互斥锁控制回源查询并发;雪崩则通过给缓存过期时间增加随机抖动来规避。

4.3 历史消息冷热分离

很多团队做IM存储,最容易忽略的就是数据量无限增长带来的存储成本和查询性能下降。消息不能无限期存着,但也不该粗暴地按天数一刀切删除。我当时设计了一个简单的三区架构:

  • 热区:最近30天的消息,存储在 MySQL 主集群,读写都走这里。
  • 温区:31天到180天的消息,保留在 MySQL 但从主查询链路中剔除非必要路径访问。
  • 冷区:超过180天的消息,异步归档到廉价的冷存储系统(比如对象存储服务),用户翻历史时通过异步任务从冷区拉取。

这个方案的关键是“归档”动作要平滑进行。当时我们用了一个定时任务扫描过期分片,把数据分批迁移到冷存储,并在原表中删除已归档数据。这里要特别提醒:删除数据时一定要用小批量多次删除的方式,每次删除几百条、sleep一下,千万不要跑一个大范围DELETE,否则会给主库带来很大的主从延迟。

注意:设置消息保存周期之前,一定要和产品确认好合规和客服审计场景的需求。有些行业消息需要保留更久,不能省存储就把合规给省了。

5. 测试方案与性能验证实录

代码写完只是开始,真正让我觉得这活干完的是测试环节。我按三层来搭:单元测试、集成测试、性能压测。每一层解决的问题不一样,缺一个都不行。

5.1 功能测试:单元与集成测试怎么搭

单元测试主要覆盖存储服务内部的纯逻辑,比如:消息ID解析、分片路由计算、消息体的序列化与反序列化、幂等判断逻辑。这些不依赖外部组件,用JUnit或同类框架直接跑,核心目的就是快速回归。

集成测试则是把存储服务和真实的MySQL、Redis、MQ连起来,模拟完整链路。我测试用例里重点覆盖如下场景:

  • 正常发送文本消息,验证落库成功、返回确认。
  • 同一client_msg_id重复投递,验证只存一条。
  • 并发发送100条消息,验证消息顺序在同一会话内不乱。
  • 模拟MySQL主库故障,验证写入被拒且明确报错。
  • 模拟进程重启,验证WAL重放能恢复未确认消息。

集成测试有个很容易犯的错:测试环境连的是同一个数据库,导致测试用例之间的数据互相污染。我的做法是每个测试用例跑完后强制清理测试数据,并且测试库和预发库物理隔离。这套流程跑熟了之后,后面改表结构、调索引,回归成本低很多。

5.2 性能压测设计与参数估算

压测前先要做容量预估,不然压测数据规模拍脑袋没有意义。我们当时的场景是:预估在线用户10万人,人均每天发送消息200条,那么日均消息量就是 2000万条,峰值一般按日均的5倍来算,也就是每秒约1157条消息写请求。考虑到消息服务会调用重试和补偿,存储服务的实际峰值写入预计要达到每秒1500到2000条。读请求按写请求的3倍估算,大概每秒4500到6000次读。

基于这个预估,我把压测目标定为:写入TPS 2000,P99延迟小于200ms;读取TPS 6000,P99延迟小于100ms。

压测工具我用了开源的压测框架模拟RPC调用,脚本里构造了单聊、群聊两种消息模型,并加入了随机热点分布。压测时主要监控四个指标:TPS吞吐量、P99/P99.9延迟、数据库连接池使用率、MySQL慢查询数量。

5.3 压测挖出的三个典型问题

第一次压测结果其实不算理想,问题主要集中在三处:

第一是数据库连接池耗尽。写入量一上来,存储服务每个线程都在等数据库连接,连接池默认配置只有50个,积压请求一多直接拒绝新任务。解决办法是连接池调到200个并设置合理的超时时间,同时把批量写入的批量大小从50条调到200条,减少数据库交互次数。调整后同样压力下TPS提升了一倍多。

第二是慢查询集中在深分页接口。压测脚本里我故意留了一版用LIMIT offset的查询接口,结果用户翻到几百页时,这条查询的P99延迟直接超过3秒。这个结果也验证了我们做游标分页的必要性。如果当时没发现这个问题,上线后被用户翻历史记录翻到卡顿,那就真被动了。

第三是热点会话导致单个分片CPU飙高。压测中模拟了一个万人活跃大群,哈希路由后所有消息都落在同一个分片上,该分片对应数据库的CPU直接打满。后来对热点会话做了单独路由和缓存优先策略,才让整体负载均衡下来。

6. 常见问题排查与运维心得

最后聊一点上线后的真实体会。消息存储子服务一旦跑起来,基本上就是24小时全天候运转,排查问题的思路和普通业务系统很不一样。

6.1 常见故障与排查思路

我把这段时间遇到的典型问题整理成了一张表,方便直接参考:

现象可能原因排查思路与解法
消息发送后长时间显示“发送中”存储子服务拒绝写入或WAL写入卡顿先看存储服务的连接池和磁盘IO;确认MySQL是否发生主从切换
部分会话翻历史很慢命中深分页或缓存失效检查查询SQL是否用了游标分页;检查该会话是否处于冷区
消息重复显示客户端重复生成ID或幂等键失效查client_msg_id是否唯一;检查客户端重试是否复用ID
消息延迟高但不丢批量写入批大小过大或磁盘IO异常调小批量大小;检查WAL落盘模式
存储服务内存飙升本地队列积压过多看消费者线程是否数过小;检查下游MySQL是否有慢查询

6.2 长期运维的实用建议

第一个建议是监控绝对不能省。消息量、存储服务队列积压量、MySQL主从延迟、慢查询数、连接池水位,这五项我建议至少按1分钟粒度采集并告警。很多故障不是突然发生的,都是指标一路走高后爆发的,提前盯住就能避免大事故。

第二个建议是消息清理任务一定要有兜底机制。归档任务跑挂了,数据全堆在热区,过段时间MySQL容量就会报警。我当时给归档任务加了“进度记录”机制,每次处理完一批都记录游标,下次启动从上次位置继续,避免重复扫描和重复迁移。这一点在数据量大时尤其重要。

第三个建议是发版和变更要慢一点。消息存储子服务最怕的不是功能BUG,而是表结构变更或索引调整引发的锁表、主从延迟。大表加索引不能直接在线执行,要用在线DDL工具,并且变更前后都要观察主从延迟。我们有一次因为直接在千万级表上加字段,导致主从延迟飙到十几分钟,差点把读链路拖垮,那次之后所有变更都强制走流程审查。

这个项目做完之后,我的体会是:消息存储子服务不像网关那样“每天都有新功能可以做”,它更像是在修一条高速公路——路修好了,大家跑得顺畅没人注意;但路上任何一个小坑,都会让所有业务方立刻感觉到颠簸。把基础链路做扎实、把测试做完善,比频繁加新功能重要得多。如果以后有人问我怎么评估这类底层服务做得好不好,我会说:看它在故障时期的反应速度和处理结果,比看它平时的功能列表更有说服力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 9:23:58

从零搭建Harbor私有镜像仓库:容器部署与运维实战指南

去年我把旧服务器淘汰的时候&#xff0c;第一反应就是把上面的Harbor私有仓库完整备份下来。那台机器上堆着我这三年攒的各种容器镜像&#xff0c;有编译缓存用的小基础镜像、不同版本的中间件、还有一堆标签写着"测试勿删"的自制包。不是说我有多爱折腾&#xff0c;…

作者头像 李华
网站建设 2026/10/22 9:23:12

Windows消息循环三剑客:GetMessage、TranslateMessage、DispatchMessage详解

1. 消息循环三剑客到底在干什么很多刚接触 Windows 桌面开发的朋友&#xff0c;第一次看到GetMessage、TranslateMessage、DispatchMessage这三个函数排成一排出现在WinMain里&#xff0c;脑子里基本是一片空白。教程告诉你“照抄就行”&#xff0c;但没人告诉你为什么非得是这…

作者头像 李华
网站建设 2026/10/22 9:23:02

AI编程助手失控?用技能框架+TDD契约+工程纪律终结代码雪崩

1. 先看看“氛围编程”翻车现场&#xff1a;代码雪崩到底从哪儿开始先说明一下&#xff0c;我这里的“氛围编程”不是贬低任何人&#xff0c;我也天天用 AI 编程助手写代码。但你必须承认&#xff0c;很多人已经把“让模型自动写代码”当成了默认工作方式&#xff1a;需求丢进去…

作者头像 李华
网站建设 2026/10/22 9:22:59

STM32+ESP8266+EMQX+Android:MQTT智能家居全链路实践

简介&#xff1a;这是一套基于STM32、ESP8266、EMQX和Android的智能家居系统完整源码包&#xff0c;面向物联网开发者、嵌入式学习者和移动端应用爱好者&#xff0c;帮助理解从硬件控制到云端消息分发的完整链路。项目以STM32作主控&#xff0c;负责采集传感器数据并执行控制逻…

作者头像 李华
网站建设 2026/10/22 9:22:42

OpenGL纹理加载实战:SOIL库编译、避坑与RAII封装

简介&#xff1a;SOIL-master_soil_ 是开源 OpenGL 图像加载库 SOIL 的完整源码包&#xff0c;面向需要在 Visual Studio 等环境下开发图形应用&#xff0c;或希望深入理解图像纹理加载机制的开发者。该库支持 BMP、GIF、JPEG、PNG、TGA 等多种常见格式&#xff0c;借助 SOIL_l…

作者头像 李华
网站建设 2026/10/22 9:22:29

OpenMMLab新手实战:从环境配置到训练自己的目标检测模型

OpenMMLab&#xff1a;一键搞定深度学习训练&#xff08;新手向&#xff09;说到深度学习训练&#xff0c;很多新手的第一个反应是恐惧&#xff1a;环境装不明白&#xff0c;数据格式搞不懂&#xff0c;训练循环写了一周还在报错&#xff0c;最后连"模型到底有没有在学东西…

作者头像 李华