Golang ElasticSearch库使用 Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎。所以可以通过它部署自己的搜索服务,数据分析的话暂时好像还用不到。
下载官方的库
1 go get -u github.com/olivere/elastic
定义自己的索引
文档类型:
keyword: 表示精确值不会参与分词
text: 会通过分词插件或分词模式对字段进行分词,对中文来说,比较常用的是ik分词器,看了下效果,确实不错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // 索引mapping定义,这里仿微博消息结构定义 const mapping = ` { "mappings": { "properties": { "user": { "type": "keyword" }, "message": { "type": "text" }, "image": { "type": "keyword" }, "created": { "type": "date" }, "tags": { "type": "keyword" }, "location": { "type": "geo_point" }, "suggest_field": { "type": "completion" } } } }
创建客户端client:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func main() { // 创建client client, err := elastic.NewClient( elastic.SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"), elastic.SetBasicAuth("user", "secret") // 设置监控检查时间间隔 elastic.SetHealthcheckInterval(10*time.Second), // 设置请求失败最大重试次数 elastic.SetMaxRetries(5), // 设置错误日志输出 elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC ", log.LstdFlags)), // 设置info日志输出 elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)) ) if err != nil { // Handle error fmt.Printf("连接失败: %v\n", err) } else { fmt.Println("连接成功") } // ! 执行ES请求需要提供一个上下文对象 ctx := context.Background() // 首先检测下你定义的索引是否存在 exists, err := client.IndexExists("yourIndex").Do(ctx) if err != nil { // Handle error panic(err) } if !exists { // 你的索引不存在,则创建一个 _, err := client.CreateIndex("yourIndex").BodyString(mapping).Do(ctx) if err != nil { // Handle error panic(err) } } }
查询,插入,更新,删除这些操作比较简单,也不是我们的用elastic的重点,所以这里主要介绍下 查询操作。
elastic的文档元数据:
_index - 代表当前JSON文档所属的文档名字
_type - 代表当前JSON文档所属的类型,虽然新版ES废弃了type的用法,但是元数据还是可以看到。
_id - 文档唯一Id, 如果我们没有为文档指定id,系统会自动生成
_source - 代表我们插入进去的JSON数据
_version - 文档的版本号,每修改一次文档数据,字段就会加1, 这个字段新版的ES已经不使用了
_seq_no - 文档的版本号, 替代老的_version字段
_primary_term - 文档所在主分区,这个可以跟_seq_no字段搭配实现乐观锁。
通过 term 查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // 执行ES请求需要提供一个上下文对象 ctx := context.Background() // 创建term查询条件,用于精确查询 //第一个参数是查询字段,第二个是要匹配的值 termQuery := elastic.NewTermQuery("Content", keywords) searchResult, err := client.Search(). Index("blogs"). // 设置索引名 Query(termQuery). // 设置查询条件 Sort("Created", true). // 设置排序字段,根据Created字段升序排序,第二个参数false表示逆序 From(0). // 设置分页参数 - 起始偏移量,从第0行记录开始 Size(10). // 设置分页参数 - 每页大小 Pretty(true). // 查询结果返回可读性较好的JSON格式 Do(ctx) // 执行请求 if err != nil { // Handle error panic(err) } fmt.Printf("查询消耗时间 %d ms, 结果总数: %d\n", searchResult.TookInMillis, searchResult.TotalHits()) if searchResult.TotalHits() > 0 { // 查询结果不为空,则遍历结果 var b1 Article // 通过Each方法,将es结果的json结构转换成struct对象 for _, item := range searchResult.Each(reflect.TypeOf(b1)) { // 转换成Article对象 if t, ok := item.(Article); ok { fmt.Println(t.Title) } } } }
对于client.Search()方法会返回一个 SearchResult 结构体,它有两个方法:
func (r *SearchResult) Each(typ reflect.Type) []interface{}
官方解释是它能迭代所有的命中,这样你就不用去判断nil。所以我们可以用这个方法加上类型断言 ,将搜索出来的结果赋值给我们定义的结构体。
eg:
1 2 3 4 5 6 7 8 var ttyp YourStruct var ttypes []YourStruct for _, item := range searchResult.Each(reflect.TypeOf(ttyp)) { t := item.(YourStruct) fmt.Printf("YourStruct by %s: %s\n", t.field1, t.field2) ttypes.append(ttypes, t) }
func (r *SearchResult) TotalHits() int64
返回你搜索结果命中的数量,可以在循环之前进行一个判断,很简单。
另外很重要的一点就是 SearchResult
有一个字段叫 Hits
, 它是一个 *SearchHits
类型, 而 SearchHits
也包含一个字段 Hits
,不同的是它是 *SearchHit
类型的数组,我们继续看到 SearchHit
就比较明显了,它包含了我们上面介绍的所有文档元数据字段,还包含一个 Source
字段,这个字段是 json.RawMessage
即查询的json格式的数据,那么我们就可以通过 Unmarshal
方法赋给我们定义的结构体。
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 // Iterate through results for _, hit := range searchResult.Hits.Hits { // hit.Index contains the name of the index // Deserialize hit.Source into your self-defined struct (could also be just a map[string]interface{}). var t YourStruct err := json.Unmarshal(*hit.Source, &t) if err != nil { // Deserialization failed } fmt.Printf("YourStruct by %s: %s\n", t.field1, t.field2) }
参考资料 梯子网的,感觉很不错
官方的,不用说,就是英语看的有点累