三人行必有我师
elasticsearch

elasticsearch

ElasticSearch 同一个query查询响应时间差异大?

Elasticsearchzmc 回复了问题 • 4 人关注 • 4 个回复 • 937 次浏览 • 2021-07-21 19:33 • 来自相关话题

Es Filelddata 占用6G的jvm堆内存 ,_cache/clear清理后,马上又占用了6G,频繁full gc

Elasticsearchzqc0512 回复了问题 • 4 人关注 • 3 个回复 • 361 次浏览 • 2021-07-15 09:35 • 来自相关话题

Elastic日报 第1272期 (2021-07-08)

Elastic日报BKing 发表了文章 • 0 个评论 • 194 次浏览 • 2021-07-08 20:18 • 来自相关话题

1.Elastic Security 可防止 100% 的 REvil 勒索软件样本 https://www.elastic.co/cn/blog ... mples 2.Shard Math 在 Elasticsearch 中的重要性 https://bonsai.io/blog/the-imp ... -math 3.用 流式 ETL 对 AIS 数据进行分析 https://www.confluent.io/blog/ ... king/ 编辑:涌动同学 归档:https://ela.st/cn-daily-all 订阅:https://ela.st/cn-daily-sub 沙龙:https://ela.st/cn-meetup  

elasticsearch query与agg分开是否可以提高性能?

Elasticsearchtongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 304 次浏览 • 2021-07-08 09:24 • 来自相关话题

elasticsearch,bulk批量导入会有数据丢失,如何在大批量数据导入的情况下,数据完全写入?用logstash缓冲?

Elasticsearchimpwang 回复了问题 • 9 人关注 • 8 个回复 • 5650 次浏览 • 2021-07-05 11:47 • 来自相关话题

elasticsearch如何使用script(python)的复杂语句?

ElasticsearchCharele 回复了问题 • 3 人关注 • 3 个回复 • 269 次浏览 • 2021-07-02 14:57 • 来自相关话题

關於watcher 判斷

回复

ElasticsearchWalterX 发起了问题 • 1 人关注 • 0 个回复 • 208 次浏览 • 2021-07-01 16:04 • 来自相关话题

kibana如何做出只查询出自己创建的索引的?

回复

Elasticsearchstartjava 发起了问题 • 1 人关注 • 0 个回复 • 240 次浏览 • 2021-07-01 16:04 • 来自相关话题

docs.count与hits.total数值不一致,是什么原因导致

Elasticsearchzqc0512 回复了问题 • 5 人关注 • 4 个回复 • 430 次浏览 • 2021-06-21 15:57 • 来自相关话题

一直搞不清楚,分片和索引的区别?

Elasticsearchliujiacheng 回复了问题 • 6 人关注 • 7 个回复 • 518 次浏览 • 2021-06-19 20:42 • 来自相关话题

search after取出全量数据会比scoll更快吗

Elasticsearchguoyanbiao520 回复了问题 • 7 人关注 • 6 个回复 • 1261 次浏览 • 2021-06-11 18:05 • 来自相关话题

对ES集群的读写操作,读应该连接哪些节点,写应该连接哪些节点?

Elasticsearchyuechen323 回复了问题 • 4 人关注 • 3 个回复 • 382 次浏览 • 2021-06-10 17:38 • 来自相关话题

在ES单机模式下,"number_of_shards": 10出现什么效果呢??

ElasticsearchCharele 回复了问题 • 2 人关注 • 2 个回复 • 399 次浏览 • 2021-06-10 17:36 • 来自相关话题

tasks 和 pending_tasks

ElasticsearchCharele 回复了问题 • 2 人关注 • 1 个回复 • 368 次浏览 • 2021-05-28 14:54 • 来自相关话题

es nested 根据多条件排除数据

回复

Elasticsearchyangcj 发起了问题 • 1 人关注 • 0 个回复 • 421 次浏览 • 2021-05-28 10:55 • 来自相关话题

【杭州站】Elastic & 阿里云 Meetup 6月5号

活动liaosy 发表了文章 • 0 个评论 • 518 次浏览 • 2021-05-22 17:38 • 来自相关话题

banner

活动介绍

本次Meetup杭州站由阿里云和Elastic联合举办,邀请来自滴滴、安恒信息、阿里云的资深技术专家探讨在搜索、安全、内核优化等方向的实践与创新,以及发布由数十位优秀技术开发者共创而成的实战指南,共同分享 Elasticsearch技术大咖的一线经验与深度思考。

报名方式

链接:https://www.bagevent.com/event/7449056
扫码:
sign up

活动时间

2021年06月05日 13:00-17:30

活动地址

浙江省杭州市余杭区阿里巴巴西溪园区访客中心 206-S越秀书院

活动流程

13:00~13:15 签到入座
13:15~13:30 开发者共创书籍《Elastic Stack 实战手册V1.0》发布
13:30~14:30 滴滴Elasticsearch内核优化之路
            韩宝君 滴滴Elasticsearch资深开发工程师/ES社区Contributor
14:30~15:30 Elasticsearch在SIEM的应用与实践
            汤乐奇 安恒信息AiLPHA大数据资深研发经理
15:30~16:30 Elasticsearch Serverless 云原生时代的创新实践
            马华标(城破) 阿里巴巴高级技术专家/阿里云ES内核研发负责人
16:30~17:20 圆桌交流
17:20~17:30 抽奖合影

活动奖品

阿里云双肩包、T恤、Elastic定制礼物

阿里云ACE

阿里云ACE即全称 Alibaba Cloud Engineer,是意为阿里云的工程师、代表着云计算的建设者。同时“ACE”又是扑克牌中的“A”,因此阿里云ACE也寓意着是云计算领域王牌的一群人。在线上,ACE拥有专属的页面和29个社群,承载论坛及专栏等内容; 在线下,ACE通过组织丰富的活动,包括技术沙龙、TechDay、Meetup、官方互动等来形成本地化的开发者的学习、社交平台。

通过ACE组织的各种活动,ACE用户可以结识本地的开发者,收获前沿知识,积累行业经验,并加深对阿里云的了解。

活动交流及杭州ACE同城会钉群

DingTalk

活动海报

poster

2021 Elastic 中文社区深圳线下 Meetup 重磅重启,讲师招募进行中!

活动howardhuang 发表了文章 • 0 个评论 • 515 次浏览 • 2021-05-21 15:49 • 来自相关话题

各位社区的小伙伴们大家好!过去一年多以来,疫情阻挡了我们聚会的脚步,但阻挡不了我们 ES 技术演进的脚步,ES 中文社区前期持续举办了多期线上 meetup 活动,输出了多个高质量的技术主题分享。如今在深圳,线下 meetup 活动重磅重启!6月26日我们将在深圳腾讯滨海大厦多功能厅,和大家欢聚一堂,畅谈 ES 各个领域最新的黑科技。目前讲师招募持续进行中,欢迎各路大佬扫码报名!
Elastic_中文社区深圳_Meetup.jpg
活动链接:https://www.bagevent.com/event/7440283
各位社区的小伙伴们大家好!过去一年多以来,疫情阻挡了我们聚会的脚步,但阻挡不了我们 ES 技术演进的脚步,ES 中文社区前期持续举办了多期线上 meetup 活动,输出了多个高质量的技术主题分享。如今在深圳,线下 meetup 活动重磅重启!6月26日我们将在深圳腾讯滨海大厦多功能厅,和大家欢聚一堂,畅谈 ES 各个领域最新的黑科技。目前讲师招募持续进行中,欢迎各路大佬扫码报名!
Elastic_中文社区深圳_Meetup.jpg
活动链接:https://www.bagevent.com/event/7440283

《腾讯Elasticsearch海量规模背后的内核优化剖析》答疑

Elasticsearchhowardhuang 发表了文章 • 37 个评论 • 4405 次浏览 • 2020-05-09 17:05 • 来自相关话题

今天下午的《腾讯Elasticsearch海量规模背后的内核优化剖析》分享 大家反映强烈,由于时间关系,大家的问题没能及时答复,这里集中解答,大家如果还有其它疑问也可以持续提问。感谢大家的关注! 另外腾讯云上有内核增强版的ES服务,包含了我们所有的内核优化项,欢迎大家体验! 团队也在持续招聘,欢迎简历来砸:danielhuang@tencent.com; johngqjiang@tencent.com

今天下午的《腾讯Elasticsearch海量规模背后的内核优化剖析》分享 大家反映强烈,由于时间关系,大家的问题没能及时答复,这里集中解答,大家如果还有其它疑问也可以持续提问。感谢大家的关注! 另外腾讯云上有内核增强版的ES服务,包含了我们所有的内核优化项,欢迎大家体验! 团队也在持续招聘,欢迎简历来砸:danielhuang@tencent.com; johngqjiang@tencent.com

【深圳ES Meetup】李猛:DB与ES结合,是业务系统实践值得探讨的事

活动nodexy 发表了文章 • 2 个评论 • 2989 次浏览 • 2019-11-09 17:41 • 来自相关话题

1月16日 Elastic 中文社区深圳 Meetup 上,李猛将带来关于ES 实践方面的主题分享。借此机会,我们邀请到他聊聊作为ES深度用户在探索ES过程中的心得以及对ES社区、活动、未来生态发展等方面的看法。   李猛 架构师/Elastic认证工程师 Elastic Stack产品深度用户,2012/2013年接触Elasticsearch,对Elastic Stack开发、架构、运维等方面有深入体验,实践过多种ES项目,最暴力的大数据分析应用,最复杂的业务系统应用等。   Q1、作为深度用户,当初是基于什么样的动机和考量选择使用 ES 产品并持续深入探索的? A:易用性与功能强大,个人平常比较爱好算法研究,选择任何数据产品本质上都是选择算法,算法的本质决定了产品的强大。 架构是个宏观问题,ES的设计是标准的分布式,架构的优秀设计带来的是易用性; 算法是个微观问题,ES集成了很多优秀的算法,这样在很多业务场景下都可满足,而不用更换不同的数据产品,这样也会带来产品运维方面的便利性,保障应用的技术栈不至于过多复杂。 Q2、在探索 DB 与 ES 的互通方面,有遇到什么难题吗?最后是如何解决的呢? A:数据一致性与实时性问题。应用架构思维变化,业务调整变化与技术方案变化。 Q3、结合您的实践经历,对 ES 目前的生态发展、应用以及未来有什么样的看法? A:ES目前主要应用是在单索引条件下查询,附带有简单的聚合分析能力。复杂的分析能力不具备,ES未来会增强复杂的分析能力,比如有条件的支持2个索引的关联分析。 Q4、您对本次技术沙龙活动的主题分享有什么期待? A:期望更多人参加探讨更多的业务应用场景,比如传统应用方面、大数据方面、机器学习方面;帮助大家以后项目实战有更多的案例参考。 Q5、您对 Elastic 中文社区发展有什么意见或建议呢? A:ES目前在国内的热度几乎超过任何数据库,几乎大大小小的公司,都在使用;从开始入门到成熟运用多多少少都会遇到很多问题,  希望社区活动更加多一点,业余的交流更多一点。比如可以办一些,走进某些企业的活动。 11月16日 Elastic 中文社区深圳 Meetup 火热报名中 分享主题:《DB到ES数据实时同步之路》 李猛  主题摘要:关系型数据库天然具备最严格的事务特性,有效的保证数据库一致性,但在高效查询方面显得很无力;Elasticsearch天然具备高效的查询算法,但在数据一致性方面却是先天缺陷;如何将DB与ES的优点结合,是任何一个企业公司业务系统实践都值得探讨的事。
b89578fa-29bc-49c3-a515-837cf1dcf7c3.png
 

【线下活动-分享主题征集-武汉】 2019年3月 Elastic&尚德机构技术沙龙

活动medcl 回复了问题 • 3 人关注 • 1 个回复 • 3468 次浏览 • 2019-02-22 15:42 • 来自相关话题

ElasticSearch 同一个query查询响应时间差异大?

回复

Elasticsearchzmc 回复了问题 • 4 人关注 • 4 个回复 • 937 次浏览 • 2021-07-21 19:33 • 来自相关话题

Es Filelddata 占用6G的jvm堆内存 ,_cache/clear清理后,马上又占用了6G,频繁full gc

回复

Elasticsearchzqc0512 回复了问题 • 4 人关注 • 3 个回复 • 361 次浏览 • 2021-07-15 09:35 • 来自相关话题

elasticsearch query与agg分开是否可以提高性能?

回复

Elasticsearchtongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 304 次浏览 • 2021-07-08 09:24 • 来自相关话题

elasticsearch,bulk批量导入会有数据丢失,如何在大批量数据导入的情况下,数据完全写入?用logstash缓冲?

回复

Elasticsearchimpwang 回复了问题 • 9 人关注 • 8 个回复 • 5650 次浏览 • 2021-07-05 11:47 • 来自相关话题

elasticsearch如何使用script(python)的复杂语句?

回复

ElasticsearchCharele 回复了问题 • 3 人关注 • 3 个回复 • 269 次浏览 • 2021-07-02 14:57 • 来自相关话题

kibana如何做出只查询出自己创建的索引的?

回复

Elasticsearchstartjava 发起了问题 • 1 人关注 • 0 个回复 • 240 次浏览 • 2021-07-01 16:04 • 来自相关话题

關於watcher 判斷

回复

ElasticsearchWalterX 发起了问题 • 1 人关注 • 0 个回复 • 208 次浏览 • 2021-07-01 16:04 • 来自相关话题

docs.count与hits.total数值不一致,是什么原因导致

回复

Elasticsearchzqc0512 回复了问题 • 5 人关注 • 4 个回复 • 430 次浏览 • 2021-06-21 15:57 • 来自相关话题

一直搞不清楚,分片和索引的区别?

回复

Elasticsearchliujiacheng 回复了问题 • 6 人关注 • 7 个回复 • 518 次浏览 • 2021-06-19 20:42 • 来自相关话题

search after取出全量数据会比scoll更快吗

回复

Elasticsearchguoyanbiao520 回复了问题 • 7 人关注 • 6 个回复 • 1261 次浏览 • 2021-06-11 18:05 • 来自相关话题

对ES集群的读写操作,读应该连接哪些节点,写应该连接哪些节点?

回复

Elasticsearchyuechen323 回复了问题 • 4 人关注 • 3 个回复 • 382 次浏览 • 2021-06-10 17:38 • 来自相关话题

在ES单机模式下,"number_of_shards": 10出现什么效果呢??

回复

ElasticsearchCharele 回复了问题 • 2 人关注 • 2 个回复 • 399 次浏览 • 2021-06-10 17:36 • 来自相关话题

tasks 和 pending_tasks

回复

ElasticsearchCharele 回复了问题 • 2 人关注 • 1 个回复 • 368 次浏览 • 2021-05-28 14:54 • 来自相关话题

es nested 根据多条件排除数据

回复

Elasticsearchyangcj 发起了问题 • 1 人关注 • 0 个回复 • 421 次浏览 • 2021-05-28 10:55 • 来自相关话题

elasticsearch中match无法进行匹配查询(匹配查询match查不结果)

回复

Elasticsearchguoyanbiao520 回复了问题 • 5 人关注 • 4 个回复 • 1043 次浏览 • 2021-05-21 15:50 • 来自相关话题

Elastic日报 第1272期 (2021-07-08)

Elastic日报BKing 发表了文章 • 0 个评论 • 194 次浏览 • 2021-07-08 20:18 • 来自相关话题

1.Elastic Security 可防止 100% 的 REvil 勒索软件样本 https://www.elastic.co/cn/blog ... mples 2.Shard Math 在 Elasticsearch 中的重要性 https://bonsai.io/blog/the-imp ... -math 3.用 流式 ETL 对 AIS 数据进行分析 https://www.confluent.io/blog/ ... king/ 编辑:涌动同学 归档:https://ela.st/cn-daily-all 订阅:https://ela.st/cn-daily-sub 沙龙:https://ela.st/cn-meetup  

【杭州站】Elastic & 阿里云 Meetup 6月5号

活动liaosy 发表了文章 • 0 个评论 • 518 次浏览 • 2021-05-22 17:38 • 来自相关话题

banner

活动介绍

本次Meetup杭州站由阿里云和Elastic联合举办,邀请来自滴滴、安恒信息、阿里云的资深技术专家探讨在搜索、安全、内核优化等方向的实践与创新,以及发布由数十位优秀技术开发者共创而成的实战指南,共同分享 Elasticsearch技术大咖的一线经验与深度思考。

报名方式

链接:https://www.bagevent.com/event/7449056
扫码:
sign up

活动时间

2021年06月05日 13:00-17:30

活动地址

浙江省杭州市余杭区阿里巴巴西溪园区访客中心 206-S越秀书院

活动流程

13:00~13:15 签到入座
13:15~13:30 开发者共创书籍《Elastic Stack 实战手册V1.0》发布
13:30~14:30 滴滴Elasticsearch内核优化之路
            韩宝君 滴滴Elasticsearch资深开发工程师/ES社区Contributor
14:30~15:30 Elasticsearch在SIEM的应用与实践
            汤乐奇 安恒信息AiLPHA大数据资深研发经理
15:30~16:30 Elasticsearch Serverless 云原生时代的创新实践
            马华标(城破) 阿里巴巴高级技术专家/阿里云ES内核研发负责人
16:30~17:20 圆桌交流
17:20~17:30 抽奖合影

活动奖品

阿里云双肩包、T恤、Elastic定制礼物

阿里云ACE

阿里云ACE即全称 Alibaba Cloud Engineer,是意为阿里云的工程师、代表着云计算的建设者。同时“ACE”又是扑克牌中的“A”,因此阿里云ACE也寓意着是云计算领域王牌的一群人。在线上,ACE拥有专属的页面和29个社群,承载论坛及专栏等内容; 在线下,ACE通过组织丰富的活动,包括技术沙龙、TechDay、Meetup、官方互动等来形成本地化的开发者的学习、社交平台。

通过ACE组织的各种活动,ACE用户可以结识本地的开发者,收获前沿知识,积累行业经验,并加深对阿里云的了解。

活动交流及杭州ACE同城会钉群

DingTalk

活动海报

poster

2021 Elastic 中文社区深圳线下 Meetup 重磅重启,讲师招募进行中!

活动howardhuang 发表了文章 • 0 个评论 • 515 次浏览 • 2021-05-21 15:49 • 来自相关话题

各位社区的小伙伴们大家好!过去一年多以来,疫情阻挡了我们聚会的脚步,但阻挡不了我们 ES 技术演进的脚步,ES 中文社区前期持续举办了多期线上 meetup 活动,输出了多个高质量的技术主题分享。如今在深圳,线下 meetup 活动重磅重启!6月26日我们将在深圳腾讯滨海大厦多功能厅,和大家欢聚一堂,畅谈 ES 各个领域最新的黑科技。目前讲师招募持续进行中,欢迎各路大佬扫码报名!
Elastic_中文社区深圳_Meetup.jpg
活动链接:https://www.bagevent.com/event/7440283
各位社区的小伙伴们大家好!过去一年多以来,疫情阻挡了我们聚会的脚步,但阻挡不了我们 ES 技术演进的脚步,ES 中文社区前期持续举办了多期线上 meetup 活动,输出了多个高质量的技术主题分享。如今在深圳,线下 meetup 活动重磅重启!6月26日我们将在深圳腾讯滨海大厦多功能厅,和大家欢聚一堂,畅谈 ES 各个领域最新的黑科技。目前讲师招募持续进行中,欢迎各路大佬扫码报名!
Elastic_中文社区深圳_Meetup.jpg
活动链接:https://www.bagevent.com/event/7440283

ES 7.4.2的Sort Script查询性能比较差

Elasticsearchquan子里的世界 发表了文章 • 0 个评论 • 897 次浏览 • 2021-01-19 14:15 • 来自相关话题

我们的es从5.3.2升级到7.4.1,遇到一个脚本的性能问题。同样的一段Sort Script脚本, 在ES 7.4.1的执行要比ES 5.3.2要慢至少一倍。一个主要的特点就是departure_city_ids可能会很多,通过doc获取一个deparature_city_ids的数据, 数据的大小最大的时候达到两千多个左右。Type为keyword类型。

  • 具体的sort script脚本实例已经贴在下面了。

脚本实例:

"sort": [
    {
      "_script": {
        "script": {
          "inline": "return doc['departure_city_ids'].size()",
          "lang": "painless"
        },
        "type": "number",
        "order": "desc"
      }
    }
  ]

https://discuss.elastic.co/t/the-performance-of-script-based-sorting-in-es-7-4-2-verison-is-very-poor/261241

极限网关 INFINI Gateway 初体验

Elasticsearchliaosy 发表了文章 • 3 个评论 • 2252 次浏览 • 2020-12-09 00:57 • 来自相关话题

最近在elasticsearch中文社区看到medcl大神写的一篇文章《Elasticsearch 极限网关测试版本发布》,在es外层接了一个极限网关gateway,所有的请求先走网关,再到es。gateway能提供索引级别的限速限流,降低重复请求,缓存常见查询,起到查询加速的效果等等很多特性。看着很强大的样子,赶紧下载体验了一下。 下载 下载地址:https://github.com/medcl/infini-gateway/releases 找到当前最新版1.1.0_SNAPSHOT
image_(1).png
根据自己的操作系统环境选择相应的包下载,本人用的是Macbook,选择了GATEWAY-darwin64.tar.gz
#切换该路径下(路径自定)
cd /Users/shiyang/code/elastic/gateway
#下载
wget https://github.com/medcl/infin ... ar.gz
#下载完后解压
tar -zxvf GATEWAY-darwin64.tar.gz
#解压后能看到两个新文件,一个可执行二进制文件,一个yml配置文件
ls
#gateway-darwin64   gateway.yml
安装部署 在run之前需要先运行elastisearch,否则会报错,如图所示:
image_(1).png
接下来先启动es集群(如果你本地还没有部署es,建议先参考官网的es安装教程下载部署) 本机用的es版本为7.9.0,如下图表示启动es成功:
image_(2).png
接下来再启动gateway,yml配置文件可以先默认,后续可根据需要再修改。
#启动 
./gateway-darwin64
启动成功如下图所示:
image_(3).png
成功启动后,我们就可以直接访问gateway了。
curl http://0.0.0.0:8000
image_(4).png
到此,gateway就算本地部署完毕了。 是不是很简单?嗯,下载即使用,简单方便。 (接下来可以试用一下gateway的特性了。将发布在下一篇文章。)

Elasticsearch索引拆分方案

Elasticsearch 发表了文章 • 0 个评论 • 2983 次浏览 • 2020-10-19 09:08 • 来自相关话题

Elasticsearch索引拆分方案

[TOC]

一、概况

项目中,由于Elasticsearch单个索引数据量大,索引中部分数据不常用,在搜索和写入文档时,效率较低。为了减小单个索引的数据量,提升搜索和文档写入效率,将大索引根据一定的规则拆分为小的索引。拆分索引的关键点在于建立索引,文档同步,多索引搜索。

建立索引的关键问题是索引的设置以及字段的属性设置,最常见的问题是,某个字段我们希望Elasticsearch 按照我们的想法进行分词。采用自动生成索引(默认模板),索引字段的类型就会根据第一条文档的数据进行字段转换,无法实现具体某个字段使用我们想要的分词方式。另外就是无法使用自定义分词器,索引的默认分片数为5,无法根据我们制定的分片数进行分配。

为了实现我们这种自动创建索引的特殊要求,Elasticsearch也提供了索引模板API。

索引模板,就是创建索引的模板,模板中包含公共的配置(Settings)和映射(Mappings),并包含一个简单触发条件,及条件满足时使用该模板创建一个新的索引。

模板只在创建索引时应用。更改模板不会对现有索引产生影响。当使用create index API时,作为create index调用的一部分定义的设置/映射将优先于模板中定义的任何匹配设置/映射。

文档同步和搜索,我们都采用了别名的形式。索引别名,就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何一个需要索引名的API来使用,别名不能与索引具有相同的名称。别名带给我们极大的灵活性,允许我们在运行的集群中可以无缝的从一个索引切换到另一个索引,给多个索引分组 ,给索引的一个子集创建。因为使用别名,你的应用可以在零停机的情况下从旧索引迁移到新索引。

由于文档同步,必须指定一个唯一的索引才能成功。原来单索引时,我们的索引采取了 “索引名称_v1”的形式,为方便在零停机的情况下重建索引,文档更新也新建了一个专门的索引别名。 拆分索引后,索引名称规范为“索引名称_YYMM”按月拆分(包括但不限于此种方式),就会出现多个索引,此时就不在方便新增专门的索引别名用于文档更新,反而用索引名字直接进行文档更新,就会更加的方便,直接和准确。

文档同步使用索引名称,搜索依旧使用别名的形式。多个索引,有相同的别名,索引拆分,文档分属不同的索引,但因为有相同的别名,使用别名搜索时,依然可以将数据搜索出来。

通过建立索引,文档同步,多索引搜索实现了单索引到多索引的拆分。数据还是那些数据,依然能搜索出来,索引数变多了,每个索引的数据减少,同步文档速度就可以提高。搜索也可以根据业务需求只查询部分索引,提升了查询速度,也可以查询所有数据,根据实际场景可自由变换。

二、索引拆分规则

索引拆分,可以根据创建时间拆分,如:”索引名称_yyyyMM“,”索引名称_yyyy“;也可以根据主键ID求余的方式来进行拆分,如:”索引名称_0“,”索引名称_1“。

具体的拆分规则根据业务需要进行,需要注意的是,无论根据创建时间还是根据主键ID求余来拆分,都要求根据拆分的值,是文档中不变的值,才能唯一确定一个索引,进行文档的存储,如:主键ID,创建时间;不可为变化的值,有可能变化的值,就无法唯一确定一个索引进行文档存储,如:状态,那就会出现当前在这个索引,状态改变后再另外的索引,这样每个索引都有同一条状态不同的数据,搜索时就会不准确。

本文将根据创建时间进行索引拆分。

思路:

  • 创建索引模板
  • 同步文档时,选用的索引名称以"索引名称_yyyyMM"命名,自动创建带别名的索引
  • 如果文档同步到新索引,原索引中的文档需删除

三、创建索引模板

以商品评论索引为例,将单索引拆分为多索引,根据以下规则,在同步文档时,如果无索引会字段根据模板生成:

  • 索引名称的规则“goods_comment_202010”
  • 索引别名为“goods_comment”
  • number_of_shards分片数为3
  • 配置Settings
  • 定义Mappings字段及其类型

具体模板如下所示:

{
    "order" : 0,
    "index_patterns" : [
      "goods_comment*"
    ],
    "settings" : {
      "index" : {
        "max_result_window" : "100000",
        "analysis" : {
          "filter" : {
            "by_stop_filter" : {
              "type" : "stop",
              "stopwords" : [
                " "
              ]
            },
            "pinyin_first_letter_and_full_pinyin_filter" : {
              "keep_none_chinese_in_first_letter" : "true",
              "lowercase" : "true",
              "keep_original" : "true",
              "keep_first_letter" : "true",
              "trim_whitespace" : "true",
              "type" : "pinyin",
              "keep_none_chinese" : "true",
              "limit_first_letter_length" : "16",
              "keep_full_pinyin" : "true"
            },
            "by_synonym_filter" : {
              "type" : "synonym",
              "synonyms_path" : "analysis/synonym.txt"
            },
            "full_pinyin_filter" : {
              "keep_none_chinese_in_first_letter" : "true",
              "lowercase" : "true",
              "keep_original" : "true",
              "keep_first_letter" : "false",
              "trim_whitespace" : "true",
              "type" : "pinyin",
              "keep_none_chinese" : "true",
              "limit_first_letter_length" : "16",
              "keep_full_pinyin" : "true"
            }
          },
          "char_filter" : {
            "by_char_filter" : {
              "type" : "mapping",
              "mappings" : [
                "| => |"
              ]
            }
          },
          "analyzer" : {
            "by_max_word" : {
              "filter" : [
                "by_synonym_filter",
                "lowercase"
              ],
              "char_filter" : [
                "html_strip"
              ],
              "type" : "custom",
              "tokenizer" : "ik_max_word"
            }
          },
          "tokenizer" : {
            "my_pinyin" : {
              "lowercase" : "true",
              "keep_original" : "true",
              "remove_duplicated_term" : "true",
              "keep_separate_first_letter" : "false",
              "type" : "pinyin",
              "limit_first_letter_length" : "16",
              "keep_full_pinyin" : "true"
            }
          }
        },
        "number_of_shards" : "3",
        "number_of_replicas" : "1"
      }
    },
    "mappings" : {
      "_doc" : {
        "properties" : {
          "is_img" : {
            "type" : "integer"
          },
          "gid" : {
            "type" : "integer"
          },
          "pubtime" : {
            "type" : "integer"
          }
            ....
        }
      }
    },
    "aliases" : {
      "goods_comment" : { }
    }
  }

上述模板定义,看似复杂,拆分来看,主要为如下几个部分:

{
  "order": 0,                               // 模板优先级
  "index_patterns": ["goods_comment*"],// 模板匹配的名称方式
  "settings": {...},                        // 索引设置
  "mappings": {...},                        // 索引中各字段的映射定义
  "aliases": {...}                          // 索引的别名
}

3.1 模板优先级

有时候,一个模板可能绝大部分符合新建索引的需求,但是局部需要微调,此时,如果复制旧的模板,修改该模板后,成为一个新的索引模板即可达到我们的需求,但是这操作略显重复。此时,可以采用模板叠加与覆盖来操作。模板的优先级是通过模板中的 order 字段定义的,数字越大,优先级越高。 如下为定义所有以 te 开头的索引的模板:

{
    "order": 0,
    "index_patterns": "te*",
    "settings": {
        "number_of_shards": 1
    },
    "mappings": {
        "type1": {
            "_source": {
                "enabled": false
            }
        }
    }
}

索引模板是有序合并的。如何想单独修改某一小类索引的一两处单独设置,可以在累加一层模板。

{
    "order": 1,
    "index_patterns": "tete*",
    "settings": {
        "number_of_shards": 2
    },
    "mappings": {
        "type1": {
            "_all": {
                "enabled": false
            }
        }
    }
}

上述第一个模板的 order 为0,第二个模板的 order 为1,优先级高于第一个模板,其会覆盖第一个模板中的相同项。所以对于所有以 tete 开头的索引模板效果如下:

{
    "settings": {
        "number_of_shards": 2
    },
    "mappings": {
        "type1": {
            "_source": {
                "enabled": false
            },
            "_all": {
                "enabled": false
            }
        }
    }
}

两个模板叠加了,项目的字段,优先级高的覆盖了优先级低的,如分片数。

3.2 模板匹配的名称

索引模板中的 "index_patterns" 字段定义的是该索引模板所应用的索引情况。如 "index_patterns": "tete*" 所表示的含义是,当新建索引时,所有以 tete 开头的索引都会自动匹配到该索引模板。利用该模板进行相应的设置和字段添加等。

3.3 settings 部分

索引模板中的 settings 部分一般定义的是索引的主分片、拷贝分片、刷新时间、自定义分析器等。常见的 settings 部分结构如下:


"settings": {
    "index": {
      "analysis": {...},                // 自定义的分析器
      "number_of_shards": "32",         // 主分片的个数
      "number_of_replicas": "1",        // 主分片的拷贝分片个数
      "refresh_interval": "5s"          // 刷新时间
    }
  }

建立的索引,不会立马查到,这是为什么 Elasticsearch 为 near-real-time(接近实时)的原因,需要配置刷新时间,默认的是 1s。settings 的设置中,重点是自定义分析器的设置。

  • 分析器是三个顺序执行的组件的结合。他们分别是字符过滤器、分词器、标记过滤器。字符过滤器是让字符串在被分词前变得更加整洁。一个分析器可能包含零到多个字符过滤器(character_filter)。

  • 分词器将字符串分割成单独的词(terms)或标记(tokens)。一个分析器必须包含一个分词器。

  • 分词器分词的结果的标记流会根据各自的情况,传递给特定的标记过滤器。标记过滤器可能修改、添加或删除标记。

创建的创建自定义分析器结构如下:

"settings": {
    "index": {
      "analysis": {
            "char_filter": { ... },              // 用户自定义字符过滤器
            "tokenizer":   { ... },             // 用户自定义分词器
            "filter":      { ... },             // 用户自定义标记过滤器
            "analyzer":    { ... }              // 用户自定义分析器
      },
      ...
    }
  }

3.4 索引类型的字段映射

索引模板中,映射字段所对应的常用结构是:

"mappings": {
    "_doc": {                               // 索引下的类型 _doc 应用该映射
      "dynamic_templates": [ ... ],         // 动态映射部分,用于未定义的 my_type 下字段
      "properties": { ... }                 // 自定义字段的响应映射
    }
}

"_doc" 是索引下的一个类型,Elasticsearch 7.x仅支持"_doc"作为索引类型,Elasticsearch 6.x推荐使用"_doc"为索引类型。

动态映射

动态映射 "dynamic_templates" 字段对应的是一个数组,数组中的元素是一个个字段的映射模板。每个字段的映射模板都有一个名字用户描述这个模板的用途,一个 mapping 字段由于指明这个映射如何使用,和至少一个参数(例如 match)来定义这个模板适用于哪个字段。 dynamic_templates 字段对应的字段模板结构如下:

{
    "string_fields": {                                  // 字段映射模板的名称,一般为"类型_fields"的命名方式
        "match": "*",                                   // 匹配的字段名为所有
        "match_mapping_type": "string",                 // 限制匹配的字段类型,只能是 string 类型
        "mapping": { ... }                              // 字段的处理方式
 }

自定义字段映射

针对索引类型中存在的字段,除了可以采用动态模板的方式,还可以采用定义定义的方式,常见的自定义结构如下:

"mappings": {
    "my_type": {
      "dynamic_templates": [ ... ],
      "properties": {
          "user_city": {                                // 字段名
             "analyzer": "lowercase_analyzer",          // 字段分析器
             "index": "analyzed",                       // 字段索引方式定义索引
             "type": "string",                          // 字段数据类型定义为 string
             "fields": {                                // 定义一个名为 user_city.raw 的嵌入的不分析字段
                "raw": {
                    "ignore_above": 512,
                    "index": "not_analyzed",
                    "type": "string"
                }
            }
         },
         "money":{
            "type": "double",
            "doc_values": true
         }
         ...
      }
    }
}

3.5 别名

即使你认为现在的索引设计已经是完美的了,当你的应用在生产环境使用时,还是有可能在今后有一些改变的。所以请做好准备:在应用中使用别名而不是索引。然后你就可以在任何时候重建索引。别名的开销很小,应当广泛使用。利用索引别名,可以实现零停机时间重新索引。 定义方式如下:

{
  "order": 0,                               // 模板优先级
  "index_patterns": "goods_comment*",         // 模板匹配的名称方式
  "settings": {...},                        // 索引设置
  "mappings": {...},                        // 索引中各字段的映射定义
  "aliases": {
     "goods_comment":{}
  }
}

以上只是简单的介绍了索引模板和模板内的组成部分的介绍,详情请见Elasticsearch官方文档。

有了以上的知识,我们就可以利用索引模板的API,来对模板进行创建,查询,删除操作了。

3.6 索引模板管理

创建索引模板

PUT _template/goods_comment_template
{
  "order": 0,                               // 模板优先级
  "index_patterns": "goods_comment*",  // 模板匹配的名称方式
  "settings": {...},                        // 索引设置
  "mappings": {...},                        // 索引中各字段的映射定义
  "aliases": {
     "goods_comment":{}
  }
}

查看索引模板

GET _template                // 查看所有模板
GET _template/temp*          // 查看与通配符相匹配的模板
GET _template/temp1,temp2    // 查看多个模板
GET _template/shop_template  // 查看指定模板

判断模板是否存在

HEAD _template/shop_tem

结果: a) 如果存在, 响应结果是: 200 - OK b) 如果不存在, 响应结果是: 404 - Not Found

删除索引模板

DELETE _template/shop_template    // 删除上述创建的模板

如果模板不存在, 将抛出404 错误

四、同步文档,自动创建索引

前面创建了商品评论的索引模板(goods_comment_template),同步文档时,指定索引名称为“goods_comment_202010”,如果索引不存在,便会创建名为“goods_comment_202010”的索引,同时创建好“goods_comment”别名。索引的settings和mappings都会根据模板定义的规则生成好。索引创建成功,此时该索引便能正常使用啦。

商品评论业务中,同步文档是在代码中实现,需要根据商品评论的创建时间,以“goods_comment_yyyyMM”的形式获取完整的索引名称(如:goods_comment_202010),同步文档指定goods_comment_202010,即可将数据同步到该索引。

五、别名搜索

多个商品评论索引,每个索引都有“goods_comment“别名,使用别名进行搜索,便能从这多个索引中获取数据。

同理,其他业务索引实现搜索,都要求使用别名形式。

六、可能存在的问题点

索引创建后,并不是一成不变的,随着业务的发展,新增字段也是较常见的。原来单索引,新增一个字段,只需要在mappings新增字段,重建索引,迁移数据,切换别名即可。拆分后的多索引,工作量便会成被增加。

修改索引模板,只会对后续生成的索引有作用,之前生成的索引,如需调整,需要手动或者使用脚本的形式进行重建并迁移数据。

七、附录

demo演示,也是体验索引拆分的一个实现过程。

7.1 查询索引模板列表

查看ES中的所有索引模板列表

命令:

GET _cat/templates?v

结果:

name                                  index_patterns             order       version
kibana_index_template:.kibana         [.kibana]                  0           
.monitoring-kibana                    [.monitoring-kibana-6-*]   0           6050399
.management-beats                     [.management-beats]        0           67000

7.2 创建索引模板

命令:

PUT _template/demo_template
{
  "order": 0,
  "index_patterns": [
    "demo*"
  ],
  "settings": {
    "index": {
      "number_of_shards": 2,
      "number_of_replicas": 0,
      "max_result_window": 100000
    }
  },
  "aliases": {
    "demo": {}
  }
}

结果:

{
  "acknowledged" : true
}

7.3 查看索引模板详情

命令:

GET _template/demo_template

结果:

{
  "demo_template" : {
    "order" : 0,
    "index_patterns" : [
      "demo*"
    ],
    "settings" : {
      "index" : {
        "max_result_window" : "100000",
        "number_of_shards" : "2",
        "number_of_replicas" : "0"
      }
    },
    "mappings" : { },
    "aliases" : {
      "demo" : { }
    }
  }
}

7.4 查询索引数据

命令:

GET demo_v1/_search

结果:

{
  "error" : {
    "root_cause" : [
      {
        "type" : "index_not_found_exception",
        "reason" : "no such index",
        "resource.type" : "index_or_alias",
        "resource.id" : "demo_v1",
        "index_uuid" : "_na_",
        "index" : "demo_v1"
      }
    ],
    "type" : "index_not_found_exception",
    "reason" : "no such index",
    "resource.type" : "index_or_alias",
    "resource.id" : "demo_v1",
    "index_uuid" : "_na_",
    "index" : "demo_v1"
  },
  "status" : 404
}

7.5 创建文档

在此之前demo_v1索引不存在,通过创建文档,自动生成索引,新创建的demo_v1将根据demo_template索引模板生成。

命令:

POST demo_v1/_doc
{
  "id": 1,
  "title": "这是一条数据"
}

结果:

{
  "_index" : "demo_v1",
  "_type" : "_doc",
  "_id" : "20upIHUBO6Fj2CIJUFPr",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

查看数据

GET demo_v1/_search 用索引名称进行查询
GET demo/_search 用别名进行查询
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "demo_v1",
        "_type" : "_doc",
        "_id" : "20upIHUBO6Fj2CIJUFPr",
        "_score" : 1.0,
        "_source" : {
          "id" : 1,
          "title" : "这是一条数据"
        }
      }
    ]
  }
}

发现使用索引名称和别名都能搜索出来。但是我们并未单独创建索引别名。我们来查看一下demo_v1索引的结构。

GET demo_v1
{
  "demo_v1" : {
    "aliases" : {
      "demo" : { }
    },
    "mappings" : {
      "_doc" : {
        "properties" : {
          "id" : {
            "type" : "long"
          },
          "title" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "number_of_shards" : "2",
        "provided_name" : "demo_v1",
        "max_result_window" : "100000",
        "creation_date" : "1602570768526",
        "number_of_replicas" : "0",
        "uuid" : "WrXtDB5eRzmU-xX1vAUCrA",
        "version" : {
          "created" : "6070099"
        }
      }
    }
  }
}

我们可以看到,demo_v1 索引中的数据:

  • 分片数(number_of_shards): 2
  • 副本(number_of_replicas): 0
  • 别名(aliases):demo
  • 最大结果窗口(max_result_window):100000

这些都是我们在demo_template模板中设置的,在自动创建索引时,根据索引模板的index_patterns值,只要我们的索引名称是以“demo”为前缀,都会根据该模板生成索引。因此,无论是demo_v1,还是demo_v2,只要是以“demo”为前缀,直接创建文档,如果不存在索引,ES也会自动给我们创建以“demo_template”为模板的索引。实现索引拆分最关键的点,就在于索引模板。

同样,我们通过创建文档,来生成一个没有索引模板的索引进行对比。

查询demo

GET demo/_search

确定demo索引不存在

{
  "error" : {
    "root_cause" : [
      {
        "type" : "index_not_found_exception",
        "reason" : "no such index",
        "resource.type" : "index_or_alias",
        "resource.id" : "demo",
        "index_uuid" : "_na_",
        "index" : "demo"
      }
    ],
    "type" : "index_not_found_exception",
    "reason" : "no such index",
    "resource.type" : "index_or_alias",
    "resource.id" : "demo",
    "index_uuid" : "_na_",
    "index" : "demo"
  },
  "status" : 404
}

创建一条文档

POST demo/_doc
{
  "id": 1,
  "title": "这是一条数据"
}

创建成功

{
  "_index" : "demo",
  "_type" : "_doc",
  "_id" : "PmXEIHUBwM4PCvJbG7Xw",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

查看数据

GET demo/_search
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "demo",
        "_type" : "_doc",
        "_id" : "PmXEIHUBwM4PCvJbG7Xw",
        "_score" : 1.0,
        "_source" : {
          "id" : 1,
          "title" : "这是一条数据"
        }
      }
    ]
  }
}

数据同步成功,索引也因此创建完成,我们来看看这个索引结构

GET demo
{
  "demo" : {
    "aliases" : { },
    "mappings" : {
      "_doc" : {
        "properties" : {
          "id" : {
            "type" : "long"
          },
          "title" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "number_of_shards" : "2",
        "provided_name" : "demo",
        "creation_date" : "1602572524390",
        "number_of_replicas" : "1",
        "uuid" : "p8kNddGzQzWOaz5xLcSWhA",
        "version" : {
          "created" : "6070099"
        }
      }
    }
  }
}

我们可以看到,demo索引中的数据:

  • 分片数(number_of_shards): 2
  • 副本(number_of_replicas): 1
  • 别名(aliases):无
  • 最大结果窗口(max_result_window):无

为了直观比较,请看下表:

有索引模板(demo_v1) 无索引模板(demo)
number_of_shards 2 2
number_of_replicas 0 1
aliases demo
max_result_window 10w 无,默认是1w

上表可知,通过索引模板的创建的索引,有利于我们更好的掌控索引的结构。

通过demo演示,我们可以进一步的理解索引拆分的一个过程及其实现原理,重点在索引模板。

八、参考

训练营报名中 | 5天突破Elasticsearch全观测日志分析能力

Elasticsearchzengcici 发表了文章 • 0 个评论 • 906 次浏览 • 2020-09-08 11:43 • 来自相关话题

报名时间:截止 2020年9月13日 23:59分 报名链接:https://developer.aliyun.com/learning/trainingcamp/es/1?spm=a2c6h.17708739.J_1655470630.1.20d5624dXGy59Z

训练营介绍

Elastic.png

奖品介绍

礼品-Elastic版.png

云栖大会 | Elasticsearch 场景化应用专场,将于2020年9月18日 下午13:00开始,如果你不想错过,马上订阅吧 https://yunqi.aliyun.com/2020/session55?dtrid=6bW7hKei95

更多Elasticsearch 场景化应用,请下载白皮书【Elasticsearch 八大经典应用】 下载链接:https://files.alicdn.com/tpsservice/d95297082f35355f91528a2020afd926.pdf

报名时间:截止 2020年9月13日 23:59分 报名链接:https://developer.aliyun.com/learning/trainingcamp/es/1?spm=a2c6h.17708739.J_1655470630.1.20d5624dXGy59Z

训练营介绍

Elastic.png

奖品介绍

礼品-Elastic版.png

云栖大会 | Elasticsearch 场景化应用专场,将于2020年9月18日 下午13:00开始,如果你不想错过,马上订阅吧 https://yunqi.aliyun.com/2020/session55?dtrid=6bW7hKei95

更多Elasticsearch 场景化应用,请下载白皮书【Elasticsearch 八大经典应用】 下载链接:https://files.alicdn.com/tpsservice/d95297082f35355f91528a2020afd926.pdf

1.ES之从零开始 | 搜索建议 - 简介

ElasticsearchRicky_Lau 发表了文章 • 0 个评论 • 2593 次浏览 • 2020-07-22 18:59 • 来自相关话题

1.ES之从零开始 | 搜索建议 - 简介

自从个人博客数据意外丢失后,已经有一年没写过博客了。 觉得地下有点凉,是时候爬起来了,所以就有了这个系列的分享。

为什么要用搜索建议 ?

通过两个小例子,了解搜索建议主要功能:纠错(Did You Mean)和补全(Auto-complete).

  1. 手残党,有些时间在输入比较长的英文单词的时候总会有缺胳膊少腿的问题,是否可以通过技术来解决这类问题提升用户的体验?
  2. 公司里业务开发对Elasticsearch的query DSL 不是特别熟悉,若是纯手写,基本是无法完成正确查询语法的编写 ,但如果使用kibna却是可以写出正确的查询的。

相信通过上面两个例子,都了解了搜索建议的两个主要功能纠错和补全,为什么要使用也就迎刃而解了。 接下来,主要从技术和落地的维度来聊一聊搜索建议。

搜索建议的技术架构

  • 纠错(Did You Mean)
    1. 用户在搜索框中输入有问题的词,elastisearch(少了个字母c )
    2. 由于少了个字母,倒排表里未找到相应的文档,所以返回的命中结果为0.
    3. 因为命中0结果,发送一个纠错请求,希望通过增加、删除、替换一个字符来取得一个系统里存在的词。
    4. 把纠错后的词发送到客户端
    5. 获取系统中存在的,最可能的纠错词。
    6. 选择正确的纠错词发送查询请求
    7. 返回希望的检索结果。
  • 补全(Auto-Complete)
    1. 用户输入第一个字符
    2. 异步发送第一个字符尝试进行前缀补全
    3. 返回前缀被全词, 相同前缀的根据权重排序返回
    4. 选择下拉正确的补全词
    5. 根据正确的词,发送检索请求
    6. 返回期望的查询结果。

小结

  • 纠错可以解决小范围输错的问题,增加容错率来提升用户体验。
  • 补全,可以有效的减少用户输入,提升搜索的准确率。

总结最近半年对Elasticsearch开源项目的贡献

Elasticsearchbellengao 发表了文章 • 2 个评论 • 2068 次浏览 • 2020-06-22 10:07 • 来自相关话题

自从2019年对Elasticsearch项目提交过一次代码之后,开始逐渐关注社区里的新动态,并且尝试去解决一些issue,通过这个过程去理解源码从而可以深入理解Elasticsearch的实现机制。现在把最近半年(2020年1月-2020年6月)对Elasticsearch项目所做的工作进行一次总结,记录遇到的问题和解决办法。

ingest set processor增加ignore_empty_value参数

issue: #54783

PR: #57030

使用ingest set processor时, 如果对于value字段为空字符串或者null的情况不需要进行处理,当前只能通过脚本判断value是否为空字符串或者null。本次提交对set processor增加了ignore_empty_value参数,设置该参数为true后,set processor会自动规避value字段为空字符串或者null的情况, 不对文档进行任何修改,优雅的退出处理逻辑。

修复reindex api bug

issue: #52786

PR: #54901

调用reindex api,当max_docs参数<slices时,会报错max_docs为0,实际上是因为没有提前校验max_docs是否<slices,导致max_docs被设置为0。本次提交修复了这个bug,并且给出比较清晰的错误提示。

当使用date_nanos字段作为过滤条件并且使用now时,无法创建filtered alias

issue: #54315

PR: #54785

PUT date_source/_alias/date_nanos_alias
{
  "filter": {
   "range": {
      "date_nanos": {
        "gt": "now-7d/d"
      }
    }
  }
}

如上述操作,创建filtered alias时,以date_nanos字段为过滤条件,并且使用了now,会导致创建别名失败;该提交主要是修改了queryShardContext中的nowInMillis值,设置为当前时间戳。

禁止修改nested字段的include_in_root、include_in_parent参数

issue: #53792

PR: #54386

nested字段的include_in_root、include_in_parent参数,是无法进行修改的,但是当前调用PUT {index}/_mapping API进行修改时却没有报错,本次提交的改动是在修改两个参数时抛出400参数错误。

对所有处理字符串类型数据的ingest processor,支持字段值为数组

issue: #51087

PR: #53343

对Lowercase Processors、Uppercase Processors、Trim Processors等处理字符串类型数据的ingest processor, 都支持要处理的字段类型为数组类型。

修复_search/template API返回结果总量不准的bug

issue: #52801

PR: #53155

调用GET _search/template API时,如果设置了rest_total_hits_as_int为true,处理逻辑应该和GET _search API一致,trackTotalHitsUpTo变量会被设置为Integer.MAX_VALUE,因此都能够获取到准确的total hits count。但是在_search/template API的处理逻辑中,虽然rest_total_hits_as_int设置为了true, trackTotalHitsUpTo值却没有被设置,因此只能获取到最多为10000的total hits。

修复ingest pipeline simulate API异常处理bug

issue: #52833

PR: #52937

调用POST _ingest/pipeline/_simulate API时,如果传入的docs参数是空列表,则什么结果都不会返回。 Bug产生的原因是,在异步请求的ActionListener中没有对docs参数进行判空,导致始终没有响应给客户端。

修复删除enrich policy时的bug

issue: #5122.

PR: #52179

enrich policy关联的索引名称的格式为[policy_name]-*,在调用删除enrich policy的API:DELETE /_enrich/policy/时,需要删除所有的以[policy_name]开头的索引,因为代码直接通过通配符进行删除,如果设置了action.destructive_requires_name参数为true,则删除enrich policy会报错‘Wildcard expressions or all indices are not allowed’. 本次提交的改动是不直接通过通配符删除索引,获取到所有的索引名称后进行批量删除。

当因磁盘写满而导致ES自动对索引设置read_only_allow_delete block时,对http请求返回429状态码而不是403

issue: #49393

PR: #50166

这个提交有意思了,耗时也非常久,中间经过数次代码调整与优化。这个改动的初衷是因为在磁盘写满的情况下,ES会自动地把对应节点上的索引设置为只读(index.read_only_allow_delete=true), 后续有新的写入请求进来后,会直接返回403状态码拒绝进行写入。实际上,ES对所有类型的block,对应的http状态码都设置为403, 这就会导致一个问题,在部分客户端比如rest client碰到403的状态码,是不会对写入请求进行重试的,直接丢弃掉请求,导致数据丢失。所以该提交就需要针对因为index.read_only_allow_delete为true的情况,返回429状态码(429意思是TOO_MANY_REQUESTS, 请求太多,需要限流)。在提交代码之后,和社区的maintainer针对单元测试代码经过数次讨论,最终才被合并进master分支。讨论的焦点在于,6.8版本之后,如果磁盘空间释放出来,索引的只读的状态会被自动的release,有单独的线程轮询检查磁盘来确定要不要释放只读状态,所以需要对auto release机制是否开启进行随机选择。一方面,auto release开启,因为客户端接收到429状态码,写入请求经过重试后能够成功执行;另一方面,关闭auto release, 写入请求经过数次重试后仍然执行失败而报错。

elasticsearch-croneval工具异常捕获

issue: #49642

PR: #49744

elasticsearch-croneval工具是一个社区提供的用于校验cron表达式是否正确的一个工具,放置在elasticsearch安装目录的bin目录下。该工具的执行实际上调用了项目中的CronEvalTool类的main方法,实际上在执行的过程中,因为没有正确地捕获异常,导致在对非法的cron表达式进行校验时,工具直接把整个stacktrace信息都打印出来了。针对这个issue所做的提交捕获了这个异常,并给出了较为简明的错误信息。第一次提交之后,项目的maintainer表示要对这个改动进行team-discuss, 最终讨论下来的结果是:对该工具增加一个默认关闭的命令行参数,如果用户有需要查看完整的异常信息,添加该参数即可,默认情况下只显示简短的错误信息。

自定义normalizer无法使用bug修复

issue: #48650

PR: #48866

该bug是在7.x版本引入的,因为对自定义analyzer的代码进行了重构,导致所有custom normalizer都无法正常使用。可能因为normalizer的使用者并不是很多,一直到7.5发布后才被发现,该提交在7.6版本已经发布。关于这个bug的修复,有单独一篇文章进行介绍记一次向Elasticsearch开源社区贡献代码的经历.

Elasticsearch 迁移工具 ESM 更新 0.4.4

资料分享medcl 发表了文章 • 9 个评论 • 3305 次浏览 • 2020-05-14 16:07 • 来自相关话题

timg.jpeg
 ESM 0.4.4 修复一堆 bug,支持几个新的特性:
  • 可用于生成测试数据,一般用于压力测试,基于源 ES 或者导入到本地的 JSON 数据,随机修改 ID,可以指定重复次数
  • 修复 Routing 参数在不同 ES 版本下的参数差异,支持 1.x\2.x\3.x\5.x 到 6.x\7.x 的相互导入
  • 修复终端下不能切换到后台执行的 bug,可以以 crontab 定时执行
  • 支持指定 _source 字段导出
  • 支持 _source 字段重命名
  • 支持文档 _type 重命名
  下载地址:   https://github.com/medcl/esm/releases/tag/v0.4.4     生成测试数据示例:   1.直接以来源 es 的 my_index1 的索引数据生成到目标 es 集群的索引 my_index2,产生 10 份一样的数据
./bin/esm -s http://localhost:9201 -d http://localhost:9200 -x my_index1 -y 
my_index2
 -n elastic:pass  --regenerate_id  --repeat_times=10
2.先导出索引文档到本地的文件 dump.json
./bin/esm -s http://localhost:9201 -x my_index1 -o dump.json
再基于这份样本,生成 10 份一样的数据到目标集群
./bin/esm -i dunp.json -d  http://localhost:9201 -y target-index1  --regenerate_id  --repeat_times=10 

更多使用示例参照项目 README

《腾讯Elasticsearch海量规模背后的内核优化剖析》答疑

Elasticsearchhowardhuang 发表了文章 • 37 个评论 • 4405 次浏览 • 2020-05-09 17:05 • 来自相关话题

今天下午的《腾讯Elasticsearch海量规模背后的内核优化剖析》分享 大家反映强烈,由于时间关系,大家的问题没能及时答复,这里集中解答,大家如果还有其它疑问也可以持续提问。感谢大家的关注! 另外腾讯云上有内核增强版的ES服务,包含了我们所有的内核优化项,欢迎大家体验! 团队也在持续招聘,欢迎简历来砸:danielhuang@tencent.com; johngqjiang@tencent.com

今天下午的《腾讯Elasticsearch海量规模背后的内核优化剖析》分享 大家反映强烈,由于时间关系,大家的问题没能及时答复,这里集中解答,大家如果还有其它疑问也可以持续提问。感谢大家的关注! 另外腾讯云上有内核增强版的ES服务,包含了我们所有的内核优化项,欢迎大家体验! 团队也在持续招聘,欢迎简历来砸:danielhuang@tencent.com; johngqjiang@tencent.com

类比mysql查询,适合新手学习Elasticsearch的DSL查询语句

Elasticsearch 发表了文章 • 0 个评论 • 3729 次浏览 • 2020-04-29 10:44 • 来自相关话题

Mysql查询与Elasticsearch的DSL查询语句对照

作者:

小森同学,互联网公司搜索开发工程师。

前言

作为新入门的后端开发人员,一般对Mysql,SqlServer这类的关系型数据库或多或少都有了解。当入门Elasticsearch时,发现其DSL语句与关系型数据库的查询完全不一样,不再是那熟悉的语法,顿感门槛有点高。为了方便熟悉关系型数据库查询的同学,更加容易,快捷的理解并掌握DSL基础语法,本文将进行Mysql与DSL语句进行类比。

一、Mysql数据库与Elasticsearch的类比

关系型数据库(比如Mysql) 非关系型数据库(Elasticsearch)
数据库 Database 索引 Index
表 Table 类型 Type
数据行 Row 文档 Document
数据列 Column 字段 Field
约束 Schema 映射 Mapping

二、Mysql查询语句与DSL查询类比

Mysql查询语句与Elasticsearch的DSL查询类比,主要通过mysql库中的search_lexicon表和es中的search_lexicon_v1索引进行比较。

2.1 search_lexicon 表结构

CREATE TABLE `search_lexicon` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `keyword` varchar(50) NOT NULL DEFAULT '' COMMENT '关键词',
  `keyword_crc32` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '关键词校验',
  `search_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '类型',
  `consumer_id` varchar(50) NOT NULL DEFAULT '' COMMENT '消费者ID',
  `num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '文档数',
  `views` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '搜索次数',
  `state` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态 0 关闭 1 开启',
  `is_del` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 正常 1 删除',
  `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '数据创建时间',
  `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据最后更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_search_lexicon_views` (`views`),
  KEY `idx_search_lexicon_updatetime` (`updatetime`) USING BTREE,
  KEY `idx_search_lexicon_keyword_type` (`keyword_crc32`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='搜索词库';

2.2 search_lexicon_v1 索引结构

{
  "search_lexicon_v1" : {
    "mappings" : {
      "_doc" : {
        "properties" : {
          "@timestamp" : {
            "type" : "date"
          },
          "@version" : {
            "type" : "long"
          },
          "consumer_id" : {
            "type" : "keyword"
          },
          "createtime" : {
            "type" : "date",
            "format" : "yyyy-MM-dd HH:mm:ss||strict_date_optional_time||epoch_millis"
          },
          "id" : {
            "type" : "integer"
          },
          "is_del" : {
            "type" : "integer"
          },
          "keyword" : {
            "type" : "text",
            "fields" : {
              "standard" : {
                "type" : "text",
                "analyzer" : "by_standard_no_synonym"
              }
            },
            "analyzer" : "by_max_word_pinyin_no_synonym"
          },
          "num" : {
            "type" : "long"
          },
          "search_type" : {
            "type" : "integer"
          },
          "state" : {
            "type" : "integer"
          },
          "updatetime" : {
            "type" : "date",
            "format" : "yyyy-MM-dd HH:mm:ss||strict_date_optional_time||epoch_millis"
          },
          "views" : {
            "type" : "long"
          }
        }
      }
    }
  }
}

2.3 查询语句对照

注意:dsl查询,每次默认展示10(size默认为10)条

以下的查询条件,是为了写查询而构造的,无任何实质性的意义,仅供mysql查询与dsl查询对比用

布尔查询支持的子查询类型共有四种,分别是:must,should,must_not和filter:

查询字句 说明 类型
must 文档必须符合must中所有的条件,会影响相关性得分 数组
should 文档应该匹配should子句查询的一个或多个 数组
must_not 文档必须不符合must_not 中的所有条件 数组
filter 过滤器,文档必须匹配该过滤条件,跟must子句的唯一区别是,filter不影响查询的score ,会缓存 字典

A、查询所有数据

mysql

SELECT * FROM search_lexicon

dsl

GET search_lexicon/_search
{

}
或
GET search_lexicon/_search
{
  "query": {
    "match_all": {}
  }
}

B、 查询一个条件且条件只有一个值(consumer_id=demo)的数据

mysql

SELECT * FROM search_lexicon WHERE consumer_id='demo'

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "consumer_id": "demo"
        }
      }
    }
  }
}
或
GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "consumer_id": "demo"
          }
        }
      ]
    }
  }
}

两者的区别在于前一个filter是一个对象,filter中只能放一个条件,后者filter是一个数组,里面可以放多个对象(多个查询条件),后续都将按照第二种方式查询

C、 查询一个条件且条件有多个值(consumer_id的值为demo,demo2)的数据

mysql

SELECT * FROM search_lexicon WHERE consumer_id in('demo','demo2')

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "terms": {
            "consumer_id": [
              "demo",
              "demo2"
            ]
          }
        }
      ]
    }
  }
}

D、 查询consumer_id=demo 且 state=1的数据

mysql

SELECT * FROM search_lexicon WHERE consumer_id ='demo' and state=1

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "consumer_id": "demo"
          }
        },
         {
          "term": {
            "state": 1
          }
        }
      ]
    }
  }
}

E、 查询consumer_id=demo , state=1 且 is_del<>1的数据

mysql

SELECT * FROM search_lexicon WHERE consumer_id ='demo' and state=1 and is_del <>1

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "consumer_id": "demo"
          }
        },
         {
          "term": {
            "state": 1
          }
        }
      ],
      "must_not": [
        {
          "term": {
            "is_del": {
              "value": 1
            }
          }
        }
      ]
    }
  }
}

F、查询Sconsumer_id ='demo' or (state=1 and is_del =0)的数据

mysql

SELECT * FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0)

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "consumer_id": {
              "value": "demo"
            }
          }
        },
        {
          "bool": {
            "filter": [
              {
                "term": {
                  "state": 1
                }
              },
              {
                "term": {
                  "is_del": 0
                }
              }
            ]
          }
        }
      ]
    }
  }
}

G、在F的基础上,查询指定字段

mysql

SELECT id,keyword,consumer_id,num,views,state,is_del FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0)

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "consumer_id": {
              "value": "demo"
            }
          }
        },
        {
          "bool": {
            "filter": [
              {
                "term": {
                  "state": 1
                }
              },
              {
                "term": {
                  "is_del": 0
                }
              }
            ]
          }
        }
      ]
    }
  },
  "_source": {
    "includes": [
      "id",
      "keyword",
      "num",
      "is_del",
      "state",
      "consumer_id",
      "views"
    ]
  }
}

H、在G的基础上,增加排序

mysql

SELECT id,keyword,consumer_id,num,views,state,is_del FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0) ORDER BY state DESC,id DESC

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "consumer_id": {
              "value": "demo"
            }
          }
        },
        {
          "bool": {
            "filter": [
              {
                "term": {
                  "state": 1
                }
              },
              {
                "term": {
                  "is_del": 0
                }
              }
            ]
          }
        }
      ]
    }
  },
  "_source": {
    "includes": [
      "id",
      "keyword",
      "num",
      "is_del",
      "state",
      "consumer_id",
      "views"
    ]
  },
  "sort": [
    {
      "state": {
        "order": "desc"
      }
    },
    {
      "id": {
        "order": "desc"
      }
    }
  ]
}

I、在H的基础上,添加分页

mysql

SELECT id,keyword,consumer_id,num,views,state,is_del FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0) ORDER BY state DESC,id DESC LIMIT 0,20

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "consumer_id": {
              "value": "demo"
            }
          }
        },
        {
          "bool": {
            "filter": [
              {
                "term": {
                  "state": 1
                }
              },
              {
                "term": {
                  "is_del": 0
                }
              }
            ]
          }
        }
      ]
    }
  },
  "_source": {
    "includes": [
      "id",
      "keyword",
      "num",
      "is_del",
      "state",
      "consumer_id",
      "views"
    ]
  },
  "sort": [
    {
      "state": {
        "order": "desc"
      }
    },
    {
      "id": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 20
}

# from 是一个偏移量,size为每页显示条数

J、去重查询

mysql

SELECT DISTINCT state FROM search_lexicon WHERE consumer_id = 'demo'

dsl

# 通过折叠去重查询
GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "consumer_id": {
              "value": "demo"
            }
          }
        }
      ]
    }
  },
  "collapse": {
    "field": "state"
  }
}

K、分组查询

mysql

SELECT  * FROM search_lexicon WHERE consumer_id = 'demo' GROUP BY state

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "consumer_id": {
              "value": "demo"
            }
          }
        }
      ]
    }
  },
  "size": 0, 
  "aggs": {
    "aaa": {
      "terms": {
        "field": "state",
        "size": 10
      }
    }
  }
}

L、模糊匹配

mysql

SELECT * FROM search_lexicon WHERE consumer_id="demo" and keyword LIKE '%渴望%'

dsl

GET search_lexicon/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "consumer_id": {
              "value": "demo"
            }
          }
        }
      ],
      "must": [
        {
          "match": {
            "keyword": "渴望"
          }
        }
      ]
    }
  }
}

三、总结

Mysql查询与DSL查询对照,用心体会二者之间,上下文之间,各查询条件的差异与相似,快速掌握DSL的语法结构,You can do it!

声明:

本文版权归作者所有,未经许可不得擅自转载或引用。 原文地址:https://elasticsearch.cn/article/13760

基于ES的aliyun-knn插件,开发的以图搜图搜索引擎

Elasticsearch 发表了文章 • 1 个评论 • 3969 次浏览 • 2020-03-15 12:47 • 来自相关话题

基于ES的aliyun-knn插件,开发的以图搜图搜索引擎

本例是基于Elasticsearch6.7 版本, 安装了aliyun-knn插件;设计的图片向量特征为512维度.
如果自建ES,是无法使用aliyun-knn插件的,自建建议使用ES7.x版本,并按照fast-elasticsearch-vector-scoring插件(https://github.com/lior-k/fast-elasticsearch-vector-scoring/)

由于我的python水平有限,文中设计到的图片特征提取,使用了yongyuan.name的VGGNet库,再此表示感谢!

一、 ES设计

1.1 索引结构

# 创建一个图片索引
PUT images_v2
{
  "aliases": {
    "images": {}
  }, 
  "settings": {
    "index.codec": "proxima",
    "index.vector.algorithm": "hnsw",
    "index.number_of_replicas":1,
    "index.number_of_shards":3
  },
  "mappings": {
    "_doc": {
      "properties": {
        "feature": {
          "type": "proxima_vector",
          "dim": 512
        },
        "relation_id": {
          "type": "keyword"
        },
        "image_path": {
          "type": "keyword"
        }
      }
    }
  }
}

1.2 DSL语句

GET images/_search
{
  "query": {
    "hnsw": {
      "feature": {
        "vector": [255,....255],
        "size": 3,
        "ef": 1
      }
    }
  },
  "from": 0,
  "size": 20, 
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    }
  ], 
  "collapse": {
    "field": "relation_id"
  },
  "_source": {
    "includes": [
      "relation_id",
      "image_path"
    ]
  }
}

二、图片特征

extract_cnn_vgg16_keras.py

# -*- coding: utf-8 -*-
# Author: yongyuan.name
import numpy as np
from numpy import linalg as LA
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
class VGGNet:
    def __init__(self):
        # weights: 'imagenet'
        # pooling: 'max' or 'avg'
        # input_shape: (width, height, 3), width and height should >= 48
        self.input_shape = (224, 224, 3)
        self.weight = 'imagenet'
        self.pooling = 'max'
        self.model = VGG16(weights = self.weight, input_shape = (self.input_shape[0], self.input_shape[1], self.input_shape[2]), pooling = self.pooling, include_top = False)
        self.model.predict(np.zeros((1, 224, 224 , 3)))
    '''
    Use vgg16 model to extract features
    Output normalized feature vector
    '''
    def extract_feat(self, img_path):
        img = image.load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
        img = image.img_to_array(img)
        img = np.expand_dims(img, axis=0)
        img = preprocess_input(img)
        feat = self.model.predict(img)
        norm_feat = feat[0]/LA.norm(feat[0])
        return norm_feat
# 获取图片特征
from extract_cnn_vgg16_keras import VGGNet
model = VGGNet()
file_path = "./demo.jpg"
queryVec = model.extract_feat(file_path)
feature = queryVec.tolist()

三、将图片特征写入ES

helper.py

import re
import urllib.request
def strip(path):
    """
    需要清洗的文件夹名字
    清洗掉Windows系统非法文件夹名字的字符串
    :param path:
    :return:
    """
    path = re.sub(r'[?\\*|“<>:/]', '', str(path))
    return path

def getfilename(url):
    """
    通过url获取最后的文件名
    :param url:
    :return:
    """
    filename = url.split('/')[-1]
    filename = strip(filename)
    return filename

def urllib_download(url, filename):
    """
    下载
    :param url:
    :param filename:
    :return:
    """
    return urllib.request.urlretrieve(url, filename)

train.py

# coding=utf-8
import mysql.connector
import os
from helper import urllib_download, getfilename
from elasticsearch5 import Elasticsearch, helpers
from extract_cnn_vgg16_keras import VGGNet
model = VGGNet()
http_auth = ("elastic", "123455")
es = Elasticsearch("http://127.0.0.1:9200", http_auth=http_auth)
mydb = mysql.connector.connect(
    host="127.0.0.1",  # 数据库主机地址
    user="root",  # 数据库用户名
    passwd="123456",  # 数据库密码
    database="images"
)
mycursor = mydb.cursor()
imgae_path = "./images/"
def get_data(page=1):
    page_size = 20
    offset = (page - 1) * page_size
    sql = """
    SELECT id, relation_id, photo FROM  images  LIMIT {0},{1}
    """
    mycursor.execute(sql.format(offset, page_size))
    myresult = mycursor.fetchall()
    return myresult

def train_image_feature(myresult):
    indexName = "images"
    photo_path = "http://域名/{0}"
    actions = []
    for x in myresult:
            id = str(x[0])
    relation_id = x[1]
    # photo = x[2].decode(encoding="utf-8")
    photo = x[2]
    full_photo = photo_path.format(photo)
    filename = imgae_path + getfilename(full_photo)
    if not os.path.exists(filename):
        try:
            urllib_download(full_photo, filename)
        except BaseException as e:
            print("gid:{0}的图片{1}未能下载成功".format(gid, full_photo))
            continue
    if not os.path.exists(filename):
         continue
    try:
        feature = model.extract_feat(filename).tolist()
        action = {
        "_op_type": "index",
        "_index": indexName,
        "_type": "_doc",
        "_id": id,
        "_source": {
                            "relation_id": relation_id,
                            "feature": feature,
                            "image_path": photo
        }
        }
        actions.append(action)
    except BaseException as e:
        print("id:{0}的图片{1}未能获取到特征".format(id, full_photo))
        continue
    # print(actions)
    succeed_num = 0
    for ok, response in helpers.streaming_bulk(es, actions):
        if not ok:
            print(ok)
            print(response)
        else:
            succeed_num += 1
            print("本次更新了{0}条数据".format(succeed_num))
            es.indices.refresh(indexName)

page = 1
while True:
    print("当前第{0}页".format(page))
    myresult = get_data(page=page)
    if not myresult:
        print("没有获取到数据了,退出")
        break
    train_image_feature(myresult)
    page += 1

四、搜索图片

import requests
import json
import os
import time
from elasticsearch5 import Elasticsearch
from extract_cnn_vgg16_keras import VGGNet
model = VGGNet()
http_auth = ("elastic", "123455")
es = Elasticsearch("http://127.0.0.1:9200", http_auth=http_auth)
#上传图片保存
upload_image_path = "./runtime/"
upload_image = request.files.get("image")
upload_image_type = upload_image.content_type.split('/')[-1]
file_name = str(time.time())[:10] + '.' + upload_image_type
file_path = upload_image_path + file_name
upload_image.save(file_path)
# 计算图片特征向量
queryVec = model.extract_feat(file_path)
feature = queryVec.tolist()
# 删除图片
os.remove(file_path)
# 根据特征向量去ES中搜索
body = {
    "query": {
        "hnsw": {
            "feature": {
                "vector": feature,
                "size": 5,
                "ef": 10
            }
        }
    },
    # "collapse": {
    # "field": "relation_id"
    # },
    "_source": {"includes": ["relation_id", "image_path"]},
    "from": 0,
    "size": 40
}
indexName = "images"
res = es.search(indexName, body=body)
# 返回的结果,最好根据自身情况,将得分低的过滤掉...经过测试, 得分在0.65及其以上的,比较符合要求

五、依赖的包

mysql_connector_repackaged
elasticsearch
Pillow
tensorflow
requests
pandas
Keras
numpy

一种处理Elasticsearch对象数组类型的方式

Elasticsearchtrycatchfinal 发表了文章 • 0 个评论 • 3852 次浏览 • 2020-03-10 20:27 • 来自相关话题

目前情况

Elasticsearch中处理对象数组有两种格式array和nested,但这两种都有一定的不足。
以下面的文档为例:

{
  "user": [
    {
      "first": "John",
      "last": "Smith"
    },
    {
      "first": "Alice",
      "last": "White"
    }
  ]
}

如果在mapping中以array存储,那么实际存储为:

user.first:["John","Alice"]
user.last:["Smith","White"]

如果以must的方式查询user.first:Johnuser.last:White,那么这篇文档也会命中,这不是我们期望的。

如果在mapping中以array存储,Elasticsearch将每个对象视为一个doc,这例子会存储3个doc,会严重影响ES写入和查询的效率。

Flatten格式

我想到的存储方式很简单,就是将对象数组打平保存为一个keyword类型的字符串数组,故起名Flatten格式。 以上面文档为例,数组对象需要转换为下面的格式

"user.flatten": [
    "first:John",
    "last:Smith",
    "first:John&last:Smith",
    "first:Alice",
    "last:White",
    "first:Alice&last:White"
  ]

这样以must的方式查询user.first:Johnuser.last:White,可以转换为term查询first:John&last:White,并不会命中文档。
同时,这种方式还是保存1个doc,避免了nested的缺点。

对于flatten格式有几点说明

user.flatten数组的大小

如果user对象个数为M,user属性个数为N,那么其数组大小为(2^N-1)*M

对象为空怎么处理

建议以null方式保存,例如:

    {
              "first": "John",
             "last": null
    }

转换后的格式

    [
        "first:John",
        "last:null",
        "first:John&last:null",
    ]

保存和查询对于对象属性的处理顺序要保持一致

上述例子都是按first&last顺序存储的,那么以must的方式查询user.first:Johnuser.last:White也要以first:John&last:White方式查询,不能用last:White&first:John

不足

  • 需要自己编码将JSON对象转换为字符串数组
  • 需要自己编码转换查询语句
  • 只支持term查询

使用Elasticsearch实现同段和同句搜索

Elasticsearchtrycatchfinal 发表了文章 • 2 个评论 • 2140 次浏览 • 2020-03-07 10:38 • 来自相关话题

同句搜索要求搜索多个关键词时,返回的文章不只要包含关键词,而且这些关键词必须在同一句中。
同段搜素类似,只是范围为同一段落。

SpanQuery

同段、同句搜索,使用常用的term、match查询,没有找到办法可以实现。
Elasticsearch提供了SpanQuery,官方文档中如下的介绍:

Span queries are low-level positional queries which provide expert control over the order and proximity of the specified terms. These are typically used to implement very specific queries on legal documents or patents.

上面提到,SpanQuery常常应用在法律或专利的特定搜索。这些领域,常常提供同段 /同句搜索 。
下面我们看一下三种类型的SpanQuery,能否实现我们的需求:

准备数据

PUT article

POST article/_mapping
{
  "properties": {
    "maincontent": {
      "type": "text"
    }
  }
}

POST article/_doc/1
{
   "maincontent":"the quick red fox jumps over the sleepy cat"
}

POST article/_doc/2
{
   "maincontent":"the quick brown fox jumps over the lazy dog"
}

SpanTermQuery

SpanTermQuery 和 Term Query类似, 下面的查询会返回_id为1的doc。 the quick red fox jumps over the sleepy cat

POST article/_search
{
  "profile": "true",
  "query": {
    "span_term": {
      "maincontent": {
        "value": "red"
      }
    }
  }
}

SpanNearQuery

SpanNearQuery 表示邻近搜索,查找多个term是否邻近,slop可以设置邻近距离,如果设置为0,那么代表两个term是挨着的,相当于matchphase in_order参数,代表文档中的term和查询设置的term保持相同的顺序。

POST article/_search
{
  "query": {
    "span_near": {
      "clauses": [
        {
          "span_term": {
            "maincontent": {
              "value": "quick"
            }
          }
        },
        {
          "span_term": {
            "maincontent": {
              "value": "brown"
            }
          }
        }
      ],
      "slop": 0,
      "in_order": true
    }
  }
}

上面的查询会返回_id为2的doc。

the quick brown fox jumps over the lazy dog

SpanNotQuery

SpanNotQuery非常重要,它要求两个SpanQuery的跨度,不能够重合。
看下面的例子:

  • include: 匹配的SpanQuery,例子为需要一个包含quick和fox两个词的邻近搜索。
  • exclude:设置一个SpanQuery,要求include中的SpanQuery不能包含这个SpanQuery
    POST article/_search
    {
    "query": {
    "span_not": {
      "include": {
        "span_near": {
          "clauses": [
            {
              "span_term": {
                "maincontent": {
                  "value": "quick"
                }
              }
            },
            {
              "span_term": {
                "maincontent": {
                  "value": "fox"
                }
              }
            }
          ],
          "slop": 1,
          "in_order": true
        }
      },
      "exclude": {
        "span_term": {
          "maincontent": {
            "value": "red"
          }
        }
      }
    }
    }
    }

    上面的查询会返回_id为2的doc。
    因为_id为1的文档,虽然quick red fox符合include中的SpanQuery,但是red也符合exclude中的SpanQuery。因此,这篇文章需要排除掉。 the quick red fox jumps over the sleepy cat

同句/同段搜索原理

同句搜索,反向来说,就是搜索词不能够跨句。再进一步,就是搜索词之间不能够有等其他标点符号。
其对应的查询类似如下:

POST article/_search
{
  "query": {
    "span_not": {
      "include": {
        "span_near": {
          "clauses": [
            {
              "span_term": {
                "maincontent": {
                  "value": "word1"
                }
              }
            },
            {
              "span_term": {
                "maincontent": {
                  "value": "word2"
                }
              }
            }
          ],
          "slop": 1,
          "in_order": true
        }
      },
      "exclude": {
        "span_term": {
          "maincontent": {
            "value": "。/?/!"
          }
        }
      }
    }
  }
}

同段搜素类似,对应分隔符变为\n,或者<p>,</p>

同段/同句搜索实现

文本为HTML格式

创建索引

PUT sample1
{
  "settings": {
    "number_of_replicas": 0,
    "number_of_shards": 1,
    "analysis": {
      "analyzer": {
        "maincontent_analyzer": {
          "type": "custom",
          "char_filter": [
            "sentence_paragrah_mapping",
            "html_strip"
          ],
          "tokenizer": "ik_max_word"
        }
      },
      "char_filter": {
        "sentence_paragrah_mapping": {
          "type": "mapping",
          "mappings": [
            """<h1> => \u0020paragraph\u0020""",
            """</h1> => \u0020sentence\u0020paragraph\u0020 """,
            """<h2> => \u0020paragraph\u0020""",
            """</h2> => \u0020sentence\u0020paragraph\u0020 """,
            """<p> => \u0020paragraph\u0020""",
            """</p> => \u0020sentence\u0020paragraph\u0020 """,
            """! => \u0020sentence\u0020 """,
            """? => \u0020sentence\u0020 """,
            """。 => \u0020sentence\u0020 """,
            """? => \u0020sentence\u0020 """,
            """! => \u0020sentence\u0020"""
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "mainContent": {
        "type": "text",
        "analyzer": "maincontent_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

我们创建了一个名称为sentence_paragrah_mapping的char filter,它的目的有两个:

  • 替换p,h1,h2标签为统一的分段符:paragraph
  • 替换中英文 ,, 标点符号为统一的分页符:sentence

有几个细节,需要说明:

  • paragraphsentence前后都需要添加空格,并且需要使用Unicode \u0020表示空格。

    # 期望
    hello world! => hello world sentence
    # 不合理的配置,可能会出现下面的情况
    hello world! => hello worldsentence
  • </p>,</h1>,</h2>的结尾标签需要添加paragraphsentence两个分隔符,避免结尾没有标点符号的情况
# 期望
<h1>hello world</h1> <p>hello china</p> => paragraph hello world sentence paragraph hello china sentence

# </p>,</h1>,</h2>只使用paragraph替换的结果
# 此时 hello world hello china 为同句
<h1>hello world</h1> <p>hello china</p> => paragraph hello world  paragraph hello china sentence

# 上面配置结果有些冗余:有两个连续的paragraph 
# 如果能保证HTML文本都符合标准,可以只替换</p>,</h1>,</h2>,不替换<p>,<h1>,<h2>
<h1>hello world</h1> <p>hello china</p> => paragraph hello world sentence paragraph paragraph hello china sentence
  • 注意sentence_paragrah_mapping和html_strip的配置顺序

插入测试数据

POST sample1/_doc/1
{
  "mainContent":"<p>java python javascript</p><p>oracle mysql sqlserver</p>"
} 

# 测试分词
POST sample1/_analyze
{
  "text": ["<p>java python javascript</p><p>oracle mysql sqlserver</p>"],
  "analyzer": "maincontent_analyzer"
}

# 返回结果
{
  "tokens" : [
    {
      "token" : "paragraph",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "ENGLISH",
      "position" : 0
    },
    {
      "token" : "java",
      "start_offset" : 3,
      "end_offset" : 7,
      "type" : "ENGLISH",
      "position" : 1
    },
    {
      "token" : "python",
      "start_offset" : 8,
      "end_offset" : 14,
      "type" : "ENGLISH",
      "position" : 2
    },
    {
      "token" : "javascript",
      "start_offset" : 15,
      "end_offset" : 25,
      "type" : "ENGLISH",
      "position" : 3
    },
    {
      "token" : "sentence",
      "start_offset" : 26,
      "end_offset" : 28,
      "type" : "ENGLISH",
      "position" : 4
    },
    {
      "token" : "paragraph",
      "start_offset" : 28,
      "end_offset" : 28,
      "type" : "ENGLISH",
      "position" : 5
    },
    {
      "token" : "paragraph",
      "start_offset" : 30,
      "end_offset" : 31,
      "type" : "ENGLISH",
      "position" : 6
    },
    {
      "token" : "oracle",
      "start_offset" : 32,
      "end_offset" : 38,
      "type" : "ENGLISH",
      "position" : 7
    },
    {
      "token" : "mysql",
      "start_offset" : 39,
      "end_offset" : 44,
      "type" : "ENGLISH",
      "position" : 8
    },
    {
      "token" : "sqlserver",
      "start_offset" : 45,
      "end_offset" : 54,
      "type" : "ENGLISH",
      "position" : 9
    },
    {
      "token" : "sentence",
      "start_offset" : 55,
      "end_offset" : 57,
      "type" : "ENGLISH",
      "position" : 10
    },
    {
      "token" : "paragraph",
      "start_offset" : 57,
      "end_offset" : 57,
      "type" : "ENGLISH",
      "position" : 11
    }
  ]
}

测试查询

  • 同段查询:java python
GET sample1/_search
{
  "query": {
    "span_not": {
      "include": {
        "span_near": {
          "clauses": [
            {
              "span_term": {
                "mainContent": {
                  "value": "java"
                }
              }
            },
            {
              "span_term": {
                "mainContent": {
                  "value": "python"
                }
              }
            }
          ],
          "slop": 12,
          "in_order": false
        }
      },
      "exclude": {
        "span_term": {
          "mainContent": {
            "value": "paragraph"
          }
        }
      }
    }
  }
}

//结果
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.1655603,
    "hits" : [
      {
        "_index" : "sample1",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.1655603,
        "_source" : {
          "mainContent" : "<p>java python javascript</p><p>oracle mysql sqlserver</p>"
        }
      }
    ]
  }
}
  • 同段查询:java oracle
GET sample1/_search
{
  "query": {
    "span_not": {
      "include": {
        "span_near": {
          "clauses": [
            {
              "span_term": {
                "mainContent": {
                  "value": "java"
                }
              }
            },
            {
              "span_term": {
                "mainContent": {
                  "value": "oracle"
                }
              }
            }
          ],
          "slop": 12,
          "in_order": false
        }
      },
      "exclude": {
        "span_term": {
          "mainContent": {
            "value": "paragraph"
          }
        }
      }
    }
  }
}

#结果:没有文档返回
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

纯文本格式

纯文本和HTML的区别是段落分割符不同,使用\n.

创建索引

PUT sample2
{
  "settings": {
    "number_of_replicas": 0,
    "number_of_shards": 1,
    "analysis": {
      "analyzer": {
        "maincontent_analyzer": {
          "type": "custom",
          "char_filter": [
            "sentence_paragrah_mapping"
          ],
          "tokenizer": "ik_max_word"
        }
      },
      "char_filter": {
        "sentence_paragrah_mapping": {
          "type": "mapping",
          "mappings": [
            """\n => \u0020sentence\u0020paragraph\u0020 """,
            """! => \u0020sentence\u0020 """,
            """? => \u0020sentence\u0020 """,
            """。 => \u0020sentence\u0020 """,
            """? => \u0020sentence\u0020 """,
            """! => \u0020sentence\u0020"""
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "mainContent": {
        "type": "text",
        "analyzer": "maincontent_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

测试分词

POST sample2/_analyze
{
  "text": ["java python javascript\noracle mysql sqlserver"],
  "analyzer": "maincontent_analyzer"
}

# 结果
{
  "tokens" : [
    {
      "token" : "java",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "ENGLISH",
      "position" : 0
    },
    {
      "token" : "python",
      "start_offset" : 5,
      "end_offset" : 11,
      "type" : "ENGLISH",
      "position" : 1
    },
    {
      "token" : "javascript",
      "start_offset" : 12,
      "end_offset" : 22,
      "type" : "ENGLISH",
      "position" : 2
    },
    {
      "token" : "sentence",
      "start_offset" : 22,
      "end_offset" : 22,
      "type" : "ENGLISH",
      "position" : 3
    },
    {
      "token" : "paragraph",
      "start_offset" : 22,
      "end_offset" : 22,
      "type" : "ENGLISH",
      "position" : 4
    },
    {
      "token" : "oracle",
      "start_offset" : 23,
      "end_offset" : 29,
      "type" : "ENGLISH",
      "position" : 5
    },
    {
      "token" : "mysql",
      "start_offset" : 30,
      "end_offset" : 35,
      "type" : "ENGLISH",
      "position" : 6
    },
    {
      "token" : "sqlserver",
      "start_offset" : 36,
      "end_offset" : 45,
      "type" : "ENGLISH",
      "position" : 7
    }
  ]
}