Go 连接和使用ES | 青训营

346 阅读7分钟

一、ES介绍

1、ES定义

ES是elaticsearch简写, Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。 Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

2、ES的使用场景

  • 为用户提供按关键字查询的全文搜索功能
  • 实现企业海量数据的处理分析的解决方案。大数据领域的重要一份子,如著名的ELK框架(ElasticSearch,Logstash,Kibana)

3、ES工作原理

当ElasticSearch的节点启动后,它会利用多播(multicast)(或者单播,如果用户更改了配置)寻找集群中的其它节点,并与之建立连接。这个过程如下图所示:

v2-5d26f5fe795a06fcec9d8954c1e017f0_1440w

4、ES核心概念

  • Cluster:集群,ES可以作为一个独立的单个搜索服务器。不过,为了处理大型数据集,实现容错和高可用性,ES可以运行在许多互相合作的服务器上。这些服务器的集合称为集群。
  • Node:节点,形成集群的每个服务器称为节点。
  • Shard:分片,当有大量的文档时,由于内存的限制、磁盘处理能力不足、无法足够快的响应客户端的请求等,一个节点可能不够。这种情况下,数据可以分为较小的分片。每个分片放到不同的服务器上。 当你查询的索引分布在多个分片上时,ES会把查询发送给每个相关的分片,并将结果组合在一起,而应用程序并不知道分片的存在。即:这个过程对用户来说是透明的。
  • Replia:副本,为提高查询吞吐量或实现高可用性,可以使用分片副本。 副本是一个分片的精确复制,每个分片可以有零个或多个副本。ES中可以有许多相同的分片,其中之一被选择更改索引操作,这种特殊的分片称为主分片。 当主分片丢失时,如:该分片所在的数据不可用时,集群将副本提升为新的主分片。
  • 全文检索:全文检索就是对一篇文章进行索引,可以根据关键字搜索,类似于mysql里的like语句,全文索引就是把内容根据词的意义进行分词,然后分别创建索引,例如”你们的激情是因为什么事情来的” 可能会被分词成:“你们“,”激情“,“什么事情“,”来“ 等token,这样当你搜索“你们” 或者 “激情” 都会把这句搜出来。

5、ES特点和优势

  • 分布式实时文件存储,可将每一个字段存入索引,使其可以被检索到。
  • 实时分析的分布式搜索引擎。 分布式:索引分拆成多个分片,每个分片可有零个或多个副本。集群中的每个数据节点都可承载一个或多个分片,并且协调和处理各种操作; 负载再平衡和路由在大多数情况下自动完成。
  • 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。也可以运行在单台PC上
  • 支持插件机制,分词插件、同步插件、Hadoop插件、可视化插件等。

二、ES安装(Windows端)

1、安装JDK环境与环境变量配置

因为ElasticSearch是用Java语言编写的,所以必须安装JDK的环境,并且是JDK 1.8以上

2、官网下载,选择windows端

image-20230828211225479

3、解压压缩包至合适目录

4、修改配置文件

打开config/elasticsearch.yml文件,修改里面的 xpack.security.enabled: true -> xpack.security.enabled: false

另外es配置默认端口号为:9200,如果需要修改也可以在上面配置文件中修改

image-20230828221901937

5、启动ES

切换到bin目录下,双击 elasticsearch.bat 即可运行ES

6、查看运行状态

使用浏览器打开 http://127.0.0.1:9200/ 地址查看运行状态,出现如下内容表示成功

image-20230828222042621

三、Go 连接和使用ES

1、安装es驱动

go get github.com/olivere/elastic/v7

2、创建客户端

在开始之前,我们需要创建一个 Elasticsearch 客户端实例。需要初始化客户端连接,然后去获取相关节点信息,之后会输出一个elasticsearch结构体

package main
​
import (
    "context"
    "fmt"
    "github.com/olivere/elastic/v7"
    "log"
)
​
func main() {
    // 初始化es客户端
    client, err := elastic.NewClient(
        elastic.SetURL("http://localhost:9200"),
        elastic.SetSniff(false),
    )
    if err != nil {
        log.Fatal("ES连接错误: ", err)
    }
    info, err := client.NodesInfo().Do(context.Background())
    if err != nil {
        log.Fatal("获取节点信息错误: ", err)
    }
    fmt.Println(info)
    fmt.Println("ES连接成功")
}

2、使用 ES API

ES 提供了一系列的 API 来操作 ES 集群中的数据,包括索引操作、搜索、聚合等。在使用 API 时,需要注意相应的请求方法和请求路径、请求体等细节。

(1)创建索引文档

createIndex函数中,我们使用client.CreateIndex(indexName).Do(ctx)来创建索引。如果创建索引失败,将返回错误信息。

addDocument函数中,我们使用client.Index().Index(indexName).Id(doc.ID).BodyJson(doc).Do(ctx)来添加文档。它指定了要添加到的索引、文档的ID和内容。如果添加文档失败,将返回错误信息。

package main
​
import (
    "context"
    "fmt"
    "github.com/olivere/elastic/v7"
    "log"
)
​
type Document struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}
​
func createIndex(ctx context.Context, client *elastic.Client, indexName string) error {
    _, err := client.CreateIndex(indexName).Do(ctx)
    if err != nil {
        return fmt.Errorf("error creating the index: %s", err)
    }
​
    return nil
}
​
func addDocument(ctx context.Context, client *elastic.Client, indexName string, doc Document) error {
    _, err := client.Index().Index(indexName).Id(doc.ID).BodyJson(doc).Do(ctx)
    if err != nil {
        return fmt.Errorf("error indexing document: %s", err)
    }
​
    return nil
}
​
func main() {
    // 初始化es客户端
    client, err := elastic.NewClient(
        elastic.SetURL("http://localhost:9200"),
        elastic.SetSniff(false),
    )
    if err != nil {
        log.Fatal("ES连接错误: ", err)
    }
    fmt.Println("ES连接成功")
​
    indexName := "my_index"
    ctx := context.Background()
​
    // 创建索引
    err = createIndex(ctx, client, indexName)
    if err != nil {
        log.Fatalf("Error creating the index: %s", err)
    } else {
        log.Printf("Index created: %s", indexName)
    }
​
    // 添加文档
    doc := Document{
        ID:   "1",
        Name: "测试test",
    }
​
    err = addDocument(ctx, client, indexName, doc)
    if err != nil {
        log.Fatalf("Error indexing document: %s", err)
    } else {
        log.Printf("Document indexed: %s", doc.ID)
    }
}

运行结果

image-20230828235703496

(2)搜索文档

searchDocuments函数中,我们使用client.Search()创建一个搜索请求,并设置要搜索的索引名称和搜索条件(例如,使用elastic.NewMatchQuery进行简单的字段匹配)

package main
​
import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/olivere/elastic/v7"
    "log"
)
​
// 省略代码// 搜索文档
func searchDocuments(ctx context.Context, client *elastic.Client, indexName string, searchTerm string) (*elastic.SearchResult, error) {
   searchResult, err := client.Search().
      Index(indexName).
      Query(elastic.NewMatchQuery("name", searchTerm)).
      Do(ctx)
​
   if err != nil {
      return nil, fmt.Errorf("error searching documents: %s", err)
   }
​
   return searchResult, nil
}
​
func main() {
    // 省略上面代码
    
    // 搜索文档
    searchTerm := "测试"
    results, err := searchDocuments(ctx, client, indexName, searchTerm)
    if err != nil {
        log.Fatalf("Error searching documents: %s", err)
    } else {
        log.Printf("Search results for '%s':", searchTerm)
        for _, hit := range results.Hits.Hits {
            var doc Document
            err := json.Unmarshal(hit.Source, &doc)
            if err != nil {
                log.Printf("Error unmarshaling document: %s", err)
            } else {
                log.Printf("ID: %s, Name: %s", doc.ID, doc.Name)
            }
        }
    }
}

运行结果

image-20230829000326521

(3)更新文档
// 更新文档
func updateDocument(ctx context.Context, client *elastic.Client, index string, docID string, updatedData map[string]interface{}) error {
    updateResult, err := client.Update().
        Index(index).
        Id(docID).
        Doc(updatedData).
        Do(ctx)
​
    if err != nil {
        return fmt.Errorf("更新文档时发生错误:%v", err)
    }
​
    if updateResult.Result != "updated" {
        return fmt.Errorf("文档未被更新:%s", updateResult.Result)
    }
​
    return nil
}
​
func main() {
    // 省略上面代码
    
    // 更新文档
    docID := "1"
    updatedData := map[string]interface{}{
        "name": "张三",
    }
    err = updateDocument(context.Background(), client, indexName, docID, updatedData)
    if err != nil {
        fmt.Println("更新文档失败:", err)
        return
    }
​
    fmt.Println("文档已成功更新")
}

运行结果

image-20230829001932527

(4)删除文档

在下面函数中,我们使用 client.Delete().Index(indexName).Id(docID).Do(ctx) 来构建并执行删除请求。然后,我们检查删除结果是否成功,以及返回结果是否为 "deleted"。如果删除过程中出现错误,函数将返回带有错误信息的错误对象。如果文档未被删除,则函数返回一个表示删除失败的错误。

// 删除文档
func deleteDocument(ctx context.Context, client *elastic.Client, indexName string, docID string) error {
   deleteResult, err := client.Delete().
      Index(indexName).
      Id(docID).
      Do(ctx)
​
   if err != nil {
      return fmt.Errorf("删除文档时发生错误:%s", err)
   }
​
   if deleteResult.Result != "deleted" {
      return errors.New("文档未被删除")
   }
​
   return nil
}
​
func main() {
    // 省略上面代码
    
    // 删除文档
    docID := "1"
    err = deleteDocument(ctx, client, indexName, docID)
    if err != nil {
        fmt.Println("删除文档失败:", err)
        return
    }
​
    fmt.Println("文档已成功删除")
}

运行结果

image-20230829001202654

(5)更多操作,请查看官方文档