優化了破網站的搜索功能

使用 ES + 雲開發實戰優化網站搜索javascript

你們好,我是魚皮,今天搞一場技術實戰,需求分析 => 技術選型 => 設計實現,從 0 到 1,帶你們優化網站搜索的靈活性。前端

ES + 雲開發搜索優化實戰

本文大綱: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 檢索就好了。它提供了必定的免費空間,對於小型網站和學習使用徹底足夠了。

Algolia 檢索服務

選擇

那麼個人編程導航網站選擇哪一種實現方式呢?

首先,該網站的資源數是不固定的、無規律動態更新的,所以不適合前端全文檢索。

其次,考慮到往後網站的數據量會比較大,並且可能要根據用戶的搜索動態地去優化檢索系統(好比自定義編程詞典),所以考慮使用 Elasticsearch 技術 自行搭建搜索引擎,而不用現成的全文檢索服務,這樣從此本身想怎麼定製系統均可以。此外,不用向其餘平臺發送網站數據,能保證數據的安全。

ES 安裝

肯定使用 Elasticsearch 後,要先搭建環境。

能夠本身購買服務器,再按照官方文檔一步步手動安裝。對於有必定規模的我的網站來講,雖然搭建過程不難,但後期的維護成本倒是巨大的,好比性能分析、監控、告警、安全等等,都須要本身來配置。尤爲是後期網站數據量更大了,還要考慮搭建集羣、水平擴容等等。

所以,我選擇直接使用雲服務商提供的 Elasticsearch 服務,這裏選擇騰訊雲,自動爲你搭建了現成的 ES 集羣服務,還提供了可視化架構管理、集羣監控、日誌、高級插件、智能巡檢等功能。

雲 ES 集羣架構圖

雖然 ES 服務的價格貴,但節省下大量時間成本,對我來講是值得的。

還有個很方便的定製化搜索服務 Elastic App Search,你們感興趣能夠試試。

ES 公共服務

咱們的目標是優化網站資源的搜索功能,但接下來要作的不是直接編寫具體的業務邏輯,而是先開發一個 公共的 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 公共服務後,就能夠編寫具體的業務邏輯了。

首先要在 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 就行了。

聽上去挺簡單的,但這種方式存在一些問題:

  1. 會改動之前的代碼,每一個寫數據庫的地方都要補充寫入 ES。
  2. 會存在一邊兒寫入失敗、另外一邊兒成功的狀況,致使數據庫和 ES 的數據不一致。

那有沒有對現有代碼 侵入更小 的方法呢?

定時同步

若是對數據實時性的要求不高,能夠選擇定時同步,每隔一段時間將最新插入或修改的數據從數據庫複製到 ES 上。

實現方式有不少種,好比用 Logstash 數據傳輸管道,或者本身編寫定時任務程序,這樣就徹底不用改現有的代碼。

實時同步

若是對數據實時性要求很高,剛剛插入數據庫的數據就要能馬上就能被搜索到,那麼就要實時同步。除了雙寫外,還能夠監聽數據庫的 binlog,在數據庫發生任何變動時,咱們都能感知到。

阿里有個開源項目叫 Canal ,可以實時監聽 MySQL 數據庫,並推送通知給下游,感興趣的朋友能夠看看。

Canal 項目

實現

因爲編程資源的搜索對實時性要求不高,因此定時同步就 ok。

雲開發默認提供了定時函數功能,我就直接寫一個雲函數,每 1 分鐘執行一次,每次讀取數據庫中近 5 分鐘內發生了變動的數據,以防止上次執行失敗的狀況。此外,還要配置超時時間,防止函數執行時間過長致使的執行失敗。

在雲開發 - 雲函數控制檯就能可視化配置了,須要爲定時任務指定一個 crontab 表達式:

配置雲函數定時和超時

開啓定時同步後,不要忘了再編寫並執行一個 首次 同步函數,用於將歷史的全量數據同步到 ES。

數據檢索

如今 ES 上已經有數據了,只剩最後一步,就是怎麼把數據搜出來呢?

首先咱們要學習 ES 的搜索 DSL(語法),包括如何取列、搜索、過濾、分頁、排序等,對新手來說,仍是有點麻煩的,尤爲是查詢條件中布爾表達式的組合,稍微不注意就查不出數據。因此建議你們先在 Kibana 提供的調試工具中編寫查詢語法:

Kibana 調試

查出預期的數據後,再編寫後端的搜索函數,接受的請求參數最好和原接口保持一致,減小改動。

能夠根據前端傳來的請求動態拼接查詢語法,好比要按照資源名搜索:

// 傳了資源名if (name) { // 拼接查詢語句 query.bool.should = [ { match: { name } } ];}
複製代碼

由此,整個網站的搜索優化完畢。

再去試一下效果,如今哪怕我輸入一些多 「魚」 的詞,也能搜到了!

ES 是怎麼實現靈活搜索的呢?歡迎閱讀 這篇文章

新 ES 搜索接口的發佈並不意味着老的數據庫查詢接口淘汰,能夠同時保留。按名稱搜索資源時用新接口,更靈活;而根據審覈狀態、搜索某用戶發佈過的資源時,能夠用老接口,從數據庫查。從而分攤負載,職責分離,讓對的技術作對的事情!


以上就是本期分享,有幫助的話點個贊吧 ❤️

我是魚皮,最後再送你們一些 幫助我拿到大廠 offer 的學習資料

跑了,留下 6T 的資源!

歡迎閱讀 我從 0 自學進入騰訊的編程學習、求職、考證、寫書經歷,再也不迷茫!

我學計算機的四年,共勉!

相關文章
相關標籤/搜索