使用 ES + 雲開發實戰優化網站搜索javascript
你們好,我是魚皮,今天搞一場技術實戰,需求分析 => 技術選型 => 設計實現,從 0 到 1,帶你們優化網站搜索的靈活性。前端
本文大綱:java
我開發的 編程導航網站 已經上線 6 個月了,可是從上線之初,網站一直存在一個很嚴重的問題,就是搜索功能並很差用。node
此前,爲了追求快速上線,搜索功能就簡單地使用了數據庫模糊查詢(包含)來實現,開發是方便了,但這種方式很不靈活。git
舉個例子,網站上有個資源叫 「Java 設計模式」,而用戶搜索 「Java設計模式」 就啥都搜不出來,緣由是資源名中包含了空格,而用戶搜索時輸入的關鍵詞並不包含空格。程序員
空格只是一種特例,相似的狀況還有不少,好比網站上有個資源叫 「Java 併發編程實戰」,但用戶搜索 「Java 實戰」 時,明明前者包含 「Java」 和 「實戰」 這兩個詞,但倒是什麼都搜不出來的。github
要知道,搜索功能對於一個信息聚合類站點是相當重要的,直接影響用戶的體驗。在你的網站上搜不到資源,誰還會用?shell
因此我也收到了一些小夥伴的禮貌建議,好比這位禿頭 Tom:數據庫
以前沒有優化搜索,主要是兩個緣由:窮 + 怕麻煩。但隨着網站用戶量的增大,是時候填坑了!編程
想要提升網站搜索靈活性,可使用 全文搜索 技術,在前端和後端均可以實現。
有時,咱們要檢索的數據是有限的,且全部數據都是 存儲在客戶端 的。
好比我的博客網站,咱們一般會把每篇文章做爲一個文件存放在某目錄下,而不是存在後臺數據庫中,這種狀況下,不須要再從服務器上去請求動態數據,那麼能夠直接在前端搜索數據。
有一些現成的搜索庫,好比 Lunr.js
(GitHub 7k+ star),先添加要檢索的內容:
var idx = lunr(function () {
this.field('title')
this.field('body')
// 內容
this.add({
"title": "yupi",
"body": "wx搜程序員魚皮,閱讀個人原創文章",
"id": "1"
})
})
複製代碼
而後搜索就能夠了:
idx.search("魚皮")
複製代碼
純前端全文搜索的好處是無需後端、簡單方便,能夠節省服務器的壓力;無需連網,也沒有額外的網絡開銷,檢索更快速。
區別於前端,後端全文搜索在服務器上完成,從遠程數據庫中搜索符合要求的數據,再直接返回給前端。
目前主流的後端全文搜索技術是 Elasticsearch,一個分佈式、RESTful 風格的搜索和數據分析引擎。
它的功能強大且靈活,可是須要本身搭建、定義數據、管理詞典、上傳和維護數據等,可操做性很強,須要一些水平,新手和大佬設計出的 ES 搜索系統那是天差地別。
因此,對於不熟悉 Elasticsearch 的同窗,也能夠直接使用現成的全文檢索服務。好比 Algolia,直接經過它提供的 API 上傳須要檢索的數據,再用它提供的 API 檢索就好了。它提供了必定的免費空間,對於小型網站和學習使用徹底足夠了。
那麼個人編程導航網站選擇哪一種實現方式呢?
首先,該網站的資源數是不固定的、無規律動態更新的,所以不適合前端全文檢索。
其次,考慮到往後網站的數據量會比較大,並且可能要根據用戶的搜索動態地去優化檢索系統(好比自定義編程詞典),所以考慮使用 Elasticsearch 技術 自行搭建搜索引擎,而不用現成的全文檢索服務,這樣從此本身想怎麼定製系統均可以。此外,不用向其餘平臺發送網站數據,能保證數據的安全。
肯定使用 Elasticsearch 後,要先搭建環境。
能夠本身購買服務器,再按照官方文檔一步步手動安裝。對於有必定規模的我的網站來講,雖然搭建過程不難,但後期的維護成本倒是巨大的,好比性能分析、監控、告警、安全等等,都須要本身來配置。尤爲是後期網站數據量更大了,還要考慮搭建集羣、水平擴容等等。
所以,我選擇直接使用雲服務商提供的 Elasticsearch 服務,這裏選擇騰訊雲,自動爲你搭建了現成的 ES 集羣服務,還提供了可視化架構管理、集羣監控、日誌、高級插件、智能巡檢等功能。
雖然 ES 服務的價格貴,但節省下大量時間成本,對我來講是值得的。
還有個很方便的定製化搜索服務 Elastic App Search,你們感興趣能夠試試。
咱們的目標是優化網站資源的搜索功能,但接下來要作的不是直接編寫具體的業務邏輯,而是先開發一個 公共的 ES 服務 。
其實對 ES 的操做比較簡單,能夠先簡單地把它理解爲一個數據庫,那麼公共的 ES 服務應具備基本的增刪改查功能,供其餘函數調用。
因爲編程導航的後端使用的是騰訊雲開發技術,用 Node.js 來編寫服務,因此選用官方推薦的 @elastic/elasticsearch
庫來操做 ES。
沒用過雲開發也沒事,能夠先把它理解爲一個後端,歡迎閱讀我以前的文章:瞭解雲開發 。
代碼很簡單,先是創建和 ES 的鏈接,此處爲了保證數據安全,使用內網地址:
const client = new Client({
// 內網地址
node: 'http://10.0.61.1:9200',
// 用戶名和密碼
auth: {
username: esConfig.username,
password: esConfig.password,
},
});
複製代碼
而後是編寫增刪改查。這裏作一步 抽象,經過 switch
等分支語句,根據請求參數來區分操做、要操做的數據等,這樣就不用把每一個操做都獨立寫成一個接口了。
// 接受請求參數
const { op, index, id, params } = event;
// 根據操做執行增刪改查
switch (op) {
case 'add':
return doAdd(index, id, params);
case 'delete':
return doDelete(index, id);
case 'search':
return doSearch(index, params);
case 'update':
return doUpdate(index, id, params);
}
複製代碼
在雲開發中,假如某個函數過久沒被調用,就會釋放資源。下次請求時,會進行冷啓動,從新建立資源,致使接口返回較慢。所以,把多個操做封裝到同一個函數中,也能夠減小冷啓動的概率。
具體的增刪改查代碼就不贅述了,對着 ES Node 的官方文檔看一遍就好了,後面會把代碼開源到編程導航倉庫中(github.com/liyupi/code…
編寫好代碼後,能夠用雲開發自帶的 tcb
命令行工具在本地執行該函數。
記得先把 ES 的鏈接地址改爲公網,而後輸入一行命令就好了。好比咱們要向 ES 插入一條數據,傳入要執行的函數名、請求參數、代碼路徑:
tcb fn run
--name <functionName>
--params "{\"op\": \"add\"}"
--path <functionPath>
複製代碼
執行成功後,就能在 ES 中看到新插入的數據了(經過 Kibana 面板或 curl 查看):
本地測試好公共服務代碼後,把 ES 鏈接地址改爲內網 IP,而後發佈到雲端。
接下來試着編寫一個其餘的函數來訪問公共 ES 服務,好比插入資源到 ES,經過 callFunction
請求:
// 添加資源到 ES
function addData() {
// 請求公共服務
app.callFunction({
name: 'esService',
data: {
op: 'add',
index: 'resource',
id,
params: data,
}
});
}
複製代碼
可是,數據並無被成功插入,而是返回了接口超時,Why?
經過日誌得知是 ES 鏈接不上,會不會是由於發佈上線的 ES 公共服務所在的機器和 ES 不在同一個內網呢?
因此須要在雲開發控制檯更改 ES 公共服務的私有網絡配置,選擇和購買 ES 時一樣的子網就好了:
修改以後,再次遠程請求 ES 公共服務,數據就插入成功了~
開發好 ES 公共服務後,就能夠編寫具體的業務邏輯了。
首先要在 ES 中創建一個索引(相似數據庫的表),來約定數據的類型、分詞等信息,而不是容許隨意插入數據。
好比爲了更靈活搜索,資源名應該指定爲 "text" 類型,以開啓分詞,並指定 ik
中文分詞器:
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
複製代碼
而點贊數應設置爲 "long" 類型,只容許傳入數字:
"likeNum": {
"type": "long"
}
複製代碼
最好還要爲索引指定一個別名,便於後續修改字段時重建索引:
"aliases" : { "resource": {}}
複製代碼
編寫好創建索引的 json 配置後,經過 curl 或 Kibana 去調用 ES 新建索引接口就好了。
以前,編程導航網站的資源數據都是存在數據庫中的,用戶從數據庫中查詢。而如今要改成從 ES 中查詢,ES 空空如也可不行,得想辦法把數據庫中的資源數據同步到 ES 中。
這裏有幾種同步策略。
之前,用戶推薦的資源只會插入到數據庫,雙寫是指在資源插入數據庫的時候,同時插入到 ES 就行了。
聽上去挺簡單的,但這種方式存在一些問題:
那有沒有對現有代碼 侵入更小 的方法呢?
若是對數據實時性的要求不高,能夠選擇定時同步,每隔一段時間將最新插入或修改的數據從數據庫複製到 ES 上。
實現方式有不少種,好比用 Logstash
數據傳輸管道,或者本身編寫定時任務程序,這樣就徹底不用改現有的代碼。
若是對數據實時性要求很高,剛剛插入數據庫的數據就要能馬上就能被搜索到,那麼就要實時同步。除了雙寫外,還能夠監聽數據庫的 binlog,在數據庫發生任何變動時,咱們都能感知到。
阿里有個開源項目叫 Canal
,可以實時監聽 MySQL 數據庫,並推送通知給下游,感興趣的朋友能夠看看。
因爲編程資源的搜索對實時性要求不高,因此定時同步就 ok。
雲開發默認提供了定時函數功能,我就直接寫一個雲函數,每 1 分鐘執行一次,每次讀取數據庫中近 5 分鐘內發生了變動的數據,以防止上次執行失敗的狀況。此外,還要配置超時時間,防止函數執行時間過長致使的執行失敗。
在雲開發 - 雲函數控制檯就能可視化配置了,須要爲定時任務指定一個 crontab 表達式:
開啓定時同步後,不要忘了再編寫並執行一個 首次 同步函數,用於將歷史的全量數據同步到 ES。
如今 ES 上已經有數據了,只剩最後一步,就是怎麼把數據搜出來呢?
首先咱們要學習 ES 的搜索 DSL(語法),包括如何取列、搜索、過濾、分頁、排序等,對新手來說,仍是有點麻煩的,尤爲是查詢條件中布爾表達式的組合,稍微不注意就查不出數據。因此建議你們先在 Kibana 提供的調試工具中編寫查詢語法:
查出預期的數據後,再編寫後端的搜索函數,接受的請求參數最好和原接口保持一致,減小改動。
能夠根據前端傳來的請求動態拼接查詢語法,好比要按照資源名搜索:
// 傳了資源名if (name) { // 拼接查詢語句 query.bool.should = [ { match: { name } } ];}
複製代碼
由此,整個網站的搜索優化完畢。
再去試一下效果,如今哪怕我輸入一些多 「魚」 的詞,也能搜到了!
ES 是怎麼實現靈活搜索的呢?歡迎閱讀 這篇文章 。
新 ES 搜索接口的發佈並不意味着老的數據庫查詢接口淘汰,能夠同時保留。按名稱搜索資源時用新接口,更靈活;而根據審覈狀態、搜索某用戶發佈過的資源時,能夠用老接口,從數據庫查。從而分攤負載,職責分離,讓對的技術作對的事情!
以上就是本期分享,有幫助的話點個贊吧 ❤️
我是魚皮,最後再送你們一些 幫助我拿到大廠 offer 的學習資料:
歡迎閱讀 我從 0 自學進入騰訊的編程學習、求職、考證、寫書經歷,再也不迷茫!