使用netstat -lntp来看看有侦听在网络某端口的进程。当然,也可以使用 lsof。

再提IndexOrDocValuesQuery的问题

ChrisLu 回复了问题 • 2 人关注 • 1 个回复 • 1128 次浏览 • 2023-08-10 16:53 • 来自相关话题

删除一个并不存在的文档

Charele 回复了问题 • 2 人关注 • 2 个回复 • 1324 次浏览 • 2023-08-15 11:53 • 来自相关话题

这种关于聚合bucket说法对吗?

回复

Charele 发起了问题 • 1 人关注 • 0 个回复 • 1551 次浏览 • 2023-08-05 11:48 • 来自相关话题

ES中加入陈旧doc是什么意思

kin122 回复了问题 • 2 人关注 • 1 个回复 • 1156 次浏览 • 2023-08-08 14:46 • 来自相关话题

执行refresh的时候会存盘吗?

Charele 回复了问题 • 4 人关注 • 10 个回复 • 1365 次浏览 • 2023-08-15 14:18 • 来自相关话题

elasticsearch 奇怪的慢查询

yangmf2040 回复了问题 • 6 人关注 • 5 个回复 • 1530 次浏览 • 2023-08-17 00:42 • 来自相关话题

Scroll查询实现的机制是什么

Charele 回复了问题 • 4 人关注 • 8 个回复 • 1646 次浏览 • 2023-08-09 11:49 • 来自相关话题

都不取数据了,还要排序吗?

Ombres 回复了问题 • 2 人关注 • 1 个回复 • 1287 次浏览 • 2023-08-01 08:15 • 来自相关话题

ES7.9.3写入数据一段时间后提示Data is large

Charele 回复了问题 • 2 人关注 • 1 个回复 • 1312 次浏览 • 2023-09-19 02:19 • 来自相关话题

有关于ES分片的主副问题

emmning 回复了问题 • 2 人关注 • 1 个回复 • 1220 次浏览 • 2023-07-27 11:14 • 来自相关话题

如何在ES中搜索值为空的键值对

liaosy 发表了文章 • 0 个评论 • 2450 次浏览 • 2023-07-24 18:19 • 来自相关话题

问题背景


今天早上,接到开发那边一个特殊的查询需求,在 Kibana 中搜索一个 json 类型日志中值为一个空大括号的键值对, 具体的日志示例如下:

aidl<br /> {<br /> "clientIp": "10.111.121.51",<br /> "query": "{}",<br /> "serviceUrl": "/aaa/bbb/cc",<br /> }<br />

也就是说针对这个类型的日志过滤出 query 值为空的请求 "query": "{}", 开发同学测试了直接在 kibana 中查询这个字符串 "query": "{}" 根本查不到我们想要的结果。
我们使用的是 ELK 8.3 的全家桶, 这个日志数据使用的默认 standard analyzer 的分词器。

初步分析


我们先对这个要查询的字符串进行下分词测试:

aidl<br /> GET /_analyze<br /> {<br /> "analyzer" : "standard",<br /> "text": "\"query\":\"{}\""<br /> }<br /> <br />

结果不出所料,我们想要空大括号在分词的时候直接就被干掉了,仅保留了 query 这一个 token:

aidl<br /> {<br /> "tokens": [{<br /> "token": "query",<br /> "start_offset": 1,<br /> "end_offset": 6,<br /> "type": "<ALPHANUM>",<br /> "position": 0<br /> }]<br /> }<br />

我们使用的 standard analyzer 在数据写入分词时直接抛弃掉{}等特殊字符,看来直接搜索 "query": "{}" 关键词这条路肯定是走不通。

换个思路


在网上搜索了一下解决的办法,有些搜索特殊字符的办法,但需要修改分词器,我们已经写入的日志数据量比较大,不太愿意因为这个搜索请求来修改分词器再 reindex。 但是我们的日志格式是固定的,serviceUrl 这个键值对总是在 query 后面的,那么我们可以结合前后文实现相同的
搜索效果:

aidl<br /> GET /_analyze<br /> {<br /> "analyzer" : "standard",<br /> "text": "\"query\":\"{}\",\"serviceUrl\""<br /> }<br /> <br />

可以看到这段被分为 2 个相邻的单词

aidl<br /> {<br /> "tokens": [{<br /> "token": "query",<br /> "start_offset": 1,<br /> "end_offset": 6,<br /> "type": "<ALPHANUM>",<br /> "position": 0<br /> },<br /> {<br /> "token": "serviceurl",<br /> "start_offset": 14,<br /> "end_offset": 24,<br /> "type": "<ALPHANUM>",<br /> "position": 1<br /> }<br /> ]<br /> }<br /> <br />

那么通过搜索 query 和 serviceUrl 为相邻的 2 个字是完全可以实现 query 的值为空的同样的查询效果。
为了确认在我们已经写入的数据中 query 和 serviceurl 也是相邻的,我们通过 ES termvectors API 确认了已经在 es 中的数据和我们这里测试的情况相同:

aidl<br /> GET /<index>/_termvectors/<_id>?fields=message<br /> <br /> "query" : {<br /> "term_freq" : 1,<br /> "tokens" : [<br /> {<br /> "position" : 198,<br /> "start_offset" : 2138,<br /> "end_offset" : 2143<br /> }<br /> ]<br /> },<br /> "serviceurl" : {<br /> "term_freq" : 1,<br /> "tokens" : [<br /> {<br /> "position" : 199,<br /> "start_offset" : 2151,<br /> "end_offset" : 2161<br /> }<br /> ]<br /> },<br />

这里我们可以看到 query 在 message 字段里面出现一次,其 end_offset 和 serviceurl 的 start_offset 之前也是相差 8, 和我们测试的结果相同。
这个时候我们就将原来的查询需求,转化为了对 "query serviceurl" 进行按顺序的精准查询就行了, 使用 match_phrase 可以达到我们的目的。

aidl<br /> GET /_search<br /> "query": {<br /> "match_phrase": {<br /> "message": {<br /> "query": "query serviceurl",<br /> "slop" : 0<br /> <br /> }<br /> }<br /> }<br />

这里顺便说一下,slop 这个参数,slop=n 表示,表示可以隔 n 个字(英文词)进行匹配, 这里设置为 0 就强制要求 query 和 serviceurl 这 2 个单词必须相邻,0 也是 slop 的默认值,在这个请求中是可以省略的,这是为什么 match_phrase 是会获得精准查询的原因之一。
好了,我们通过 console 确定了有效的 query 之后,对于开发同学查看日志只需要在 Kibana 的搜索栏中直接使用双引号引起来的精确搜索 "query serviceurl" 就可以了。

继续深挖一下,ngram 分词器


虽然开发同学搜索的问题解决了,但我仍然不太满意,毕竟这次的问题我们的日志格式是固定的,如果我们一定要搜索到 "query": "{}" 这个应该怎么办呢? 首先很明确,使用我们默认的 standard analyzer 不修改任何参数肯定是不行的,"{}" 这些特殊字符都直接被干掉了,
参考了网上找到的这篇文章,https://blog.csdn.net/fox_233/ ... 88058 按照这个 ngram 分词器的思路,我动手对我们的需求进行了下测试

首先先看看我们使用 ngram 分词器的分词效果, 我们这里简化了一下,去掉了原来的双引号,以避免过多 \:

aidl<br /> GET _analyze<br /> {<br /> "tokenizer": "ngram",<br /> "text": "query:{}"<br /> }<br /> <br /> {<br /> "tokens" : [<br /> {<br /> "token" : "q",<br /> "start_offset" : 0,<br /> "end_offset" : 1,<br /> "type" : "word",<br /> "position" : 0<br /> },<br /> ...<br /> {<br /> "token" : "{",<br /> "start_offset" : 6,<br /> "end_offset" : 7,<br /> "type" : "word",<br /> "position" : 12<br /> },<br /> {<br /> "token" : "{}",<br /> "start_offset" : 6,<br /> "end_offset" : 8,<br /> "type" : "word",<br /> "position" : 13<br /> },<br /> {<br /> "token" : "}",<br /> "start_offset" : 7,<br /> "end_offset" : 8,<br /> "type" : "word",<br /> "position" : 14<br /> }<br /> ]<br /> }<br />

可以很明显的看到大括号被成功的分词了,果然是有戏。 直接定义一个 index 实战一下搜索效果

aidl<br /> PUT specialchar_debug<br /> {<br /> "settings": {<br /> "analysis": {<br /> "analyzer": {<br /> "specialchar_analyzer": {<br /> "tokenizer": "specialchar_tokenizer"<br /> }<br /> },<br /> "tokenizer": {<br /> "specialchar_tokenizer": {<br /> "type": "ngram",<br /> "min_gram": 1,<br /> "max_gram": 2<br /> }<br /> }<br /> }<br /> },<br /> "mappings": {<br /> "properties": {<br /> "text": {<br /> "analyzer": "specialchar_analyzer",<br /> "type": "text"<br /> }<br /> }<br /> }<br /> }<br />

插入几条测试数据:

aidl<br /> PUT specialchar_debug/_doc/1<br /> { "text": "query:{},serviceUrl"<br /> }<br /> <br /> PUT specialchar_debug/_doc/2<br /> { "text": "query:{aaa},serviceUrl"<br /> }<br /> <br /> PUT specialchar_debug/_doc/3<br /> { "text": "query:{bbb}, ccc, serviceUrl"<br /> }<br />

我们再测试一下搜索效果,

aidl<br /> GET specialchar_debug/_search<br /> {<br /> "query": {<br /> "match_phrase": {<br /> "text": "query:{}"<br /> }<br /> }<br /> }<br />

结果完全是我们想要的,看来这个方案可行

aidl<br /> "hits" : [<br /> {<br /> "_index" : "specialchar_debug",<br /> "_id" : "1",<br /> "_score" : 2.402917,<br /> "_source" : {<br /> "text" : "query:{},serviceUrl"<br /> }<br /> }<br /> ]<br />

小结


对于日志系统,我们一直在使用 ES 默认的 standard analyzer 的分词器, 基本上满足我们生产遇到的 99% 的需求,但面对特殊字符的这种搜索请求,确实比较无奈。这次遇到的空键值对的需求,我们通过搜索 2 个相邻的键绕过了问题。
如果一定要搜索这个字符串的话,我们也可以使用 ngram 分词器重新进行分词再进行处理, 条条大路通罗马。


作者介绍

卞弘智,研发工程师,10 多年的 SRE 经验,工作经历涵盖 DevOps,日志处理系统,监控和告警系统研发,WAF 和网关等系统基础架构领域,致力于通过优秀的开源软件推动自动化和智能化基础架构平台的演进。

这段ES英文如何正确的理解啊?

Charele 回复了问题 • 3 人关注 • 4 个回复 • 1221 次浏览 • 2023-07-31 17:11 • 来自相关话题

使用script类型的pipeline时报错

I_Like 回复了问题 • 2 人关注 • 2 个回复 • 1095 次浏览 • 2023-07-21 22:07 • 来自相关话题

用极限网关实现ES容灾,简单!

yangmf2040 发表了文章 • 0 个评论 • 2709 次浏览 • 2023-07-20 10:33 • 来自相关话题

身为 IT 人士,大伙身边的各种系统肯定不少吧。系统虽多,但最最最重要的那套、那几套,大伙肯定是捧在手心,关怀备至。如此重要的系统,万一发生故障了且短期无法恢复,该如何保障业务持续运行?
有过这方面思考或经验的同学,肯定脱口而出--切灾备啊。
是的,接下来我来介绍下我们的 ES 灾备方案。当然如果你有更好的,请使用各种可用的渠道联系我们。

总体设计


通过[极限网关](https://www.infinilabs.com/docs/latest/gateway/)将应用对主集群的写操作,复制到灾备集群。应用发送的读请求则直接转发到主集群,并将响应结果转发给应用。应用对网关无感知,访问方式与访问 ES 集群一样。

方案优势


  • 轻量级

    极限网关使用 Golang 编写,安装包很小,只有 10MB 左右,没有任何外部环境依赖,部署安装都非常简单,只需要下载对应平台的二进制可执行文件,启动网关程序的二进制程序文件执行即可。

  • 跨版本支持

    极限网关针对不同的 Elasticsearch 版本做了兼容和针对性处理,能够让业务代码无缝的进行适配,后端 Elasticsearch 集群版本升级能够做到无缝过渡,降低版本升级和数据迁移的复杂度。

  • 高可用

    极限网关内置多种高可用解决方案,前端请求入口支持基于虚拟 IP 的双机热备,后端集群支持集群拓扑的自动感知,节点上下线能自动发现,自动处理后端故障,自动进行请求的重试和迁移。

  • 灵活性

    主备集群都是可读可写,切换迅速,只需切换网关到另一套配置即可。回切灵活,恢复使用原配置即可。

    架构图


    ![](https://www.infinilabs.com/img ... /1.png)

    网关程序部署


    下载


    根据操作系统和平台选择下面相应的[安装包](https://release.infinilabs.com/gateway/stable/):
    解压到指定目录:

    <br /> mkdir gateway<br /> tar -zxf xxx.gz -C gateway<br />

    修改网关配置


    在此[ 下载 ](https://raw.githubusercontent. ... ch.yml)网关配置,默认网关会加载配置文件 gateway.yml ,如果要指定其他配置文件使用 -config 选项指定。
    网关配置文件内容较多,下面展示必要部分。

    ```

    primary

    PRIMARY_ENDPOINT: http://192.168.56.3:7171
    PRIMARY_USERNAME: elastic
    PRIMARY_PASSWORD: password
    PRIMARY_MAX_QPS_PER_NODE: 10000
    PRIMARY_MAX_BYTES_PER_NODE: 104857600 #100MB/s
    PRIMARY_MAX_CONNECTION_PER_NODE: 200
    PRIMARY_DISCOVERY_ENABLED: false
    PRIMARY_DISCOVERY_REFRESH_ENABLED: false

    backup

    BACKUP_ENDPOINT: http://192.168.56.3:9200
    BACKUP_USERNAME: admin
    BACKUP_PASSWORD: admin
    BACKUP_MAX_QPS_PER_NODE: 10000
    BACKUP_MAX_BYTES_PER_NODE: 104857600 #100MB/s
    BACKUP_MAX_CONNECTION_PER_NODE: 200
    BACKUP_DISCOVERY_ENABLED: false
    BACKUP_DISCOVERY_REFRESH_ENABLED: false
    ```

    PRIMARY_ENDPOINT:配置主集群地址和端口
    PRIMARY_USERNAME、PRIMARY_PASSWORD: 访问主集群的用户信息
    BACKUP_ENDPOINT:配置备集群地址和端口
    BACKUP_USERNAME、BACKUP_PASSWORD: 访问备集群的用户信息

    运行网关


    前台运行
    直接运行网关程序即可启动极限网关了,如下:

    <br /> ./gateway-linux-amd64<br />

    后台运行

    <br /> ./gateway-linux-amd64 -service install<br /> Success<br /> ./gateway-linux-amd64 -service start<br /> Success<br />

    卸载服务

    <br /> ./gateway-linux-amd64 -service stop<br /> Success<br /> ./gateway-linux-amd64 -service uninstall<br /> Success<br />

    灾备功能测试


    在灾备场景下,为保证数据一致性,对集群的访问操作都通过网关进行。注意只有 bulk API 的操作才会被复制到备集群。
    在此次测试中,网关灾备配置功能为:

  • 主备集群正常时

    读写请求正常执行;
    写请求被记录到队列,备集群实时消费队列数据。

  • 当主集群故障时

    写入请求报错,主备集群都不写入数据;
    查询请求转到备集群执行,并返回结果给客户端。

  • 当备集群故障时

    读写请求都正常执行;
    写操作记录到磁盘队列,待备集群恢复后,自动消费队列数据直到两个集群一致。

    主备集群正常时写入、查询测试


    写入数据


    ```

    通过网关写入数据

    curl -X POST "localhost:18000/_bulk?pretty" -H 'Content-Type: application/json' -uelastic:password -d'
    { "index" : { "_index" : "test", "_id" : "1" } }
    { "field1" : "value1" }
    { "create" : { "_index" : "test", "_id" : "2" } }
    { "field2" : "value2" }
    '
    ```

    ![](https://www.infinilabs.com/img ... /2.png)

    查询数据


    ```

    查询主集群

    curl 192.168.56.3:7171/test/_search?pretty -uelastic:password
    <br /> <br /> ![](<a href="https://www.infinilabs.com/img/blog/2023/backup-system-with-gatewy/3.pn" rel="nofollow" target="_blank">https://www.infinilabs.com/img ... /3.pn</a>g)<br /> <br />

    查询备集群

    curl 192.168.56.3:9200/test/_search?pretty -uadmin:admin
    <br /> <br /> ![](<a href="https://www.infinilabs.com/img/blog/2023/backup-system-with-gatewy/4.pn" rel="nofollow" target="_blank">https://www.infinilabs.com/img ... /4.pn</a>g)<br /> <br />

    查询网关,网关转发给主集群执行

    curl 192.168.56.3:18000/test/_search?pretty -uelastic:password
    ```

    ![](https://www.infinilabs.com/img ... /5.png)

    主备集群都已写入数据,且数据一致。通过网关查询,也正常返回。

    删除和更新文档


    ```

    通过网关删除和更新文档

    curl -X POST "192.168.56.3:18000/_bulk?pretty" -H 'Content-Type: application/json' -uelastic:password -d'
    { "delete" : { "_index" : "test", "_id" : "1" } }
    { "update" : {"_id" : "2", "_index" : "test"} }
    { "doc" : {"field2" : "value2-updated"} }
    '
    ```

    ![](https://www.infinilabs.com/img ... /6.png)

    查询数据


    ```

    查询主集群

    curl 192.168.56.3:7171/test/_search?pretty -uelastic:password
    <br /> <br /> ![](<a href="https://www.infinilabs.com/img/blog/2023/backup-system-with-gatewy/7.pn" rel="nofollow" target="_blank">https://www.infinilabs.com/img ... /7.pn</a>g)<br /> <br />

    查询备集群

    curl 192.168.56.3:9200/test/_search?pretty -uadmin:admin
    ```

    ![](https://www.infinilabs.com/img ... /8.png)

    两个集群都已执行删除和更新操作,数据一致。

    主集群故障时写入、查询测试


    为模拟主集群故障,直接关闭主集群。

    写入数据


    ```

    通过网关写入数据

    curl -X POST "192.168.56.3:18000/_bulk?pretty" -H 'Content-Type: application/json' -uelastic:password -d'
    { "index" : { "_index" : "test", "_id" : "3" } }
    { "field3" : "value3" }
    { "create" : { "_index" : "test", "_id" : "4" } }
    { "field4" : "value4" }
    '
    ```

    写入数据报错

    ![](https://www.infinilabs.com/img ... /9.png)

    查询数据


    ```

    通过网关查询,因为主集群不可用,网关将查询转发到备集群执行

    curl 192.168.56.3:18000/test/_search?pretty -uelastic:password
    ```

    ![](https://www.infinilabs.com/img ... 10.png)

    正常查询到数据,说明请求被转发到了备集群执行。

    备集群故障时写入、查询测试


    为模拟备集群故障,直接关闭备集群。

    写入数据


    ```

    通过网关写入数据

    curl -X POST "192.168.56.3:18000/_bulk?pretty" -H 'Content-Type: application/json' -uelastic:password -d'
    { "index" : { "_index" : "test", "_id" : "5" } }
    { "field5" : "value5" }
    { "create" : { "_index" : "test", "_id" : "6" } }
    { "field6" : "value6" }
    '
    ```

    ![](https://www.infinilabs.com/img ... 11.png)

    数据正常写入。

    查询数据


    ```

    通过网关查询

    curl 192.168.56.3:18000/test/_search?pretty -uelastic:password
    ```

    ![](https://www.infinilabs.com/img ... 12.png)

    查询成功返回。主集群成功写入了两条新数据。同时此数据会被记录到备集群的队列中,待备集群恢复后,会消费此队列追数据。

    恢复备集群


    启动备集群。

    查询数据


    等待片刻或通过 [INFINI Console](https://www.infinilabs.com/docs/latest/console/) 确定网关队列消费完毕后,查询备集群的数据。
    (生产和消费 offset 相同,说明消费完毕。)
    ![](https://www.infinilabs.com/img ... 13.png)

    ```

    查询备集群的数据

    curl 192.168.56.3:9200/test/_search?pretty -uadmin:admin
    ```

    ![](https://www.infinilabs.com/img ... 14.png)

    备集群启动后自动消费队列数据,消费完后备集群数据达到与主集群数据一致。

    灾备切换


    测试了这么多,终于到切换的时刻了。切换前我们判断下主系统是否短期无法修复。
    ![](https://www.infinilabs.com/img ... 15.png)

    如果我们判断主用系统无法短时间恢复,要执行切换。非常简单,我们直接将配置文件中定义的主备集群互换,然后重启网关程序就行了。但我们推荐在相同主机上另部署一套网关程序--网关B,先前那套用网关A指代。网关B中的配置文件把原备集群定义为主集群,原主集群定义为备集群。若要执行切换,我们先停止网关A,然后启动网关B,此时应用连接到网关(端口不变),就把原备系统当作主系统使用,把原主系统当作备系统,也就完成了主备系统的切换。

    灾备回切


    当原主集群修复后,正常启动,就会从消费队列追写修复期间产生数据直到主备数据一致,同样我们可通过 INFINI Console 查看消费的进度。如果大家还是担心数据的一致性,INFINI Console 还能帮大家做[校验数据]()任务,做到数据完全一致后(文档数量及文档内容一致),才进行回切。
    ![](https://www.infinilabs.com/img ... 16.png)

    回切也非常简单,停止网关B,启动网关A即可。

    网关高可用


    网关自带浮动 IP 模块,可进行双机热备。客户端通过 VIP 连接网关,网关出现故障时,VIP 漂移到备网关。
    视频教程戳[这里](https://www.bilibili.com/video ... f57628)。
    ![](https://www.infinilabs.com/img ... 17.png)

    这样的优点是简单,不足是只有一个网关在线提供服务。如果想多个网关在线提供服务,则需搭配分布式消息系统一起工作,架构如下。
    ![](https://www.infinilabs.com/img ... 18.png)

    前端通过负载均衡将流量分散到多个在线网关,网关将消息存入分布式消息系统。此时,网关可看作无状态应用,可根据需要扩缩规模。

    以上就是我介绍的ES灾备方案,是不是相当灵活了。有问题还是那句话 Call me 。

    关于极限网关

    INFINI Gateway 是一个面向搜索场景的高性能数据网关,所有请求都经过网关处理后再转发到后端的搜索业务集群。基于 INFINI Gateway,可以实现索引级别的限速限流、常见查询的缓存加速、查询请求的审计、查询结果的动态修改等等。

    官网文档:<https://www.infinilabs.com/docs/latest/gateway>;

postFilter跟聚合的作用关系

Ombres 回复了问题 • 2 人关注 • 2 个回复 • 1143 次浏览 • 2023-07-24 08:47 • 来自相关话题