从MongoDB迁移到KES:一次文档库国产化替换的实战手记

前言

我们这套 MongoDB,在生产上跑了整整四年。前阵子接了个活,要把整个 MongoDB 迁移到 KES 上,我前前后后磨了快仨礼拜。"MongoDB迁移"这四个字听着轻飘飘的,没干过的人多半觉得,不就换个库嘛,改改连接串、倒一份数据的事。等你真把袖子撸起来就晓得了,宣传册上那个"兼容",跟你半夜到底睡不睡得着,中间隔着十万八千里。下面这些就是我那段日子的流水账,写得有点啰嗦,你凑合看吧。

@TOC

一、先说说,为啥非动 MongoDB 不可

背景得先唠两句,不然我后头为啥这么选库,你看着会发懵。

我们这摊业务,是典型"烟囱式"一点点堆出来的。关系库管业务数据,MongoDB 存半结构化的日志和商品快照,Redis 扛缓存,后来眼红向量检索想加上,再后来还想塞点 GIS。每来一类新数据,得,又立一根新烟囱。起步那阵没感觉,等数据量上来了、人也凑齐了,毛病就一个接一个往外蹦。

最闹心的是数据一致性。商品基本信息在关系库里,富文本和那堆乱七八糟的属性在 MongoDB,缓存又是 Redis 一摊。改个价?对不住,三个地儿都得写。补偿逻辑写到后头,我自己瞅代码都犯恶心。

机器资源也糟践得让人心疼。MongoDB 那台服务器,CPU 常年 20% 上下晃悠,内存倒让 WiredTiger 霸着不撒手,想匀一点给别人,门儿都没有。关系库那边还时不时抽风来个 burst,资源对不上号。各搞各的独立部署,硬件钱等于花了两遍。

运维这块,提起来全是泪。团队得同时养三拨人,懂关系库的、懂 Mongo 的、懂缓存的,每套库还各配一套备份、监控、告警。一出事,三份日志对着扒,定位个毛病能熬一整宿。

你想想,光把 MongoDB 换成另一个国产文档库,上面这些破事一件都摆不平,无非是把旧烟囱重新刷了遍漆。我真正惦记的,是"收敛"这俩字:能不能一个引擎,把关系、文档、向量、GIS 全塞进去,库跟库之间再也别来回倒腾数据。这才是后来定 KES 的根本理由。人家主打多模一体化存储,文档不过是其中一个模,迁 MongoDB,是整体收敛落下的第一颗子。

这儿有个岔路口得提前想明白。你要是只图"换个能跑的文档库",直接平替就完事;可要是跟我一样想趁机把技术栈收一收,得先把目标钉死,别等迁完一拍大腿,嘿,还是一堆孤岛。

二、兼容这俩字,到底掺没掺水

动手前我最虚的,就是"兼容"这俩字的水分。市面上喊"兼容 MongoDB"的方案我看过不少,扒开包装一瞧,多半是给你套个 JDBC 驱动,让你把 NoSQL 全改成 SQL 去写。这叫哪门子兼容?应用基本等于推倒重来,忽悠谁呢。

KES 这版不太一样,走的是协议级兼容。说人话:它在数据库进程里头,硬给自己实现了一套 MongoDB 的通信协议,对外就监听 27017,Mongo 的老本行端口。你应用还是拿原来那套 Mongo 原生驱动去连,库自己把 Mongo 的报文拆开消化掉。应用那边压根不知道,对面坐着的已经不是 MongoDB 了。

画个草图,你一眼就能看明白它到底"伪装"在哪一层:

sql 复制代码
应用层 : 原 Mongo 驱动 / Spring Data 等
协议层 : 27017(Mongo 协议组件) ← 应用以为连的是真 Mongo
语法层 : Mongo-parser(和 Oracle / SQL 等多套 parser 并列)
存储层 : 文档 / 关系 / 向量 / GIS ...

这么搞有个肉眼可见的好处,应用代码基本不用动,把驱动里那个连接串的地址端口拨过来就行。它敢喊"0 代码完成迁移",底气就在这儿。不过"0 修改"这个词,我得在后头专门泼盆冷水,先摁下不表。

兼容度这块,我从资料里抠了几个数,自己也抽验过,靠得住。查询和写入命令,7 个全支持,百分百;更新操作符 22 个也是全支持;查询操作符 32 个里支持 31 个,96.88%,差那一个你基本碰不上。聚合那块要分开看,我一开始还以为聚合就是聚合一回事,后来才搞明白,操作符和阶段是两码事。操作符 169 个支持 167 个,98.82%,挺能打;可聚合阶段 38 个只支持 32 个,一下掉到 84.21%。管理类命令 32 个支持 22 个,68.75%。最惨的是角色管理,10 个命令一个没支持,直接 0%。这几个掉队的地方,正好是我后来栽跟头栽最狠的所在,下面慢慢说。

三、动手前,我先给自己泼了三盆冷水

迁之前我给自己压了三条预期,事后一对,全中。你要是也准备迁,先把这三条刻脑门上,能少走好几天冤枉路。

头一条,也是最要命的:别指望性能跟原生 Mongo 打平,尤其写操作。KES 底层是拿关系表那套来存文档的,BSON 类型全靠自定义类型和函数硬模拟出来,中间这层转换物理上省不掉。我后来压测,同样 100 万条数据,插入这项原生 Mongo 跑了 2275 毫秒,KES 是 3498;全表查询,Mongo 2087,KES 3320。最扎眼的是 UPDATE,Mongo 3083 毫秒,KES 直接飙到 6413,快翻倍了。连标量查询这种轻活,Mongo 174 毫秒搞定,KES 也得 270。

我第一次跑出这数,当场心里咯噔一下。这玩意儿要是原样顶到线上,写密集的接口分分钟爆给你看。后来冷静扒拉了下,我们业务写没那么频繁,再加上建对索引、改成批量写、把热数据圈在小表里,差距还能往回收。但话说回来,你要是写特别重的场景,这块一定提前压测,别等上线了才发现天塌了。

第二条,关于那个"0 代码修改"。这话本身不算骗人,可它藏着前提。驱动版本得对得上,连接参数得写对,业务里最好别碰那些 0% 支持的命令。这三条里随便塌一条,"0 修改"就成了空头支票。

最后一条,也是最被低估的一条:迁移这活儿本身不难,难的是验证。代码能连上,跟行为完全一致,那是两码事。聚合管道里那几个没覆盖到的阶段,ObjectId 那点微妙的脾气,都得拿真实流量回放去磨。

三条想透了再动手,心态稳得多。真的。

四、initdb:第一锹土

唠够了,上手干活。我那会儿是在一台干净的测试机上搭的,生产另说,这儿只讲怎么把一个兼容 Mongo 的实例最小化跑起来。

第一步,初始化实例。这步最容易翻车,没商量。初始化的时候兼容模式必须指定对,不然后面装 documentdb 扩展,能给你报一屏幕的类型不匹配。

bash 复制代码
# 初始化实例,-U 指定初始用户,-m 指定兼容模式
# -m 的取值决定系统内置类型、函数、系统视图的形态,
# 做文档库迁移时按部署手册要求选对应模式即可
initdb -U system -m <兼容模式标识> -D /data/kes_data

踩坑提醒,你认真听。我头一回就是没加 -m,用的默认形态,结果 create extension documentdb 时直接卡在"依赖类型不存在",前前后后耗了大半天。这参数必须在 initdb 那一刻就钉死,实例一旦建好,改不了了,想改只能 rebuild 从头来。血的教训,不骗你。

实例起来之后,启动服务:

bash 复制代码
# -D 指向数据目录,-p 是默认端口
kingbase -D /data/kes_data -p 54321

到这步实例本身是通的,可以拿它原生的 ksql 客户端进去验一眼:

bash 复制代码
ksql -U system -p 54321 test

蹦出提示符了,说明实例没毛病。下面才是真正的重头戏,让它"听懂"Mongo 的话。

五、kingbase.conf 里那四个坑死人的配置

这步是整个迁移里最容易出玄学问题的地方,没有之一。原理前面讲过了,KES 靠一组扩展外加一组参数,给自己"长"出一套 Mongo 协议监听来。配置全窝在 kingbase.conf 里,我把最后跑通的那版贴出来,逐行给你加注释:

ini 复制代码
# ===== kingbase.conf 关键配置 =====

# 1. 协议兼容总开关。忘了它,重启无数次 mongosh 都连不上
enable_protocol_compat = on

# 2. Mongo 协议监听端口。用 27017 是为让应用少改配置,
#    但机器上还跑着原 Mongo 的话,一定要改别的口,否则端口冲突
extension_protocol_port = 27017

# 3. BSON 序列化开关。开着之后,日期、Decimal、二进制等类型
#    走 EJson 序列化,跨驱动兼容性最好,强烈建议开着
documentdb_core.bsonUseEJson = on

# 4. 预加载库的顺序很重要!三个扩展必须按顺序加载:
#    kdb_cron 在前,documentdb_core 在中,documentdb 在后
shared_preload_libraries = 'kdb_cron, kdb_documentdb_core, kdb_documentdb'

说句题外话,这几个参数的官方说明写得跟天书似的,哪个该开哪个不该开,我是连蒙带猜加试错才捋顺的,光这一段配置我啃了小两天。你要是也卡在这儿,别怀疑自己,不是你的问题。

就这四行,我起码返工了三回,每回栽的还都不是同一行。

头一回,忘了开 enable_protocol_compat。重启完服务,mongosh 死活连不上,telnet 27017 明明是通的,可 Mongo 协议握手就是失败。日志翻了大半个钟头才瞄见,这个开关压根没动。排障心法送你一句:协议层出问题,先查这个开关和端口,别一上来就怀疑扩展。

第二回更有意思。那是周五晚上十一点多,测试机上赖着一个旧 Mongo 进程,把 27017 给占着。我的 KES 起是起来了,端口却被抢了,连上去一看数据"全是旧的",那会儿我真当自己穿越了。后来 netstat -ano | findstr 27017 一敲,当场破案。

第三回,shared_preload_libraries 顺序让我写反了,documentdb 摆到 core 前头,服务直接起不来,张嘴就报依赖找不到。这顺序是有依赖关系的,乱排不得。

配置改完,千万记得是重启,不是 reload。shared_preload_libraries 这玩意儿必须重启进程才肯加载。

六、一句 cascade,救了老张一命

配置弄妥了,还得进数据库里把文档扩展真正建出来。拿 ksql 连进去:

bash 复制代码
ksql -U system -p 54321 test

然后建扩展,顺手把 system 用户的口令设上,待会儿 mongosh 要用:

sql 复制代码
-- cascade 会自动把 documentdb_core 等依赖一起装上
CREATE EXTENSION documentdb CASCADE;

-- 设置口令,连接串里要用
ALTER USER system WITH PASSWORD '123456';

这里的 cascade 你可千万别手抖省掉。我有个同事,就叫他老张吧,图省事敲了个不带 cascade 的 CREATE EXTENSION documentdb,结果库反手一个"依赖扩展 documentdb_core 不存在",又得吭哧吭哧手动一个个补。带上 cascade,依赖链自动给你拉齐,省心。

建完验一下扩展在不在位:

sql 复制代码
\dx

列表里要是能瞧见 documentdbdocumentdb_core 这俩,得,数据库已经"听懂"Mongo 协议了。下面轮到真正的 Mongo 客户端登场。

七、mongosh 连上的那一刻,我差点信了

这是整个迁移最有仪式感的一刻,不夸张。我拿着最原生的 Mongo 客户端去连一个国产库,就想看它能不能像真 Mongo 那样跟我对上话。

bash 复制代码
mongosh "mongodb://system:123456@127.0.0.1:27017/test?maxPoolSize=1&directConnection=true&authMechanism=SCRAM-SHA-256"

连接串里头有几个参数,我必须给你单独拎出来点名,这几个全是拿血汗踩出来的。

directConnection=true,最容易漏,没有之一。你不加它,驱动会以为对面是个副本集,屁颠屁颠跑去探集群拓扑,半天探不到,要么卡死要么报错。KES 这边是单实例,你得明明白白告诉驱动,直连就行,别整那些虚的。

authMechanism=SCRAM-SHA-256,认证机制得写死。我头一回用默认方式连,握手那一步直接挂了,指定成 SHA-256 才一次过。

maxPoolSize=1,测试阶段我故意压到 1,图个看连接行为清楚,上了生产你按并发往上加。

连上之后,我敲了第一组冒烟测试,就是产品资料里那段经典示例,亲测能跑:

javascript 复制代码
// 插入一批文档
db.products.insertMany([
  { item: "card",     qty: 15 },
  { item: "envelope", qty: 20 },
  { item: "stamps",   qty: 30 }
]);

// 给 item 字段建索引
db.products.createIndex({ item: 1 });

// 查一条
db.products.find({ item: "envelope" });

find 老老实实把 envelope 那条吐出来,我这才真把心放回肚子里。协议层,通了,是真通了。那一下我还挺恍惚的,就......你明明知道对面是国产库,可它应答起来跟真 Mongo 一模一样,你甚至会忘掉这回事。这会儿后台其实已经把文档悄悄转成了关系表行存在那儿,item 上自动挂了个 RUM 索引,_id 自动挂了 B-Tree 索引,对应用都是透明的。但你心里得有这笔账,后头性能调优靠的就是它们。

光连通还不够。为了把"兼容"这俩字彻底坐实,我又随手甩了几条带操作符的查询过去,嘿,全都能跑,这可比盯着百分比表直观多了:

javascript 复制代码
// 范围 + 多值匹配($gt、$in)
db.products.find({ qty: { $gt: 15, $in: [20, 30] } });

// 逻辑组合($or)
db.products.find({
  $or: [
    { item: "card" },
    { qty: { $gte: 30 } }
  ]
});

// 只取需要的字段(投影),再按数量倒序
db.products.find(
  { qty: { $gte: 20 } },
  { item: 1, qty: 1, _id: 0 }
).sort({ qty: -1 });

范围查询、逻辑或、投影加排序,基本平时写业务常用的那几样都试了一遍,没掉链子。迁过来想最快自证一把兼容,照着敲一遍就成。

八、搬家,两条道,看家当多大

应用能连了,下一步搬存量数据。这块没你想象的那么玄乎,常用就两条道,我都趟过。

第一条道,mongodump 加 mongorestore,省事到家。数据量不大的话(我们几个百万级的小集合就走这条),直接 dump 完再 restore,最快:

bash 复制代码
# 从老库导出(bson 格式)
mongodump --host 旧库IP --port 27017 -d olddb -c products -o /backup/dump

# 导入到 KES(参数和前面连接串一致)
mongorestore --host 127.0.0.1 --port 27017 \
  --username system --password 123456 \
  --authenticationMechanism SCRAM-SHA-256 \
  /backup/dump/olddb/products.bson

第二条道,应用层双写加灰度切流,专治不停机的大表。那张上千万的大表,你让我停服 dump?借我个胆子也不敢。打法是这样的:先全量同步一遍打底,接着在应用层开双写,新数据两边都落,读还暂时走老库;等增量追平了,再把读流量一点一点灰度切到 KES 上,观察一阵,最后才把老库下了。这条路最稳当,但活儿全压在应用侧,得加读写开关,跟数据库反倒没多大关系。

这儿有个坑专门拎出来提醒,因为太容易忘。迁大表的时候,索引一定得等数据导完再建,千万别先建索引再灌数据。不然每塞进去一条,库都得去伺候一遍索引,慢到你怀疑人生。道理跟原生 Mongo 一模一样,可人一紧张,脑子就容易短路。

九、说好的 0 代码呢,我真没敢全信

兜了一圈,回到开头我摁住没表的那个关子。所谓"0 代码修改",搁我们项目里基本成立,但绝不是字面那个零。我把实际逃不掉要碰的地方,给你摊开。

连接串得改指向,这是唯一真·必须动的。把原来指着老 Mongo 的那段配置,IP、端口、账号全换成 KES 的,参数再补上 directConnection=true 和认证机制:

yaml 复制代码
# Spring Boot 的 application.yml
spring:
  data:
    mongodb:
      uri: mongodb://system:123456@127.0.0.1:27017/test?directConnection=true&authMechanism=SCRAM-SHA-256

驱动版本得对齐。我们用的 Java 驱动偏老,连 KES 时有个别报文字段解析起来犯别扭,升到较新版本就消停了。这算改了吗?算,但改的是依赖版本,业务代码一行没碰。

管理类命令得收拾。项目里有那么一处用了角色管理相关的命令,给运营开只读账号的脚本,而这块 KES 是 0% 支持,一拍两散。咋办?改用 KES 自带的管理工具或 SQL 去建角色授权,效果一模一样,就是写法换了个样。这块务必专项排查,把代码里的 db.runCommanddb.createRoledb.grantRolesToUser 这类玩意儿全 grep 一遍,别漏。

冷门聚合阶段也得盯。我们报表里有几个用的聚合阶段比较偏门,正好撞在那 84.21% 的盲区里。处理方式要么改写成等价逻辑,要么干脆把那段计算挪到应用侧去。建议你把线上慢查询和聚合语句一锅端捞出来,一条一条过。

所以那句口号,我给你翻译翻译。"0 代码修改",准确说法是业务读写逻辑基本 0 改,但连接配置、依赖版本、管理脚本这三处,该动的还得动。别被宣传话术一冲脑门,以为一个字都不碰就敢往线上怼。

十、验,往死里验

应用能连、能跑,可不等于行为就一致了。这步我耗的时间,比迁移本身还长,分了三层来磨。

第一层,CRUD 等价性。抽一批生产查询日志回放,拿两边返回的条数和内容对着比。重点盯更新操作符,$set$inc$push$pull$addToSet 这几个覆盖率 100%,行为理应完全一致。但凡有一条对不上,立刻停手查。

javascript 复制代码
// 我用来回归的一组更新用例,覆盖字段、数组两类操作符
db.orders.updateOne(
  { _id: 1 },
  { $set: { status: "paid" }, $inc: { version: 1 }, $push: { logs: "paid_at_3pm" } }
);
db.orders.updateOne(
  { _id: 1 },
  { $pull: { tags: "draft" }, $addToSet: { tags: "done" } }
);
db.orders.find({ _id: 1 });

第二层,聚合管道等价性。这层最容易翻车,原因前面说过,聚合阶段支持率才 84.21%。我把所有 $group$lookup$facet$bucket 全列单子上,挨个在新库里跑、对结果。下面这种业务里最常见的分组统计,就是重点回归对象:

javascript 复制代码
// 按类目统计订单数和总金额,取 TOP5------典型的报表聚合
db.orders.aggregate([
  { $match: { status: "paid", create_time: { $gte: ISODate("2026-01-01") } } },
  { $group: {
      _id: "$category",
      orderCount: { $sum: 1 },
      totalAmount: { $sum: "$amount" }
  }},
  { $sort: { totalAmount: -1 } },
  { $limit: 5 }
]);

这种 $match → $group → $sort → $limit 的连招,常用阶段基本都支棱着,能稳稳跑通。可一旦你动了 $facet(一口气跑好几个分支管道)这种偏门货,就得给自己留个心眼,先在测试库把结果对明白了,再谈上线。

第三层,边界类型。重点盯 ObjectId 的生成和排序、Decimal128、日期时区、二进制。这些走的是 EJson 序列化,还记得前面那个 bsonUseEJson=on 吗,就是为它们开的。我在这儿结结实实撞过一个坑,有条记录的时间字段,老库读出来是 UTC,新库读出来却带个时区偏移,前端一展示,时间全错位了,那叫一个漂亮。最后是统一在应用层把时区 normalize 掉才算完。这种类型语义上的微妙出入,文档里是一个字都不会写的,只能靠回放真实数据一条条抠出来。

十一、UPDATE 拉胯那晚,我熬到了天亮

最后单拎性能出来说,因为这块差点把我们卡在上线门口。

不出前面所料,写操作慢于原生 Mongo,UPDATE 尤其拉胯。线上那个批量改价接口迁过来没两天,P99 直接翻倍,告警短信半夜把我炸醒。我当时是这么一步步排查的。

先怀疑是不是全表扫描,掏出 explain 看执行计划:

javascript 复制代码
db.products.find({ sku: "A10023" }).explain("executionStats");

一瞧,好家伙,果然没走索引。sku 在老库明明是有索引的,可迁过来的时候索引没跟着搬。前面提过,大表是先导数据后建索引,结果我那建索引的脚本把 sku 给漏了,你说气不气。麻溜补上:

javascript 复制代码
db.products.createIndex({ sku: 1 });
// 组合查询字段,建复合索引
db.products.createIndex({ category: 1, status: 1, create_time: -1 });

接着优化写批量。把应用里头一条一条往外蹦的 updateOne,归拢成 bulkWrite 一拨提交,省下大把网络往返:

javascript 复制代码
db.orders.bulkWrite([
  { updateOne: { filter: { _id: 1 }, update: { $set: { price: 99 } } } },
  { updateOne: { filter: { _id: 2 }, update: { $set: { price: 88 } } } },
  { updateOne: { filter: { _id: 3 }, update: { $set: { price: 77 } } } }
], { ordered: false });

那个 ordered: false 是有讲究的,它让库并行去跑那些互不依赖的操作,批量改价这种场景,提速肉眼可见。

最后,把热数据的体量摁住。历史冷数据归档到单独的集合里去,让热表保持小巧,缓存命中率和索引效率都会跟着漂亮起来。

三招打完,P99 乖乖降回了接近原来的水平,那会儿窗外天都蒙蒙亮了。我个人的体会是这么个理,KES 走的这条文档兼容路子,性能天花板确实够不到原生 Mongo,但对绝大多数读多写少的业务,靠"建对索引 + 批量写 + 摁住热数据"这三板斧,差距完全能拽回可接受的范围。而它换回来的,是文档和关系数据头一回真正躺在同一个库里,再也不用互相倒腾。这笔账怎么算,你得拿自己的业务去掂量。

十二、清单:抄作业专用

上头这些散落各处的经验,我拢成了一份清单,按你执行的先后排好了。照着走,八成的坑能直接绕过去。

initdb 那一步,-m 兼容模式必须指定正确,实例建好就改不了了,这事儿没商量。接着是 kingbase.conf 里那四件套,enable_protocol_compatextension_protocol_portdocumentdb_core.bsonUseEJson,外加 shared_preload_libraries 的加载顺序,一个都不能少,改完一定重启,别图省事 reload。起服务前先 netstat 探一下 27017 有没有被人占着。建扩展的时候,CREATE EXTENSION documentdb CASCADE,cascade 那个字眼千万别手抖删了。

连接串这边,directConnection=trueauthMechanism=SCRAM-SHA-256 务必带上,少一个都可能让你怀疑人生。数据导入的顺序记牢,先导数据,后建索引,大表走双写灰度。应用改造就三处,连接配置、驱动版本、管理类脚本,角色和用户管理改用 KES 原生工具搞定。上线前再做一次兼容盲区预扫,grep 一遍代码里的 runCommandcreateRole 和冷门聚合阶段,提前改写。然后是三层验证,CRUD 等价、聚合等价、边界类型,ObjectId、Decimal、时区一个都别漏。最后性能那三板斧,建对索引、写批量化、摁住热数据,记心里。

最后扯两句

仨礼拜磨下来,账我自个儿算,值。技术上门槛比想的低,协议级兼容让业务读写逻辑几乎没怎么动,文档库就悄没声地换了。真让我觉得值的,是它顺手把我一开始最头疼那个"烟囱"给拆了,关系数据和文档数据如今搁一个引擎里养着,库间那摊同步、补偿、对账的破事一下子全没了,运维兄弟也终于不用再同时盯三套监控三套备份。再往后等向量检索和 GIS 也并进来,那种一个库通吃多模、技术栈收成一摊的减负感,只会更明显。安全这块我也踏实了,它过了最高等级的安全认证,从访问控制、身份鉴别到传输存储加密再到事后审计,是一整套纵深防御,比原来 Mongo 那种单点防护扎实太多。对我们这种得过等保、过名录评审的项目,这就是硬门槛,过不去一切免谈。多集群架构也给我留了后手,往后真要按业务连续性弹性扩,地方是现成的。写到最后啰嗦一句吧,别光盯着那张兼容率表发呆,把这次迁移当成一次收敛技术栈、顺手把安全合规一块儿补齐的机会来做,KES 这条路的真正价值,到那时候才显出来。

相关推荐
用户1861558008601 小时前
MinIO 数据迁移实战:导出压缩包、下载到本地并绑定目标服务器
后端
程序边界1 小时前
SQL Server数据迁移这件事,远比你想的复杂——但也远比你想的简单(上)
后端
JakeJiang1 小时前
抓到接口还不够:用 AIProxy 改返回、Mock 数据、切测试环境
前端·后端
YuePeng1 小时前
Java 开发者的 Django Admin,终于来了
后端·架构·github
XuCoder1 小时前
Redis 分片集群:它到底是怎么把数据分片又路由的?
后端
吃饱了得干活1 小时前
Java Map 核心原理:从数据结构到 put/get 执行,一篇彻底讲透
java·后端
不才不才不不才2 小时前
Spring 源码系列(17): HandlerMapping 与 HandlerAdapter 两大体系
java·后端·spring
Seven972 小时前
AI杂谈:别再问AI会不会替代你,先看你是不是驾驶员
人工智能·后端
ERD Online2 小时前
我们怎么设计 good first issue:让第一个 PR 两小时内合入
数据库·git·后端·开源·issue