從零開始SpringCloud Alibaba電商系統(十四)——簡單商品模塊需求、使用ElasticSearch構建商品搜索

零、系列

歡迎來嫖從零開始SpringCloud Alibaba電商系列:

  1. 從零開始SpringCloud Alibaba電商系統(一)——Alibaba與Nacos服務註冊與發現
  2. 從零開始SpringCloud Alibaba電商系統(二)——Nacos配置中心
  3. 從零開始SpringCloud Alibaba電商系統(三)——Sentinel流量防衛兵介紹、流量控制demo
  4. 從零開始SpringCloud Alibaba電商系統(四)——Sentinel的fallback和blockHandler
  5. 從零開始SpringCloud Alibaba電商系統(五)——Feign Demo,Sentinel+Feign實現多節點間熔斷/服務降級
  6. 從零開始SpringCloud Alibaba電商系統(六)——Sentinel規則持久化到Nacos配置中心
  7. 從零開始SpringCloud Alibaba電商系統(七)——Spring Security實現登錄認證、權限控制
  8. 從零開始SpringCloud Alibaba電商系統(八)——用一個好看的Swagger接口文檔
  9. 從零開始SpringCloud Alibaba電商系統(九)——基於Spring Security OAuth2實現SSO-認證服務器(非JWT)
  10. 從零開始SpringCloud Alibaba電商系統(十)——基於Redis Session的認證鑑權
  11. 從零開始SpringCloud Alibaba電商系統(十一)——spring security完善之動態url控制
  12. 從零開始SpringCloud Alibaba電商系統(十二)——spring aop記錄用戶操作日誌
  13. 從零開始SpringCloud Alibaba電商系統(十三)——ElasticSearch介紹、logback寫入ES

一、需求簡述

前文我們介紹瞭如何使用logback+ElasticSearch+kibana實現日誌收集,今天我們繼續在業務和技術的角度深入商品模塊和ElasticSearch。

(一) 商品模塊

電商系統離不開商品,而我們這裏的商品也就是大家常說的spu的概念,是商家可以上下架、用戶可以搜索並且點開查看詳情的那個東西。

  1. 功能接口。
    • 管理員(商家)上傳商品。
    • 管理員(商家)上/下架商品。
    • 管理員(商家)編輯商品。
    • 管理員(商家)刪除商品。
    • 用戶 關鍵字搜索/條件排序 商品。
    • 用戶查看商品分類。
    • 用戶查看商品。
      暫時不包含購物車、訂單相關功能。
      上述功能,有一點非常重要,那就是用戶查看/搜索商品的體驗感,在一定量級的情況下,如何做到豐富的搜索並且近實時的速度是我們的重點。
  2. 表結構。
    由於我們是 廠家直銷類電商,所以商品分類劃分很粗糙,主要維護商品的信息,商品的規格及價格信息(即sku),具體表結構及關係如下圖(結構sql在demo的flywaydb配置中):

在這裏插入圖片描述

(二) 結合ElasticSearch

ElasticSearch可以很好的幫助我們實現複雜的且近實時的搜索,這點在前文已經提到了。那麼ElasticSearch究竟是如何使用或者說搜索的呢?和我們在mysql中直接 where filed={value}是一樣的嗎?
ElasticSearch的搜索大致分爲兩種:

  1. 傳統匹配(filtered)。
    這是我個人的分法,覺得比較好理解。所謂的傳統匹配就是類似sql中where的查詢過濾,可以進行精準等值查詢模糊匹配/通配符查詢,範圍查詢,null值查詢等等。這就是單純的查詢、過濾,可以多層疊加條件,但原理都是一樣的。
    這種傳統的匹配/排序方式可以用來做商品的分類搜索、各種排序的結果展示,es的索引查詢更快,爲什麼倒排索引就更快詳見第二部分:倒排索引。

  2. 相似度匹配。
    相似度匹配是ES的一大亮點,當我們將一條文檔存入es的時候,es大概會做下面這些事情。
    tf-idf簡述可見文本類型處理-詞袋法、TF-IDF理解

    a. es會對這條`文檔`數據中爲`text`類型的字段進行分詞。
    b. 分詞後的數據會計算tf-idf值並保存起來。
    c. 當我們要查詢的時候,我們的查詢條件會被計算一個tf-idf值,然後用這個值來找最近似的數據,並給這些數據都打分。
    當然es必然還有一些優化,這裏的介紹只是爲了理解es能做什麼。
    

我們會發現,對於text類型的字段,我們可以搜索它的近語義數據,這個能力十分貼近商品的文本框搜索功能。

二、倒排索引

說倒排索引之前,我們先回顧一下mysql innodb的索引結構。

mysql索引

mysql索引基於B+樹,簡單來說,是一個多叉樹,且只有葉子節點存儲數據,中間節點只存儲子節點指針。
這種多叉索引樹的構建相對直接掃描原數數據來說可以提升一定的搜索速度,同時也兼顧了寫入的速度,多叉樹的數據寫入可以直接通過節點裂變來實現。
而es沒有寫入的顧慮,它就是衝着搜索來的。

es索引

所以es很直接,我需要A字段的索引,那就把A字段所有數據拿出來,和文檔指針一起保存成一個字典,我要通過A字段查詢直接在這個字典裏面找到對應的值,然後就拿到了文檔指針,有了指針,就能直接拿到文檔整體數據。
這就是es的搜索方式,這種方式顯然不太適合頻繁的修改,尤其是在索引多的情況下,修改或新增一條數據可能意味着對n個文件進行調整。
倒排,理解了嗎?
所謂的倒排,就是又原本的 id->其他字段 的映射方式變成了 其他字段->(id)數據 。這就是倒排。

三、代碼實現

本次demo實現使用了基於common的product項目,不再需要配置es依賴等東西,不明白怎麼配置spring-data-elasticsearch的同學可見前文
本次主要實現創建商品、查詢商品這兩個功能

  1. 生成表,相關實體類、dao、service等,可見最後demo。

  2. 創建商品的邏輯不再贅述,主要點在於商品數據入數據庫之後,還要入一次es。
    筆者認爲這裏用消息隊列來解耦這兩次寫庫操作比較好一些,以後聊到MQ再說。

     按照需求,商家在創建商品時,需要填寫商品信息以及多個商品的規格信息。
     下面貼一段該接口的swagger json測試數據,可做了解。
    
{
    "description": "i222222phone xr描述,這是一個抗揍的手機",
    "keywords": "222222iphone xr,氪金系列,霸氣側漏,漏了",
    "name": "2222iphone xr",
    "picUrls": "",
    "price": 5000.0,
    "productClassId": 1265305040261787650,
    "productClassName": "手機",
    "specs": [
        {
            "price": 51000.0,
            "productSpecs": "{\"顏色\":\"紅色\"}",
            "stock": 12
        },
        {
            
            "price": 52000.0,
            "productSpecs": "{\"顏色\":\"黑色\"}",
            "stock": 12
        },
        {
            
            "price": 53000.0,
            "productSpecs": "{\"顏色\":\"深藍色\"}",
            "stock": 15
        }
    ]
}

大致邏輯如下,代碼實現全部放在demo。
在這裏插入圖片描述
新增數據後可以直接查看es數據。
這裏沒什麼難點,但是不熟悉es或nosql的同學可能對於es的搜索api、新增api不熟悉,更不熟悉spring-ElasticSearch的調用方法,這個還是多看官方API並調試回來的快。
https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html在這裏插入圖片描述
3.商品搜索。
商品搜索主要涉及四個點:按照名稱/分類(精確搜索)、搜索框搜索(語義匹配搜索,這裏使用keywords關鍵字代表商戶填寫的關鍵字描述)、分頁、排序。
這裏只使用DSL(結構體查詢),簡易查詢功能太弱。

  • 精確搜索
    es的精確搜索語法如下:
精確搜索name值爲'iphone xr'的數據,name.keyword的keyword是name字段的第二類型,有疑問可見前文。
{
  "query": {
  	"bool":{
  		"must" :[{"term":{"name.keyword":"iphone xr"} }  ]
  	}
  }
}

在 spring-data-elasticsearch中可以使用NativeSearchQueryBuilder+QueryBuilders來實現上述語法:

NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.termQuery("name.keyword", request.getName()));}
queryBuilder.withQuery( boolQueryBuilder )
// 使用es repository執行query
Object o = productRepository.search( queryBuilder.build());
  • 語義匹配
查詢keywords字段值語義可匹配'氪金',且name字段爲'iphone xr'的數據。
{
  "query": {
  	"bool":{
  		"must" :[{"match": { "keywords":"氪金 " } },{"term":{"name.keyword":"iphone xr"} }  ]
  	}
  }
}
// 這裏就貼一行,可以配合上一個例子來完整使用
boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.matchQuery("keywords", request.getKeywords()));}
  • 排序
在上述查詢基礎上,增加排序:使用name字段排序
{
  "query": {
  	"bool":{
  		"must" :[{"match": { "keywords":"氪金 " } },{"term":{"name.keyword":"iphone xr"} }  ]
  	}
  },
  "sort":"name.keyword"
}
queryBuilder.withSort( SortBuilders.fieldSort("name.keyword").order(SortOrder.ASC) );}
  • 分頁
從下標爲0的數據開始,一共要十條
{
  "query": {
  	"bool":{
  		"must" :[{"match": { "keywords":"氪金 " } },{"term":{"name.keyword":"iphone xr"} }  ]
  	}
  },
  "sort":"name.keyword",
  "size": 10,
  "from": 0
}

在 spring-data-elasticsearch中需要在repository.search時增加分頁參數:

Object o = productRepository.search( queryBuilder.build().getQuery(),PageRequest.of(0,10));

有同學可能會發現這裏search方法第一個參數變成了queryBuilder.build().getQuery(),這樣獲取到的是NativeSearchQueryBuilder的接口類QueryBuilder,效果是一樣的。

上面是一些操作也是項目demo中的實際邏輯,截個圖可以清楚的看明白,有興趣的同學獲取下方demo。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

四、demo地址

本節內容實操性強,建議多操兩遍api和代碼。
本demo結構包含authorization認證中心,需要先啓動認證中心才能正常登陸到Product模塊。
user數據可在user模塊的flyway中去找。
https://github.com/flyChineseBoy/lel-mall/tree/master/mall14

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章