go操作elasticSearch

54 阅读43分钟

基本操作

连接elasticSearch

连接远程服务器上的Elasticsearch需要进行以下步骤:

  1. 确保您的服务器已经安装并运行了Elasticsearch,并且已经配置允许外部访问。可以使用宝塔面板或其他方法来安装和配置Elasticsearch。
  2. 在服务器上配置Elasticsearch,使其监听所有网络接口。在Elasticsearch配置文件中,将network.host配置项设置为0.0.0.0,以允许所有网络接口访问Elasticsearch。重启Elasticsearch使配置生效。
  3. 在服务器的防火墙和安全组中,开放Elasticsearch的端口(默认为9200和9300端口),允许从外部访问这些端口。
  4. 在您的Go项目中,使用第三方库(例如github.com/elastic/go-elasticsearch)连接到远程的Elasticsearch服务器。首先,使用Go模块管理初始化项目:
go mod init your_project_name
  1. 安装github.com/elastic/go-elasticsearch库:
//必须有版本号
go get github.com/elastic/go-elasticsearch/v8
  1. 在Go代码中,导入github.com/elastic/go-elasticsearch库,并使用elasticsearch.Config创建一个Elasticsearch客户端:
package DB

import (
	"fmt"
	"github.com/elastic/go-elasticsearch/v8"
)

func main() {
	// 创建一个 Elasticsearch 客户端
	cfg := elasticsearch.Config{
		Addresses: []string{"http://your_server_ip:9200"},
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		fmt.Println("Error creating Elasticsearch client:", err)
		return
	}

	// 检查连接是否成功
	res, err := es.Info()
	if err != nil {
		fmt.Println("Error getting response:", err)
		return
	}
	defer res.Body.Close()

	fmt.Println("Elasticsearch response status:", res.Status())
}

your_server_ip替换为您远程服务器的IP地址。以上代码将连接到Elasticsearch服务器,并检查是否连接成功,输出Elasticsearch的响应状态。

  1. 运行Go代码,并查看输出,确保Elasticsearch连接成功。

请注意,Go语言中有多个Elasticsearch的客户端库可供选择,您也可以根据项目需要使用其他库。在实际项目中,可能需要更多的配置和设置来进行索引、搜索、聚合等操作。您可以参考github.com/elastic/go-elasticsearch库的文档和示例代码来学习如何使用Elasticsearch的Go客户端。

增删改查

当然,下面是使用Go语言和Elasticsearch 8.x版本进行增删改查操作的完整示例代码。这些示例涵盖了将文档索引(创建/更新)、获取文档、更新文档和删除文档的基本操作。

package DB

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/elastic/go-elasticsearch/v8"
	"log"
	"net/http"
	"time"
)

func EsTest() {
	// 创建一个 Elasticsearch 客户端
	cfg := elasticsearch.Config{
		Addresses: []string{"http://120.53.94.106:9200"},
		//Logger:    log.New(os.Stdout, "", 0),
		Transport: &http.Transport{
			MaxIdleConnsPerHost:   10,
			ResponseHeaderTimeout: time.Second * 10, // 设置超时时间
		},
		Username: "elastic",
		Password: "nrec1234",
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		fmt.Println("Error creating Elasticsearch client:", err)
		return
	}

	// 1. Indexing (Creating) a Document
	doc := map[string]interface{}{
		"title": "Go and Elasticsearch",
		"body":  "A short example of using Elasticsearch in Go.",
	}
	docBytes, _ := json.Marshal(doc)
	res, err := es.Index("posts", bytes.NewReader(docBytes))
	if err != nil {
		log.Fatalf("Error indexing document: %s", err)
	}
	defer res.Body.Close()
	fmt.Println("Indexing document response status:", res.Status()) // 输出: 201 Created

	// 2. Getting a Document
	getRes, err := es.Get("posts", "your_document_id")
	if err != nil {
		log.Fatalf("Error getting document: %s", err)
	}
	defer getRes.Body.Close()
	fmt.Println("Getting document response status:", getRes.Status()) // 输出: 200 OK

	// 3. Updating a Document
	updateDoc := map[string]interface{}{
		"doc": map[string]string{
			"title": "Updated Title",
		},
	}
	updateBytes, _ := json.Marshal(updateDoc)
	updateRes, err := es.Update("posts", "your_document_id", bytes.NewReader(updateBytes))
	if err != nil {
		log.Fatalf("Error updating document: %s", err)
	}
	defer updateRes.Body.Close()
	fmt.Println("Updating document response status:", updateRes.Status()) // 输出: 200 OK

	// 4. Deleting a Document
	deleteRes, err := es.Delete("posts", "your_document_id")
	if err != nil {
		log.Fatalf("Error deleting document: %s", err)
	}
	defer deleteRes.Body.Close()
	fmt.Println("Deleting document response status:", deleteRes.Status()) // 输出: 200 OK
}

请注意,你需要替换your_document_id为你想操作的文档的实际ID。此外,确保你的Elasticsearch实例正在运行,并且可以在提供的地址上访问。

该代码展示了与Elasticsearch进行基本交互的四个主要步骤:索引文档、获取文档、更新文档和删除文档。每个操作的响应状态代码已经以注释的形式标注在代码后面。

查看集群信息

使用 github.com/elastic/go-elasticsearch/v8 库查看 Elasticsearch 集群信息主要涉及到使用 Cluster.Info 和 Cluster.Health API。以下是如何使用这个库来查看 Elasticsearch 集群的信息:

  1. 初始化客户端:首先,你需要创建一个 Elasticsearch 客户端实例。
  2. 查看集群信息:使用 Info API 可以获取集群的基本信息。
  3. 查看集群健康状态:使用 Cluster.Health API 可以获取集群的健康状态。
func ClusterInfo(es *elasticsearch.Client) {
	res, err := es.Info()
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()

	var r map[string]interface{}
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		log.Fatalf("Error parsing the response body: %s", err)
	}

	for key, value := range r {
		fmt.Printf("%s: %v\n", key, value)
	}

	for key, value := range r["version"].(map[string]interface{}) {
		fmt.Printf("%s: %v\n", key, value)
	}
}

func HealthInfo(es *elasticsearch.Client) {
	// 3. 查看集群健康状态
	req := esapi.ClusterHealthRequest{}
	res, err := req.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()

	if res.IsError() {
		log.Fatalf("Error: %s", res.String())
	}

	var health map[string]interface{}
	if err := json.NewDecoder(res.Body).Decode(&health); err != nil {
		log.Fatalf("Error parsing the response body: %s", err)
	}

	for key, value := range health {
		fmt.Printf("%s: %v\n", key, value)
	}
}

这段代码首先初始化一个 Elasticsearch 客户端,然后使用 Info API 获取集群的基本信息,如集群名称和 Elasticsearch 版本。接着,它使用 Cluster.Health API 获取集群的健康状态。

elasticsearch.Client.Info()返回的res.Body中内容

{
  "name": "61ef1fa75d46", // 节点名称
  "cluster_name": "docker-cluster", // 集群的名称
  "cluster_uuid": "R_l7EQAJScSa7I5FIjN4tg", // 集群的唯一标识符
  "version": {
    "lucene_version": "9.6.0", // 使用的Lucene版本
    "minimum_index_compatibility_version": "7.0.0", // 最小的索引兼容版本
    "number": "8.8.1", // Elasticsearch的版本号
    "build_flavor": "default", // 构建的版本类型
    "build_hash": "f8edfccba429b6477927a7c1ce1bc6729521305e", // 构建的哈希值
    "minimum_wire_compatibility_version": "7.17.0", // 最小的线路兼容版本
    "build_type": "docker", // 构建的类型
    "build_date": "2023-06-05T21:32:25.188464208Z", // 构建的日期和时间
    "build_snapshot": false // 是否是快照版本
  }
}

esapi.ClusterHealthRequest.Do()返回的res.Body中内容

{
  "cluster_name": "docker-cluster", // 集群的名称
  "number_of_nodes": 1, // 集群中的节点数量
  "number_of_data_nodes": 1, // 专用数据节点的数量
  "relocating_shards": 0, // 正在重新定位的分片数量
  "delayed_unassigned_shards": 0, // 其分配已被超时设置延迟的分片数量
  "number_of_pending_tasks": 0, // 尚未执行的集群级更改的数量
  "status": "yellow", // 集群的健康状态(绿色、黄色或红色)
  "active_primary_shards": 25, // 活动的主分片数量
  "initializing_shards": 0, // 正在初始化的分片数量
  "number_of_in_flight_fetch": 0, // 未完成的获取的数量
  "timed_out": false, // 如果为false,则响应在由timeout参数指定的时间段内返回
  "active_shards": 25, // 活动的主分片和副本分片的总数
  "unassigned_shards": 1, // 未分配的分片数量
  "task_max_waiting_in_queue_millis": 0, // 自最早启动的任务等待执行以来的时间(以毫秒为单位)
  "active_shards_percent_as_number": 96.15384615384616 // 集群中活动分片的比率,以百分比表示
}

多种查询方式

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/elastic/go-elasticsearch/v8"
    "github.com/elastic/go-elasticsearch/v8/esapi"
)

func main() {
    cfg := elasticsearch.Config{
        Addresses: []string{"http://localhost:9200"},
    }
    es, err := elasticsearch.NewClient(cfg)
    if err != nil {
        log.Fatalf("Error creating the client: %s", err)
    }

    // 1. 全文搜索
    fullTextSearch(es, "your_index_name", "your_search_term")

    // 2. 结构化搜索
    structuredSearch(es, "your_index_name", "field_name", "value")

    // 3. 模糊搜索
    fuzzySearch(es, "your_index_name", "field_name", "value")

    // 4. 范围搜索
    rangeSearch(es, "your_index_name", "field_name", "start_value", "end_value")

    // 5. 聚合查询
    aggregationSearch(es, "your_index_name", "field_name")

    // 6. 地理位置查询
    geoSearch(es, "your_index_name", "lat", "lon", "distance")
}

//全文搜索,可以指定在哪些字段进行搜索
func fullTextSearch(es *elasticsearch.Client, index, term string) {
    //旧版本使用_all参数,已弃用
	query := fmt.Sprintf(`{
    "query": {
        "multi_match": {
            "query": "%s",
            "fields": ["*"]
        }
    }
    }`, term)
	search(es, index, query)
}

//结构化搜索
func structuredSearch(es *elasticsearch.Client, index, field, value string) {
    query := fmt.Sprintf(`{
        "query": {
            "term": {
                "%s": "%s"
            }
        }
    }`, field, value)
    search(es, index, query)
}

//模糊搜索
func fuzzySearch(es *elasticsearch.Client, index, field, value string) {
    query := fmt.Sprintf(`{
        "query": {
            "fuzzy": {
                "%s": {
                    "value": "%s",
                    "fuzziness": "AUTO"
                }
            }
        }
    }`, field, value)
    search(es, index, query)
}

//范围搜索
func rangeSearch(es *elasticsearch.Client, index, field, start, end string) {
    query := fmt.Sprintf(`{
        "query": {
            "range": {
                "%s": {
                    "gte": "%s",
                    "lte": "%s"
                }
            }
        }
    }`, field, start, end)
    search(es, index, query)
}

//聚合查询
func aggregationSearch(es *elasticsearch.Client, index, field string) {
    //group_by_%s 是聚合名称
    query := fmt.Sprintf(`{
        "aggs": {
            "group_by_%s": {
                "terms": {
                    "field": "%s"
                }
            }
        }
    }`, field, field)
    search(es, index, query)
}

//地理位置查询
func geoSearch(es *elasticsearch.Client, index, lat, lon, distance string) {
    query := fmt.Sprintf(`{
        "query": {
            "geo_distance": {
                "distance": "%s",
                "location": {
                    "lat": %s,
                    "lon": %s
                }
            }
        }
    }`, distance, lat, lon)
    search(es, index, query)
}

func search(es *elasticsearch.Client, index, query string) {
    req := esapi.SearchRequest{
        Index: []string{index},
        Body:  strings.NewReader(query),
    }
    res, err := req.Do(context.Background(), es)
    if err != nil {
        log.Fatalf("Error getting response: %s", err)
    }
    defer res.Body.Close()

    // Print the response body
    var r map[string]interface{}
    if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
        log.Fatalf("Error parsing the response body: %s", err)
    }
    fmt.Println(r)
}

全文搜索

//全文搜索,可以指定在哪些字段进行搜索
func fullTextSearch(es *elasticsearch.Client, index, term string) {
    //旧版本使用_all参数,已弃用
    query := fmt.Sprintf(`{
    "query": {
        "multi_match": {
            "query": "%s",
            "fields": ["*"]
        }
    }
    }`, term)
    search(es, index, query)
}

结构化查询

Elasticsearch中的结构化搜索与全文搜索不同。结构化搜索是基于文档中的明确字段和值进行的搜索,而全文搜索则是基于文本内容进行的搜索,不特定于任何字段。

结构化搜索的特点:

  1. 字段特定:结构化搜索是针对特定的字段进行的。例如,你可能想要查找所有年龄在30到40岁之间的用户,或者所有在特定日期之后注册的用户。
  2. 精确匹配:结构化搜索通常涉及到精确值的匹配,例如查找status字段为active的所有文档。
  3. 范围查询:结构化搜索允许你进行范围查询,例如查找所有价格在10到50之间的产品。
  4. 组合查询:你可以组合多个结构化查询条件,例如查找所有状态为active且年龄在30到40岁之间的用户。
  5. 使用数据类型:由于结构化搜索是基于字段的,所以它考虑到了字段的数据类型。例如,数字字段可以进行数值比较,日期字段可以进行日期范围查询。

示例:

考虑一个包含用户信息的Elasticsearch索引,其中每个文档都有name、age和registration_date字段。以下是一些结构化查询的示例:

  1. 查找所有年龄为30岁的用户:
{
  "query": {
    "term": {
      "age": 30
    }
  }
}
  1. 查找所有年龄在30到40岁之间的用户:
{
  "query": {
    "range": {
      "age": {
        "gte": 30,
        "lte": 40
      }
    }
  }
}
  1. 查找所有在2022年1月1日之后注册的用户:
{
  "query": {
    "range": {
      "registration_date": {
        "gte": "2022-01-01"
      }
    }
  }
}

结构化搜索是Elasticsearch中非常强大的功能,它允许你根据文档的具体字段和值进行精确的查询。这与传统的关系数据库查询非常相似,但Elasticsearch提供了更多的灵活性和速度。

要查找所有状态为active且年龄在30到40岁之间的用户,您可以使用Elasticsearch的bool查询,结合must子句来组合多个查询条件。以下是相应的查询示例:

{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "status": "active"
          }
        },
        {
          "range": {
            "age": {
              "gte": 30,
              "lte": 40
            }
          }
        }
      ]
    }
  }
}

在这个查询中,bool查询的must子句确保所有返回的文档都满足列出的所有条件。term查询用于匹配status字段的精确值active,而range查询用于匹配age字段的值在30到40之间。

模糊查询

Elasticsearch的模糊搜索允许用户查询与指定的词条相似但不完全相同的词条。这种搜索特别适用于处理拼写错误或近似词条的情况。模糊搜索基于所谓的“编辑距离”或“Levenshtein距离”,这是指从一个字符串转换为另一个字符串所需的最小单字符编辑次数(例如插入、删除或替换)。

模糊搜索的特点:

  1. 处理拼写错误:如果用户在查询时犯了一个小错误,模糊搜索仍然可以返回相关的结果。
  2. 灵活性:可以指定允许的最大编辑距离。
  3. 基于词条:模糊搜索是基于词条级别的,而不是全文本级别的。

示例:

考虑一个索引,其中包含一个字段title。如果你想查找与词条apple相似的所有词条,但允许一个字符的编辑距离,你可以使用以下的模糊查询:

{
  "query": {
    "fuzzy": {
      "title": {
        "value": "apple",
        "fuzziness": 1
      }
    }
  }
}

在这个查询中,fuzziness参数定义了允许的最大编辑距离。例如,fuzziness为1意味着apple、apples、aple等都会被匹配。

注意事项:

  • 模糊搜索可能会增加查询的计算负担,因为它需要检查与查询词条编辑距离在指定范围内的所有词条。
  • 对于非常大的文档集合,应谨慎使用模糊搜索,或考虑其他方法,如使用ngram或edge ngram分析器来处理拼写错误。

总之,Elasticsearch的模糊搜索提供了一种强大的方法来处理拼写错误和近似匹配,但在使用时应注意其对性能的影响。

多字段

模糊搜索不仅限于单个字段。虽然单个fuzzy查询通常针对一个特定字段,但您可以组合多个fuzzy查询来在多个字段上执行模糊搜索。这可以通过使用bool查询的should子句来实现,其中每个子句都是一个fuzzy查询针对不同的字段。

示例:

假设您有一个索引,其中包含两个字段:title和description。您想在这两个字段上执行与词条apple相似的模糊搜索。以下是如何组合两个fuzzy查询来实现这一目标的示例:

{
  "query": {
    "bool": {
      "should": [
        {
          "fuzzy": {
            "title": {
              "value": "apple",
              "fuzziness": 1
            }
          }
        },
        {
          "fuzzy": {
            "description": {
              "value": "apple",
              "fuzziness": 1
            }
          }
        }
      ]
    }
  }
}

在上述查询中,bool查询的should子句包含两个fuzzy查询,一个针对title字段,另一个针对description字段。这意味着,只要文档的title或description字段中的任何一个与apple相似,它就会被返回。

此外,您还可以使用multi_match查询的fuzziness参数来在多个字段上执行模糊搜索,但这通常用于全文搜索场景,而不是模糊匹配单个词条。

总之,虽然单个fuzzy查询是针对单个字段的,但您可以组合多个查询来在多个字段上执行模糊搜索。

fuzziness参数

fuzziness参数在Elasticsearch的模糊查询中定义了允许的最大编辑距离。编辑距离是指从一个字符串转换为另一个字符串所需的最小单字符编辑次数(例如插入、删除或替换)。

当fuzziness设置为"AUTO"时,Elasticsearch会根据查询词条的长度自动选择一个合适的编辑距离。这是为了确保较短的词条不会因为过高的模糊度而匹配到太多无关的结果,同时较长的词条可以容忍更多的拼写错误。

具体的"AUTO"参数行为如下:

  • 长度为0-2的词条:编辑距离为0
  • 长度为3-5的词条:编辑距离为1
  • 长度为6及以上的词条:编辑距离为2

例如,对于词条apple(长度为5),当fuzziness设置为"AUTO"时,允许的编辑距离为1。这意味着apple、apples、aple等都会被匹配,但appple(两个p的拼写错误)则不会,因为它的编辑距离为2。

使用"AUTO"作为fuzziness值的好处是,它为不同长度的词条提供了一个合理的默认编辑距离,这有助于提高查询的准确性和相关性。当然,如果您对特定查询有特定的需求,您也可以手动设置fuzziness的值。

范围查询

范围查询(Range Query)在Elasticsearch中允许您基于数字、日期或字符串字段的范围来匹配文档。这种查询对于查找在特定范围内的值非常有用,例如价格、日期或其他数值数据。

范围查询的关键参数:

  1. gte:大于或等于 (Greater Than or Equal to)
  2. gt:大于 (Greater Than)
  3. lte:小于或等于 (Less Than or Equal to)
  4. lt:小于 (Less Than)
  5. boost:设置查询的权重,以影响查询的得分。
  6. format:用于日期字段,指定日期的格式。
  7. time_zone:用于日期字段,指定日期的时区。

示例:

  1. 数字范围查询查找价格在10到100之间的文档:
{
  "query": {
    "range": {
      "price": {
        "gte": 10,
        "lte": 100
      }
    }
  }
}
  1. 日期范围查询查找在特定日期范围内发布的文档:
{
  "query": {
    "range": {
      "date": {
        "gte": "2022-01-01",
        "lte": "2022-12-31",
        "format": "yyyy-MM-dd"
      }
    }
  }
}
  1. 字符串范围查询字符串范围查询基于字符串的字典顺序:

这个查询是一个范围查询,针对title字段,它会匹配字段值在字典顺序上介于"A"和"M"之间的文档。这意味着任何以"A"到"M"之间的字母开头的title都会被匹配。但是,注意是大小写敏感的,axxx mxxx不会被匹配到

{
  "query": {
    "range": {
      "title": {
        "gte": "A",
        "lte": "M"
      }
    }
  }
}

注意事项:

  • 范围查询对于不分词的字段(如关键字字段)最为有效。对于分词字段,范围查询可能不会按预期工作,因为它会基于单个词条而不是整个字段值进行操作。
  • 对于日期范围查询,建议使用format参数明确指定日期格式,并考虑时区差异。
  • 范围查询可以与其他查询类型结合使用,例如bool查询,以构建更复杂的查询逻辑。

总的来说,范围查询为基于特定范围的匹配提供了一种强大的方法,无论是数字、日期还是字符串。

日期查询format参数

format字段在Elasticsearch的范围查询中用于指定日期的格式。这样,当您的日期字符串与Elasticsearch中默认的日期格式或映射中指定的格式不匹配时,您可以明确告诉Elasticsearch如何解析查询中的日期字符串。

在上面的查询中,format字段的值为"yyyy-MM-dd",这意味着日期字符串应该是"2022-01-01"这样的格式,其中yyyy代表4位年份,MM代表2位月份,dd代表2位日期。

Elasticsearch支持许多预定义的日期格式,以及自定义的格式。以下是一些常见的预定义格式和自定义格式的示例:

  1. 预定义格式
    • date_optional_time或strict_date_optional_time: 例如"2022-01-01T12:10:30Z"
    • basic_date: 例如"20220101"
    • epoch_millis: 以毫秒为单位的Unix时间戳,例如"1641034835000"
    • epoch_second: 以秒为单位的Unix时间戳,例如"1641034835"
  1. 自定义格式
    • yyyy-MM-dd: 例如"2022-01-01"
    • dd/MM/yyyy: 例如"01/01/2022"
    • yyyy-MM-dd HH:mm:ss: 例如"2022-01-01 12:10:30"
    • MMM dd, yyyy: 例如"Jan 01, 2022"

当使用自定义格式时,您可以使用以下模式:

  • yyyy: 4位年份
  • yy: 2位年份
  • MM: 2位月份
  • dd: 2位日期
  • HH: 2位小时(24小时制)
  • mm: 2位分钟
  • ss: 2位秒
  • SSS: 毫秒
  • Z: UTC偏移,例如"+01:00"或"-05:00"
  • X: Unix时间戳

您可以组合这些模式以创建自己的自定义日期格式。当使用范围查询时,确保查询中的日期字符串与指定的格式匹配,以确保正确的解析和匹配。

聚合查询

Elasticsearch的聚合功能允许您对数据进行分组并提取统计信息。聚合不仅可以用于数字数据的统计,还可以用于结构化数据的分组、日期数据的分桶等。聚合查询通常与搜索查询结合使用,以在搜索结果上进行聚合。

以下是一些常见的聚合类型及其简要描述和示例:

  1. Terms Aggregation对文档的文本值进行分组。
{
    "aggs": {
        "popular_tags": {               // 定义一个名为"popular_tags"的聚合
            "terms": {"field": "tag"}     // 对"tag"字段的值进行分组
        }
    }
}
  1. Histogram Aggregation对数字或日期字段进行分桶。
{
    "aggs": {
        "sales_per_month": {                 // 定义一个名为"sales_per_month"的聚合
            "date_histogram": {                // 使用日期直方图聚合
                "field": "date",                 //"date"字段进行分桶
                "calendar_interval": "month"     // 按月份进行分桶
            }
        }
    }
}
  1. Range Aggregation根据指定的范围对数字或日期字段进行分桶。
{
    "aggs": {
        "age_ranges": {                      // 定义一个名为"age_ranges"的聚合
            "range": {                         // 使用范围聚合
                "field": "age",                  //"age"字段进行分桶
                "ranges": [                      // 指定分桶的范围
                    {"to": 20},                    // 小于20
                    {"from": 20, "to": 30},        // 2030之间
                    {"from": 30}                   // 大于30
                ]
            }
        }
    }
}
  1. Stats Aggregation提供字段的统计信息,如平均值、最大值、最小值、总和和计数。
{
    "aggs": {
        "price_stats": {                    // 定义一个名为"price_stats"的聚合
            "stats": {"field": "price"}       // 获取"price"字段的统计信息
        }
    }
}
  1. Cardinality Aggregation估计字段的不同值的数量。
{
    "aggs": {
        "unique_colors": {                  // 定义一个名为"unique_colors"的聚合
            "cardinality": {"field": "color"} // 估计"color"字段的不同值的数量
        }
    }
}
  1. Nested Aggregation对嵌套文档进行聚合。
{
    "aggs": {
        "products": {                       // 定义一个名为"products"的聚合
            "nested": {"path": "products"},   // 指定嵌套文档的路径
            "aggs": {
                "top_brands": {                 // 定义一个名为"top_brands"的子聚合
                    "terms": {"field": "products.brand"} // 对"products.brand"字段的值进行分组
                }
            }
        }
    }
}
  1. Top Hits Aggregation返回匹配每个桶的最顶部的文档。
{
    "aggs": {
        "top_tags": {                       // 定义一个名为"top_tags"的聚合
            "terms": {"field": "tag"},        //"tag"字段的值进行分组
            "aggs": {
                "top_tag_hits": {               // 定义一个名为"top_tag_hits"的子聚合
                    "top_hits": {                 // 使用top_hits聚合
                        "size": 1                   // 返回每个桶的顶部1个文档
                    }
                }
            }
        }
    }
}

分桶

"分桶"(Bucketing)和"分组"(Grouping)在许多上下文中都是数据聚合的概念,但在Elasticsearch中,这两个词有特定的含义和区别。

分桶 (Bucketing)

在Elasticsearch中,分桶是将数据集分成多个子集的过程。每个子集(或"桶")包含一组满足特定条件的文档。例如,您可以使用日期直方图聚合按月份对数据进行分桶,或使用术语聚合按字段值对数据进行分桶。

分桶的关键特点是:

  • 桶是互斥的,这意味着每个文档只能属于一个桶。
  • 桶可以是不均匀的,即它们可以包含不同数量的文档。
  • 桶的数量和大小取决于数据和聚合的类型。

分组 (Grouping)

分组通常是关系型数据库中的一个概念,例如SQL中的GROUP BY子句。它将数据集分为基于某些属性的多个组,然后对每个组应用聚合函数(如计数、平均值、总和等)。

分组的关键特点是:

  • 分组通常基于一个或多个属性。
  • 分组的目的通常是为了应用某种聚合函数。

为什么Elasticsearch使用"分桶"而不是"分组"?

  1. 灵活性:分桶提供了比传统的分组更大的灵活性。例如,您可以创建范围桶、直方图桶、地理位置桶等,这些在传统的分组操作中不常见。
  2. 语义:在Elasticsearch中,聚合可以是嵌套的。这意味着您可以在一个桶内部进行另一个聚合。这种嵌套结构更适合"桶"这个词,因为您实际上是在一个大桶内部创建了更小的桶。
  3. 避免混淆:由于Elasticsearch不是关系型数据库,使用不同的术语可以帮助避免与传统的关系型数据库操作混淆。

总的来说,虽然分桶和分组在某些方面是相似的,但Elasticsearch选择使用"分桶"这个词是为了强调其聚合操作的灵活性和多样性。

地理位置查询

Elasticsearch 提供了强大的地理位置查询和聚合功能,允许您在地理空间数据上执行各种操作。以下是一些常见的地理位置查询及其用法:

  1. geo_distance 查询

这种查询用于查找距离给定地点一定距离内的文档。

示例:

{
  "query": {
    "bool": {
      "must": {
        "match_all": {} //查找索引中的所有文档,默认行为,可以不加
      },
      "filter": {
        "geo_distance": {
          "distance": "200km",  // 查询半径
          "location": {        // 中心点
            "lat": 40.73,
            "lon": -74.00
          }
        }
      }
    }
  }
}
  1. geo_bounding_box 查询

这种查询用于查找位于给定边界框内的文档。

示例:

{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {       // 左上角坐标
          "lat": 40.73,
          "lon": -74.1
        },
        "bottom_right": {   // 右下角坐标
          "lat": 40.01,
          "lon": -71.12
        }
      }
    }
  }
}
  1. geo_polygon 查询

这种查询用于查找位于给定多边形内的文档。

示例:

{
  "query": {
    "geo_polygon": {
      "location": {
        "points": [           // 多边形的顶点
          {"lat": 40.73, "lon": -74.1},
          {"lat": 40.83, "lon": -74.2},
          {"lat": 40.93, "lon": -74.3}
        ]
      }
    }
  }
}
  1. geo_shape 查询

这种查询用于查找与给定形状相交的文档。它支持多种形状,如点、线、多边形等。

示例:

{
  "query": {
    "geo_shape": {
      "location": {
        "shape": {
          "type": "envelope",   // 形状类型
          "coordinates": [     // 坐标
            [-74.1, 40.73],
            [-71.12, 40.01]
          ]
        }
      }
    }
  }
}

这只是 Elasticsearch 地理位置查询功能的冰山一角。为了使用这些查询,您需要确保您的索引已经正确地映射了地理位置字段。

查询语法

Elasticsearch提供了丰富的查询DSL(Domain Specific Language),允许用户执行各种类型的查询。除了bool查询,还有许多其他类型的查询。以下是一些常见的查询类型及其简要描述和示例:

bool查询/组合查询

在Elasticsearch中,bool查询提供了一种方式来组合多个子查询,以便可以构建复杂的查询逻辑。bool查询中的子句包括must、should、must_not和filter。下面是这些子句的详细介绍:

  1. must:所有列在must子句中的查询都必须匹配,才能使文档满足整个bool查询。这相当于逻辑“AND”。
  2. should:至少有一个在should子句中的查询必须匹配,才能使文档满足整个bool查询。但如果与must子句一起使用,那么should子句只是影响得分,而不是过滤文档。这相当于逻辑“OR”。
  3. must_not:在must_not子句中的查询都不能匹配,才能使文档满足整个bool查询。这相当于逻辑“NOT”。
  4. filter:这是一个无评分、过滤上下文的子句。它的行为类似于must,但不会影响得分。它用于过滤文档而不计算得分。

代码示例:

{
  "query": {
    "bool": {
      "must": [
        {"term": {"status": "active"}}
      ],
      "should": [
        {"term": {"tag": "popular"}},
        {"term": {"tag": "latest"}}
      ],
      "must_not": [
        {"term": {"status": "archived"}}
      ],
      "filter": [
        {"range": {"date": {"gte": "2022-01-01"}}}
      ]
    }
  }
}

在上述示例中:

  • must子句要求status字段的值必须为active。
  • should子句要求tag字段的值为popular或latest中的至少一个。
  • must_not子句要求status字段的值不能为archived。
  • filter子句要求date字段的值必须在2022-01-01或之后。

总之,bool查询提供了一种强大的方式来组合多个子查询,以构建复杂的查询逻辑。通过正确使用这些子句,您可以精确地定义您想要的查询条件。

Term & Terms查询

用于执行精确值匹配的查询。

查询user=kimchy

查询user=kimchy或elasticsearch

{
  "query": {
    "term": {"user": "kimchy"}
  }
}

{
  "query": {
    "terms": {"user": ["kimchy", "elasticsearch"]}
  }
}

Range Query

用于基于范围的匹配。

{
  "query": {
    "range": {
      "age": {
        "gte": 10,
        "lte": 20
      }
    }
  }
}

Match Query

用于全文搜索。

{
  "query": {
    "match": {"message": "this is a test"}
  }
}

Match_All查询

match_all 查询是 Elasticsearch 中的一个简单查询,用于匹配索引中的所有文档。它不需要任何特定的参数来执行其主要功能,即返回所有文档。默认就会返回所有文档

参数:

虽然 match_all 主要用于匹配所有文档,但它确实接受一个可选参数:

  • boost: 这是一个浮点数,用于为查询结果中的每个文档设置一个乘法权重。默认值为 1.0。

示例:

  1. 基本的 match_all 查询:
{
  "query": {
    "match_all": {}
  }
}

这将匹配并返回索引中的所有文档。

  1. 使用 boost 参数的 match_all 查询:
{
  "query": {
    "match_all": {
      "boost": 1.5
    }
  }
}

在这个示例中,虽然查询仍然匹配所有文档,但每个文档的得分都会乘以 1.5。

总的来说,match_all 查询是一个非常基础的查询,通常用于简单地返回所有文档或作为更复杂查询结构的一部分。在大多数实际应用中,您可能不会经常直接使用它,除非您确实需要匹配并检索所有文档。

Multi Match Query

在多个字段上执行文本查询。

{
  "query": {
    "multi_match": {
      "query": "this is a test",
      "fields": ["message", "description"]
    }
  }
}

Wildcard Query

使用通配符进行匹配。

{
  "query": {
    "wildcard": {"user": "ki*chy"}
  }
}

Prefix Query

查询字段值的前缀。

{
  "query": {
    "prefix": {"user": "kim"}
  }
}

Phrase Match Query

搜索精确的短语。

{
  "query": {
    "match_phrase": {"message": "this is"}
  }
}

Common Terms Query

用于执行全文查询,但更加关注于更不常见的词。

{
  "query": {
    "common": {
      "message": {
        "query": "this is a test",
        "cutoff_frequency": 0.001
      }
    }
  }
}

Fuzzy Query

基于编辑距离的模糊匹配。

{
  "query": {
    "fuzzy": {
      "user": {
        "value": "kimchy",
        "fuzziness": 2
      }
    }
  }
}

Regexp Query

使用正则表达式进行匹配。

{
  "query": {
    "regexp": {
      "name.first": "s.*y"
    }
  }
}

这只是Elasticsearch查询DSL中的一部分。Elasticsearch提供了许多其他查询类型,包括但不限于嵌套查询、has_child和has_parent查询、function_score查询等,以满足各种搜索和分析需求。

嵌套查询

嵌套查询(nested query)在Elasticsearch中用于查询嵌套文档。嵌套文档是一种特殊的数据结构,允许您将一组相关的子文档存储在一个父文档内部。由于嵌套文档在索引时被视为独立的文档,因此需要使用特定的查询方式来查询这些文档。

示例

假设您有以下的索引结构:

{
  "mappings": {
    "properties": {
      "user": {
        "type": "text"
      },
      "comments": {
        "type": "nested",
        "properties": {
          "name": {"type": "text"},
          "comment": {"type": "text"}
        }
      }
    }
  }
}

在这个结构中,comments字段是一个嵌套字段,它包含两个子字段:name和comment。

现在,假设您想查询名为"John"的用户中,评论内容包含"great"的所有文档。您可以使用以下的嵌套查询:

{
  "query": {
    "bool": {
      "must": [
        {"match": {"user": "John"}},
        {
          "nested": {
            "path": "comments",
            "query": {
              "match": {"comments.comment": "great"}
            }
          }
        }
      ]
    }
  }
}

在上述查询中,我们首先使用match查询来查找名为"John"的用户。然后,我们使用nested查询来查找comments字段中评论内容包含"great"的文档。注意,我们需要指定嵌套文档的路径(在这种情况下是comments)。

总的来说,嵌套查询允许您在嵌套文档中执行精确的查询,确保返回的结果满足所有指定的条件。

elasticsearch库方法介绍

以下是 github.com/elastic/go-elasticsearch/v8 库中的一些常用方法和属性的表格展示:

名称函数签名描述
NewClientfunc NewClient(cfg Config) (*Client, error)创建一个新的Elasticsearch客户端实例。
Indexfunc (c *Client) Index(index string, body io.Reader, o ...func(*IndexRequest) error) (*Response, error)索引一个文档到指定的索引中。
Getfunc (c *Client) Get(index, id string, o ...func(*GetRequest) error) (*Response, error)从指定索引中获取指定ID的文档。
Searchfunc (c *Client) Search(o ...func(*SearchRequest) error) (*Response, error)执行搜索查询,并返回搜索结果。
Deletefunc (c *Client) Delete(index, id string, o ...func(*DeleteRequest) error) (*Response, error)从指定索引中删除指定ID的文档。
Bulkfunc (c *Client) Bulk(body io.Reader, o ...func(*BulkRequest) error) (*Response, error)执行批量操作,如索引、更新和删除文档。
Updatefunc (c *Client) Update(index, id string, body io.Reader, o ...func(*UpdateRequest) error) (*Response, error)更新指定索引和ID的文档。
Pingfunc (c *Client) Ping(o ...func(*PingRequest) error) (*Response, error)检查Elasticsearch集群是否可用。
Infofunc (c *Client) Info(o ...func(*InfoRequest) error) (*Response, error)获取Elasticsearch集群的基本信息。
Cluster.Healthfunc (c *Client) Cluster.Health(o ...func(*ClusterHealthRequest) error) (*Response, error)获取Elasticsearch集群的健康状态。
Indices.Createfunc (c *Client) Indices.Create(name string, o ...func(*IndicesCreateRequest) error) (*Response, error)创建一个新的索引。
Indices.Deletefunc (c *Client) Indices.Delete(indices []string, o ...func(*IndicesDeleteRequest) error) (*Response, error)删除一个或多个索引。
Indices.Getfunc (c *Client) Indices.Get(index []string, o ...func(*IndicesGetRequest) error) (*Response, error)获取一个或多个索引的信息。

这些方法和属性提供了与Elasticsearch进行交互的基本工具,包括索引和查询文档、管理索引和检查集群状态等。

package main

import (
    "bytes"
    "encoding/json"
    "github.com/elastic/go-elasticsearch/v8"
    "log"
)

func main() {
    cfg := elasticsearch.Config{
        Addresses: []string{"http://localhost:9200"},
        //用户名、密码等
    }

    // 创建一个新的Elasticsearch客户端实例。
    client, err := elasticsearch.NewClient(cfg)
    if err != nil {
        log.Fatalf("Error creating the client: %s", err)
    }

    // 索引一个文档到指定的索引中。
    doc := map[string]interface{}{
        "title": "Go and Elasticsearch",
        "body":  "A short example of using Elasticsearch in Go.",
    }
    docBytes, _ := json.Marshal(doc)
    res, err := client.Index("posts", bytes.NewReader(docBytes))
    if err != nil {
        log.Fatalf("Error indexing document: %s", err)
    }
    // 输出:Indexing document response status: 201 Created

    // 从指定索引中获取指定ID的文档。
    res, err = client.Get("posts", "1")
    // 输出:Document details...

    // 执行搜索查询,并返回搜索结果。
    res, err = client.Search()
    // 输出:Search results...

    // 从指定索引中删除指定ID的文档。
    res, err = client.Delete("posts", "1")
    // 输出:Document deleted.

    // 执行批量操作,如索引、更新和删除文档。
    // 输出:Bulk operation results...

    // 更新指定索引和ID的文档。
    res, err = client.Update("posts", "1", bytes.NewReader(docBytes))
    // 输出:Document updated.

    // 检查Elasticsearch集群是否可用。
    res, err = client.Ping()
    // 输出:Cluster is available.

    // 获取Elasticsearch集群的基本信息。
    res, err = client.Info()
    // 输出:Cluster information...

    // 获取Elasticsearch集群的健康状态。
    res, err = client.Cluster.Health()
    // 输出:Cluster health status...

    // 创建一个新的索引。
    res, err = client.Indices.Create("new-index")
    // 输出:Index created.

    // 删除一个或多个索引。
    res, err = client.Indices.Delete([]string{"old-index"})
    // 输出:Indices deleted.

    // 获取一个或多个索引的信息。
    res, err = client.Indices.Get([]string{"new-index"})
    // 输出:Indices information...
}

elasticsearch.Config

以下是 elasticsearch.Config 结构中的所有参数的表格表示:

名称签名描述
AddressesAddresses []stringElasticsearch节点的URL列表。例如:["http://localhost:9200"]
UsernameUsername string用于基本HTTP身份验证的用户名。
PasswordPassword string用于基本HTTP身份验证的密码。
APIKeyAPIKey string用于API密钥身份验证的API密钥。
ServiceTokenServiceToken string用于服务令牌身份验证的服务令牌。
CloudIDCloudID stringElasticsearch云ID,用于指定Elasticsearch云部署。
TransportTransport http.RoundTripperHTTP传输对象,用于自定义HTTP请求的底层行为。
RetryBackoffRetryBackoff func(attempt int) time.Duration自定义重试回退函数,用于控制重试之间的延迟。
EnableRetryOnTimeoutEnableRetryOnTimeout bool指定是否在超时时重试请求。
MaxRetriesMaxRetries int设置请求的最大重试次数。
RetryOnStatusRetryOnStatus []int指定要重试的HTTP状态代码。
DisableRetryDisableRetry bool禁用请求重试。
EnableMetricsEnableMetrics bool启用度量收集。
MetricsIntervalMetricsInterval time.Duration用于记录度量的间隔。
ResponseHeadersResponseHeaders http.Header用于设置响应头。
TimeoutTimeout time.Duration设置HTTP请求的超时时间。
HeadersHeaders http.Header用于设置默认HTTP头。
CompressionLevelCompressionLevel int设置HTTP请求正文的压缩级别。
DiscoverNodesIntervalDiscoverNodesInterval time.Duration设置节点发现的间隔时间。
DiscoverNodesOnStartDiscoverNodesOnStart bool指定是否在启动时发现节点。
DiscoverNodesFrequencyDiscoverNodesFrequency int设置执行节点发现的频率。
ConnectionPoolIdleTimeoutConnectionPoolIdleTimeout time.Duration设置连接池中空闲连接的超时时间。
ConnectionPoolFuncConnectionPoolFunc ConnectionPool自定义连接池函数。
ResolversResolvers []Resolver自定义域名解析器。
EnableDebugLoggerEnableDebugLogger bool启用调试记录器。
LoggerLogger Logger自定义日志记录器。
CertificateAuthoritiesCertificateAuthorities []string用于TLS身份验证的证书颁发机构的路径。
CACertCACert []byte包含证书颁发机构的PEM编码证书。
InsecureSkipVerifyInsecureSkipVerify bool控制客户端是否验证服务器的证书链和主机名。如果设置为true,则不会验证。
RenegotiationRenegotiation tls.RenegotiationSupport指定TLS重协商的行为。
ServerNameServerName string用于TLS握手的服务器名称。可以用于SNI或验证服务器证书的CN字段。

这个表格提供了 elasticsearch.Config 结构中可用的所有字段的概述,并描述了它们的作用和用途。

package main

import (
	"github.com/elastic/go-elasticsearch/v8"
	"net/http"
	"time"
	"crypto/tls"
)

func main() {
	cfg := elasticsearch.Config{
		// Elasticsearch节点的URL列表,之所以是数组,因为可能有集群
		Addresses: []string{"http://localhost:9200"},

		// 用于基本HTTP身份验证的用户名和密码
		Username: "username",
		Password: "password",

		// 用于API密钥身份验证的API密钥
		APIKey: "your_api_key",

		// 用于服务令牌身份验证的服务令牌
		ServiceToken: "your_service_token",

		// Elasticsearch云ID
		CloudID: "your_cloud_id",

		// HTTP传输对象,自定义HTTP请求的底层行为
		Transport: http.DefaultTransport,
        Transport: &http.Transport{
			MaxIdleConnsPerHost:   10,
			ResponseHeaderTimeout: time.Second * 10, // 设置超时时间
		},

		// 自定义重试回退函数
		RetryBackoff: func(attempt int) time.Duration { return time.Second * time.Duration(attempt) },

		// 指定是否在超时时重试请求
		EnableRetryOnTimeout: true,

		// 设置请求的最大重试次数
		MaxRetries: 3,

		// 指定要重试的HTTP状态代码
		RetryOnStatus: []int{502, 503},

		// 禁用请求重试
		DisableRetry: false,

		// 启用度量收集
		EnableMetrics: true,

		// 用于记录度量的间隔
		MetricsInterval: time.Second * 10,

		// 用于设置响应头
		ResponseHeaders: http.Header{"Accept-Encoding": []string{"gzip"}},

		// 设置HTTP请求的超时时间
		Timeout: time.Second * 2,

		// 用于设置默认HTTP头
		Headers: http.Header{"Content-Type": []string{"application/json"}},

		// 设置HTTP请求正文的压缩级别
		CompressionLevel: 5,

		// 设置节点发现的间隔时间
		DiscoverNodesInterval: time.Minute * 5,

		// 指定是否在启动时发现节点
		DiscoverNodesOnStart: true,

		// 设置执行节点发现的频率
		DiscoverNodesFrequency: 3,

		// 设置连接池中空闲连接的超时时间
		ConnectionPoolIdleTimeout: time.Minute * 1,

		// 自定义连接池函数
		// ConnectionPoolFunc: ...

		// 自定义域名解析器
		// Resolvers: ...

		// 启用调试记录器
		EnableDebugLogger: true,

		// 自定义日志记录器
		// Logger: ...

		// 用于TLS身份验证的证书颁发机构的路径
		// CertificateAuthorities: ...

		// 包含证书颁发机构的PEM编码证书
		// CACert: ...

		// 控制客户端是否验证服务器的证书链和主机名
		InsecureSkipVerify: false,

		// 指定TLS重协商的行为
		Renegotiation: tls.RenegotiateFreelyAsClient,

		// 用于TLS握手的服务器名称
		ServerName: "server_name",
	}

	// 使用配置创建Elasticsearch客户端
	es, _ := elasticsearch.NewClient(cfg)

	// 执行Elasticsearch操作
	// ...
}

elasticsearch.Client

elasticsearch.Client 是 Elasticsearch Go 客户端的主要组件,它允许与 Elasticsearch 集群进行通信。以下是 elasticsearch.Client 的关键方法和属性的详细描述。

名称函数签名描述
NewClientNewClient(cfg elasticsearch.Config) (*Client, error)使用给定的配置创建一个新的 Elasticsearch 客户端
IndexIndex(index string, body io.Reader, opts ...func(IndexRequest)) ( Response, error)将文档添加到指定的索引中
SearchSearch(opts ...func(SearchRequest)) ( Response, error)在 Elasticsearch 中执行搜索请求
DeleteDelete(index, id string, opts ...func(DeleteRequest)) ( Response, error)从指定索引中删除文档
UpdateUpdate(index, id string, body io.Reader, opts ...func(UpdateRequest)) ( Response, error)更新指定索引中的文档
BulkBulk(body io.Reader, opts ...func(BulkRequest)) ( Response, error)执行批量操作,如索引、删除、更新等

代码

package main

import (
	"bytes"
	"encoding/json"
	"log"
	"strings"

	"github.com/elastic/go-elasticsearch/v8"
	"github.com/elastic/go-elasticsearch/v8/esapi"
)

func main() {
	// 创建新的 Elasticsearch 客户端
	cfg := elasticsearch.Config{
		Addresses: []string{"http://localhost:9200"},
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		log.Fatalf("Error creating client: %s", err)
	}

	// Index: 将文档添加到指定的索引中
	doc := map[string]interface{}{
		"title": "Go and Elasticsearch",
		"body":  "A short example.",
	}
	docBytes, _ := json.Marshal(doc)
	res, err := es.Index("posts", bytes.NewReader(docBytes))
	if err != nil {
		log.Fatalf("Error indexing document: %s", err)
	}
	defer res.Body.Close()

	// Search: 在 Elasticsearch 中执行搜索请求
	var buf strings.Builder
	query := map[string]interface{}{
		"query": map[string]interface{}{
			"match": map[string]interface{}{
				"title": "Go",
			},
		},
	}
	if err := json.NewEncoder(&buf).Encode(query); err != nil {
		log.Fatalf("Error encoding query: %s", err)
	}
	res, err = es.Search(es.Search.WithBody(&buf))
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()

	// Delete: 从指定索引中删除文档
	res, err = es.Delete("posts", "1")
	if err != nil {
		log.Fatalf("Error deleting document: %s", err)
	}
	defer res.Body.Close()

	// Update: 更新指定索引中的文档
	updateDoc := map[string]interface{}{
		"doc": map[string]interface{}{
			"title": "Updated Title",
		},
	}
	updateBytes, _ := json.Marshal(updateDoc)
	res, err = es.Update("posts", "1", bytes.NewReader(updateBytes))
	if err != nil {
		log.Fatalf("Error updating document: %s", err)
	}
	defer res.Body.Close()

	// Bulk: 执行批量操作
	bulkBody := strings.NewReader(`{ "index" : { "_index" : "posts", "_id" : "1" } }
{ "title" : "Bulk index" }
{ "delete" : { "_index" : "posts", "_id" : "2" } }`)
	res, err = es.Bulk(bulkBody)
	if err != nil {
		log.Fatalf("Error executing bulk request: %s", err)
	}
	defer res.Body.Close()
}

上述代码展示了 Elasticsearch 客户端的关键方法,如索引、搜索、删除、更新和批量操作。每个方法与 Elasticsearch 中的特定操作对应,并允许在 Go 代码中方便地与 Elasticsearch 集群进行交互。

esapi库方法介绍

github.com/elastic/go-elasticsearch/v8github.com/elastic/go-elasticsearch/v8/esapi 是 Elasticsearch 的 Go 客户端库,但它们在使用上有一些不同。

  • github.com/elastic/go-elasticsearch/v8 是一个较为高级的包,它提供了一些方便的方法,如 SearchIndexGetDelete 等,以及用于配置和创建 Elasticsearch 客户端的 ConfigNewClient 方法。这个包适用于大多数常见的用例,它隐藏了许多底层的复杂性。
  • github.com/elastic/go-elasticsearch/v8/esapi 则是一个较为底层的包,它提供了更直接、更具体的 Elasticsearch API 方法,如 IndexRequestGetRequestUpdateRequestDeleteRequest 等。这些方法通常需要更多的参数和配置,但同时也提供了更多的灵活性和控制。这个包适用于需要使用 Elasticsearch 更高级或更复杂功能的高级用户。

总的来说,如果你只是需要执行一些基本的 Elasticsearch 操作,如搜索、索引文档等,那么 github.com/elastic/go-elasticsearch/v8 包应该就能满足你的需求。但如果你需要更多的控制,或者需要使用 Elasticsearch 的一些特定的、高级的功能,那么你可能需要使用 github.com/elastic/go-elasticsearch/v8/esapi 包。

github.com/elastic/go-elasticsearch/v8/esapi 库为 Elasticsearch 提供了多个用于构建和执行请求的函数。以下是其中的一些函数:

名称函数签名描述
Indexfunc Index(index string, body io.Reader, o ...func(IndexRequest)) ( Response, error)Index 方法用于向指定索引添加或更新文档。
Getfunc Get(index string, id string, o ...func(GetRequest)) ( Response, error)Get 方法用于从指定索引获取文档。
Updatefunc Update(index string, id string, body io.Reader, o ...func(UpdateRequest)) ( Response, error)Update 方法用于更新指定索引的文档。
Deletefunc Delete(index string, id string, o ...func(DeleteRequest)) ( Response, error)Delete 方法用于删除指定索引的文档。
Searchfunc Search(o ...func(SearchRequest)) ( Response, error)Search 方法用于执行搜索请求。
Bulkfunc Bulk(body io.Reader, o ...func(BulkRequest)) ( Response, error)Bulk 方法用于执行批量操作,例如批量插入、更新或删除文档。
IndicesCreateRequestfunc NewIndicesCreateRequest(index string) *IndicesCreateRequestNewIndicesCreateRequest 创建一个新的 IndicesCreateRequest,它是用于创建索引的请求。参数 'index' 是需要创建的索引的名称。

请注意,上述函数签名中的 o ...func(*RequestType) 是一个可变参数,用于接收一个或多个函数,这些函数可以修改请求的配置。例如,可以使用 func(*IndexRequest) 类型的函数来设置 Index 请求的一些选项,如版本控制、超时等。

在下面的代码中,我首先创建了一个新的Elasticsearch客户端,然后分别演示了如何使用 esapi 包中的 IndexRequestGetRequestUpdateRequestDeleteRequestSearchBulkRequest 方法。这些方法都需要一个上下文参数和一个可选的 Elasticsearch 客户端参数。我在每个方法的前面都添加了注释,解释了每个方法的用途。

import (
	"bytes"
	"context"
	"encoding/json"
	"log"

	"github.com/elastic/go-elasticsearch/v8"
	"github.com/elastic/go-elasticsearch/v8/esapi"
)

func main() {
	// 连接Elasticsearch
	cfg := elasticsearch.Config{
		Addresses: []string{
			"http://localhost:9200",
		},
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		log.Fatalf("Error creating the client: %s", err)
	}

	// 插入文档
	// 使用 Index 方法向指定索引添加或更新文档
	doc := map[string]interface{}{
		"title": "Test",
		"body":  "This is a test document",
	}
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(doc); err != nil {
		log.Fatalf("Error encoding doc: %s", err)
	}
	res, err := esapi.IndexRequest{
		Index:      "test-index",
		DocumentID: "1",
		Body:       &buf,
	}.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()

	// 获取文档
	// 使用 Get 方法从指定索引获取文档
	getRes, err := esapi.GetRequest{
		Index:      "test-index",
		DocumentID: "1",
	}.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer getRes.Body.Close()

	// 更新文档
	// 使用 Update 方法更新指定索引的文档
	updateDoc := map[string]interface{}{
		"doc": map[string]interface{}{
			"title": "Updated Test",
		},
	}
	buf.Reset()
	if err := json.NewEncoder(&buf).Encode(updateDoc); err != nil {
		log.Fatalf("Error encoding doc: %s", err)
	}
	updateRes, err := esapi.UpdateRequest{
		Index:      "test-index",
		DocumentID: "1",
		Body:       &buf,
	}.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer updateRes.Body.Close()

	// 删除文档
	// 使用 Delete 方法删除指定索引的文档
	deleteRes, err := esapi.DeleteRequest{
		Index:      "test-index",
		DocumentID: "1",
	}.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer deleteRes.Body.Close()

	// 执行搜索请求
	// 使用 Search 方法执行搜索请求
	var r map[string]interface{}
	res, err = es.Search(
		es.Search.WithContext(context.Background()),
		es.Search.WithIndex("test-index"),
		es.Search.WithBody(&buf),
		es.Search.WithTrackTotalHits(true),
		es.Search.WithPretty(),
	)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()
	
	// 批量操作
	// 使用 Bulk 方法执行批量操作,例如批量插入、更新或删除文档。
	bulkBody := bytes.NewBufferString(`
	{ "index" : { "_index" : "test-index", "_id" : "1" } }
	{ "field1" : "value1" }
	{ "delete" : { "_index" : "test-index", "_id" : "2" } }
	`)
	bulkRes, err := esapi.BulkRequest{
		Body: bulkBody,
	}.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer bulkRes.Body.Close()

}

IndicesCreateRequest结构体

IndicesCreateRequest 是一个用于创建索引的 API 请求。其结构中包含的各个属性如下:

名称类型描述
Indexstring需要创建的索引的名称
Bodyio.Reader包含索引设置和映射的可选正文
Timeouttime.Duration一个可选的超时时间,当索引创建操作超过此时间时,请求将会被中断
MasterTimeouttime.Duration一个可选的超时时间,仅适用于等待主节点的响应
Prettybool是否需要美化 JSON 响应
Humanbool是否需要在响应中包含人类可读的单位(例如,字节,时间)
ErrorTracebool是否需要在响应中包含堆栈跟踪
FilterPath[]string一个字符串数组,用于过滤响应中的字段

下面的代码示例展示了如何使用 IndicesCreateRequest 来创建一个新的索引:

// 创建一个新的 IndicesCreateRequest
req := esapi.IndicesCreateRequest{
    Index: "my_index", // 索引名称
    //settings是索引设置 mappings是索引正文
    Body: strings.NewReader(`
        {
            "settings" : { 
                "number_of_shards" : 1
            },
            "mappings" : {
                "properties" : {
                    "field1" : { "type" : "text" }
                }
            }
        }
    `), // 包含索引设置和映射的正文
    Timeout: time.Duration(2) * time.Minute, // 超时时间
}

// 执行请求
//假定 es 是一个已经初始化的 Elasticsearch 客户端实例
res, err := req.Do(context.Background(), es)
if err != nil {
    log.Fatalf("Error getting response: %s", err)
}
defer res.Body.Close()

// 输出:响应的 HTTP 状态码,例如 "200 OK"

结构体的主要方法

名称函数签名描述
Do*func (r IndicesCreateRequest) Do(ctx context.Context, transport Transport) (Response, error)执行请求并返回响应
Pathfunc (r IndicesCreateRequest) Path() string返回请求的 URL 路径
Methodfunc (r IndicesCreateRequest) Method() string返回请求的 HTTP 方法
Bodyfunc (r IndicesCreateRequest) Body() io.Reader返回请求的主体内容
SetBody*func (r IndicesCreateRequest) SetBody(body io.Reader)设置请求的主体内容
WithPretty**func (r IndicesCreateRequest) WithPretty() IndicesCreateRequest设置请求的 'pretty' 参数,使得返回的 JSON 格式更易读
WithHuman**func (r IndicesCreateRequest) WithHuman() IndicesCreateRequest设置请求的 'human' 参数,使得返回的统计数据更易读
WithErrorTrace**func (r IndicesCreateRequest) WithErrorTrace() IndicesCreateRequest设置请求的 'error_trace' 参数,使得返回的错误信息包含堆栈跟踪
WithFilterPath**func (r IndicesCreateRequest) WithFilterPath(v ...string) IndicesCreateRequest设置请求的 'filter_path' 参数,用于过滤返回的 JSON 结构

代码

package main

import (
	"context"
	"fmt"
	"github.com/elastic/go-elasticsearch/v8"
	"github.com/elastic/go-elasticsearch/v8/esapi"
	"log"
	"strings"
)

func main() {
	cfg := elasticsearch.Config{
		Addresses: []string{
			"http://localhost:9200",
		},
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		log.Fatalf("Error creating the client: %s", err)
	}

	req := esapi.IndicesCreateRequest{
		Index: "test", // 设置索引名
        //number_of_shards,索引的分片数;number_of_replicas,索引的副本数
		Body:  strings.NewReader(`{"settings" : {"number_of_shards" : 1, "number_of_replicas" : 0}}`), // 设置请求体
	}

	// 执行请求并返回响应
	res, err := req.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()

	// 输出请求的 URL 路径
	fmt.Println("Path:", req.Path())
	// 输出请求的 HTTP 方法
	fmt.Println("Method:", req.Method())

    //WithPretty WithHuman WithErrorTrace这些方法没有参数
    
	// 设置请求的 'pretty' 参数
	req.WithPretty()
	// 设置请求的 'human' 参数
	req.WithHuman()
	// 设置请求的 'error_trace' 参数
	req.WithErrorTrace()
	// 设置请求的 'filter_path' 参数
	req.WithFilterPath("filterPath")

	// 输出响应体的字符串表示
	fmt.Println(res.String())
}

pretty human error_trace filter_path参数

这些参数都是 Elasticsearch 提供的一些 URL 参数,它们可以用于调整 Elasticsearch 返回的响应。下面是这些参数的具体说明:

  1. pretty: 该参数使 Elasticsearch 以更人性化的格式返回 JSON 或 YAML 响应。如果没有显式设置,Elasticsearch 会检查 HTTP 请求头的 Accept 字段,如果没有找到明确的指示,则默认返回压缩过的 JSON 格式。例如,如果你添加了 ?pretty 到 URL,响应会格式化为漂亮的 JSON 格式。
  2. human: 该参数使统计信息(如时间间隔、字节数等)更容易被人类理解。默认值为 false,此时所有的统计信息都以基本单位(毫秒、字节等)表示。如果设置为 true,Elasticsearch 会以单位(如小时、MB 等)格式化统计信息,使其更易于理解。
  3. error_trace: 该参数使 Elasticsearch 在出现错误时返回堆栈跟踪。默认为 false,如果设置为 true,响应体中将包含一个 error_trace 字段,其中包含出错时的堆栈跟踪信息。
  4. filter_path: 该参数可以过滤响应中的字段。这对于大型响应非常有用,例如在获取集群的状态时,你可能只对某些字段感兴趣。该参数可以接受一个由逗号分隔的字段列表,Elasticsearch 仅返回这些字段。你也可以使用通配符(*)来匹配字段名。

注意:这些参数的设置只会影响当前的请求,不会影响 Elasticsearch 客户端的其他请求。

IndexRequest结构体

以下是 IndexRequest 结构体的主要字段:

名称类型描述
Indexstring索引名称
DocumentIDstring文档的唯一标识
Bodyio.Reader要索引的文档的内容
Refreshstring控制何时刷新索引,以便新内容对搜索可见
Prettybool控制是否以更易读的格式返回响应
Timeouttime.Duration控制等待操作完成的时间
Versionint如果设置,Elasticsearch会确保版本与现有文档的版本匹配,否则拒绝请求
VersionTypestring控制如何处理版本冲突
OpTypestring控制索引操作的类型(例如,创建新文档或更新现有文档)
Pipelinestring如果设置,指定一个在索引文档之前应用的摄取管道
Routingstring控制将文档路由到哪个分片

请注意,不是所有的字段都需要在每次请求中设置。例如,DocumentID 是可选的。如果不设置,Elasticsearch会自动生成一个唯一的文档 ID。另外,Refresh 参数默认为 "false",这意味着新内容可能在一段时间后才对搜索可见。如果你希望新内容立即可见,可以将 Refresh 设置为 "true"。

同样,Pretty、Timeout、Version、VersionType、OpType、Pipeline 和 Routing 这些字段在许多情况下也是可选的,具体取决于你的具体需求和使用场景。

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"github.com/elastic/go-elasticsearch/v8"
	"github.com/elastic/go-elasticsearch/v8/esapi"
	"log"
	"strings"
	"time"
)

func main() {
	// 创建一个新的 Elasticsearch 客户端
	es, err := elasticsearch.NewDefaultClient()
	if err != nil {
		log.Fatalf("Error creating the client: %s", err)
	}

	// 索引文档的内容
	doc := map[string]interface{}{
		"title": "Go and Elasticsearch",
		"body":  "A short example of using Elasticsearch in Go.",
	}
	docBytes, _ := json.Marshal(doc)

	// 创建一个新的索引请求
	req := esapi.IndexRequest{
		Index:      "posts",   // 索引名称
		DocumentID: "1",  // 文档的唯一标识
		Body:       bytes.NewReader(docBytes), // 要索引的文档的内容
		Refresh:    "true", // 控制何时刷新索引,以便新内容对搜索可见
		Pretty:     true,  // 控制是否以更易读的格式返回响应
		Timeout:    time.Second * 5, // 控制等待操作完成的时间
		Version:    1, // 如果设置,Elasticsearch会确保版本与现有文档的版本匹配,否则拒绝请求
		VersionType: "external",  // 控制如何处理版本冲突
		OpType:     "create", // 控制索引操作的类型(例如,创建新文档或更新现有文档)
		Pipeline:   "my_ingest_pipeline", // 如果设置,指定一个在索引文档之前应用的摄取管道
		Routing:    "my_routing_value",  // 控制将文档路由到哪个分片
	}

	// 执行请求
	res, err := req.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()

	if res.IsError() {
		log.Printf("Error indexing document: %s", res.String())
	} else {
		// 解析响应
		var r map[string]interface{}
		if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
			log.Printf("Error parsing the response body: %s", err)
		} else {
			// 打印响应
			fmt.Println(strings.Repeat("=", 37))
			log.Printf("Document %s indexed successfully. Version: %v", r["_id"], r["_version"])
		}
	}
}

esapi.Response

IndicesCreateRequest.Do方法返回一个esapi.Response类型,该类型有下面的属性或方法

名称类型描述
StatusCodeintHTTP 状态码,如 200, 404 等
Headerhttp.HeaderHTTP 响应头,包含了如 Content-TypeContent-Length 等字段
Bodyio.ReadCloser响应体,包含了 Elasticsearch 返回的 JSON 数据
Stringfunc() string将响应体转换为字符串的方法
IsErrorfunc() bool判断响应是否为错误(HTTP 状态码 >= 400)的方法

代码示例

res, err := req.Do(context.Background(), es)
if err != nil {
    log.Fatalf("Error getting response: %s", err)
}
defer res.Body.Close()

// 输出响应的 HTTP 状态码
fmt.Println(res.StatusCode)

// 输出响应头中的 'content-type' 字段
fmt.Println(res.Header.Get("content-type"))

// 将响应体转换为字符串并输出
fmt.Println(res.String())

// 判断响应是否为错误并输出结果
fmt.Println(res.IsError())

elasticsearch.Client.Info()返回的res.Body中内容

{
  "name": "61ef1fa75d46", // 节点名称
  "cluster_name": "docker-cluster", // 集群的名称
  "cluster_uuid": "R_l7EQAJScSa7I5FIjN4tg", // 集群的唯一标识符
  "version": {
    "lucene_version": "9.6.0", // 使用的Lucene版本
    "minimum_index_compatibility_version": "7.0.0", // 最小的索引兼容版本
    "number": "8.8.1", // Elasticsearch的版本号
    "build_flavor": "default", // 构建的版本类型
    "build_hash": "f8edfccba429b6477927a7c1ce1bc6729521305e", // 构建的哈希值
    "minimum_wire_compatibility_version": "7.17.0", // 最小的线路兼容版本
    "build_type": "docker", // 构建的类型
    "build_date": "2023-06-05T21:32:25.188464208Z", // 构建的日期和时间
    "build_snapshot": false // 是否是快照版本
  }
}

esapi.ClusterHealthRequest.Do()返回的res.Body中内容

{
  "cluster_name": "docker-cluster", // 集群的名称
  "number_of_nodes": 1, // 集群中的节点数量
  "number_of_data_nodes": 1, // 专用数据节点的数量
  "relocating_shards": 0, // 正在重新定位的分片数量
  "delayed_unassigned_shards": 0, // 其分配已被超时设置延迟的分片数量
  "number_of_pending_tasks": 0, // 尚未执行的集群级更改的数量
  "status": "yellow", // 集群的健康状态(绿色、黄色或红色)
  "active_primary_shards": 25, // 活动的主分片数量
  "initializing_shards": 0, // 正在初始化的分片数量
  "number_of_in_flight_fetch": 0, // 未完成的获取的数量
  "timed_out": false, // 如果为false,则响应在由timeout参数指定的时间段内返回
  "active_shards": 25, // 活动的主分片和副本分片的总数
  "unassigned_shards": 1, // 未分配的分片数量
  "task_max_waiting_in_queue_millis": 0, // 自最早启动的任务等待执行以来的时间(以毫秒为单位)
  "active_shards_percent_as_number": 96.15384615384616 // 集群中活动分片的比率,以百分比表示
}