據(jù)層)
1. zeit/micro 里 Mongoose 連 Mongo 為什么總在冷啟動(dòng)翻車如果你正在用 zeit/micro 寫輕量接口又想讓數(shù)據(jù)落到 MongoDB大概率會(huì)搜到「zeit micro mongoose mongo 連接復(fù)用」這類關(guān)鍵詞。micro 本身極簡(jiǎn)一個(gè)module.exports (req, res) {}就能跑起來(lái)但它沒(méi)有內(nèi)置數(shù)據(jù)庫(kù)生命周期管理。Serverless 環(huán)境下每次冷啟動(dòng)都會(huì)重新執(zhí)行模塊頂層代碼如果你把mongoose.connect()直接寫在文件頂部就會(huì)出現(xiàn)兩個(gè)典型問(wèn)題一是每次請(qǐng)求都新建連接連接數(shù)暴漲二是函數(shù)實(shí)例被回收后連接懸空下一次請(qǐng)求拿到的是失效句柄報(bào)MongooseError: Operation buffering timed out或者Cannot use a closed connection。我試過(guò)在本地micro-dev跑得好好的一部署到 Serverless 平臺(tái)就間歇性 500日志里全是buffering timed out after 10000ms。根因不是 Mongoose 本身而是連接沒(méi)有做「實(shí)例級(jí)復(fù)用 就緒等待」。micro 的請(qǐng)求處理函數(shù)是 async 的但模塊加載階段是同步的mongoose.connect()返回 Promise你不 await 它第一個(gè)請(qǐng)求進(jìn)來(lái)時(shí)連接還沒(méi)建立Mongoose 默認(rèn)會(huì)緩沖操作緩沖超時(shí)就拋錯(cuò)。這篇要解決的就是這條鏈路用 Mongoose 在 zeit/micro 里建立可復(fù)用的 Mongo 連接處理冷啟動(dòng)、連接超時(shí)、讀寫驗(yàn)證。適合已經(jīng)在寫 micro 接口、準(zhǔn)備接 MongoDB、或者被 Serverless 連接問(wèn)題卡住的開(kāi)發(fā)者。下面從連接封裝開(kāi)始一步步給出可復(fù)制的配置和驗(yàn)證請(qǐng)求。2. TaoToken 前置給 micro 項(xiàng)目準(zhǔn)備模型調(diào)用與 Key 管理在寫數(shù)據(jù)庫(kù)層之前先把項(xiàng)目里可能用到的模型調(diào)用能力準(zhǔn)備好。很多 micro 服務(wù)除了讀寫 Mongo還會(huì)調(diào)用大模型做日志分類、內(nèi)容摘要。TaoToken 提供統(tǒng)一的 API 入口兼容常見(jiàn)模型調(diào)用格式適合放在 Serverless 函數(shù)里做輕量推理。你需要先拿到 API Key。打開(kāi) https://taotoken.net/api-keys 登錄后創(chuàng)建一個(gè) Key復(fù)制保存。這個(gè) Key 就是后面所有請(qǐng)求的憑證不要寫死在代碼里放到環(huán)境變量。模型調(diào)用入口是 https://taotoken.net/api 它兼容 OpenAI 風(fēng)格的/v1/chat/completions。在 micro 里你可以直接用node-fetch或內(nèi)置https模塊請(qǐng)求。如果你要做長(zhǎng)期編碼或 Agent 類任務(wù)可以看 Coding Planhttps://taotoken.net/coding-plan 它更適合持續(xù)性的代碼生成場(chǎng)景。想先驗(yàn)證模型是否通用模型對(duì)話頁(yè)面https://taotoken.net/chat 發(fā)一條消息看返回。接入文檔在 https://taotoken.net/doc 里面有 Base URL、鑒權(quán)頭、請(qǐng)求體格式的完整說(shuō)明??刂婆_(tái)在 https://taotoken.net/console 可以查看用量和調(diào)用記錄。Claude Code 相關(guān)接入?yún)⒖?https://taotoken.net/claude-code 。這里的關(guān)鍵是micro 函數(shù)是無(wú)狀態(tài)的Key 必須通過(guò)環(huán)境變量注入不能硬編碼。本地開(kāi)發(fā)用.env部署時(shí)在平臺(tái)的環(huán)境變量面板里配置。下面第三節(jié)會(huì)給出完整的連接封裝和配置片段。3. 可復(fù)制配置Mongoose 連接封裝與 micro 路由先裝依賴。micro 生態(tài)的核心包是micro、micro-dev、microrouter數(shù)據(jù)庫(kù)用mongoose。npm install micro micro-dev microrouter mongoose --savepackage.json的 scripts 建議這樣寫本地開(kāi)發(fā)用micro-dev帶熱重載生產(chǎn)用micro{ name: micro-mongo, version: 1.0.0, main: index.js, scripts: { dev: micro-dev -p 5670, start: micro -p 5500 }, dependencies: { micro: ^9.3.4, microrouter: ^3.1.3, mongoose: ^7.6.3 }, devDependencies: { micro-dev: ^3.0.0 } }連接封裝單獨(dú)放db.js。核心是「緩存連接 Promise」而不是緩存連接對(duì)象。這樣并發(fā)請(qǐng)求進(jìn)來(lái)時(shí)大家 await 同一個(gè) Promise不會(huì)重復(fù)建連。// db.js const mongoose require(mongoose); const MONGODB_URI process.env.MONGODB_URI || mongodb://127.0.0.1:27017/micro_log; let cached global.__mongooseCache; if (!cached) { cached global.__mongooseCache { conn: null, promise: null }; } async function connectWithDB() { if (cached.conn) { return cached.conn; } if (!cached.promise) { const opts { serverSelectionTimeoutMS: 5000, socketTimeoutMS: 45000, maxPoolSize: 10, bufferCommands: false }; cached.promise mongoose.connect(MONGODB_URI, opts).then((m) m); } cached.conn await cached.promise; return cached.conn; } module.exports connectWithDB;注意bufferCommands: false這樣連接沒(méi)就緒時(shí)操作會(huì)立刻報(bào)錯(cuò)而不是緩沖 10 秒再超時(shí)排障更快。maxPoolSize控制單實(shí)例連接數(shù)Serverless 下別設(shè)太大。模型定義放model/log.js用mongoose.models.Log做存在性判斷避免熱重載時(shí)重復(fù)注冊(cè)模型報(bào)OverwriteModelError// model/log.js const mongoose require(mongoose); const LogSchema new mongoose.Schema({ name: { type: String, required: Please fill name, trim: true }, time: { type: String }, type: { type: String }, status: { type: String }, tag: { type: String }, handler: { type: String, default: SCM }, description: { type: String, default: SystemLog } }, { timestamps: true }); module.exports mongoose.models.Log || mongoose.model(Log, LogSchema);micro 入口index.js用 microrouter 組織路由每個(gè) handler 先 await 連接再操作// index.js const { send, json } require(micro); const { router, get, post } require(microrouter); const connectWithDB require(./db); const Log require(./model/log); const notfound (req, res) send(res, 404, { msg: Not found route }); const create async (req, res) { await connectWithDB(); const data await json(req); const log new Log(data); await log.save(); send(res, 200, { msg: create-ok, id: log._id }); }; const findDatas async (req, res) { await connectWithDB(); const datas await Log.find({}).limit(50).lean(); send(res, 200, datas); }; const findDatasByTime async (req, res) { await connectWithDB(); const data await json(req); const p { time: { $gte: data.startTime, $lt: data.endTime } }; const datas await Log.find(p).lean(); send(res, 200, datas); }; module.exports router( get(/loglist, findDatas), post(/logcreate, create), post(/loglist, findDatasByTime), get(/*, notfound) );環(huán)境變量在本地用.env配合micro-dev加載或者直接export MONGODB_URI...。部署時(shí)在平臺(tái)環(huán)境變量里配置MONGODB_URI格式是mongodbsrv://user:passhost/dbname或標(biāo)準(zhǔn)連接串。4. 驗(yàn)證請(qǐng)求本地與部署環(huán)境的讀寫鏈路確認(rèn)先本地起服務(wù)npm run dev默認(rèn)監(jiān)聽(tīng) 5670。用 curl 驗(yàn)證寫入curl -X POST http://localhost:5670/logcreate \ -H Content-Type: application/json \ -d {name:alarm-001,time:2024-06-01T10:00:00Z,type:cpu,status:open,tag:prod}預(yù)期返回{msg:create-ok,id:665f1a2b3c4d5e6f7a8b9c0d}再驗(yàn)證讀取curl http://localhost:5670/loglist預(yù)期返回一個(gè)數(shù)組包含剛寫入的記錄。如果返回[]說(shuō)明寫入到了不同數(shù)據(jù)庫(kù)或集合檢查MONGODB_URI里的 dbname。按時(shí)間范圍查詢curl -X POST http://localhost:5670/loglist \ -H Content-Type: application/json \ -d {startTime:2024-06-01T00:00:00Z,endTime:2024-06-02T00:00:00Z}部署后把域名替換掉本地地址重復(fù)上面三條請(qǐng)求。Serverless 平臺(tái)第一次請(qǐng)求會(huì)觸發(fā)冷啟動(dòng)可能慢 1-2 秒第二次開(kāi)始走復(fù)用連接響應(yīng)明顯變快。你可以連續(xù)發(fā) 10 次/loglist觀察日志里connectWithDB是否只打印一次連接信息。如果每次都打印說(shuō)明緩存沒(méi)生效檢查global.__mongooseCache是否被平臺(tái)重置或者模塊是否被重復(fù)加載。驗(yàn)證模型調(diào)用鏈路時(shí)用環(huán)境變量里的 Key 請(qǐng)求curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:ping}]}返回里有choices[0].message.content就說(shuō)明 Key 和網(wǎng)絡(luò)都通。這一步和 Mongo 無(wú)關(guān)但能確認(rèn)你的 micro 函數(shù)在部署環(huán)境里能正常發(fā)出外部請(qǐng)求。5. 本篇常見(jiàn)錯(cuò)排查401、local proxy failed、reading choices、OAuth報(bào)錯(cuò)一MongooseError: Operation buffering timed out after 10000ms這是連接沒(méi)就緒就執(zhí)行查詢。檢查兩點(diǎn)connectWithDB()是否在每個(gè) handler 里 await 了bufferCommands是否設(shè)成了false。如果設(shè)了 false 還報(bào)這個(gè)錯(cuò)說(shuō)明連接根本沒(méi)建立看MONGODB_URI是否正確、Mongo 是否允許當(dāng)前 IP 訪問(wèn)。報(bào)錯(cuò)二MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017本地沒(méi)起 Mongo或者連接串指向了 localhost 但部署環(huán)境里沒(méi)有本地 Mongo。部署時(shí)必須用云數(shù)據(jù)庫(kù)連接串通過(guò)環(huán)境變量注入。報(bào)錯(cuò)三OverwriteModelError: Cannot overwrite Log model once compiled.熱重載時(shí)重復(fù)注冊(cè)模型。用mongoose.models.Log || mongoose.model(Log, LogSchema)解決已經(jīng)在第 3 節(jié)代碼里處理了。報(bào)錯(cuò)四模型調(diào)用返回 401{error:{message:Invalid API key,type:invalid_request_error}}。檢查Authorization頭是否是Bearer KeyKey 是否有多余空格環(huán)境變量是否真的注入到了運(yùn)行環(huán)境。在 TaoToken 控制臺(tái) https://taotoken.net/console 確認(rèn) Key 狀態(tài)。報(bào)錯(cuò)五local proxy failed或連接超時(shí)這類錯(cuò)誤通常出現(xiàn)在函數(shù)運(yùn)行環(huán)境無(wú)法直連外部服務(wù)時(shí)。檢查平臺(tái)是否限制了出站網(wǎng)絡(luò)或者連接串里的 host 是否可達(dá)。TaoToken 的 API 入口是標(biāo)準(zhǔn) HTTPS正常環(huán)境可直接請(qǐng)求。報(bào)錯(cuò)六Cannot read properties of undefined (reading choices)模型返回體結(jié)構(gòu)和你解析的字段不匹配。先打印完整響應(yīng)體確認(rèn)choices存在。常見(jiàn)原因是請(qǐng)求被攔截返回了 HTML 錯(cuò)誤頁(yè)或者用了錯(cuò)誤的 endpoint。確認(rèn)請(qǐng)求的是https://taotoken.net/api/v1/chat/completions。報(bào)錯(cuò)七OAuth 相關(guān)錯(cuò)誤如果你在接入 Claude Code 或類似工具時(shí)遇到 OAuth 報(bào)錯(cuò)檢查回調(diào)地址和 token 是否過(guò)期。Claude Code 接入?yún)⒖?https://taotoken.net/claude-code 按文檔里的步驟重新授權(quán)。排查順序建議先確認(rèn) Mongo 連接串和網(wǎng)絡(luò)再確認(rèn) Mongoose 緩存邏輯最后確認(rèn)模型調(diào)用的 Key 和 endpoint。每一步都用最小請(qǐng)求驗(yàn)證不要一次改多個(gè)地方。6. 把連接復(fù)用和 Key 管理固定成項(xiàng)目模板micro Mongoose 這套組合的關(guān)鍵就三件事連接 Promise 緩存、bufferCommands: false、每個(gè) handler await 連接。把db.js和model/log.js抽成模板新項(xiàng)目直接復(fù)制能省掉大量冷啟動(dòng)排障時(shí)間。模型調(diào)用這邊Key 走環(huán)境變量Base URL 用 https://taotoken.net/api 需要長(zhǎng)期跑編碼任務(wù)就上 Coding Plan臨時(shí)驗(yàn)證用模型對(duì)話頁(yè)面。接入文檔在 https://taotoken.net/doc API Key 在 https://taotoken.net/api-keys 創(chuàng)建。最后留一個(gè)實(shí)用技巧在connectWithDB里加一行連接事件監(jiān)聽(tīng)mongoose.connection.on(error, console.error)部署后日志里能第一時(shí)間看到連接斷開(kāi)比等請(qǐng)求超時(shí)再查快得多。