ElasticSearch使用

前言

Lucene使用起来有点麻烦,其实现在企业中使用原生Lucene来进行搜索的很少了,使用Solr和ElasticSearch比较多,Solr和ElasticSearch都是基于Lucene开发的搜索服务器,使用它们远远比使用原生Lucene简单容易的多。尤其是ElasticSearch,功能非常强大,提供了许多好用强大的插件。最近接触到了ElasticSearch,所以这里做一下记录。

1、ElasticSearch介绍

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及可能出现的更多其它问题。

总结

  • elasticsearch是一个基于Lucene的高扩展的分布式搜索服务器,支持开箱即用。
  • elasticsearch隐藏了Lucene的复杂性,对外提供Restful 接口来操作索引、搜索。

elasticsearch优点

  • 扩展性好,可部署上百台服务器集群,处理PB级数据。
  • 近实时的去索引数据、搜索数据。

关于elasticsearch和solr的选择问题

  • 如果公司现在用的solr可以满足需求就不需要更换。
  • 如果公司准备进行全文检索项目的开发,建议优先考虑elasticsearch,因为像Github这样大规模的搜索都在用它。

2、原理和应用

(1)索引结构

以图说明:

黑色部分是物理结构,上边黄色部分是逻辑结构,逻辑结构也是为了更好的
去描述ElasticSearch的工作原理及去使用物理结构中的索引文件。

逻辑结构部分是一个倒排索引表:

  • 将要搜索的文档内容分词,所有不重复的词组成分词列表。
  • 将搜索的文档最终以Document方式存储起来。
  • 每个词和docment都有关联。

如下图:

现在,如果想搜索quick brown,只需要查找包含每个词条的文档,搜索到的结果如下:

两个文档都匹配,但是明显第一个文档比第二个匹配度更高。因为第一个文档既包含quick又包含brown,第二个文档只包含brown。如果我们使用仅计算匹配词条数量的简单相似性算法 ,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。

(2)RESTful应用方法

Elasticsearch提供RESTful Api接口进行索引、搜索,并且支持多种客户端。

ElasticSearch在项目中的应用如下:

说明:

  • 用户在前端搜索关键字。
  • 项目前端通过http方式请求项目服务端。.
  • 项目服务端通过Http RESTful方式请求ES集群进行搜索。
  • ES集群从索引库检索数据。
  • 检索出的数据反馈给前端界面进行展示。

3、ElasticSearch的安装

(1)安装es

安装要求:

  • 新版本要求至少jdk1.8以上。
  • 支持tar、zip、rpm等多种安装方式。在windows下开发建议使用ZIP安装方式。
  • 支持docker方式安装。

去官网下载好适合版本和系统的安装包,这里选用6.2.1版本,解压后的目录如下:

bin目录如下:

里面是一些脚本目录,包括:启动、停止等可执行脚本。

config目录如下:

里面是一些配置文件。

data是自己建的,索引库就存在在这里。

lib里是一些jar包,logs里是日志文件,modules里面是模块目录,包括了es的功能模块。plugins里面是插件,es支持插件机制,比如IK分词器放在这里。

(2)配置文件

a、3个配置文件

ES的配置文件的地址根据安装形式的不同而不同:使用zip、tar安装,配置文件的地址在安装目录的config下;使用RPM安装,配置文件在/etc/elasticsearch下;使用MSI安装,配置文件的地址在安装目录的config下,并且会自动将config目录地址写入环境变量
ES_PATH_CONF。因为这里是使用的是zip安装,所以配置文件在config目录下,如下:

说明:

  • elasticsearch.yml : 用于配置Elasticsearch运行参数。
  • jvm.options : 用于配置Elasticsearch JVM设置。
  • log4j2.properties: 用于配置Elasticsearch日志。

以下就这3个配置文件进行分别说明。

b、elasticsearch.yml

配置格式是YAML,可以采用两种方式来进行配置:

方式一:层次方式,如下:

path:   data: /var/lib/elasticsearch   logs: /var/log/elasticsearch

方式二:属性方式如下:

path.data: /var/lib/elasticsearch path.logs: /var/log/elasticsearch

这里就统一采用属性方式配置了,具体信息如下:

cluster.name: xuechengnode.name: xc_node_1network.host: 0.0.0.0http.port: 9200transport.tcp.port: 9300node.master: truenode.data: truediscovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]discovery.zen.minimum_master_nodes: 1node.ingest: truebootstrap.memory_lock: falsenode.max_local_storage_nodes: 2path.data: C:\dev\elasticsearch\es_1\datapath.logs: C:\dev\elasticsearch\es_1\logshttp.cors.enabled: truehttp.cors.allow-origin: /.*/

参数说明如下:

  • cluster.name:配置elasticsearch的集群名称,默认是elasticsearch。
  • node.name:节点名,通常一台物理服务器就是一个节点,es会默认随机指定一个名字,建议指定一个有意义的名称,方便管理,一个或多个节点组成一个cluster集群,集群是一个逻辑的概念,节点是物理概念。
  • path.conf: 设置配置文件的存储路径,tar或zip包安装默认在es根目录下的config文件夹,rpm安装默认在/etc/。
  • elasticsearch path.data: 设置索引数据的存储路径,默认是es根目录下的data文件夹,可以设置多个存储路径,用逗号隔开。
  • path.logs::设置日志文件的存储路径,默认是es根目录下的logs文件夹。
  • path.plugins::设置插件的存放路径,默认是es根目录下的plugins文件夹。
  • bootstrap.memory_lock::设置为true可以锁住ES使用的内存,避免内存与swap分区交换数据。
  • network.host::设置绑定主机的ip地址,设置为0.0.0.0表示绑定任何ip,允许外网访问,生产环境建议设置为具体的ip。
  • http.port:设置对外服务的http端口,默认为9200。
  • ransport.tcp.port:集群结点之间通信端口。
  • node.master:指定该节点是否有资格被选举成为master结点,默认是true,如果原来的master宕机会重新选举新的master。
  • node.data:指定该节点是否存储索引数据,默认为true。
  • discovery.zen.ping.unicast.hosts:设置集群中master节点的初始列表。
  • discovery.zen.ping.timeout:设置ES自动发现节点连接超时的时间,默认为3秒,如果网络延迟高可设置大些。
  • discovery.zen.minimum_master_nodes:主结点数量的最少值 ,此值的公式为:(master_eligible_nodes / 2) 1 ,比如:有3个符合要求的主结点,那么这里要设置为2。
  • node.max_local_storage_nodes:单机允许的最大存储结点数,通常单机启动一个结点建议设置为1,开发环境如果单机启动多个节点可设置大于1。

c、jvm.options

设置最小及最大的JVM堆内存大小。在jvm.options中设 -Xms和-Xmx:

  • 两个值设置为相等。
  • 将Xmx设置为不超过物理内存的一半。

具体信息如下:

## GC configuration-XX: UseConcMarkSweepGC-XX:CMSInitiatingOccupancyFraction=75-XX: UseCMSInitiatingOccupancyOnly## optimizations# pre-touch memory pages used by the JVM during initialization-XX: AlwaysPreTouch## basic# explicitly set the stack size-Xss1m# set to headless, just in case-Djava.awt.headless=true# ensure UTF-8 encoding by default (e.g. filenames)-Dfile.encoding=UTF-8# use our provided JNA always versus the system one-Djna.nosys=true# turn off a JDK optimization that throws away stack traces for common# exceptions because stack traces are important for debugging-XX:-OmitStackTraceInFastThrow# flags to configure Netty-Dio.netty.noUnsafe=true-Dio.netty.noKeySetOptimization=true-Dio.netty.recycler.maxCapacityPerThread=0# log4j 2-Dlog4j.shutdownHookEnabled=false-Dlog4j2.disable.jmx=true-Djava.io.tmpdir=${ES_TMPDIR}## heap dumps# generate a heap dump when an allocation from the Java heap fails# heap dumps are created in the working directory of the JVM-XX: HeapDumpOnOutOfMemoryError# specify an alternative path for heap dumps# ensure the directory exists and has sufficient space#-XX:HeapDumpPath=/heap/dump/path## JDK 8 GC logging8:-XX: PrintGCDetails8:-XX: PrintGCDateStamps8:-XX: PrintTenuringDistribution8:-XX: PrintGCApplicationStoppedTime8:-Xloggc:logs/gc.log8:-XX: UseGCLogFileRotation8:-XX:NumberOfGCLogFiles=328:-XX:GCLogFileSize=64m# JDK 9  GC logging9-:-Xlog:gc*,gc age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m# due to internationalization enhancements in JDK 9 Elasticsearch need to set the provider to COMPAT otherwise# time/date parsing will break in an incompatible way for some date patterns and locals9-:-Djava.locale.providers=COMPAT

d、log4j2.properties

日志文件设置,ES使用log4j,注意日志级别的配置。

status = error# log action execution errors for easier debugginglogger.action.name = org.elasticsearch.actionlogger.action.level = debugappender.console.type = Consoleappender.console.name = consoleappender.console.layout.type = PatternLayoutappender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%nappender.rolling.type = RollingFileappender.rolling.name = rollingappender.rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}.logappender.rolling.layout.type = PatternLayoutappender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%.-10000m%nappender.rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}-%d{yyyy-MM-dd}-%i.log.gzappender.rolling.policies.type = Policiesappender.rolling.policies.time.type = TimeBasedTriggeringPolicyappender.rolling.policies.time.interval = 1appender.rolling.policies.time.modulate = trueappender.rolling.policies.size.type = SizeBasedTriggeringPolicyappender.rolling.policies.size.size = 128MBappender.rolling.strategy.type = DefaultRolloverStrategyappender.rolling.strategy.fileIndex = nomaxappender.rolling.strategy.action.type = Deleteappender.rolling.strategy.action.basepath = ${sys:es.logs.base_path}appender.rolling.strategy.action.condition.type = IfFileNameappender.rolling.strategy.action.condition.glob = ${sys:es.logs.cluster_name}-*appender.rolling.strategy.action.condition.nested_condition.type = IfAccumulatedFileSizeappender.rolling.strategy.action.condition.nested_condition.exceeds = 2GBrootLogger.level = inforootLogger.appenderRef.console.ref = consolerootLogger.appenderRef.rolling.ref = rollingappender.deprecation_rolling.type = RollingFileappender.deprecation_rolling.name = deprecation_rollingappender.deprecation_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_deprecation.logappender.deprecation_rolling.layout.type = PatternLayoutappender.deprecation_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%.-10000m%nappender.deprecation_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_deprecation-%i.log.gzappender.deprecation_rolling.policies.type = Policiesappender.deprecation_rolling.policies.size.type = SizeBasedTriggeringPolicyappender.deprecation_rolling.policies.size.size = 1GBappender.deprecation_rolling.strategy.type = DefaultRolloverStrategyappender.deprecation_rolling.strategy.max = 4logger.deprecation.name = org.elasticsearch.deprecationlogger.deprecation.level = warnlogger.deprecation.appenderRef.deprecation_rolling.ref = deprecation_rollinglogger.deprecation.additivity = falseappender.index_search_slowlog_rolling.type = RollingFileappender.index_search_slowlog_rolling.name = index_search_slowlog_rollingappender.index_search_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog.logappender.index_search_slowlog_rolling.layout.type = PatternLayoutappender.index_search_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%nappender.index_search_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog-%d{yyyy-MM-dd}.logappender.index_search_slowlog_rolling.policies.type = Policiesappender.index_search_slowlog_rolling.policies.time.type = TimeBasedTriggeringPolicyappender.index_search_slowlog_rolling.policies.time.interval = 1appender.index_search_slowlog_rolling.policies.time.modulate = truelogger.index_search_slowlog_rolling.name = index.search.slowloglogger.index_search_slowlog_rolling.level = tracelogger.index_search_slowlog_rolling.appenderRef.index_search_slowlog_rolling.ref = index_search_slowlog_rollinglogger.index_search_slowlog_rolling.additivity = falseappender.index_indexing_slowlog_rolling.type = RollingFileappender.index_indexing_slowlog_rolling.name = index_indexing_slowlog_rollingappender.index_indexing_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog.logappender.index_indexing_slowlog_rolling.layout.type = PatternLayoutappender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%nappender.index_indexing_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog-%d{yyyy-MM-dd}.logappender.index_indexing_slowlog_rolling.policies.type = Policiesappender.index_indexing_slowlog_rolling.policies.time.type = TimeBasedTriggeringPolicyappender.index_indexing_slowlog_rolling.policies.time.interval = 1appender.index_indexing_slowlog_rolling.policies.time.modulate = truelogger.index_indexing_slowlog.name = index.indexing.slowlog.indexlogger.index_indexing_slowlog.level = tracelogger.index_indexing_slowlog.appenderRef.index_indexing_slowlog_rolling.ref = index_indexing_slowlog_rollinglogger.index_indexing_slowlog.additivity = false

(3)启动es

进入bin目录,双击elasticsearch.bat。

启动完毕后浏览器访问:http://localhost:9200。显示如下:

说明es是启动成功的。

(4)安装head插件

head插件是ES的一个可视化管理插件,用来监视ES的状态,并通过head客户端和ES服务进行交互,比如创建映射、创建索引等。从ES6.0开始,head插件运行得先安装运行node.js。

a、安装node.js

这里直接省略。

b、下载head

去官网下载好合适版本,解压安装,安装后的目录如下:

c、运行head

在head安装目录下执行命令:npm run start,启动完成后如下:

d、连接到es

浏览器访问:http://localhost:9100。

标记的地方输入es的地址,然后连接,如图连接成功,可以看到目前之前一个节点xc_node_1,且集群的健康值为良好。

4、ElasticSearch入门

ES作为一个索引及搜索服务,对外提供丰富的REST接口,这里使用head插件来测试,目的是对ES的使用方法及流程有个初步的认识。

(1)创建索引库

ES的索引库是一个逻辑概念,它包括了分词列表及文档列表,同一个索引库中存储了相同类型的文档。它就相当于MySQL中的表,对于索引这个词,要区分一下名词和动词:

  • 索引(名词):ES是基于Lucene构建的一个搜索服务,它要从索引库搜索符合条件索引数据。
  • 索引(动词):索引库刚创建起来是空的,将数据添加到索引库的过程称为索引。

以下将使用两种方法创建索引库,它们的工作原理是相同的,都是客户端向ES服务发送命令。

a、使用postman

发送put请求:http://localhost:9200/demo,body如下:

参数说明:

  • number_of_shards:设置分片的数量,在集群中通常设置多个分片,表示一个索引库将拆分成多片分别存储不同的结点,提高了ES的处理能力和高可用性,入门程序使用单机环境,这里设置为1。
  • number_of_replicas:设置副本的数量,设置副本是为了提高ES的高可靠性,单机环境设置为0。

发送,响应如下:

使用head插件查看:

说明创建索引库demo成功。

b、使用head插件创建

将刚才创建的索引库删掉,测试使用head插件创建:

删除成功了。重新创建:

点击新建索引:

点击ok:

创建成功了。推荐使用head插件来创建索引库,非常方便。

(2)创建映射

a、概念说明

在索引中每个文档都包括了一个或多个field,创建映射就是向索引库中创建field的过程,下边是document和field与关系数据库的概念的类比:
文档(Document)----------------Row记录
字段(Field)-------------------Columns 列
注意:6.0之前的版本有type(类型)概念,type相当于关系数据库的表,ES官方将在ES9.0版本中彻底删除type。

那么索引库相当于关系数据库中的数据库还是表?

  • 如果相当于数据库就表示一个索引库可以创建很多不同类型的文档,这在ES中也是允许的。
  • 如果相当于表就表示一个索引库只能存储相同类型的文档,ES官方建议在一个索引库中只存储相同类型的文档。

b、创建映射

创建映射为post请求:
http://localhost:9200/索引库名称 /类型名称/_mapping。
由于ES6.0版本还没有将type彻底删除,所以暂时把type起一个没有特殊意义的名字。那么发送的url为:http://localhost:9200/demo/doc/_mapping。body如下:

发送,返回结果:

使用head插件查看:

说明映射创建成功。

c、创建文档

ES中的文档相当于MySQL数据库表中的记录。发送post或put请求:
http://localhost:9200/索引库名称/类型/id。
如果不指定id值ES会自动生成ID,那么发送的url如下:
http://localhost:9200/demo/doc/xkfyzsq001,使用post请求,body如下:

发送,返回结果:

使用head查看:

点击数据可查看详细内容:

d、搜索文档

现在搜索索引库里面的文档数据,以下将根据不同的条件来进行查询,查询都是get请求。

按照id查询:
http://localhost:9200/demo/doc/xkfyzsq001
发送,响应如下:

查询所有记录:
http://localhost:9200/demo/doc/_search
发送,响应如下:

查询name中包含风的记录:
http://localhost:9200/demo/doc/_search?q=name:风
发送,响应如下:

查询studymodel为201001的记录:
http://localhost:9200/demo/doc/_search?q=studymodel:201001
发送,响应如下:

参数说明如下:

  • took:本次操作花费的时间,单位为毫秒。
  • timed_out:请求是否超时。
  • _shards:说明本次操作共搜索了哪些分片。
  • hits:搜索命中的记录。
  • hits.total : 符合条件的文档总数 hits.hits :匹配度较高的前N个文档。
  • hits.max_score:文档匹配得分,这里为最高分。
  • _score:每个文档都有一个匹配度得分,按照降序排列。
  • _source:显示了文档的原始内容。

5、IK分词器

(1)测试分词器

在添加文档时会进行分词,索引中存放的就是一个一个的词(term),当你去搜索时就是拿关键字去匹配词,最终找到词关联的文档。
下面测试当前索引库使用的分词器。发送post请求:
http://localhost:9200/_analyze
body如下:

发送,响应如下:

可以看到,分词效果非常的差,每个中文默认为一个词。因为当前索引库使用的分词器对中文就是单字分词。下面将使用IK分词器来进行分词。

(2)安装IK分词器

使用IK分词器可以实现对中文分词的效果。去网上下载IK分词器,将下载好的zip包解压,将解压后的文件放到plugins下新建的ik文件夹下:

重新测试分词效果。发送post请求:
http://localhost:9200/_analyze
body如下:

可以看到,下面加了 “analyzer”:“ik_max_word”,发送请求,响应如下:

可以看到不再是单字分词了,而是有规律的分词。

(3)IK的两种分词模式

ik分词器有两种分词模式:ik_max_word和ik_smart模式。

a、ik_max_word

会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。

b、ik_smart

会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。

修改分词模式,再测试:

发送,响应如下:

(4)自定义词库

如果要让分词器支持一些专有词语,可以自定义词库。iK分词器自带一个main.dic的文件,此文件为词库文件。

在config目录中创建一个my.ini文件,注意尾UTF-8格式,然后在my.ini中自定义语汇:

然后在IKAnalyzer.cfg.xml中配置加载my.ini扩展词典,如下:

停用词典也可以扩展。配置好以后保存,重启es,测试分词效果:

发送,响应如下:

可以看到,扩展词典面规定的,会分为一个词。

6、映射

已经安装了ik分词器,如果在索引和搜索时去使用ik分词器呢?如何指定其它类型的field,比如日期类型、数值类型等,下面进行介绍。

(1)映射的维护

a、查询所有映射

get请求:http://localhost:9200/_mapping
响应如下:

b、创建映射

post请求:http://localhost:9200/demo/doc/_mapping

c、更新映射

映射创建成功可以添加新字段,已有字段不允许更新。

d、删除映射

通过删除索引来删除映射。

(2)映射常用类型

ES6.2核心的字段类型如下:

a、string类型

字符串包括 text和keyword两种类型。如下:

text类型

属性说明如下:

  • analyzer:通过analyzer属性指定分词器。

    指定name的字段类型为text,使用ik分词器的ik_max_word分词模式。这个是索引和搜索都使用ik_max_word。

  • search_analyzer:指定搜索时使用的分词器。

    指定name的字段类型为text,索引时使用ik分词器的ik_max_word分词模式,搜索时使用ik分词器的ik_smart模式。对于ik分词器建议是索引时使用ik_max_word将搜索内容进行细粒度分词,搜索时使用ik_smart提高搜索精确性。

  • index:通过index属性指定是否索引。默认为true,即要进行索引,只有进行索引才可以从索引库搜索到。但是也有一些内容不需要索引,比如:商品图片地址只被用来展示图片,不进行搜索图片,此时可以将index设置为false。如果不索引,按照这个域搜是搜不到的。
  • store:否在source之外存储,每个文档索引后会在ES中保存一份原始文档,存放在"_source"中,一般情况下不需要设置store为true,因为在_source中已经有一份原始文档了。

下进行测试,因为原索引库中存在映射,删除索引库,重新创建,然后创建索引。
put请求: http://localhost:9200/demo/doc/_mapping
body如下:

发送,响应如下:

映射创建成功,然后插入文档。
put请求:http://localhost:9200/demo/doc/4028e58161bcf7f40161bcf8b77c0000
body如下:

发送,响应如下:

文档插入成功。以下进行查询测试。

get请求:http://localhost:9200/demo/doc/_search?q=name:开发
响应结果:

get请求:http://localhost:9200/demo/doc/_search?q=description:开发
响应结果:

get请求:http://localhost:9200/demo/doc/_search?q=pic:group1
响应结果:

是查不到的,pic没有索引,不能进行搜索。

get请求:http://localhost:9200/demo/doc/_search?q=studymodel:201002
响应结果:

keyword类型

text文本字段在映射时要设置分词器,keyword字段为关键字字段,通常搜索keyword是按照整体搜索,所以创建keyword字段的索引时是不进行分词的,比如:邮政编码、手机号码、身份证等。keyword字段通常用于过滤、排序、聚合等。

下面进行测试,先删除索引库,重建。

先创建映射。put请求:http://localhost:9200/demo/doc/_mapping
body如下:

发送,响应如下:

映射创建成功。

插入文档。put请求:http://localhost:9200/demo/doc/j1111111111
body如下:

发送,响应如下:

文档插入成功。

进行搜索测试。get请求:http://localhost:9200/demo/doc/_search?q=name:java
响应如下:

查不到,因为keyword是精确匹配,修改url为:
http://localhost:9200/demo/doc/_search?q=name:java编程基础
响应如下:

可以查到,因为name类型设置成了keyword,必须精确匹配,也就是完全匹配上才行。

b、日期date类型

日期类型不用设置分词器,通常日期类型的字段用于排序。可以通过format来设置日期格式。
添加新的映射:

更新现有文档的timestamp的值。

查看head:

添加成功了。

c、数值类型

下边是ES支持的数值类型:

说明:

  • 尽量选择范围小的类型,提高搜索效率。
  • 对于浮点数尽量用比例因子,比如一个价格字段,单位为元,我们将比例因子设置为100这在ES中会按分存储。

    由于比例因子为100,如果我们输入的价格是23.45则ES中会将23.45乘以100存储在ES中。如果输入的价格是23.456,ES会将23.456乘以100再取一个接近原始值的数,得出2346。使用比例因子的好处是整型比浮点型更易压缩,节省磁盘空间。
    如果比例因子不适合,则从下表选择范围小的去用:

    更新已有映射,添加price:

    插入文档:

    head查看:

    d、综合例子

以下以一个综合例子来说明。

删除索引库,重新创建。

创建映射:http://localhost:9200/demo/doc/_mapping

{                "properties": {                    "description": {                        "type": "text",                        "analyzer": "ik_max_word",                        "search_analyzer": "ik_smart"                    },                    "name": {                        "type": "text",                        "analyzer": "ik_max_word",                        "search_analyzer": "ik_smart"                    },                    "pic":{                                            "type":"text",                                                "index":false                                            },                                        "price": {                        "type": "float"                    },                    "studymodel": {                        "type": "keyword"                    },                    "timestamp": {                        "type": "date",                        "format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis"                    }                }            }

插入文档:

使用head查看:

那么映射的维护和映射的常用类型就介绍到这里。

7、索引管理

(1)es客户端

想要连接到elasticsearch,必须通过它提供的客户端来连接。那么es提供了多种不同的客户端,这里只说两种:

  • TransportClient:ES提供的传统客户端,官方计划8.0版本删除此客户端。
  • RestClient:RestClient是官方推荐使用的,它包括两种:Java Low Level REST Client和Java High Level REST Client。ES在6.0之后提供Java High Level REST Client, 两种客户端官方更推荐使用Java High Level REST Client,不过当前它还处于完善中,有些功能还没有。

这里就用RestClient客户端了,使用Java High Level REST Client高版本的,如果有不支持的,那么再使用Java High Level REST Client低版本的。

(2)创建工程

创建新的maven工程:test-elasticsearch。

  • pom依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.1.RELEASE</version></parent><dependencies><!-- 依赖的model模块 --><dependency><groupId>com.ycz</groupId><artifactId>ycz-model</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- 依赖api模块 --><dependency><groupId>com.ycz</groupId><artifactId>ycz-api</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- 依赖的web模块 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- commons工具包 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.4</version></dependency><!-- json转换包 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.73</version></dependency><!-- es客户端依赖 --><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>6.3.1</version></dependency><!-- es依赖 --><dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>6.3.1</version></dependency><!-- 测试包 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

ps:es6.3版本以前貌似不支持通过映射来删除索引库。

  • yml配置
    application.yml的配置信息如下:
server:  port: 50000 spring:  application:    name: test-es    ##自定义属性xuecheng:  elasticsearch:    ## 多个节点之间用逗号隔开    hostlist: 121.42.xxx.xxx:9200     # 索引库  course:         # 索引库名称    index: xc_course         # 索引库名称    type: doc #类型    # 索引库  media:    index: xc_course_media    type: doc         # 源字段    source_field: courseid,media_id,media_url,teachplan_id,media_fileoriginalname
  • 启动类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,MongoAutoConfiguration.class,MongoDataAutoConfiguration.class})@EntityScan(basePackages = {"com.ycz.domain"})@ComponentScan(basePackages = {"com.ycz.api"})@ComponentScan(basePackages = {"com.ycz.test.es"})public class EsTestApplication {        public static void main(String[] args) {        SpringApplication.run(EsTestApplication.class, args);    }}

说明一下:因为我这里没有在yml中配置dataSource数据源和mongodb的数据源,所以把有关数据源的自动配置类给排除掉了,不排除启动会报错。

  • 启动测试
    建议项目搭建完成后先启动,看是否能启动成功,因为每个人的配置环境不一样,有可能哪里不小心写错了,造成项目无法启动,我这里是可以启动的:

    项目启动成功不报错后就可以开始写代码了。

(3)es配置类

配置类的作用是向spring中注册es客户端,创建config目录,在目录下创建es的配置类,如下:

/* * es配置类 */@Configurationpublic class ElasticSearchConfig {    // 获取配置信息中的es节点连接列表    @Value("${xuecheng.elasticsearch.hostlist}")    private String hostlist;    // 注册es高版本客户端    @Bean    public RestHighLevelClient restHighLevelClient() {        // 解析hostlist的配置信息        String[] hosts = hostlist.split(",");        // 创建HttpHost数组,其中存放es主机和端口的配置信息        HttpHost[] httpHosts = new HttpHost[hosts.length];        for (int i = 0; i < hosts.length; i  ) {            String item = hosts[i];            // 获取主机            String host = item.split(":")[0];            // 获取端口            int port = Integer.parseInt(item.split(":")[1]);            httpHosts[i] = new HttpHost(host, port, "http");        }        // 创建客户端        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(                RestClient.builder(httpHosts));        return restHighLevelClient;    }    // 针对低版本    @Bean    public RestClient restClient() {        String[] hosts = hostlist.split(",");        HttpHost[] httpHosts = new HttpHost[hosts.length];        for (int i = 0; i < hosts.length; i  ) {            String item = hosts[i];            String host = item.split(":")[0];            int port = Integer.parseInt(item.split(":")[1]);            httpHosts[i] = new HttpHost(host, port, "http");        }        RestClient restClient = RestClient.builder(httpHosts).build();        return restClient;    }}

(4)删除索引库

下面使用RestClient客户端来删除已建立的索引库。在测试包中创建测试类,如下:

@SpringBootTest@RunWith(SpringRunner.class)public class TestIndex {        //注入客户端    @Autowired    RestHighLevelClient restHighLevelClient;        //低版本    @Autowired    RestClient restClient;        //测试删除索引库    @Test    public void testDelIndex() throws IOException {        //创建删除索引请求对象        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("demo");        //执行删除,返回响应对象        DeleteIndexResponse deleteIndexResponse = restHighLevelClient.                       indices().delete(deleteIndexRequest);        //获取响应结果        boolean res = deleteIndexResponse.isAcknowledged();        if(res == true) {            System.out.println("索引库删除成功!");        }else {            System.out.println("索引库删除失败!");          }    }}

先使用head插件查看:

目前是有一个名为demo的索引库的。

执行testDelIndex这个方法,控制台:

head插件再查看:

索引删除成功。

(5)创建索引库及映射

使用postman的话,创建索引库请求的body如下:

创建映射的话,请求的body如下:

那么现在使用客户端来创建索引库和映射mapping,在TestIndex中添加以下方法:

    //测试创建索引库    @Test    public void testCreateIndex() throws IOException {        //创建创建索引请求对象        CreateIndexRequest createIndexRequest = new CreateIndexRequest("xc_course");        //设置索引库的分片数和副本        createIndexRequest.settings(                Settings.builder()                .put("number_of_shards", 1)                .put("number_of_replicas", 0));        //构建XContentBuilder对象,用来创建映射        XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("properties")                                  .startObject("name").field("type", "text").field("analyzer", "ik_max_word")                                  .field("search_analyzer", "ik_smart").endObject().startObject("description").field("type", "text")                                  .field("analyzer", "ik_max_word").field("search_analyzer", "ik_smart").endObject()                                  .startObject("studymodel").field("type", "keyword").endObject().startObject("price")                                  .field("type", "float").endObject().endObject().endObject();        //设置映射        createIndexRequest.mapping("doc", mapping);        //执行,返回响应对象        CreateIndexResponse createIndexResponse = restHighLevelClient.                                 indices().create(createIndexRequest);        boolean res = createIndexResponse.isAcknowledged();        if(res == true) {            System.out.println("索引库创建成功!");        }else {            System.out.println("索引库创建失败!");        }    }

执行这个方法,控制台:

使用head查看:

索引库创建成功,再看映射:

映射也创建成功了。

(6)添加文档

使用postman添加文档的话,请求的body如下:

现在使用客户端来添加,在TestIndex中添加以下方法:

    //测试向索引库中添加文档    @Test    public void testAddDoc() throws IOException {        //准备要添加的数据,使用json传递        Map<String, Object> jsonMap = new HashMap<>();        jsonMap.put("name", "spring cloud实战");        jsonMap.put("description", "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud基础入门 3.实战Spring Boot 4.注册中心eureka。");        jsonMap.put("studymodel", "201001");        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy‐MM‐dd HH:mm:ss");        jsonMap.put("timestamp", dateFormat.format(new Date()));        jsonMap.put("price", 55.6f);        //创建索引请求对象        IndexRequest indexRequest = new IndexRequest("xc_course","doc");        //设置文档        indexRequest.source(jsonMap);        //执行,返回响应对象        IndexResponse indexResponse = restHighLevelClient.index(indexRequest);        //获取响应结果        DocWriteResponse.Result result = indexResponse.getResult();        if (result != null) {            System.out.println(result);        }    }

执行,控制台:

使用head查看:

具体内容:

添加成功。

(7)查询文档

在TestIndex中添加以下方法:

    //通过文档ID查询文档    @Test    public void testSearchDocById() throws IOException {        //请求对象        GetRequest getRequest = new GetRequest("xc_course", "doc", "WS3qznYBvN7iZrhPEeR2");        //执行,获取响应对象        GetResponse getResponse = restHighLevelClient.get(getRequest);        //获取响应结果        boolean res = getResponse.isExists();        if(res == true) {            Map<String,Object> source = getResponse.getSourceAsMap();            System.out.println(source);        }else {            System.out.println("文档不存在!");        }    }

执行,控制台输出如下:

查询成功了。

(8)更新文档

ES更新文档的顺序是:先检索到文档、将原来的文档标记为删除、创建新文档、删除旧文档,创建新文档就会重建索引。

在TestIndex中添加以下方法:

    //测试更新文档    @Test    public void testUpdateDoc() throws IOException {        //更新请求        UpdateRequest updateRequest = new UpdateRequest("xc_course", "doc", "WS3qznYBvN7iZrhPEeR2");        //要更新的内容        Map<String, Object> map = new HashMap<>();        map.put("name", "更新测试");        //设置文档        updateRequest.doc(map);        //执行,获取响应对象        UpdateResponse updateResponse = restHighLevelClient.update(updateRequest);        //获取响应结果        RestStatus status = updateResponse.status();        System.out.println(status);    }

执行,控制台如下:

用head查看:

更新成功。

(9)删除文档

是根据文档来删除的。在TestIndex中添加以下方法:

    //测试删除文档    @Test    public void testDelDoc() throws IOException {        String docId = "WS3qznYBvN7iZrhPEeR2";        //删除对象        DeleteRequest deleteRequest = new DeleteRequest("xc_course","doc",docId);        //执行,获取响应对象        DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest);        //获取响应结果        DocWriteResponse.Result result = deleteResponse.getResult();        if(result != null) {            System.out.println(result);        }    }

执行,控制台如下:

使用head查看:

那么文档删除成功了。

8、搜索管理

(1)准备环境

先创建索引库和映射,测试包下建立新类:TestSearch,如下:

@SpringBootTest@RunWith(SpringRunner.class)public class TestSearch {        @Autowired    RestHighLevelClient restHighLevelClient;        @Autowired    RestClient restClient;        //创建索引库及映射    @Test    public void createIndex() throws IOException {        //创建索引对象请求        CreateIndexRequest createIndexRequest = new CreateIndexRequest("xc_course");        //设置索引库的分片和副本数        createIndexRequest.settings(                Settings.builder()                .put("number_of_shards","1")                .put("number_of_replicas", 0));        //构建XContentBuilder对象        XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("properties")                .startObject("name").field("type", "text").field("analyzer", "ik_max_word")                .field("search_analyzer", "ik_smart").endObject().startObject("description").field("type", "text")                .field("analyzer", "ik_max_word").field("search_analyzer", "ik_smart").endObject()                .startObject("studymodel").field("type", "keyword").endObject().startObject("price")                .field("type", "float").endObject().startObject("pic").field("type", "text").field("index",false).endObject()                .startObject("timestamp").field("type","date").field("format","yyyy-MM-dd HH:mm:ss||yyyy-MM-dd").endObject()                .endObject().endObject();        //设置映射       createIndexRequest.mapping("doc", mapping);       //执行       CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest);       boolean res = createIndexResponse.isAcknowledged();       if(res == true) {           System.out.println("索引库创建成功!");       }else {           System.out.println("索引库创建失败!");       }    }}

执行这个方法,控制台:

head插件查看:

然后插入几条文档,在TestSearch中添加以下方法:

    //向索引库中添加文档    @Test    public void addDoc() throws IOException {        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        List<Map<String,Object>> list = new ArrayList<>();        //添加3条数据        Map<String,Object> jsonMap = new HashMap<>();        jsonMap.put("name","Bootstrap开发");        jsonMap.put("description","Bootstrap是由Twitter推出的一个前台页面开发框架,是一个非常流行的开发框架,此框架集成了\r\n"                   "多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松\r\n"                   "的实现一个不受浏览器限制的精美界面效果。");        jsonMap.put("studymodel", "201002");        jsonMap.put("pic", "group1/M00/00/00/1.jpg");        jsonMap.put("price", 30.5f);        jsonMap.put("timestamp",sdf.format(new Date()));        Map<String,Object> jsonMap2 = new HashMap<>();        jsonMap2.put("name","java编程基础");        jsonMap2.put("description","java语言是世界第一编程语言,在软件开发领域使用人数最多。");        jsonMap2.put("studymodel", "201001");        jsonMap2.put("pic", "group1/M00/00/00/2.jpg");        jsonMap2.put("price", 50.5f);        jsonMap2.put("timestamp",sdf.format(new Date()));        Map<String,Object> jsonMap3 = new HashMap<>();        jsonMap3.put("name","spring开发基础");        jsonMap3.put("description","spring在java领域非常流行,java程序员都在用。");        jsonMap3.put("studymodel", "201001");        jsonMap3.put("pic", "group1/M00/00/00/3.jpg");        jsonMap3.put("price", 100.5f);        jsonMap3.put("timestamp",sdf.format(new Date()));        list.add(jsonMap);        list.add(jsonMap2);        list.add(jsonMap3);        IndexRequest indexRequest = new IndexRequest("xc_course","doc");        for(int i=0;i<list.size();i  ) {            indexRequest.source(list.get(i));            IndexResponse indexResponse = restHighLevelClient.index(indexRequest);            DocWriteResponse.Result result = indexResponse.getResult();            if(result != null) {                System.out.println(result);            }        }    }

执行这个方法,控制台:

用head查看:

查看详细:

简单搜索的话直接用head插件:

下面将使用DSL来进行搜索。

(2)DSL搜索

DSL(Domain Specific Language)是ES提出的基于json的搜索方式,在搜索时传入特定的json格式的数据来完成不同的搜索需求。DSL比URI搜索方式功能强大,在项目中建议使用DSL方式来完成搜索。

a、查询所有文档

在TestSearch中添加以下方法:

    //测试搜索全部文档    @Test    public void testSearchAll() throws IOException, ParseException {        //搜索请求对象        SearchRequest searchRequest = new SearchRequest("xc_course");        //设置类型        searchRequest.types("doc");        //搜索源对象        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        //设置搜索方式,matchAllQuery为搜索全部        searchSourceBuilder.query(QueryBuilders.matchAllQuery());        //设置源字段过滤        //第一个参数是结果显示哪些field,第二个参数结果不显示哪些field        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp"},new String[] {});        //搜索请求对象中设置源对象        searchRequest.source(searchSourceBuilder);        //执行搜索请求,获取响应对象        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        //从响应结果中获取搜索结果        SearchHits searchHits = searchResponse.getHits();        //总记录数        long total = searchHits.getTotalHits();        //获取匹配的文档        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            //获取文档id            String docId = hit.getId();            //获取源文档内容            Map<String,Object> source = hit.getSourceAsMap();            //从源文档中获取field            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

重点是matchAllQuery这个方法,执行这个方法,控制台如下:

b、分页查询

在TestSearch中添加以下方法:

    //测试分页搜索查询    @Test    public void testSearchPaged() throws IOException, ParseException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        searchSourceBuilder.query(QueryBuilders.matchAllQuery());        //页码        int page = 1;        //每页记录条数        int size = 1;        int start = (page -1) * size;        //设置分页参数        searchSourceBuilder.from(start);        searchSourceBuilder.size(size);        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

和查询全部查不到,加了以下内容:

        //页码        int page = 1;        //每页记录条数        int size = 1;        int start = (page -1) * size;        //设置分页参数        searchSourceBuilder.from(start);        searchSourceBuilder.size(size);

执行这个方法,控制台如下:

然后将page值改为2,再执行:

c、Term Query精确查询

Term Query为精确查询,在搜索时会整体匹配关键字,不再将关键字分词。

在TestSearch中添加已以下方法:

    //测试termQuery精确查询    @Test    public void testTermQuery() throws IOException, ParseException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        //设置搜索方式,termQuery为按关键字精确匹配,不分词        searchSourceBuilder.query(QueryBuilders.termQuery("name", "java"));        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行,控制台输出:

关键是termQuery这个方法,只能匹配一个field。

d、按照id精确匹配

ES提供根据多个id值匹配的方法,可以传入多个id,在TestSearch中添加以下方法:

    //按照id搜索    @Test    public void testQueryByIds() throws IOException, ParseException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        //多个id放在数组中,传入数组参数就行了        String []ids = {"XS0h03YBvN7iZrhP1-TX","Xi0h03YBvN7iZrhP2OTS"};        //注意,这里是termsQuery方法,不是termQuery        searchSourceBuilder.query(QueryBuilders.termsQuery("_id", ids));        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行这个方法,控制台如下:

注意,这里是termsQuery方法,不是termQuery

e、match Query搜索

match Query即全文检索,它的搜索方式是先将搜索字符串分词,再使用各词条从索引中搜索。match query与Term query区别是match query在搜索前先将搜索关键字分词,再拿各词语去索引中搜索。match Query只能匹配一个field。

请求的body如下:

参数说明:

  • query:搜索的关键字,对于英文关键字如果有多个单词则中间要用半角逗号分隔,而对于中文关键字中间可以用逗号分隔也可以不用。
  • operator:or表示只要有一个词在文档中出现则就符合条件,and表示每个词都在文档中出现则才符合条件。
  • minimum_should_match:指定文档匹配词的占比。比如上面的搜索关键字会被分为三个词,"80%"表示,三个词在文档的匹配占比为80%,即3*0.8=2.4,向上取整得2,表示至少有两个词在文档中要匹配成功。

上边的搜索流程如下:

  • 将“spring开发”分词,分为spring、开发两个词。
  • 再使用spring和开发两个词去匹配索引中搜索。
  • 由于设置了operator为or,只要有一个词匹配成功则就返回该文档。

下面使用代码进行实现,在TestSearch中添加以下方法:

    //测试match Query分词搜索    @Test    public void testMatchQuery() throws ParseException, IOException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        //设置搜索方式,matchAllQuery为分词搜索        searchSourceBuilder.query(QueryBuilders.matchQuery("name", "spring开发框架")                                   .minimumShouldMatch("80%"));        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行这个方法,控制台如下:

f、multi Query

termQuery和matchQuery一次只能匹配一个Field,multiQuery一次可以匹配多个字段。

说明:查询会拿spring和框架这两个词去匹配name和description这两个field,多个词匹配的话可以提升字段的boost权重来提高得分。提升boost,通常关键字匹配上name的权重要比匹配上description的权重高,这里可以对name的权重提升。“name^10” 表示权重提升10倍,执行上边的查询,发现name中包括spring关键字的文档排在前边。

在TestSearch中添加以下方法:

    //测试multi Query搜索匹配多个field,并提升boost权重    @Test    public void testMultiQuery() throws ParseException, IOException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        //设置搜索方式,multiMatchQuery为匹配多个field,提升name的权重        searchSourceBuilder.query(QueryBuilders.multiMatchQuery("spring框架", "name","description")                                  .minimumShouldMatch("50%")                                  .field("name", 10));        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp","description"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            String description = (String)source.get("description");            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("description==========>>"   description);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行这个方法,控制台输出:

关键字是spring框架,按照50%匹配度,有spring或框架的记录都会搜索到,可以看到,第一条记录的name中包含spring,而第二条记录的name中不包含spring和框架,但是第二条记录的description中有框架这个词,由于设置了name的权重提升了10倍,因此name字段优先匹配关键字,所以排在了最前面。

g、布尔查询

布尔查询对应于Lucene的BooleanQuery查询,实现将多个查询组合起来。

参数说明:

  • must:文档必须匹配must所包括的查询条件,相当于 “AND”。
  • should:文档应该匹配should所包括的查询条件其中的一个或多个,相当于 “OR”。
  • must_not:文档不能匹配must_not所包括的该查询条件,相当于“NOT”。

在TestSearch中添加以下方法:

    //测试布尔查询    @Test    public void testBoolQuery() throws IOException, ParseException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        //MultiMatchQueryBuilder查询对象        MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("spring框架", "name","description")                                                   .minimumShouldMatch("50%")                                                   .field("name", 10);        //TermQueryBuilder查询对象        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("studymodel", "201001");        //创建布尔查询对象        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();        //将前面的两个查询对象组合在布尔查询对象里        boolQueryBuilder.must(matchQueryBuilder);        boolQueryBuilder.must(termQueryBuilder);        //将布尔查询对象设置在搜索源对象中        searchSourceBuilder.query(boolQueryBuilder);        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp","description"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            String description = (String)source.get("description");            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("description==========>>"   description);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行这个方法,控制台输出:

可以看到,布尔查询组合了MultiMatchQuery查询对象和TermQuery查询对象,这种方式在开发中用的还是比较多的。

h、过滤器

过滤是针对搜索的结果进行过滤,过滤器主要判断的是文档是否匹配,不去计算和判断文档的匹配度得分,所以过滤器性能比查询要高,且方便缓存,推荐尽量使用过滤器去实现查询或者过滤器和查询共同使用。过滤器在布尔查询中使用,下边是在搜索结果的基础上进行过滤。

参数说明:

  • range:范围过滤,保留大于等于60 并且小于等于100的记录。
  • term:项匹配过滤,保留studymodel等于"201001"的记录。

注意:range和term一次只能对一个Field设置范围过虑。

在TestSearch中添加以下方法:

    //测试过滤器对搜索结果执行过滤    @Test    public void testFilter() throws IOException, ParseException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("spring框架", "name","description")                                                   .minimumShouldMatch("50%")                                                   .field("name", 10);        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();        boolQueryBuilder.must(matchQueryBuilder);        //设置布尔查询的过滤器        //过滤studymodel为201001的问你当        boolQueryBuilder.filter(QueryBuilders.termQuery("studymodel", "201001"));        //过滤价格在50到100之间的记录        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(50).lte(100));        searchSourceBuilder.query(boolQueryBuilder);        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp","description"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            String description = (String)source.get("description");            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("description==========>>"   description);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行这个方法,控制台如下:

因为price值不满足过滤条件,无法过滤到此条文档,将lte的值改为110,再执行,控制台如下:

这时候价格就满足过滤条件了,可以将文档记录过滤出来。

i、排序

可以在字段上添加一个或多个排序,支持在keyword、date、float等类型上添加,text类型的字段上不允许添加排序。

过滤0–120元价格范围的文档,并且对结果进行排序,先按studymodel降序,再按价格升序。

测试代码如下:

    //测试过滤器后排序    @Test    public void testSort() throws IOException, ParseException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();        //过滤价格在0到120之间的记录        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(0).lte(120));        //排序        //按照studymodel降序        searchSourceBuilder.sort(new FieldSortBuilder("studymodel").order(SortOrder.DESC));        //按照price升序        searchSourceBuilder.sort(new FieldSortBuilder("price").order(SortOrder.ASC));        searchSourceBuilder.query(boolQueryBuilder);        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp","description"},new String[] {});        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            String description = (String)source.get("description");            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("description==========>>"   description);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行这个方法,控制台如下:

可以看到,是按照studymodel先进行降序的,然后按照price进行升序。

j、高亮显示

高亮显示可以将搜索结果一个或多个字突出显示,以便向用户展示匹配关键字的位置。
在搜索语句中添加highlight即可实现。

测试代码如下:

    //测试关键字高亮    @Test    public void testHighlight() throws IOException, ParseException {        SearchRequest searchRequest = new SearchRequest("xc_course");        searchRequest.types("doc");        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();        searchSourceBuilder.query(QueryBuilders.multiMatchQuery("spring框架", "name","description")                                  .minimumShouldMatch("50%")                                  .field("name",10));        searchSourceBuilder.fetchSource(                new String[] {"name","studymodel","price","timestamp","description"},new String[] {});        //高亮对象        HighlightBuilder highlightBuilder = new HighlightBuilder();        //设置前后标签        highlightBuilder.preTags("<tag>");        highlightBuilder.postTags("</tag>");        //设置需要高亮的字段        highlightBuilder.fields().add(new HighlightBuilder.Field("name"));        highlightBuilder.fields().add(new HighlightBuilder.Field("description"));        //搜索源对象中添加高亮对象        searchSourceBuilder.highlighter(highlightBuilder);        searchRequest.source(searchSourceBuilder);        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);        SearchHits searchHits = searchResponse.getHits();        long total = searchHits.getTotalHits();        SearchHit [] hits = searchHits.getHits();         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        System.out.println("一共搜索到"   total   "条数据,如下:");        for(SearchHit hit:hits) {            String docId = hit.getId();            Map<String,Object> source = hit.getSourceAsMap();            String name = (String)source.get("name");            String studymodel = (String)source.get("studymodel");            Double price = (Double)source.get("price");            Date timestamp = sdf.parse((String)source.get("timestamp"));            String description = (String)source.get("description");            //取出高亮字段            Map<String, HighlightField> highlightFields = hit.getHighlightFields();            if(highlightFields!=null) {                //取出name高亮字段                HighlightField nameHighlightField = highlightFields.get("name");                //取出description高亮字段                HighlightField desHighlightField = highlightFields.get("description");                //将name更换为高亮的                if(nameHighlightField!=null) {                    Text [] fragments = nameHighlightField.getFragments();                    StringBuilder sb = new StringBuilder();                    for(Text str:fragments) {                        sb.append(str.toString());                    }                    name = sb.toString();                }                //将description更换为高亮的                if(desHighlightField!=null) {                    Text []fragments = desHighlightField.getFragments();                    StringBuilder sb = new StringBuilder();                    for(Text str:fragments) {                        sb.append(str.toString());                    }                    description = sb.toString();                }            }            System.out.println("文档ID:"   docId);            System.out.println("name==========>>"   name);            System.out.println("description==============>"   description);            System.out.println("studymodel==========>>"   studymodel);            System.out.println("price==========>>"   price);            System.out.println("timestamp==========>>"   timestamp);            System.out.println("---------------------------------------");        }    }

执行这个方法,控制台如下:

可以看到,spring框架关键字分词之后,每个分词都是关键字,匹配到的name或descrition中含有spring或框架的都被打上了设置好的tag标签,其实这个标签很有用,前端可以事先给这个标签写一个样式,那么搜索结果返回到前端之后关键字就可以使用这个样式,就变成高亮的了,这个在开发中用的很多。

9、集群管理

(1)集群结构

ES通常以集群方式工作,这样做不仅能够提高 ES的搜索能力还可以处理大数据搜索的能力,同时也增加了系统的容错能力及高可用,ES可以实现PB级数据的搜索。

ES集群结构如下图:

说明:

  • 结点:ES集群由多个服务器组成,每个服务器即为一个Node结点(该服务只部署了一个ES进程)。
  • 分片:当我们的文档量很大时,由于内存和硬盘的限制,同时也为了提高ES的处理能力、容错能力及高可用能力,我们将索引分成若干分片,每个分片可以放在不同的服务器,这样就实现了多个服务器共同对外提供索引及搜索服务。一个搜索请求过来,会分别从各各分片去查询,最后将查询到的数据合并返回给用户。
  • 副本:为了提高ES的高可用同时也为了提高搜索的吞吐量,我们将分片复制一份或多份存储在其它的服务器,这样即使当前的服务器挂掉了,拥有副本的服务器照常可以提供服务。
  • 主节点:一个集群中会有一个或多个主节点,主节点的作用是集群管理,比如增加节点,移除节点等,主节点挂掉后ES会重新选一个主节点。
  • 节点转发:每个节点都知道其它节点的信息,我们可以对任意一个节点发起请求,接收请求的节点会转发给其它节点查询数据。

(2)搭建集群

下面创建一个两节点的集群,并且索引的分片设置2片,每片1个副本。

a、节点的三个角色

  • 主节点:master节点主要用于集群的管理及索引。比如新增节点、分片分配、索引的新增和删除等。
  • 数据节点:data节点上保存了数据分片,它负责索引和搜索操作。
  • 客户端节点:client节点仅作为请求客户端存在,client的作用也作为负载均衡器,client节点不存数据,只是将请求均衡转发到其它节点。

相关参数如下:

  • node.master:#是否允许为主结点。
  • node.data:#允许存储数据作为数据结点。
  • node.ingest:#是否允许成为协调节点。

4种组合方式:

  • master=true,data=true:即是主节点又是数据节点。
  • master=false,data=true:仅是数据节点。
  • master=true,data=false:仅是主节点,不存储数据。
  • master=false,data=false:即不是主节点也不是数据节点,此时可设置ingest为true表示它是一个客户端。

b、创建节点1

节点1对外服务的http端口是9200。
集群管理端口是9300。
配置elasticsearch.yml信息如下:

cluster.name: xuechengnode.name: xc_node_1network.host: 0.0.0.0http.port: 9200transport.tcp.port: 9300node.master: truenode.data: truediscovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]discovery.zen.minimum_master_nodes: 1node.ingest: truebootstrap.memory_lock: falsenode.max_local_storage_nodes: 2path.data: C:\dev\elasticsearch\es_1\datapath.logs: C:\dev\elasticsearch\es_1\logshttp.cors.enabled: truehttp.cors.allow-origin: /.*/

节点1是主节点,也是数据节点,允许成为数据节点。

c、创建节点2

节点2对外服务的http端口是9201。
集群管理端口是9301。
配置elasticsearch.yml信息如下:

cluster.name: xuechengnode.name: xc_node_2network.host: 0.0.0.0http.port: 9201transport.tcp.port: 9301node.master: truenode.data: truediscovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]discovery.zen.minimum_master_nodes: 1node.ingest: truebootstrap.memory_lock: falsenode.max_local_storage_nodes: 2path.data: C:\dev\elasticsearch\es_2\datapath.logs: C:\dev\elasticsearch\es_2\logshttp.cors.enabled: truehttp.cors.allow-origin: /.*/

启动节点1、节点2。使用head查看:

星星的为主节点,圆形为副节点。其实按照配置来说,这两个节点都是主节点,如果节点1挂了,那么节点2会顶上去,成为主节点。

查看节点状态:

查看集群健康值:

d、创建索引库

head连上任意一个节点,创建索引库:

分片为2,其中1个副本。点击OK。

创建成功后,这个集群一个有4个分片,其中2个副本。

e、关于集群的健康值

用3种颜色来表示集群的健康状态: green 、yellow 或者 red 。

  • green:所有的主分片和副本分片都正常运行。
  • yellow:所有的主分片都正常运行,但有些副本分片运行不正常。
  • red:存在主分片运行不正常。
  • 目前这个2节点的集群健康值为green,表示所有主、副分片都是正常运行的。

(3)测试集群

创建映射:

添加文档:

连到9201查看:

可以看到,向其中一个节点添加文档,其他节点可以看到数据。

然后现在我关闭节点1:

可以看到节点2现在成为了主节点,集群的健康值为yellow,因为节点1挂掉了。现在只能从节点2查询。

10、总结

使用ElasticSearch可以很好的解决数据量庞大查询的问题,经常被用于搜索中。现在是大数据时代,用好ES很重要。

来源:https://www.icode9.com/content-4-811801.html

(0)

相关推荐

  • 搜索引擎springboot集成(elasticSearch)

    各位小伙伴们,今天是今年的最后一天,这是我今年的最后一篇博客,在这里祝大家新年快乐!本次讲的是近几年比较流行的search搜索引擎,本文写的比较粗略,希望大家看了会有所收获,如若写错,请在评论区指出, ...

  • ES入门及安装软件

    es介绍 Elasticsearch,简称es,是一款高扩展的分布式全文检索引擎.它可以近乎实时的存储,检索数据.es是面向文档型的数据库,一条数据就是一个文档,用json做为文档序列化的格式.es是 ...

  • 分享settings和mapping的意义

    关于大数据settings和mapping的意义有哪些有不少的小伙伴不是很清楚,本篇文章就将大数据settings和mapping的意义分享给大家.下面我们一块来看一下. 一般不需要指定mapping ...

  • 中间件:ElasticSearch组件RestHighLevelClient用法详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.基础API简介 1.RestHighLevelClient RestHighLevelClient的API作为ElasticSearch备 ...

  • 在Linux中安装ElasticSearch&Kibana&ik分词器

    概述: ElasticSearch是一个基于Lucene的搜索服务器 是一个分布式.高扩展.高实时的搜索与数据分析引擎 基于RESTful web接口 Elasticsearch是用Java语言开发的 ...

  • ElasticSearch安装

    定义: Elasticsearch 是一个分布式的搜索和分析引擎,可以用于全文检索.结构化检索和分析,并能将这三者结合起来.Elasticsearch .ElasticSearch是一个基于Lucen ...

  • Python实战案例:flask结合elasticsearch实现全文搜索

    ElasticSearch简称ES,其中Elastic一词通过词典查询获得. 从名字里我们可以知道,ES的特点就在于灵活的搜索,其实ES本身就是一个全文搜索引擎. 一.全文搜索原理 如何实现全文搜索? ...

  • ElasticSearch之CURL操作

    CURL的操作 curl是利用URL语法在命令行方式下工作的开源文件传输工具,使用curl可以简单实现常见的get/post请求.简单的认为是可以在命令行下面访问url的一个工具.在centos的默认 ...

  • 「Elasticsearch」ES重建索引怎么才能做到数据无缝迁移呢?

    背景 众所周知,Elasticsearch是⼀个实时的分布式搜索引擎,为⽤户提供搜索服务.当我们决定存储某种数据,在创建索引的时候就需要将数据结构,即Mapping确定下来,于此同时索引的设定和很多固 ...

  • 一文入门.NET Core操作ElasticSearch 7.x

    原创 青城 青城同学 1周前在互联网上,随处可见的搜索框.背后所用的技术大多数就是全文检索.在全文检索领域,常见的库/组件有:Lucene.Solr.Sphinx.ElasticSearch等.简单对 ...

  • Elasticsearch分词

    分词器介绍 Elasticsearch作为全文检索服务是需要将输入的搜索关键字,也就是字符串进行一定规则的拆分,而拆分搜索关键字为一个个词,这部分功能是有ES的分词器来完成的.分词器(analyzer ...

  • Elasticsearch索引生命周期管理方案

    一.前言 在 Elasticsearch 的日常中,有很多如存储 系统日志.行为数据等方面的应用场景,这些场景的特点是数据量非常大,并且随着时间的增长 索引 的数量也会持续增长,然而这些场景基本上只有 ...