当前位置: 首页 > article >正文

Elasticsearch检索方案之一:使用from+size实现分页

      前面两篇文章介绍了elasticsearch以及Kibana的安装,检索引擎以及可视化工具都已经安装完成,接下来介绍下如何使用golang的sdk实现简单的分页查询。

1、下载Elastic官方golang sdk

在讲解elasticsearch检索之前,需要先把golang的环境安装好,go的安装可以参考官方文档,mac、linux下的安装都非常简单,参考以下文档:

go下载地址:https://go.dev/dl/
安装说明:https://go.dev/doc/install

安装完成后,把goproxy改成国内代理,执行以下命令:

go env -w GOPROXY=https://goproxy.cn,direct

下载elasticsearch官方golang包:

go get github.com/elastic/go-elasticsearch/v8

或者直接在代码里引入该包,之后通过命令:

go mod tidy

来下载各种依赖包。

2、测试数据说明

es中一共写入了11000条数据,doc_id字段标识了数据的索引,有助于说明本文分享的使用from+size参数的检索。

3、使用from+size实现分页检索

首先定义检索结果的基础数据结构:


// 最外层数据结构
type Documents struct {
  Shards   Shards      `json:"_shards"`
  Hits     HitOutLayer `json:"hits"`
  TimedOut bool        `json:"timed_out"`
  Took     int         `json:"took"`
}
type Shards struct {
  Failed     int `json:"failed"`
  Skipped    int `json:"skipped"`
  Successful int `json:"successful"`
  Total      int `json:"total"`
}
type HitOutLayer struct {
  Hits     []Hits  `json:"hits"`
  MaxScore float64 `json:"max_score"`
  Total    Total   `json:"total"`
}
type Hits struct {
  ID     string         `json:"_id"`
  Index  string         `json:"_index"`
  Score  float64        `json:"_score"`
  Source map[string]any `json:"_source"`
  Type   string         `json:"_type"`
}
type Total struct {
  Relation string `json:"relation"`
  Value    int    `json:"value"`
}

连接es,es8默认是通过https进行连接,因此在连接时需要设置证书,证书的位置在es路径下的config文件夹中:

 连接es的代码如下:

cert, _ := os.ReadFile("/Users/liupeng/Documents/study/elasticsearch-8.17.0/config/certs/http_ca.crt")
client, err := elasticsearch.NewClient(elasticsearch.Config{
  Username:  "elastic",
  Password:  "XBS=adqa799j_Aoz=A+h",
  Addresses: []string{"https://127.0.0.1:9200"},
  CACert:    cert,
})

构建检索dsl,跳过前面1000个文档,检索10条文档:

dslQuery := esbuilder.NewDsl()
boolQuery := esbuilder.NewBoolQuery()
boolQuery.Filter(esbuilder.NewRangeQuery("doc_id").Gte(1))
dslQuery.SetQuery(boolQuery)
dslQuery.SetFrom(1000)
dslQuery.SetSize(3)
dslQuery.SetOrder(esbuilder.NewSortQuery("doc_id", "asc"))
dsl := dslQuery.BuildJson()

构建dsl我使用了自研的构建dsl的库:

github.com/liupengh3c/esbuilder

主打一个方便,就不用开发者自行去拼凑dsl了,开发成本降低了许多。

执行检索过程,并把检索到的内容进行打印:

search := esapi.SearchRequest{
  Index: []string{"new_tag_202411"},
  Body:  strings.NewReader(dsl),
}
resp, err := search.Do(context.Background(), client)
if err != nil {
  fmt.Println("search err:", err.Error())
  return
}
json.NewDecoder(resp.Body).Decode(&docs)
strDoc, _ := json.MarshalToString(docs)
fmt.Println(strDoc)

理论上,应该打印doc_id从1001开始到1003的3条数据,打印出来的数据如下:

{
    "_shards": {
        "failed": 0,
        "skipped": 0,
        "successful": 1,
        "total": 1
    },
    "hits": {
        "hits": [
            {
                "_id": "JME17xx_adc_behavior_NOJUNCTION_STRAIGHT_1_1732982184160_1732982184860_1733030180041",
                "_index": "new_tag_202411",
                "_score": 0,
                "_source": {
                    "adc_behavior": "NOJUNCTION_STRAIGHT",
                    "car_id": "JME17xx",
                    "create_time": "2024-12-23 21:34:51",
                    "doc_id": 1001,
                    "end_time": 1732982184860,
                    "rule_id": 1,
                    "rule_priority": 1,
                    "rule_version": "2.1.0.0",
                    "start_time": 1732982184160,
                    "tag_additional_info": {
                        "adc_driving_mode": "COMPLETE_MANUAL",
                        "average_speed": 0.003893,
                        "driving_mode": 0,
                        "end_point": {
                            "x": 222294.311402,
                            "y": 3373118.699096,
                            "z": 14.721843
                        },
                        "start_point": {
                            "x": 222294.312234,
                            "y": 3373118.698359,
                            "z": 14.721373
                        }
                    },
                    "tag_name": "adc_behavior",
                    "user": "liupeng"
                },
                "_type": ""
            },
            {
                "_id": "JME18xx_adc_behavior_NOJUNCTION_STRAIGHT_1_1732982184140_1732982218540_1733030184257",
                "_index": "new_tag_202411",
                "_score": 0,
                "_source": {
                    "adc_behavior": "NOJUNCTION_STRAIGHT",
                    "car_id": "JME18xx",
                    "create_time": "2024-12-23 21:34:51",
                    "doc_id": 1002,
                    "end_time": 1732982218540,
                    "rule_id": 1,
                    "rule_priority": 1,
                    "rule_version": "2.1.0.0",
                    "start_time": 1732982184140,
                    "tag_additional_info": {
                        "adc_driving_mode": "COMPLETE_AUTO_DRIVE",
                        "average_speed": 3.86589,
                        "driving_mode": 1,
                        "end_point": {
                            "x": 219886.247841,
                            "y": 3374513.249021,
                            "z": 8.882025
                        },
                        "start_point": {
                            "x": 219576.290596,
                            "y": 3374298.199175,
                            "z": 9.130117
                        }
                    },
                    "tag_name": "adc_behavior",
                    "user": "liupeng"
                },
                "_type": ""
            },
            {
                "_id": "ARCFxx_adc_behavior_U_TURN_1_1732982184047_1732982227367_1733030187627",
                "_index": "new_tag_202411",
                "_score": 0,
                "_source": {
                    "adc_behavior": "U_TURN",
                    "car_id": "ARCFxx",
                    "create_time": "2024-12-23 21:34:52",
                    "doc_id": 1003,
                    "end_time": 1732982227367,
                    "rule_id": 1,
                    "rule_priority": 1,
                    "rule_version": "2.1.0.0",
                    "start_time": 1732982184047,
                    "tag_additional_info": {
                        "adc_driving_mode": "COMPLETE_AUTO_DRIVE",
                        "average_speed": 10.146669,
                        "driving_mode": 1,
                        "end_point": {
                            "x": 228474.086367,
                            "y": 3392800.40123,
                            "z": 8.736978
                        },
                        "start_point": {
                            "x": 228502.4916,
                            "y": 3392823.01595,
                            "z": 8.714573
                        }
                    },
                    "tag_name": "adc_behavior",
                    "user": "liupeng"
                },
                "_type": ""
            }
        ],
        "max_score": 0,
        "total": {
            "relation": "gte",
            "value": 10000
        }
    },
    "timed_out": false,
    "took": 2
}

结果的返回是符合预期的,perfect。

4、一个很重要的知识点

form+size的这种分页方式,是有前提的,就是只能检索10000条以内的数据,如果超过10000,那就不灵了,超过10000后,一条数据都不会返回,比如我们将设置from、size的代码改成如下:

dslQuery := esbuilder.NewDsl()
boolQuery := esbuilder.NewBoolQuery()
boolQuery.Filter(esbuilder.NewRangeQuery("doc_id").Gte(1))
dslQuery.SetQuery(boolQuery)
dslQuery.SetFrom(9998)
dslQuery.SetSize(3)
dslQuery.SetOrder(esbuilder.NewSortQuery("doc_id", "asc"))
dsl := dslQuery.BuildJson()

跳过前面9998条,检索3条数据出来,很明显,这样的检索条件是超过了10000条的,执行代码后,检索没有结果:

所以这种检索分页方式存在局限性,在10000条数据之内可以这么使用,但是一旦超过10000就会有问题,当然,我们也不用担心,elastic是有解决方案的,我会再单独写文章来介绍。

5、one more thing

我在自己的macbook上【在家里】安装了es,注意关键信息,是【在家里】安装的,上班的时候把自己电脑带到了公司,上班摸鱼的时候我打开了自己电脑,连接wifi,启动es、kibana,准备把公司mac上es的数据copy到自己mac上,问题来了,自己mac上kibnan启动不了了,启动不了了......,我很纳闷,啥情况?为啥启动不了了?难道是我替换了es文件夹下data的缘故吗?重新恢复为自己的data,问题依旧。

下班心想,还得把公司电脑上es的数据拷贝一份到自己mac上,以用来测试,就把公司mac带回家,连接家里的wifi,我满怀期待的打开电脑准备倒数据,结果公司电脑的kibana也启动不了,启动不了了,我的天哪,这是见鬼了吗,啥情况呢?自己电脑上的kibana能启动了,这是在外面认生吗,得回到自己家才能启动,这是问题1,这个时候我心想,公司电脑上kibana启动不了就算了,我直接用自己mac跑代码读取公司mac上es的数据,然后写入到本地的es上,问题又来了,竟然连接不上,提示证书授权的ip没有当前ip,这完蛋了,这是问题2。

于是我就停下来,大概想了一下问题的始末,大概明白了,es8的安全证书对网络加了校验,既然我通过自己mac连接不上公司mac,现在所处的网络是家里,那就说明自己mac的证书是有效的,于是就调换一下,从公司mac上跑代码,读取本机,写入到自己mac上,结果还真成了。

但是kibana更换网络无法启动的问题还未解决,这个问题不解决,也就是说只要不是在家里,kibana就打不开,es不能做的这么傻吧,于是今天到公司后,请教了elastic中国社区首席布道师刘晓国老师,刘老师非常热情的直接电话过来,解释了kibana无法打开的原因以及解决办法,在这里对刘老师的帮助表达诚挚感谢,非常感谢刘老师。推荐大家看看刘老师的博客:Elastic:开发者上手指南-CSDN博客

接下来我们说kibana无法打开的原因,还是证书问题,kibana在启动时,对本机的ip做了校验,更换网络后,ip发生变更,因此kibana无法启动,kibana的配置文件中有这么一行代码:

我们只要这行代码更改为以下即可:

elasticsearch.hosts: ['https://localhost:9200']

es8的证书除了对本机的实际ip做了校验,也对localhost做了验证,因此,只要改成localhost问题就会解决,即使更换网络,localhost是不会改变的。

另外再更换网络的情况下,如果要通过未更换网络的机器连接更换网络的机器,只有一个办法,那就是在当前网络下更新证书,除此别无他法。

6、所有代码

package main

import (
  "context"
  "fmt"
  "os"
  "strings"
  "time"

  "github.com/elastic/go-elasticsearch/v7/esapi"
  "github.com/elastic/go-elasticsearch/v8"
  jsoniter "github.com/json-iterator/go"
  "github.com/liupengh3c/esbuilder"
)

// 最外层数据结构
type Documents struct {
  Shards   Shards      `json:"_shards"`
  Hits     HitOutLayer `json:"hits"`
  TimedOut bool        `json:"timed_out"`
  Took     int         `json:"took"`
}
type Shards struct {
  Failed     int `json:"failed"`
  Skipped    int `json:"skipped"`
  Successful int `json:"successful"`
  Total      int `json:"total"`
}
type HitOutLayer struct {
  Hits     []Hits  `json:"hits"`
  MaxScore float64 `json:"max_score"`
  Total    Total   `json:"total"`
}
type Hits struct {
  ID     string         `json:"_id"`
  Index  string         `json:"_index"`
  Score  float64        `json:"_score"`
  Source map[string]any `json:"_source"`
  Type   string         `json:"_type"`
}
type Total struct {
  Relation string `json:"relation"`
  Value    int    `json:"value"`
}

func main() {
  SearchFromSize()
}

func SearchFromSize() {
  st := time.Now()
  defer func() {
    fmt.Println("cost:", time.Since(st).Milliseconds(), "ms")
  }()
  var json = jsoniter.ConfigCompatibleWithStandardLibrary
  docs := Documents{}
  cert, _ := os.ReadFile("/Users/liupeng/Documents/study/elasticsearch-8.17.0/config/certs/http_ca.crt")
  client, err := elasticsearch.NewClient(elasticsearch.Config{
    Username:  "elastic",
    Password:  "xpE4DQGWE9bCkoj7WXYE",
    Addresses: []string{"https://127.0.0.1:9200"},
    CACert:    cert,
  })

  if err != nil {
    fmt.Println("create client err:", err.Error())
    return
  }

  dslQuery := esbuilder.NewDsl()
  boolQuery := esbuilder.NewBoolQuery()
  boolQuery.Filter(esbuilder.NewRangeQuery("doc_id").Gte(1))
  dslQuery.SetQuery(boolQuery)
  dslQuery.SetFrom(9998)
  dslQuery.SetSize(3)
  dslQuery.SetOrder(esbuilder.NewSortQuery("doc_id", "asc"))
  dsl := dslQuery.BuildJson()
  search := esapi.SearchRequest{
    Index: []string{"new_tag_202411"},
    Body:  strings.NewReader(dsl),
  }
  resp, err := search.Do(context.Background(), client)
  if err != nil {
    fmt.Println("search err:", err.Error())
    return
  }
  json.NewDecoder(resp.Body).Decode(&docs)
  strDoc, _ := json.MarshalToString(docs)
  fmt.Println(strDoc)
}

 最后祝各位同学圣诞节快乐~~~~~~~~~~~~~


http://www.kler.cn/a/456837.html

相关文章:

  • 【信息系统项目管理师】高分论文:论信息系统项目的沟通管理(银行绩效考核系统)
  • private static final Logger log = LoggerFactory.getLogger()和@Slf4j的区别
  • ubuntu 使用samba与windows共享文件[注意权限配置]
  • Linux Shell : Process Substitution
  • VSCode快捷键Ctrl+/是注释;Ctrl+\是拆分编辑器;Ctrl+w是关闭编辑器
  • 如何使用MySQL的group_concat函数快速做关联查询?
  • 基于Oauth2的SSO单点登录---前端
  • .NET周刊【12月第3期 2024-12-15】
  • 深入解析Android Framework中的android.location包:架构设计、设计模式与系统定制
  • TensorFlow深度学习实战(2)——使用TensorFlow构建神经网络
  • 一篇文章了解 Kafka
  • 深度学习训练过程图表可视化工具总结
  • Python+Django 技术实现自动化漏洞扫描系统开发
  • Java 网络原理 ①-IO多路复用 || 自定义协议 || XML || JSON
  • DP之背包基础
  • 出海隐私合规解决方案,一文助力中企合规出海
  • Docker安装MongoDB
  • Matrix-Breakout 2 Morpheus靶场
  • MIT实验 页表(实验部分)
  • ADC(三):注入组的使用
  • 科技创新 数智未来|清科·沙丘投研院走进竹云
  • 【zookeeper核心源码解析】第四课:客户端与服务端读写的io核心流程
  • 在STM32F103xx performance line block diagram找不到某一个外设所挂载在那条总线怎么办?
  • Docker 安装全攻略:从入门到上手
  • 快云服务器小助手getdetail存在任意文件文件读取漏洞
  • 普通部署redis伪集群模式