用Elasticsearch做大规模数据的多字段、多类型索引检索

这篇具有很好参考价值的文章主要介绍了用Elasticsearch做大规模数据的多字段、多类型索引检索。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文同时发布在我的个人博客

之前尝试了用mysql做大规模数据的检索优化,可以看到单字段检索的情况下,是可以通过各种手段做到各种类型索引快速检索的,那是一种相对简单的场景。

但是实际应用往往会复杂一些 —— 各类索引(关键词匹配、全文检索、时间范围)混合使用,还有排序的需求。这种情况下mysql就有点力不从心了,复杂的索引类型,在多索引检索的时候对每个字段单独建索引于事无补,而联合索引无法在如此复杂的索引类型下建起来。

用ElasticSearch来解决这个场景的问题就要简单的多了。那么如何用elastic来解决这个问题呢? 还是带着业务需求来实践一遍吧:

①检索字段有7个,4个关键词匹配,1个特殊要求的a=b&c=d的分段全文检索,1个中文全文检索,1个时间范围

②数据量很大,需要支持3个月数据的检索,最好能按月建索引,方便冷备、恢复

1. ElasticSearch Demo Server/Client 环境搭建

为了快速学习elasticsearch的api,可以在本地快速搭建一个demo环境

1.1 elasticsearch server

step1.安装jdk1.8

https://www.oracle.com/technetwork/java/javase/downloads/index.html 官网下载安装、配置好环境变量即可

step2.安装elasticsearch

https://www.elastic.co/cn/downloads/elasticsearch 同样的,官网下载对应平台的包,这个甚至不需要,直接加压,就可以在bin目录下看到服务的启动文件

我使用的是windows平台版本的,运行bin目录下elasticsearch.bat,稍等片刻,访问 http://localhost:9200

看到此截图说明elasticsearch demo server启动成功。

step3.安装中文分词器

文章开头的需求中提到,有需要中文分词全文索引的字段,所以需要额外安装一下中文分词器。https://github.com/medcl/elasticsearch-analysis-ik/tree/v7.0.0 上官网下载对应elasticsearch版本tag的ik源码包,比如我使用的最新版本7.0.0,ik也需要下载对应版本的。

elasticsearh是用java写的,需要安装maven以编译此项目。http://maven.apache.org/download.cgi官网下载对应平台的安装包,编译或解压,配置好环境变量。

解压ik代码压缩包,在其根目录运行mvn clean && mvn compile && mvn package,编译打包

将target/releasa下生成的编译好的文件,解压到elasticsearch/plugin/ik目录下,重启elasticsearch,启动成功则说明安装成功(或者直接在github下载对应的release版本)。ik分词器没办法直接测试,需要先建好index,再在index下的分词器中测试,在后文进行。

1.2 elasticsearch client

elasticsearch server以http协议接口的方式提供服务,官方提供了客户端的nodejs sdk :https://github.com/elastic/elasticsearch-js,文档在这里https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/api-reference.html#_index

用法这里先不赘述。

2.ElasticSearch使用

正如官网所说,elasticsearch是一个简单的引擎,同时也是一个复杂的引擎,提供不同级别的配置以实现不同复杂度的需求。网上elastic入门的文章,大多以比较简单的方式介绍入门级别的使用,但是真正用到产品中的时候,还是要思考一些问题: 如何配置索引字段、如何发起检索请求、如何添加额外配置。带着这些问题通读一遍官网文档,再来真正使用它,相对来说是比较好一点的。

2.1创建Index

写入数据之前,首先得考虑如何创建各种不同类型的index,以满足分词、检索、排序、统计的需求,官方文档对这块的描述在这里:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html,真正开始创建index之前,推荐把mapping的文档通读一遍,这样才知道如何选择合适的type。

2.1.1 Keyword类型字段

首先是4个关键词索引的字段,比较简单,直接创建keyword类型的property就可以了:

const { Client } = require('@elastic/elasticsearch')
const client = new Client({ node: 'http://localhost:9200' })

client.indices.create({
        index: "asrtest1",
        include_type_name: false,
        body:{
            "mappings" : {
                "properties" : {
                    "id" :       { "type" : "keyword" },
                    "app_key" :  { "type" : "keyword" },
                    "guid" :     { "type" : "keyword" },
                    "one_shot" : { "type" : "keyword" }
                }
            }
        }
    }).then((data)=>{
        console.log("index create success:")
    }).catch((err)=>{
        console.error("index create error:", err)
    })

2.1.2 Date类型字段

然后是1个日期字段,这里日期的格式是格式化后的 2019-04-15 14:54:01或者毫秒数,参考mapping文档中date类型字段的说明,向上面创建的index中插入date类型的字段,并指定字段的格式化方法:

client.indices.putMapping({
        index: "asrtest1",
        include_type_name: false,
        body:{
            "properties" : {
                "log_time" : {
                    "type":   "date",
                    "format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
                }
            }
        }
    }).then((data)=>{
        console.log("add success:", data)
    }).catch((err)=>{
        console.error("add error:", err)
    })

2.1.3 自定义分词Text类型字段

然后是1个特殊要求的a=b&c=d的分段全文检索, 为了达到这个目标,我们需要使用一个分词器,仅使用&分词。
这里需要参考两处文档,一处是mapping文档中text类型字段说明,另一处是整个analysis部分的文档(描述了分词器的组成部分、工作原理、如何自定义分词器等)

一个analyzer由Character FiltersTokenizersToken Filters三部分组成,我们可以自己实现一个自定义分词器toenizer用于此需求,也可以直接使用内置的pattern analyzer,两者没啥区别,这里图简单就用一下内置的Pattern Analyzer:

      //1. 关闭asrtest1index
    client.indices.close({index:"asrtest1"}).then(function () {
        //2. 为index添加pattern analyzer
        return client.indices.putSettings({
            index: "asrtest1",
            body:{
                "analysis": {
                    "analyzer": {
                        "qua_analyzer": {
                            "type":      "pattern",
                            "pattern":   "&"
                        }
                    }
                }
            }
        })
    }).then(()=>{
        //3. 为index添加text类型的qua字段,并应用pattern analyzer
        return  client.indices.putMapping({
            index: "asrtest1",
            include_type_name: false,
            body:{
                "properties" : {
                    "qua" : {
                        "type":   "text",
                        "analyzer": "qua_analyzer",
                        "search_analyzer":"qua_analyzer"
                    }
                }
            }
        })
    }).then(()=>{
        //4. 打开asrtest1
        return client.indices.open({index:"asrtest1"})
    }).then(()=>{
        console.log("add qua success")
    }).catch((err)=>{
        console.error("add error:", err.message, err.meta.body)
    })

这里有个小插曲,直接putSettings的时候,报这个错,所以上面的代码中先关闭index,再添加,再open
至此自定义分词的qua字段添加完毕,好了,测试一下:

    client.indices.analyze({
        "index":"asrtest1",
        "body":{
            "analyzer" : "qua_analyzer",
            "text" : "a=b&c=d&e=f&g=h"
        }
    }).then((data)=>{
        console.log("analyzer run success:", data.body.tokens)
    }).catch((err)=>{
        console.error("analyzer run error:", err)
    })
[{token:'a=b',start_offset:0,end_offset:3,type:'word',position:0},
{token:'c=d',start_offset:4,end_offset:7,type:'word',position:1},
{token:'e=f',start_offset:8,end_offset:11,type:'word',position:2},
{token:'g=h',start_offset:12,end_offset:15,type:'word',position:3}]

结果是符合预期的。

2.1.3 中文分词Text类型字段

elasticsearch通过插件的形式来支持中文分词,在1.1小节中,我们已经安装了elasticsearch提供的中文分词插件,现在来应用并测试一下。

添加text中文分词字段:

    client.indices.putMapping({
        index: "asrtest1",
        include_type_name: false,
        body:{
            "properties" : {
                "text" : {
                    "type":   "text",
                    "analyzer": "ik_max_word",
                    "search_analyzer":"ik_max_word"
                }
            }
        }
    }).then((data)=>{
        console.log("add success:", data)
    }).catch((err)=>{
        console.error("add error:", err)
    })

测试中文分词引擎:

client.indices.analyze({
        "index":"asrtest1",
        "body":{
            "analyzer" : "ik_max_word",
            "text" : "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
        }
    }).then((data)=>{
        console.log("analyzer run success:", data.body.tokens)
    }).catch((err)=>{
        console.error("analyzer run error:", err)
    })
[{token:'中韩',start_offset:0,end_offset:2,type:'CN_WORD',position:0},
{token:'渔',start_offset:2,end_offset:3,type:'CN_CHAR',position:1},
{token:'警',start_offset:3,end_offset:4,type:'CN_CHAR',position:2},
{token:'冲突',start_offset:4,end_offset:6,type:'CN_WORD',position:3},
{token:'调查',start_offset:6,end_offset:8,type:'CN_WORD',position:4},
{token:'韩',start_offset:9,end_offset:10,type:'CN_CHAR',position:5},
{token:'警',start_offset:10,end_offset:11,type:'CN_CHAR',position:6},
{token:'平均',start_offset:11,end_offset:13,type:'CN_WORD',position:7},
{token:'每天',start_offset:13,end_offset:15,type:'CN_WORD',position:8},
{token:'扣',start_offset:15,end_offset:16,type:'CN_CHAR',position:9},
{token:'1',start_offset:16,end_offset:17,type:'ARABIC',position:10},
{token:'艘',start_offset:17,end_offset:18,type:'COUNT',position:11},
{token:'中国',start_offset:18,end_offset:20,type:'CN_WORD',position:12},
{token:'渔船',start_offset:20,end_offset:22,type:'CN_WORD',position:13}]

结果是符合预期的。

2.2 按月自动建索引

2.1小节中分步骤分析了每个字段应该如何建立索引,而业务场景下有个需求是按月建索引。可以选择跑个定时脚本,每个月去自动创建下一个月的index,也有更简单的选择 —— 插入数据的时候,如果发现索引名称不存在,则自动创建索引,elasticserch提供了这样的功能。为了实现这个目标,需要看两个部分的文档: 集群的自动index创建配置、index模板。

首先创建一个index模板:

client.indices.putTemplate({
        "name": "asrtemp",
        "include_type_name": false,
        "body":{
            "index_patterns" : ["asr*"],
            "settings": {
                "analysis": {
                    "analyzer": {
                        "qua_analyzer": {
                            "type":      "pattern",
                            "pattern":   "&"
                        }
                    }
                }
            },
            "mappings": {
                "properties": {
                    "id" :       { "type" : "keyword" },
                    "app_key" :  { "type" : "keyword" },
                    "guid" :     { "type" : "keyword" },
                    "one_shot" : { "type" : "keyword" },
                    "log_time" : {
                        "type":   "date",
                        "format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
                    },
                    "qua" : {
                        "type":   "text",
                        "analyzer": "qua_analyzer",
                        "search_analyzer":"qua_analyzer"
                    },
                    "text" : {
                        "type":   "text",
                        "analyzer": "ik_max_word",
                        "search_analyzer":"ik_max_word"
                    }
                }
            }
        }
    }).then((data)=>{
        console.log("add template success:", data)
    }).catch((err)=>{
        console.error("add template error:", err)
    })

修改集群配置,设置自动创建索引时只应用此模板(也可以修改此配置,默认是应用所有满足pattern的模板):

client.cluster.putSettings({
        body:{
            "persistent": {
                "action.auto_create_index": "asrtemp"
            }
        }
    })

写入一个document,指定一个不存在但是满足template中index_patterns的index:

client.create({
        id:"testid1",
        index:"asrtest2",
        body:{
            "id":"testid1",
            "app_key":"dc6aca3e-bc9f-45ae-afa5-39cc2ca49158",
            "guid":"4ccaee22-5ce3-11e9-9191-1b9bd38b79e0",
            "one_shot":"0",
            "log_time":"2019-05-14 09:13:20",
            "qua":"key1=asd.asda.asf&key2=2.0.20.1&key3=val3&testk=testv",
            "text":"明天武汉的天气好不好啊"

        }
    }).then((data)=>{
        console.log("create success:", data)
    }).catch((err)=>{
        console.error("create error:", err)
    })

写入成功,查询一下新生成的index的信息:

client.indices.get({
        index: "asrtest2"
    }).then((data)=>{
        console.log("get success:", data.body.asrtest2.mappings,data.body.asrtest2.settings)
    }).catch((err)=>{
        console.error("get error:", err)
    })
{ properties: 
   { app_key: { type: 'keyword' },
     guid: { type: 'keyword' },
     id: { type: 'keyword' },
     log_time: { type: 'date', format: 'yyyy-MM-dd HH:mm:ss||epoch_millis' },
     one_shot: { type: 'keyword' },
     qua: { type: 'text', analyzer: 'qua_analyzer' },
     text: { type: 'text', analyzer: 'ik_max_word' } } } 
{ index: 
   { number_of_shards: '1',
     provided_name: 'asrtest2',
     creation_date: '1555319547412',
     analysis: { analyzer: [Object] },
     number_of_replicas: '1',
     uuid: '_GcwsE4vSBCDW0Pv35w0uA',
     version: { created: '7000099' } } }

与模板是一致的,自动创建成功。

2.3检索请求

请求时,需要做到:①各种字段交叉组合检索 ②支持分页统计count、offset ③支持按时间排序 ④延时不能太长。下面首先插入几百万条模拟数据,然后实践一下上面的三个检索需求。

2.3.1 模拟数据

为了快速大批量插入数据,应该使用elasticsearch提供的bulk api来进行数据插入的操作,关键代码:

function mockIndex(index){
    var indexName = "asrtest2"
    if(index >= 4000000){
        console.log(`index mock done!`)
        return;
    }
    if(index % 10000 == 0){
        console.log(`current num:${index}`)
    }
    var mockDataList = []
    for(var i=0;i<500;i++){
        var mock = getOneRandomData(index++)
        mockDataList.push({ "index" : { "_index" : indexName, "_id" : mock.id } })
        //mock: {app_key,log_time,guid,qua,id, text, one_shot"}
        mockDataList.push(mock)
    }
    client.bulk({
        index:indexName,
        body: mockDataList
    }).then(()=>{
        mockIndex(index)
    }).catch((err)=>{
        console.error("bulk error:",err.message)
    })
}

mockIndex(0)

2.3.2 条件检索

模拟数据灌满后,测试一下多索引联合检索,并设置排序字段、获取count、观测性能、验证结果的正确性。此处建议通读elasticsearch文档Search APIs、 Query DSL(elastic自己造的一种抽象语法树)。


在这些部分可以看到,各个类型的字段应该如何生成检索条件

这些部分可以看到如何将各个字段的检索条件合理组成一个复合检索参数


这些部分可以看到如何使用字段排序、如何设置返回结果数量、偏移量。

最后,根据文档的说明,写一个测试检索代码:

client.search({
    index: "asrtest1",
    from: 0,
    size: 10,
    sort: "log_time:asc",
    body:{
        "query": {
            "bool": {
                //测试query和filter
                //"must": [
                //    { "match_phrase": { "qua":   "SDK=39.60.17160906"}},
                //    { "match_phrase": { "text": "舞麟" }}
                //],
                "filter": [

                    { "match_phrase": { "qua":   "SDK=39.60.17160906"}},
                    { "match_phrase": { "text": "舞麟" }},
                    //id和guid是unique字段
                    //{ "term":  { "id": "c2e86a41-5f73-11e9-b3d0-45c4efcbf90f" }},
                    //{ "term":  { "guid": "2081359c-5f72-11e9-b3d0-45c4efcbf90f" }},
                    { "term":  { "app_key": "faf1e695-9a97-4e8f-9339-bdce91d4848a" }},
                    { "term":  { "one_shot": "1" }},
                    { "range": { "log_time": { "gte": "2019-04-15 08:00:00" }}}
                ]
            }
        }
    }
}).then((data)=>{
    console.log("timecost:", data.body.took)
    console.log("total:", data.body.hits.total)
    console.log("hits:", data.body.hits.hits)
}).catch((err)=>{
    console.log("error:", err)
})

变换各种条件查询,条件查询、排序、返回条数、偏移、总数等都是符合预期的。

如果有细心看上面的代码,可以发现query条件中有注释掉的must部分,这是因为我面临的业务场景下不需要对document进行score计算,只需要过滤结果,所以将所有的条件塞到filter中,elastic内部会有一些缓存策略,提高效率。经测试,将两个match_phase条件放到must中,400W条数据检索平均耗时在30—40ms,而放到filter中后,平均仅为7—8ms。

减少条件,只保留时间限制,发现:

至少应该有两百多万条结果,这里total只有10000条。

可以通过修改index的settings,index.max_result_window属性,来修改这个数量。

但是!文档中提到” Search requests take heap memory and time proportional to from + size and this limits that memory”,还有这篇文档,这里可以看到es就不适合用于大规模数据的完全遍历!想要使用es完美解决所有问题,得一口老血喷在屏幕上!

这里虽然没办法直接查询到大offset数据,但是可以通过Count API查询到真实总数,然后通过其它的search方法来达到分页的目标,好在elasticsearch也是考虑了这一点,提供了Search After API来应对这种场景。

说白了就是使用了另一种分页模式,需要业务自己维护上下文,通过传入上一次查询的最后一个结果作为起点,再往后面去查询结果。

修改查询代码:

client.search({
    index: "asrtest1",
    size: 10,
    sort: "log_time:desc,id:desc",
    body:{
        "query": {
            "bool": {
                "filter": [
                    { "range": { "log_time": { "gte": "2019-04-15 08:20:00" }}}
                ]
            }
        }
    }
}).then((data)=>{
    console.log("first 10 result:", data.body.hits.hits)
    let last = data.body.hits.hits.slice(-1)[0].sort
    return client.search({
        index: "asrtest1",
        size: 10,
        sort: "log_time:desc,id:desc",
        body:{
            "query": {
                "bool": {
                    "filter": [
                        { "range": { "log_time": { "gte": "2019-04-15 08:20:00" }}}
                    ]
                }
            },
            "search_after": last,
        }
    })
}).then((data)=>{
    console.log("second 10 result:", data.body.hits.hits)
}).catch((err)=>{
    console.log("error:", err)
})

这样,就可以查到任意多的结果,又不会把集群搞死了。

本文基于一个简单的业务场景大致实践了一遍elasticsearch的使用,而实际上集群的搭建、运维,是一个非常复杂的工作,而很多云服务上都提供了包装好的PAAS服务,如腾讯云ElasticSearch Service,直接购买接入即可。

原文地址:https://www.cnblogs.com/tzyy/p/10718078.html文章来源地址https://www.toymoban.com/news/detail-846177.html

到了这里,关于用Elasticsearch做大规模数据的多字段、多类型索引检索的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Apache Doris大规模数据使用指南

    目录 一、发展历史 二、架构介绍 弹性MPP架构-极简架构 逻辑架构 基本访问架构 三、Doris的数据分布

    2024年02月12日
    浏览(48)
  • 数据挖掘的云计算与大规模数据处理

    数据挖掘是指从大量数据中发现新的、有价值的信息和知识的过程。随着互联网和人工智能技术的发展,数据量不断增加,这使得数据挖掘变得越来越重要。云计算和大规模数据处理技术为数据挖掘提供了强大的支持,使得数据挖掘能够在更短的时间内获得更好的结果。 本文

    2024年04月14日
    浏览(52)
  • 大数据处理:利用Spark进行大规模数据处理

    大数据处理是指对大规模、高速、多源、多样化的数据进行处理、分析和挖掘的过程。随着互联网、人工智能、物联网等领域的发展,大数据处理技术已经成为当今科技的核心技术之一。Apache Spark是一个开源的大数据处理框架,它可以处理批量数据和流式数据,并提供了一系

    2024年03月22日
    浏览(55)
  • 高效数据湖构建与数据仓库融合:大规模数据架构最佳实践

    🎉欢迎来到云计算技术应用专栏~高效数据湖构建与数据仓库融合:大规模数据架构最佳实践 ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:云计算技术应用 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习

    2024年02月09日
    浏览(46)
  • Python cudf库详解:加速大规模数据处理

    📚 个人网站:ipengtao.com 随着数据规模的不断增大,高效处理大规模数据成为数据科学和机器学习中的一个重要挑战。 cudf 库作为GPU加速的DataFrame库,为Python用户提供了在处理大数据集时显著提升性能的可能性。本文将深入介绍 cudf 库的使用方法,涵盖其基本概念、常用功能

    2024年02月21日
    浏览(52)
  • 大规模数据爬取 - 增量和分布式爬虫架构实战

    嗨,亲爱的爬虫开发者们!在当今的数据驱动时代,大规模数据的爬取对于许多领域的研究和应用至关重要在本文中,我将与你分享大规模数据爬取的实战经验,重点介绍增量和分布式爬虫架构的应用,帮助你高效地处理海量数据。 1.增量爬虫 增量爬虫是指只爬取新增或更新

    2024年02月10日
    浏览(49)
  • 大规模数据量下ES如何实现高性能检索?

    ElasticSearch,是基于Lucene库的搜索引擎。它提供了一个分布式、多租户的全文搜索引擎,具有HTTP web接口和无模式JSON文档。根据DB引擎排名,Elasticsearch是最受欢迎的企业搜索引擎。ES的特点是分布式、高扩展以及近实时。那么,大规模数据量下ES是如何实现高性能检索的呢? 说

    2024年02月16日
    浏览(105)
  • 【003】-Cesium中加载大规模数据稳定流畅之性能优化思路

    使用数据流技术:Cesium支持使用数据流技术,即按需加载和卸载数据,以最小化数据传输量和加载时间。 在 Cesium 中, Cesium3DTileset#maximumMemoryUsage 属性的作用是控制 3D Tiles 的最大内存使用量,从而在保证数据流畅的前提下尽可能减小内存占用。 实现原理大致如下: Cesium 会根

    2024年01月20日
    浏览(40)
  • 解密Hadoop生态系统的工作原理 - 大规模数据处理与分析

    在当今的数字时代,大规模数据处理和分析已经成为了企业和组织中不可或缺的一部分。为了有效地处理和分析海量的数据,Hadoop生态系统应运而生。本文将深入探讨Hadoop生态系统的工作原理,介绍其关键组件以及如何使用它来处理和分析大规模数据。 Hadoop是一个开源的分布

    2024年02月12日
    浏览(46)
  • 首个大规模图文多模态数据集LAION-400M介绍

    openAI的图文多模态模型CLIP证明了图文多模态在多个领域都具有着巨大潜力,随之而来掀起了一股图文对比学习的风潮。 就在前几天(2022年12月),连Kaiming都入手这一领域,将MAE的思路与CLIP的思路结合,推出了FLIP,有兴趣可戳(https://arxiv.org/abs/2212.00794)。对于迷茫的CV研究

    2024年02月09日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包