合查詢實(shí)戰(zhàn):標(biāo)簽與關(guān)鍵詞搜索及性能優(yōu)化)
做后端開發(fā)這些年我越來越確信一件事——大部分項(xiàng)目的業(yè)務(wù)邏輯本身并不復(fù)雜真正消耗精力的反而是服務(wù)器和環(huán)境那一攤子事。買一臺(tái)云服務(wù)器只是開始裝環(huán)境、配數(shù)據(jù)庫、寫接口、處理并發(fā)每一項(xiàng)都能吃掉大把時(shí)間。所以第一次接觸Bmob這類BaaS平臺(tái)時(shí)“零服務(wù)器開發(fā)”這幾個(gè)字確實(shí)戳中了我。本著“到底能不能扛住真實(shí)業(yè)務(wù)”這個(gè)疑問我在幾個(gè)實(shí)際項(xiàng)目里把Bmob的Python SDK從簡單的增刪改查一直用到了搜索場景也就是今天要重點(diǎn)聊的標(biāo)簽加關(guān)鍵詞的復(fù)合查詢以及搜索性能優(yōu)化。這篇文章會(huì)把完整的實(shí)現(xiàn)思路、代碼細(xì)節(jié)、踩坑經(jīng)過都放出來適合正在做內(nèi)容社區(qū)、商品篩選、活動(dòng)聚合這類功能的朋友參考。1. 選型復(fù)盤零服務(wù)器方案為什么能落地1.1 自建后端和BaaS的真實(shí)成本對比先說個(gè)很現(xiàn)實(shí)的問題自己做后端要花多少代價(jià)以一個(gè)小型內(nèi)容社區(qū)為例你至少要準(zhǔn)備一臺(tái)服務(wù)器、一個(gè)數(shù)據(jù)庫實(shí)例還要寫用戶、文章、評(píng)論三套接口。服務(wù)器要選配置、做安全組、處理重啟后的服務(wù)拉起數(shù)據(jù)庫要考慮備份策略和慢查詢接口要處理參數(shù)校驗(yàn)、異常捕獲、權(quán)限控制。這些還沒算上后續(xù)的需求迭代每加一個(gè)模塊就要重新走一遍“建表-寫接口-部署”的循環(huán)。更麻煩的是這些工作量和業(yè)務(wù)本身的復(fù)雜程度完全不成正比。很多時(shí)候核心業(yè)務(wù)邏輯其實(shí)只有幾十行代碼但外圍的基礎(chǔ)設(shè)施工作量是它的十倍。Bmob這類BaaS方案把數(shù)據(jù)庫、用戶體系、文件存儲(chǔ)、云函數(shù)這些后端基礎(chǔ)能力直接做成服務(wù)開發(fā)者通過SDK在客戶端或服務(wù)端調(diào)用就行服務(wù)器不用你買環(huán)境不用你配存儲(chǔ)和計(jì)算資源由平臺(tái)統(tǒng)一調(diào)度。對中小型項(xiàng)目、個(gè)人開發(fā)者、創(chuàng)業(yè)團(tuán)隊(duì)做MVP驗(yàn)證來說這是效率最高的啟動(dòng)方式。Bmob還有幾個(gè)比較實(shí)際的優(yōu)點(diǎn)國內(nèi)訪問速度穩(wěn)定文檔是中文的控制臺(tái)里可以直接看數(shù)據(jù)、建索引、查日志。Python SDK的封裝也比較貼近業(yè)務(wù)不需要自己拼REST請求。選型它比較適合“業(yè)務(wù)邏輯清晰、數(shù)據(jù)量在幾萬到幾十萬、搜索需求以簡單條件組合為主”的場景。反過來如果你的需求是復(fù)雜的全文檢索、大規(guī)模聚合分析或者需要完全自定義的查詢邏輯那BaaS會(huì)顯得有些吃力這部分后面會(huì)細(xì)講。1.2 復(fù)合查詢的業(yè)務(wù)場景和設(shè)計(jì)思路標(biāo)簽加關(guān)鍵詞的組合查詢在真實(shí)業(yè)務(wù)里出現(xiàn)的頻率非常高。我做過的幾個(gè)項(xiàng)目里最常見的就是內(nèi)容列表頁用戶先點(diǎn)一個(gè)“技術(shù)”標(biāo)簽再在搜索框里輸入“Python”期望的結(jié)果是“標(biāo)簽包含技術(shù)、標(biāo)題或摘要包含Python”的文章集合。電商場景也類似用戶在類目“手機(jī)”下搜索“驍龍”需要同時(shí)滿足類目條件和關(guān)鍵詞條件。活動(dòng)聚合頁則可能是按“城市”加“活動(dòng)名稱關(guān)鍵詞”來篩。這種復(fù)合查詢看起來很自然但真落地的時(shí)候會(huì)發(fā)現(xiàn)一些細(xì)節(jié)問題標(biāo)簽的匹配邏輯到底是“包含任意一個(gè)”還是“包含全部”關(guān)鍵詞模糊匹配是只查標(biāo)題還是連著摘要一起查兩個(gè)條件之間是AND還是OR條件組合多了之后查詢速度還能不能接受這些問題的答案會(huì)直接影響用戶體驗(yàn)和后端壓力。我在設(shè)計(jì)時(shí)定的基調(diào)很明確默認(rèn)走AND組合、標(biāo)簽用數(shù)組字段存、關(guān)鍵詞匹配標(biāo)題字段同時(shí)把性能優(yōu)化貫穿在數(shù)據(jù)設(shè)計(jì)和查詢寫法里而不是等數(shù)據(jù)量大了再回頭改。2. 環(huán)境搭建與數(shù)據(jù)模型設(shè)計(jì)2.1 初始化Bmob Python SDK的步驟在使用SDK之前先把Bmob應(yīng)用建好注冊賬號(hào)后創(chuàng)建一個(gè)應(yīng)用在“應(yīng)用設(shè)置”的“應(yīng)用密鑰”頁面里找到App ID、REST API Key和Master Key。這三個(gè)Key的分工不同App ID標(biāo)識(shí)應(yīng)用REST API Key用于常規(guī)接口調(diào)用Master Key是超級(jí)權(quán)限調(diào)試的時(shí)候很方便但切記不能在客戶端或公開代碼里暴露。Python這邊我習(xí)慣先準(zhǔn)備一個(gè)干凈的虛擬環(huán)境然后安裝官方提供的Python SDK包。安裝完成后在代碼里初始化import bmob # 三個(gè)參數(shù)分別是 App ID、REST API Key、Master Key bmob.init(your_app_id, your_rest_api_key, your_master_key)初始化之后就能操作表數(shù)據(jù)了。版本比較新的SDK在調(diào)用方式上可能有些差異比如查詢對象叫Query還是Table方法名是find還是fetch這些以你實(shí)際安裝的版本為準(zhǔn)。我這里用最通用的寫法來展示核心邏輯換到你自己的環(huán)境里無非是改幾個(gè)方法名。2.2 表結(jié)構(gòu)設(shè)計(jì)標(biāo)簽字段為什么必須用數(shù)組數(shù)據(jù)模型是搜索性能的第一道關(guān)卡。以文章表Article為例我的實(shí)際設(shè)計(jì)中常用的字段是這些字段名類型用途說明titleString文章標(biāo)題關(guān)鍵詞匹配的主要字段contentString正文列表頁展示時(shí)不需要tagsArray標(biāo)簽數(shù)組比如[Python, 后端]categoryString單一分類比如“技術(shù)專欄”authorString作者名isPublishedBoolean是否上架普通用戶只能看到true的數(shù)據(jù)createdAtDate創(chuàng)建時(shí)間排序和翻頁都用它這里最關(guān)鍵的一點(diǎn)是標(biāo)簽字段要用數(shù)組類型而不是用逗號(hào)拼接的字符串。原因有兩個(gè)。第一數(shù)組可以用Bmob查詢語法里的$all、$in操作符做包含匹配語義清晰、效率也高如果存成“Python,后端”這種字符串你還得為匹配付出正則全表掃描的代價(jià)。第二數(shù)組在后續(xù)做標(biāo)簽篩選、統(tǒng)計(jì)、去重時(shí)都方便許多。數(shù)據(jù)入庫前我還會(huì)做一層清洗標(biāo)簽統(tǒng)一轉(zhuǎn)小寫、去掉首尾空格、去掉重復(fù)項(xiàng)。別小看這一步真實(shí)環(huán)境里用戶的輸入五花八門比如“python”和“Python”在字符串匹配下是兩個(gè)完全不同值后面排查數(shù)據(jù)漏查的時(shí)候非常頭疼。清洗邏輯一般寫在寫入數(shù)據(jù)的入口處一勞永逸。關(guān)鍵詞匹配字段的取舍也要提前想清楚。我的經(jīng)驗(yàn)是搜索框里的關(guān)鍵詞默認(rèn)只匹配title頂多加上一個(gè)summary摘要字段。content全文參與匹配看起來很美但BaaS環(huán)境下全文字段掃描會(huì)讓查詢性能迅速惡化而且用戶搜索場景下命中標(biāo)題和命中正文的權(quán)重本來就不一樣混在一起會(huì)污染搜索結(jié)果的排序。等業(yè)務(wù)規(guī)模真的大到需要全文檢索時(shí)再去接專門的搜索引擎而不是在BaaS里硬扛。3. 標(biāo)簽關(guān)鍵詞復(fù)合查詢的核心實(shí)現(xiàn)3.1 標(biāo)簽篩選的兩種語義包含任一和包含全部標(biāo)簽篩選有點(diǎn)微妙的區(qū)分。有些場景用戶點(diǎn)一個(gè)標(biāo)簽只要文章命中其中任意一個(gè)就可以進(jìn)列表比如“技術(shù) OR 產(chǎn)品”的文章都展示有些場景則要求文章同時(shí)具備多個(gè)標(biāo)簽比如“Python”和“后端”必須同時(shí)出現(xiàn)在標(biāo)簽數(shù)組里。這兩種語義對應(yīng)Bmob底層的兩個(gè)操作符$in字段值等于數(shù)組中任意一個(gè)值就命中相當(dāng)于“或”的關(guān)系$all字段值包含數(shù)組中全部值才命中相當(dāng)于“且”的關(guān)系舉個(gè)例子如果我要找“標(biāo)簽包含Python”的文章REST API里對應(yīng)的查詢條件是{ tags: { $all: [Python] } }如果你要找“標(biāo)簽為Python或后端”的文章則用$in{ tags: { $in: [Python, 后端] } }大多數(shù)內(nèi)容路由場景標(biāo)簽互斥性很強(qiáng)用戶選一個(gè)標(biāo)簽就夠了用$all和$in效果差不多。如果做組合標(biāo)簽篩選比如“既是Python又是后端”那$all是唯一正確選擇。這個(gè)語義差別一定要跟產(chǎn)品同學(xué)對齊不然列表數(shù)據(jù)會(huì)出現(xiàn)“明明選了后端標(biāo)簽卻冒出一堆前端文章”的詭異問題。3.2 關(guān)鍵詞模糊匹配的正則方案Bmob的查詢語法里模糊匹配通常用正則表達(dá)式實(shí)現(xiàn)。比如要找一個(gè)標(biāo)題里包含“Django”的文章查詢條件是{ title: { $regex: Django } }這里是包含匹配也就是相當(dāng)于正則的.*Django.*不需要你在關(guān)鍵詞前后手動(dòng)加.*。如果我只是想在文章標(biāo)題里搜“Django REST Framework”相關(guān)的內(nèi)容我會(huì)把關(guān)鍵詞先用re.escape處理一遍再去構(gòu)造查詢。為什么要轉(zhuǎn)義因?yàn)檎齽t里的特殊字符太多了用戶在搜索框輸入“C”或者“Python3.12”這里的和.都是正則元字符不轉(zhuǎn)義的話查詢結(jié)果完全不是預(yù)期。正則查詢在Bmob后臺(tái)默認(rèn)可能是關(guān)閉的需要到控制臺(tái)的安全設(shè)置里把“允許正則查詢”打開否則會(huì)一直返回空數(shù)據(jù)或者直接報(bào)錯(cuò)。這一點(diǎn)放到后面“常見問題”部分再詳細(xì)說。3.3 把多個(gè)條件組合進(jìn)同一查詢Bmob查詢默認(rèn)多個(gè)條件字段之間的關(guān)系就是AND所以實(shí)現(xiàn)“標(biāo)簽關(guān)鍵詞”復(fù)合查詢最簡單的方法是把所有條件放到同一個(gè)where里{ tags: { $all: [Python] }, title: { $regex: Django }, isPublished: true }用Python SDK來寫核心邏輯是這樣# 構(gòu)造查詢表名傳入 Article query Query(Article) # 標(biāo)簽條件必須包含Python標(biāo)簽 query.add_where(tags, {$all: [Python]}) # 關(guān)鍵詞條件標(biāo)題里模糊匹配 Django query.add_where(title, {$regex: Django}) # 上架狀態(tài)條件只查已發(fā)布內(nèi)容 query.add_where(isPublished, True) # 按創(chuàng)建時(shí)間倒序排序 query.order(-createdAt) # 分頁參數(shù)每頁20條從第0條開始 query.limit(20) query.skip(0) # 執(zhí)行查詢 results query.find()這段代碼執(zhí)行后拿到的就是“標(biāo)簽含Python、標(biāo)題含Django、已發(fā)布、按時(shí)間倒序”的文章列表。如果你還要再加條件比如限定作者、限定某個(gè)分類照葫蘆畫瓢往where里塞就行。還有一種更復(fù)雜的場景條件之間需要OR組合。比如“標(biāo)題含Django或者標(biāo)簽含Python”的文章都要展示這時(shí)候需要把兩個(gè)條件整體包進(jìn)$or數(shù)組里。Bmob的查詢語法對這個(gè)的支持比較成熟{ $or: [ {title: {$regex: Django}}, {tags: {$all: [Python]}} ] }但我的建議是搜索場景默認(rèn)使用AND組合只有當(dāng)產(chǎn)品需求非常明確地要求“任選其一命中”時(shí)才使用OR。因?yàn)镺R組合會(huì)讓查詢復(fù)雜度成倍增長也更容易踩索引失效的坑。能通過交互設(shè)計(jì)規(guī)避的盡量在交互層面規(guī)避。4. 搜索性能優(yōu)化實(shí)戰(zhàn)從索引到緩存4.1 索引是決定快慢的第一要素在BaaS平臺(tái)上寫查詢很多人容易忽略一個(gè)動(dòng)作在控制臺(tái)給字段建索引。索引可以理解成書的目錄沒有目錄你只能逐頁翻有目錄就可以直接翻到對應(yīng)章節(jié)。Bmob控制臺(tái)的數(shù)據(jù)庫管理里進(jìn)入具體的表找到字段列表按提示給目標(biāo)字段加上索引即可。以文章表為例tags、category、isPublished、createdAt這幾個(gè)字段都有必要建索引。原因很簡單tags承擔(dān)標(biāo)簽篩選isPublished做權(quán)限過濾createdAt做排序和游標(biāo)翻頁。這幾個(gè)字段都屬于高頻查詢條件沒有索引意味著每次查詢都要全表掃描數(shù)據(jù)量一旦漲到幾萬條速度會(huì)肉眼可見地掉下來。我實(shí)測過一個(gè)數(shù)據(jù)量在五萬條左右的表未建索引時(shí)執(zhí)行一次按標(biāo)簽的篩選耗時(shí)在一秒以上建了索引之后直接降到幾十毫秒。對搜索接口來說從“卡一下”到“秒出”感知差異是顛覆性的。索引的創(chuàng)建成本很低但收益非常直接值得在所有查詢穩(wěn)定后第一時(shí)間補(bǔ)齊。不過索引不是萬能的。正則表達(dá)式如果寫成前導(dǎo)通配的形式比如^.*Django就無法利用索引。除非你真的需要“以某開頭”這種特殊邏輯否則默認(rèn)的包含匹配就好。還有一點(diǎn)索引建多了會(huì)拖慢寫入性能所以只針對高頻查詢字段建不用把每個(gè)字段都建一遍。4.2 只取需要的字段少傳數(shù)據(jù)就是贏Bmob查詢默認(rèn)會(huì)把符合條件記錄的完整字段都返回。文章表里那個(gè)content正文動(dòng)輒幾千字如果一個(gè)列表頁每頁20條每次查詢要把20篇完整正文都從云端傳到本地網(wǎng)絡(luò)開銷、解析開銷都會(huì)成倍增加。解決方案是指定返回字段。在REST API里用keys參數(shù)在SDK里通常也有對應(yīng)的方法。比如列表頁只需要展示標(biāo)題、標(biāo)簽、作者、發(fā)布時(shí)間那就這樣限制{ keys: title,tags,author,createdAt }這樣返回的數(shù)據(jù)量能從幾KB降到幾百字節(jié)分頁加載的速度提升非常顯著。很多性能問題其實(shí)不是查詢慢而是返回的數(shù)據(jù)太肥把傳輸帶寬占滿了。我在做列表頁和詳情頁接口時(shí)一直堅(jiān)持“列表頁永遠(yuǎn)不查詳情字段”的原則。列表就是列表詳情就是詳情這是最基礎(chǔ)也最容易被忽視的優(yōu)化手段。4.3 分頁優(yōu)化盡量別用深分頁早期做分頁大家習(xí)慣了limit加skip的方式第一頁跳過0條第二頁跳過20條第三頁跳過40條。這個(gè)方式在小數(shù)據(jù)量下沒啥問題但skip的代價(jià)是即使你只要20條數(shù)據(jù)數(shù)據(jù)庫也必須先把前面所有需要跳過的記錄都掃描一遍。翻到100頁時(shí)它要把前面1980條記錄全掃一遍然后再扔掉只會(huì)越來越慢。更友好的思路是游標(biāo)式翻頁。利用createdAt排序的唯一性和單調(diào)性每次查詢只取“比上一次最后一條更早”的數(shù)據(jù)。第一頁照常查拿到結(jié)果后記錄最后一條的createdAt下一頁查詢時(shí)把它作為過濾條件# 第一頁正常查詢 query.order(-createdAt) query.limit(20) results query.find() # 翻頁上一頁最后一條的 created_at last_created_at results[-1].created_at query2 Query(Article) query2.add_where(createdAt, {$lt: last_created_at}) query2.order(-createdAt) query2.limit(20) results2 query2.find()這種翻頁方式無論翻到多深掃描的成本基本恒定尤其適合移動(dòng)端“下拉加載更多”的場景。它有個(gè)小缺點(diǎn)是如果查詢條件變化了游標(biāo)會(huì)失效所以每次篩選條件變更時(shí)要把游標(biāo)重置。4.4 緩存熱點(diǎn)查詢結(jié)果復(fù)合查詢再怎么優(yōu)化每次請求打到BaaS云端總歸有網(wǎng)絡(luò)延遲。如果同一個(gè)篩選條件被很多人反復(fù)搜索重復(fù)查云端數(shù)據(jù)庫其實(shí)是很浪費(fèi)的。這時(shí)候可以加一層本地緩存。我用過最簡單的方案一個(gè)字典加一個(gè)過期時(shí)間。同一個(gè)搜索詞在一分鐘內(nèi)多次查詢直接復(fù)用第一次的結(jié)果不重新打云端。偽代碼如下import time search_cache {} def search_with_cache(tag, keyword, ttl60): key f{tag}:{keyword} now time.time() cached search_cache.get(key) if cached and cached[expire] now: return cached[data] data do_search(tag, keyword) # 實(shí)際執(zhí)行查詢的函數(shù) search_cache[key] { data: data, expire: now ttl, } return dataTTL的設(shè)置要結(jié)合業(yè)務(wù)對數(shù)據(jù)實(shí)時(shí)性的容忍度。列表頁多等一分鐘無感那就設(shè)置60秒或更長如果是庫存、價(jià)格這類強(qiáng)實(shí)時(shí)數(shù)據(jù)緩存要非常謹(jǐn)慎或者干脆只緩存“空結(jié)果”把熱門的無結(jié)果查詢擋在云端之外。實(shí)際項(xiàng)目里熱點(diǎn)詞命中緩存的概率很高緩存能擋掉大量重復(fù)請求。4.5 性能優(yōu)化的優(yōu)先級(jí)排序搜索引擎優(yōu)化不是閉門造車我按見效速度給你排個(gè)序先加索引再裁剪返回字段然后優(yōu)化分頁方式最后加緩存。大部分項(xiàng)目做到前兩步性能問題就已經(jīng)解決一大半了。尤其索引很多人建完表就開始寫查詢完全沒想到要去建索引等數(shù)據(jù)量漲了才補(bǔ)這是最常見的踩坑路徑。優(yōu)化這件事一定要在數(shù)據(jù)模型設(shè)計(jì)階段就想清楚而不是等線上卡了才開始急。5. 實(shí)戰(zhàn)中的坑與排查技巧5.1 正則查詢返回空結(jié)果這是我最常遇到的問題而且坑得很微妙。代碼明明寫對了條件也符合邏輯但查詢結(jié)果就是空的。排查順序是這樣的先確認(rèn)Bmob后臺(tái)是否開啟了“正則查詢”權(quán)限。這個(gè)選項(xiàng)在安全設(shè)置里默認(rèn)可能是關(guān)閉狀態(tài)關(guān)閉時(shí)正則表達(dá)式直接不生效也不會(huì)報(bào)錯(cuò)。我當(dāng)時(shí)排查了大半天才發(fā)現(xiàn)是這塊卡住了。其次檢查關(guān)鍵詞里的特殊字符比如用戶輸入“C”沒有轉(zhuǎn)義正則會(huì)把它當(dāng)量詞解析自然搜不到內(nèi)容。用re.escape處理用戶輸入是最穩(wěn)妥的。最后看一眼字段里是否真的有對應(yīng)內(nèi)容比如你搜的是title字段但數(shù)據(jù)其實(shí)都放在name字段里那肯定查不到。5.2 標(biāo)簽查不全數(shù)據(jù)忽多忽少標(biāo)簽查詢結(jié)果不穩(wěn)定的最大元兇是數(shù)據(jù)不規(guī)范。有些記錄里tags存的是[Python, 后端]有些存的是[python, 后端]還有可能是[Python , 后端]字符串帶個(gè)空格。$all和$in匹配是嚴(yán)格相等的大小寫和空格不同匹配結(jié)果就完全不一樣。出現(xiàn)這個(gè)情況后我的處理辦法是寫一個(gè)數(shù)據(jù)清洗腳本把存量數(shù)據(jù)里的標(biāo)簽全部統(tǒng)一為小寫、去除空格、去重。同時(shí)修改寫入接口確保所有新數(shù)據(jù)從源頭就符合規(guī)范。數(shù)據(jù)規(guī)范是搜索準(zhǔn)確性的地基地基歪了上層查詢寫得再漂亮也白搭。5.3 查詢速度突然從快到慢最典型的原因是數(shù)據(jù)量增長。BaaS平臺(tái)的表剛開始幾千條數(shù)據(jù)全表掃描也沒什么感覺漲到幾萬條之后沒有索引的查詢就會(huì)明顯變慢。排查時(shí)先看控制臺(tái)的數(shù)據(jù)量統(tǒng)計(jì)再看查詢?nèi)罩纠锞唧w哪些查詢耗時(shí)高最后對照字段索引情況補(bǔ)齊索引。另一個(gè)容易被忽略的原因是返回字段太多。如果之前一直把content字段返回給前端列表頁數(shù)據(jù)量隨著正文長度水漲船高網(wǎng)絡(luò)傳輸本身就會(huì)拖慢整體體驗(yàn)。這類問題不一定是查詢慢了而是數(shù)據(jù)太肥通過keys裁剪字段能立竿見影。5.4 分頁時(shí)數(shù)據(jù)重復(fù)或遺漏用limit加skip分頁時(shí)如果查詢的結(jié)果集在翻頁過程中發(fā)生了變化比如新文章插入那后面的記錄位置會(huì)整體偏移導(dǎo)致同一篇內(nèi)容在翻頁時(shí)出現(xiàn)兩次或者被跳過。游標(biāo)式翻頁能很好地解決這個(gè)問題因?yàn)樗蕾嚨氖莄reatedAt的時(shí)間比較不依賴記錄的位置偏移。如果產(chǎn)品形態(tài)是“下拉加載更多”建議直接切換到游標(biāo)方案。寫到最后聊聊這段實(shí)操下來我自己的體會(huì)。BaaS的邊界其實(shí)比很多人想象的大。像Bmob這種平臺(tái)幾萬條數(shù)據(jù)做標(biāo)簽加關(guān)鍵詞的復(fù)合查詢配合索引、字段裁剪、分頁游標(biāo)和簡單緩存完全可以扛住中小型業(yè)務(wù)的搜索壓力。我見過不少項(xiàng)目一上來就準(zhǔn)備自建后端、接ES但實(shí)際上業(yè)務(wù)體量根本到不了那個(gè)級(jí)別屬于把牛刀殺雞的過度設(shè)計(jì)。技術(shù)在項(xiàng)目里永遠(yuǎn)是匹配需求的前期用零服務(wù)器方案把業(yè)務(wù)驗(yàn)證跑通等規(guī)模真正起來了再往專職的搜索架構(gòu)遷移也不遲。至少對我來說這段經(jīng)歷最大的收獲是先讓小成本方案快速跑起來比一開始就追求完美更有效。