Elasticsearch
什么是Elasticsearch?
Elasticsearch是一個(gè)分布式文檔存儲(chǔ)。Elasticsearch存儲(chǔ)的是序列化為JSON文檔的復(fù)雜數(shù)據(jù)結(jié)構(gòu),而不是以行列數(shù)據(jù)的形式存儲(chǔ)的信息。當(dāng)集群中有多個(gè)Elasticsearch節(jié)點(diǎn)時(shí),存儲(chǔ)的文檔分布在整個(gè)集群中,可以立即從任何節(jié)點(diǎn)訪問。
當(dāng)存儲(chǔ)文檔時(shí),它幾乎是實(shí)時(shí)的——在1秒內(nèi)就可以被索引和完全搜索。Elasticsearch使用一種名為倒排索引的數(shù)據(jù)結(jié)構(gòu),它支持非??焖俚娜乃阉?。倒排索引列出任何文檔中出現(xiàn)的每個(gè)唯一單詞,并標(biāo)識(shí)每個(gè)單詞出現(xiàn)的所有文檔。
可以將索引看作是文檔的優(yōu)化集合,每個(gè)文檔都是一個(gè)字段的集合,這些字段是包含數(shù)據(jù)的鍵值對(duì)。默認(rèn)情況下,Elasticsearch對(duì)每個(gè)字段中的所有數(shù)據(jù)進(jìn)行索引,每個(gè)索引字段都有一個(gè)專用的、優(yōu)化的數(shù)據(jù)結(jié)構(gòu)。例如:文本字段存儲(chǔ)在倒排索引中,數(shù)字和地理字段存儲(chǔ)在BKD樹中。使用每個(gè)字段的數(shù)據(jù)結(jié)構(gòu)來組裝和返回搜索結(jié)果的能力是Elasticsearch如此快速的原因。
Elasticsearch還具有無模式的能力,這意味著可以在不顯示指定如何處理文檔中可能出現(xiàn)的每個(gè)不同字段的情況下,對(duì)文檔進(jìn)行索引。當(dāng)啟用動(dòng)態(tài)映射時(shí),Elasticsearch會(huì)自動(dòng)檢測(cè)并添加新的字段到索引中。這種默認(rèn)行為使得創(chuàng)建索引和瀏覽數(shù)據(jù)變得很容易——只要開始創(chuàng)建索引文檔,Elasticsearch就會(huì)檢測(cè)布爾值、浮點(diǎn)值和整數(shù)值、日期和字符串,并將其映射到適當(dāng)?shù)腅lasticsearch數(shù)據(jù)類型。
下載安裝 Elasticsearch
下載地址:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-17-7
官網(wǎng)地址:https://www.elastic.co/cn/elasticsearch/
以Windows為例:
下載后,解壓縮,雙擊bin目錄下的elasticsearch.bat
,打開即可
啟動(dòng)后,可能會(huì)發(fā)現(xiàn)一個(gè)警告,它告訴我們,需要配置一個(gè)JAVA_HOME
或者使用ES_JAVA_HOME
且建議我們最好使用Java 11,如果版本過低,可能有的功能不支持。(為什么需要配置Java環(huán)境變量?因?yàn)镋Lasticsearch是使用Java開發(fā)的。)
- 如何解決JDK版本支持問題?
我們自己開發(fā)可能使用的是JDK8或者其它版本,要想解決JDK版本問題,但又不想更改自己已配置的Java環(huán)境。我們就可以使用es自帶的jdk,在環(huán)境變量中添加以下變量:(Elasticsearch解壓包下的 jdk 目錄)
關(guān)閉之前啟動(dòng)的,重新啟動(dòng)elasticsearch.bat
即可,就沒有警告了,如下圖所示:
瀏覽器訪問:http://localhost:9200
至此Elasticsearch下載安裝完畢!
下載安裝可視化工具
下載地址:https://www.elastic.co/cn/downloads/past-releases/kibana-7-17-7
下載解壓即可
雙擊kibana.bat
即可打開
瀏覽器訪問:http://localhost:5601
ES集成整合IK分詞器
下載地址:https://github.com/medcl/elasticsearch-analysis-ik
- 可選方式1:下載與之對(duì)應(yīng)的版本:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.17.7
- 在 es 的目錄下的 plugins 目錄創(chuàng)建一個(gè)ik目錄
cd your-es-root/plugins/ && mkdir ik
解壓插件到當(dāng)前目錄中your-es-root/plugins/ik
- 在 es 的目錄下的 plugins 目錄創(chuàng)建一個(gè)ik目錄
- 可選方式2:使用插件的方式安裝:
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip
- 然后重啟es即可
示例:
創(chuàng)建索引
PUT index
創(chuàng)建映射
POST /index/_mapping
{
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
索引一些文檔
POST /index/_create/1
{"content":"美國(guó)留給伊拉克的是個(gè)爛攤子嗎"}
POST /index/_create/2
{"content":"公安部:各地校車將享最高路權(quán)"}
POST /index/_create/3
{"content":"中韓漁警沖突調(diào)查:韓警平均每天扣1艘中國(guó)漁船"}
POST /index/_create/4
{"content":"中國(guó)駐洛杉磯領(lǐng)事館遭亞裔男子槍擊 嫌犯已自首"}
帶突出顯示的查詢
POST /index/_search
{
"query" : { "match" : { "content" : "中國(guó)" }},
"highlight" : {
"pre_tags" : ["<tag1>", "<tag2>"],
"post_tags" : ["</tag1>", "</tag2>"],
"fields" : {
"content" : {}
}
}
}
查詢結(jié)果
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.642793,
"hits" : [
{
"_index" : "index",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.642793,
"_source" : {
"content" : "中韓漁警沖突調(diào)查:韓警平均每天扣1艘中國(guó)漁船"
},
"highlight" : {
"content" : [
"中韓漁警沖突調(diào)查:韓警平均每天扣1艘<tag1>中國(guó)</tag1>漁船"
]
}
},
{
"_index" : "index",
"_type" : "_doc",
"_id" : "4",
"_score" : 0.642793,
"_source" : {
"content" : "中國(guó)駐洛杉磯領(lǐng)事館遭亞裔男子槍擊 嫌犯已自首"
},
"highlight" : {
"content" : [
"<tag1>中國(guó)</tag1>駐洛杉磯領(lǐng)事館遭亞裔男子槍擊 嫌犯已自首"
]
}
}
]
}
}
字典配置
配置文件在 [你的Elasticsearch目錄]/plugins/ik/config
中的IKAnalyzer.cfg.xml
IKAnalyzer.cfg.xml
的具體內(nèi)容如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴(kuò)展配置</comment>
<!--用戶可以在這里配置自己的擴(kuò)展字典 -->
<entry key="ext_dict"></entry>
<!--用戶可以在這里配置自己的擴(kuò)展停止詞字典-->
<entry key="ext_stopwords"></entry>
<!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展停止詞字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
當(dāng)ik分詞器提供的分詞不能滿足我們的實(shí)際使用時(shí),我們可以在上面所說的字典配置文件(IKAnalyzer.cfg.xml
)中擴(kuò)展自己的字典
具體方式如下所示:
- 創(chuàng)建一個(gè)自己的字典文件,如下圖所示,我創(chuàng)建了一個(gè)新的
my_dic.dic
文件用于記錄自己擴(kuò)展的詞
我在my_dic.dic
中寫入了一個(gè)詞 - 在
IKAnalyzer.cfg.xml
中配置自己的字典my_dic.dic
- 重啟es,即可生效
通過以下所示,可以看到自定義的字典生效了:
熱更新 ik 分詞
目前該插件支持熱更新 IK 分詞,通過上文在 IK 配置文件中提到的如下配置
<!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展字典 -->
<entry key="remote_ext_dict">location</entry>
<!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展停止詞字典-->
<entry key="remote_ext_stopwords">location</entry>
其中location
是指一個(gè) url,比如http://yoursite.com/getCustomDict
,該請(qǐng)求只需滿足以下兩點(diǎn)即可完成分詞熱更新。
1、該 http 請(qǐng)求需要返回兩個(gè)頭部(header),一個(gè)是Last-Modified
,一個(gè)是ETag
,這兩者都是字符串類型,只要有一個(gè)發(fā)生變化,該插件就會(huì)去抓取新的分詞進(jìn)而更新詞庫。
2、該 http 請(qǐng)求返回的內(nèi)容格式是一行一個(gè)分詞,換行符用\n
即可。
滿足上面兩點(diǎn)要求就可以實(shí)現(xiàn)熱更新分詞了,不需要重啟 ES 實(shí)例。
可以將需自動(dòng)更新的熱詞放在一個(gè) UTF-8 編碼的 .txt
文件里,放在 nginx 或其他簡(jiǎn)易 http server 下,當(dāng) .txt
文件修改時(shí),http server 會(huì)在客戶端請(qǐng)求該文件時(shí)自動(dòng)返回相應(yīng)的 Last-Modified 和 ETag??梢粤硗庾鲆粋€(gè)工具來從業(yè)務(wù)系統(tǒng)提取相關(guān)詞匯,并更新這個(gè) .txt
文件。
Go集成ES
下載 es 組件
go get github.com/olivere/elastic/v7
Go操作es
package es
import (
"errors"
"github.com/olivere/elastic/v7"
"log"
"os"
"time"
)
type EsService struct{}
// CreateClient 創(chuàng)建一個(gè)客戶端
func (es *EsService) CreateClient() (client *elastic.Client, err error) {
// 1.創(chuàng)建客戶端
client, err = elastic.NewClient(
elastic.SetURL("http://127.0.0.1:9200"),
elastic.SetGzip(true),
elastic.SetHealthcheck(true),
elastic.SetHealthcheckTimeout(10*time.Second),
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC", log.LstdFlags)), // 設(shè)置日志輸出的名字
elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)), // 輸出日志級(jí)別
)
if err != nil {
err = errors.New("連接es失敗")
}
return
}
package main
import (
"fmt"
"go-elasticsearch/es"
)
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println(err)
} else {
fmt.Println("連接成功: ", client)
}
}
創(chuàng)建索引映射Mapping
package es
import (
"context"
"errors"
)
const IndexName = "user_info"
// mapping其實(shí)就是一種描述索引庫字段類型的一種形式
// es 存儲(chǔ)的數(shù)據(jù)的形態(tài)都是json。包括mapping字段描述也是json方式
// "analyzer":"ik_max_word" 如果這樣寫,es必須安裝ik分詞器,否則會(huì)報(bào)。此含義是未來如果搜索數(shù)據(jù)索引化的時(shí)候,它會(huì)把你的標(biāo)題進(jìn)行分詞和你的文檔進(jìn)行關(guān)聯(lián)
var mapping = `
{
"mappings":{
"properties":{
"userId":{
"type":"integer"
},
"userName":{
"type":"text",
"analyzer":"ik_max_word"
},
"userAge":{
"type":"integer"
},
"userBio":{
"type":"text",
"analyzer":"ik_max_word"
}
}
}
}
`
// CreateIndex 創(chuàng)建索引庫
func (es *EsService) CreateIndex() (bool, error) {
// 創(chuàng)建client
client, _ := es.CreateClient()
// 創(chuàng)建一個(gè)上下文
ctx := context.Background()
exists, err := client.IndexExists(IndexName).Do(ctx)
if err != nil {
return false, err
}
if !exists {
do, err := client.CreateIndex(IndexName).Body(mapping).Do(ctx)
if err != nil {
return false, err
}
return do.Acknowledged, nil
}
return false, errors.New("索引庫已存在")
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println(err)
} else {
fmt.Println("連接成功: ", client)
}
index, _ := service.CreateIndex()
fmt.Println("===>", index)
}
創(chuàng)建文檔
package es
import (
"context"
"fmt"
)
func (es *EsService) Save() (bool, error) {
// 創(chuàng)建一個(gè)對(duì)象信息
user := User{
UserId: 1,
UserName: "lhail",
UserAge: 3,
UserBio: "這是一個(gè)簡(jiǎn)介",
}
// 創(chuàng)建Client
client, _ := es.CreateClient()
// 創(chuàng)建一個(gè)上下文對(duì)象
ctx := context.Background()
put, err := client.Index().
Index(IndexName). // 設(shè)置索引名稱
Id("1"). // 設(shè)置文檔ID
BodyJson(user). // 指定前面聲明的對(duì)象信息
Do(ctx) // 執(zhí)行請(qǐng)求,需要傳入一個(gè)上下文對(duì)象
if err != nil {
panic(err)
}
fmt.Printf("文檔Id %s, 索引名 %s\n", put.Id, put.Index)
return true, nil
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
index, _ := service.CreateIndex()
fmt.Println("index==>", index)
save, _ := service.Save()
fmt.Println("save==>", save)
}
更新文檔
- 根據(jù)ID更新文檔
package es
import (
"context"
"fmt"
)
func (es *EsService) UpdateById(id string, age int) (bool, error) {
// 創(chuàng)建Client
client, _ := es.CreateClient()
// 創(chuàng)建一個(gè)上下文對(duì)象
ctx := context.Background()
put, err := client.Update().
Index(IndexName).
Id(id).
Doc(map[string]interface{}{"userAge": age}).Do(ctx)
if err != nil {
panic(err)
}
fmt.Printf("文檔Id %s, 索引名 %s\n", put.Id, put.Index)
return true, nil
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 創(chuàng)建索引庫
index, _ := service.CreateIndex()
fmt.Println("index==>", index)
// 給索引庫添加文檔數(shù)據(jù)
//save, _ := service.Save()
//fmt.Println("save==>", save)
// 根據(jù)Id更新索引庫文檔數(shù)據(jù)
updateById, _ := service.UpdateById("1", 2)
fmt.Println("updateById==>", updateById)
}
- 根據(jù)查詢條件更新文檔
// UpdateQuery 根據(jù)查詢條件更新文檔
func (es *EsService) UpdateQuery(uid int, age int) (bool, error) {
// 創(chuàng)建Client
client, _ := es.CreateClient()
// 創(chuàng)建一個(gè)上下文對(duì)象
ctx := context.Background()
do, err := client.UpdateByQuery(IndexName).
Query(elastic.NewTermQuery("userId", uid)).
Script(elastic.NewScript("ctx._source['userAge'] = " + strconv.Itoa(age))).
ProceedOnVersionConflict().Do(ctx)
if err != nil {
panic(err)
}
fmt.Printf("文檔一共更新了 %d\n", do.Total)
return true, nil
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 創(chuàng)建索引庫
//index, _ := service.CreateIndex()
//fmt.Println("index==>", index)
// 根據(jù)查詢條件更新文檔
updateQuery, _ := service.UpdateQuery(1, 6)
fmt.Println(updateQuery)
}
刪除文檔
- 根據(jù)ID刪除文檔
func (es *EsService) DeleteById(id string) (bool, error) {
// 創(chuàng)建Client
client, _ := es.CreateClient()
// 創(chuàng)建一個(gè)上下文對(duì)象
ctx := context.Background()
put, err := client.Delete().
Index(IndexName).
Id(id).
Do(ctx)
if err != nil {
panic(err)
}
fmt.Printf("文檔Id %s, 索引名 %s\n", put.Id, put.Index)
return true, nil
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 根據(jù)Id刪除文檔
id, _ := service.DeleteById("1")
fmt.Println(id)
}
- 根據(jù)查詢條件刪除文檔
func (es *EsService) DeleteQuery(uid int) (bool, error) {
// 創(chuàng)建Client
client, _ := es.CreateClient()
// 創(chuàng)建一個(gè)上下文對(duì)象
ctx := context.Background()
do, err := client.DeleteByQuery(IndexName).
Query(elastic.NewTermQuery("userId", uid)).
Do(ctx)
if err != nil {
panic(err)
}
fmt.Printf("文檔一共刪除了 %d\n", do.Total)
return true, nil
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 根據(jù)查詢條件刪除文檔
deleteQuery, _ := service.DeleteQuery(1)
fmt.Println(deleteQuery)
}
查詢文檔
- 根據(jù)ID查詢文檔
func (es *EsService) Get(docId string) (user User) {
client, _ := es.CreateClient()
// 執(zhí)行es請(qǐng)求需要提供一個(gè)上下文對(duì)象
ctx := context.Background()
// 根據(jù)ID查詢文檔
getResult, err := client.Get().
Index(IndexName).
Id(docId).
Do(ctx)
if err != nil {
panic(err)
}
if getResult.Found {
fmt.Printf("文檔id=%s 版本號(hào)=%d 索引名=%s\n", getResult.Id, getResult.Version, getResult.Index)
}
// 提取文檔內(nèi)容,原始類型是json數(shù)據(jù)
data, _ := getResult.Source.MarshalJSON()
// 將json轉(zhuǎn)為struct結(jié)果
json.Unmarshal(data, &user)
// 返回結(jié)果
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 根據(jù)文檔Id查詢文檔
user := service.Get("1")
fmt.Println(user)
}
高級(jí)查詢
精確匹配單個(gè)字段
func (es *EsService) SearchByTerm(uid string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewTermQuery("userId", uid)).
Sort("userAge", false). // 設(shè)置排序字段 false表示升序
From(0). // 設(shè)置分頁參數(shù) - 起始偏移量,從第0行記錄開始
Size(10). // 設(shè)置分頁參數(shù) - 每頁大小
Pretty(true). // 查詢結(jié)果返回可讀性較好的JSON格式
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 根據(jù)詞搜索文檔
users := service.SearchByTerm("1")
fmt.Println(users)
}
通過terms實(shí)現(xiàn)SQL的in查詢
func (es *EsService) SearchIn(uids ...interface{}) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewTermsQuery("userId", uids...)).
Sort("userAge", true). // 排序 第二個(gè)字段若為false表示逆序
From(0).
Size(10).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// terms實(shí)現(xiàn)SQL的in查詢
users := service.SearchIn("1", "2", "3", "4")
fmt.Println(users)
}
匹配單個(gè)字段
func (es *EsService) SearchOne(name string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewMatchQuery("userName", name)).
Sort("userAge", true). // 排序 第二個(gè)字段若為false表示逆序
From(0).
Size(10).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 匹配單個(gè)字段
users := service.SearchOne("lhail")
fmt.Println(users)
}
范圍查詢
func (es *EsService) SearchRangeQuery(start, end int) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
// Gte Lte: userAge >= start and userAge <= end
// Gt Lt: userAge > start and userAge < end
result, err := client.Search(IndexName).
Query(elastic.NewRangeQuery("userAge").Gte(start).Lte(end)).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 范圍查詢
users := service.SearchRangeQuery(2, 6)
fmt.Println(users)
}
根據(jù)語義查詢
func (es *EsService) SearchByMatch(str string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewMatchQuery("userBio", str)).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 語義查詢
users := service.SearchByMatch("我需要找個(gè)人簡(jiǎn)介")
fmt.Println(users)
}
根據(jù)短語搜索
func (es *EsService) SearchByMatchPhrase(field, keyword string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewMatchPhraseQuery(field, keyword)).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 短語搜索
users := service.SearchByMatchPhrase("userBio", "這是簡(jiǎn)介")
fmt.Println(users)
}
前綴搜索
func (es *EsService) SearchByPrefixQuery(field, keyword string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewPrefixQuery(field, keyword)).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
模糊查詢(通配符)
- 查詢包含通配符表達(dá)式字段的文檔(等價(jià)于 MySQL 的 like 查詢)
- 通配符 * :匹配任何字符序列(包括空字符)
- 占位符 ? :匹配任何單個(gè)字符
func (es *EsService) SearchByWildcardQuery(field, keyword string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewWildcardQuery(field, keyword)).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 模糊查詢
users1 := service.SearchByWildcardQuery("userName", "l?ail")
users2 := service.SearchByWildcardQuery("userName", "lh*")
fmt.Println(users1)
fmt.Println(users2)
}
模糊查詢(fuzzy)
fuzzy
這是一種拼寫錯(cuò)誤時(shí)模糊搜索技術(shù)。如:輸入 “l(fā)ahil” 此時(shí)也須匹配到 “l(fā)hail”
func (es *EsService) SearchByFuzzyQuery(field, keyword string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewFuzzyQuery(field, keyword)).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
多字段搜索
func (es *EsService) SearchByMultiMatchQuery(keyword string, fields ...string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
result, err := client.Search(IndexName).
Query(elastic.NewMultiMatchQuery(keyword, fields...)).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
func main() {
service := es.EsService{}
client, err := service.CreateClient()
if err != nil {
fmt.Println("err: ", err)
} else {
fmt.Println("連接成功: ", client)
}
// 多字段搜索
users := service.SearchByMultiMatchQuery("gin go", "userName", "userBio")
fmt.Println(users)
}
bool組合查詢
bool組合查詢,類似SQL語句的 and 和 or 將查詢條件組合起來
- must 條件
- 類似SQL的 and,代表必須匹配的條件
- must_not 條件
- 與 must 作用相反,用法相似
- should 條件
- 類似SQL的 or,只需匹配其中一個(gè)條件即可
func (es *EsService) SearchByBoolQuery() (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
idTermQuery := elastic.NewTermQuery("userId", 1)
ageTermQuery := elastic.NewTermQuery("userAge", 6)
bioMatchQuery := elastic.NewMatchQuery("userBio", "go")
boolQuery := elastic.NewBoolQuery().Must(idTermQuery, ageTermQuery).Should(bioMatchQuery)
result, err := client.Search(IndexName).
Query(boolQuery).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for _, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
users = append(users, t)
}
}
}
return
}
搜索詞條高亮處理
func (es *EsService) SearchByMatchHigh(keyword string, fields ...string) (users []User) {
client, _ := es.CreateClient()
ctx := context.Background()
hl := elastic.NewHighlight()
var fieldArr []*elastic.HighlighterField
for _, k := range fields {
fieldArr = append(fieldArr, elastic.NewHighlighterField(k))
}
hl = hl.Fields(fieldArr...)
hl = hl.PreTags("<em>").PostTags("</em>")
matchQuery := elastic.NewMultiMatchQuery(keyword, fields...)
result, err := client.Search(IndexName).
Query(matchQuery).
Highlight(hl).
Do(ctx)
if err != nil {
panic(err)
}
if result.TotalHits() > 0 {
// 查詢結(jié)果不為空,遍歷結(jié)果
var u User
hits := result.Hits.Hits
// 通過Each方法,將es結(jié)果的json結(jié)構(gòu)轉(zhuǎn)換成struct對(duì)象
for index, item := range result.Each(reflect.TypeOf(u)) {
// 轉(zhuǎn)換成User對(duì)象
if t, ok := item.(User); ok {
hlUserNameArr := hits[index].Highlight["userName"]
hlUserBioArr := hits[index].Highlight["userBio"]
// 如果命中到高亮的內(nèi)容就直接使用高亮內(nèi)容,否則使用原來內(nèi)容
if len(hlUserNameArr) > 0 {
t.UserName = hlUserNameArr[0]
}
if len(hlUserBioArr) > 0 {
t.UserBio = hlUserBioArr[0]
}
users = append(users, t)
}
}
}
return
}
集群配置
此處以windows為例,Linux方法類似
集群包的準(zhǔn)備
- 新建一個(gè)
elasticsearch-cluster
文件夾 - 將
elasticsearch-7.17.7-windows-x86_64.zip
文件解壓三份,分別命名:- node1
- node2
- node3
- 后面需要用到
ik分詞器
所以根據(jù)前面ES集成整合IK分詞器將IK分詞器插件分別安裝到上述3個(gè)解壓文件對(duì)應(yīng)的目錄中,如下所示:
啟動(dòng)第一個(gè)節(jié)點(diǎn)
對(duì)node1
的config
目錄下的elasticsearch.yml
進(jìn)行修改
# 集群名稱,節(jié)點(diǎn)間須保持一致
cluster.name: my-elasticsearch
# 當(dāng)前節(jié)點(diǎn)名稱 是否能成為master
node.name: node-2001
node.master: true
node.data: true
network.host: 127.0.0.1
http.port: 2001
# TCP通信端口
transport.tcp.port: 9301
# 跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
- 完成上述配置后,即可啟動(dòng)第一個(gè)節(jié)點(diǎn)
從啟動(dòng)日志中,可以看到集群名稱 - 使用 get 請(qǐng)求,可以查看集群狀態(tài)
http://localhost:2001/_cluster/health
響應(yīng)結(jié)果如下
{
"cluster_name":"my-elasticsearch",
"status":"green",
"timed_out":false,
"number_of_nodes":1,
"number_of_data_nodes":1,
"active_primary_shards":3,
"active_shards":3,
"relocating_shards":0,
"initializing_shards":0,
"unassigned_shards":0,
"delayed_unassigned_shards":0,
"number_of_pending_tasks":0,
"number_of_in_flight_fetch":0,
"task_max_waiting_in_queue_millis":0,
"active_shards_percent_as_number":100.0
}
啟動(dòng)第二個(gè)節(jié)點(diǎn)
對(duì)node2
的config
目錄下的elasticsearch.yml
進(jìn)行修改
增加了主節(jié)點(diǎn)的配置信息
并且修改對(duì)應(yīng)端口,集群名稱保持不變文章來源:http://www.zghlxwxcb.cn/news/detail-767820.html
# 集群名稱,節(jié)點(diǎn)間須保持一致
cluster.name: my-elasticsearch
# 當(dāng)前節(jié)點(diǎn)名稱 是否能成為master
node.name: node-2002
node.master: true
node.data: true
network.host: 127.0.0.1
http.port: 2002
# TCP通信端口
transport.tcp.port: 9302
# 主節(jié)點(diǎn)的信息
discovery.seed_hosts: ["localhost:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
# 跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
- 啟動(dòng)第二個(gè)節(jié)點(diǎn)
- 使用 get 請(qǐng)求,可以查看集群狀態(tài)
http://localhost:2001/_cluster/health
響應(yīng)結(jié)果如下
啟動(dòng)第三個(gè)節(jié)點(diǎn)
對(duì)node3
的config
目錄下的elasticsearch.yml
進(jìn)行修改
在discovery.seed_hosts
中,修改為可以查找 9301 和 9302 即可以去查找 node1 node2 兩個(gè)節(jié)點(diǎn)信息文章來源地址http://www.zghlxwxcb.cn/news/detail-767820.html
# 集群名稱,節(jié)點(diǎn)間須保持一致
cluster.name: my-elasticsearch
# 當(dāng)前節(jié)點(diǎn)名稱 是否能成為master
node.name: node-2003
node.master: true
node.data: true
network.host: 127.0.0.1
http.port: 2003
# TCP通信端口
transport.tcp.port: 9303
# 主節(jié)點(diǎn)的信息
discovery.seed_hosts: ["localhost:9301","localhost:9302"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
# 跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
- 啟動(dòng)第三個(gè)節(jié)點(diǎn)
- 使用 get 請(qǐng)求,可以查看集群狀態(tài)
http://localhost:2001/_cluster/health
響應(yīng)結(jié)果如下,可以看到節(jié)點(diǎn)數(shù)為3了
注意每個(gè)節(jié)點(diǎn)都要安裝:ik的插件,否則會(huì)造成失敗
golang elasticsearch連接配置
// CreateClient 創(chuàng)建一個(gè)客戶端
func (es *EsService) CreateClient() (client *elastic.Client, err error) {
// 1.創(chuàng)建客戶端
client, err = elastic.NewClient(
// 單機(jī)
//elastic.SetURL("http://127.0.0.1:9200"),
// 集群 多個(gè)服務(wù)地址用逗號(hào)分隔
elastic.SetURL("http://127.0.0.1:2001", "http://127.0.0.1:2002", "http://127.0.0.1:2003"),
elastic.SetGzip(true),
elastic.SetHealthcheck(true),
elastic.SetHealthcheckTimeout(10*time.Second),
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC", log.LstdFlags)), // 設(shè)置日志輸出的名字
elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)), // 輸出日志級(jí)別
)
if err != nil {
err = errors.New("連接es失敗")
}
return
}
到了這里,關(guān)于初識(shí)Elasticsearch——GO集成ES的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!