ElasticSearch入门 第二篇

集群配置-----------------------------

ElasticSearch共有两个配置文件,都位于config目录下,分别是elasticsearch.yml和logging.yml,其中,elasticsearch.yml 是全局配置文件,用于在服务器级别对ElasticSearch进行配置。ElasticSearch的配置选项分为静态设置和动态设置两种,静态设置必须在结点级别(node-level)设置,或配置在elasticsearch.yml配置文件中,或配置在环境变量中,或配置在命令行中,在结点启动之后,静态设置不能修改。集群的名字和结点的名称:cluster.name 和 node.name,这两个配置项是静态配置项,不能在集群运行时修改,而动态配置项可以在集群运行时通过RESTful API进行修改。

一,配置集群的标识(cluster.name)

通过 cluster.name 属性配置集群的名字,用于唯一标识一个集群,不同的集群,其 cluster.name 不同,集群名字相同的所有节点自动组成一个集群。如果不配置改属性,默认值是:elasticsearch。当启动一个结点时,该结点会自动寻找相同集群名字的主结点;如果找到主结点,该结点加入集群中;如果未找到主结点,该结点成为主结点。

注意:在yml中对Elasticsearch进行全局配置,要注意配置选项的格式:option: value,在“:”之后保留一个空格,之后是选项值;配置选项不要有前置空格。

配置集群的名字为myescluster

cluster.name: myescluster

二,结点的配置

ElasticSearch集群中,共有五种结点类型:主结点(Master),候选主结点(Master-eligible),数据结点(Data),吸收结点(Ingest)和部落结点(Tribe)。在一个集群中,主结点只有一个,负责管理集群,执行集群级别的操作,比如创建或删除索引,跟踪集群的组成结点的状态,决定将分片分配的目标结点,对集群来说,一个稳定的主结点十分重要。集群会自动进行健康检测,当主结点出现故障时,集群中的候选主结点进行选举,在选举结束之后,一个候选主结点被选举成为新的主结点,实现集群故障的自动转移。候选主结点具有投票权,其他结点没有投票权。

1,配置候选主结点和数据结点

默认情况下,ElasticSearch将当前节点配置为同时作为候选主结点和数据结点:

  • node.data:true 配置该结点是数据结点,用于保存数据,执行数据相关的操作(CRUD,Aggregation);
  • node.master:true 配置该结点有资格被选举为主结点(候选主结点),用于处理请求和管理集群。如果结点没有资格成为主结点,那么该结点永远不可能成为主结点;如果结点有资格成为主结点,只有在被其他候选主结点认可和被选举为主结点之后,才真正成为主结点。

配置当前结点只保存数据:

node.master: false
node.data: true

配置当前结点不保存数据,只做候选主结点:

node.master: false
node.data: true

2,主结点选取的配置

discovery.zen.minimum_master_nodes 属性:默认值是1,该属性定义的是为了组成一个集群,相互连接的候选主结点的最小数目,强烈推荐该属性的设置使用多数原则:(master_eligible_nodes / 2) + 1,既能避免出现脑裂(split-brain),又能在故障发生后,快速选举出新的主结点,例如,有5个候选主结点,推荐把该属性设置为3。

3,结点的路径(Path)

默认情况下,ElasticSearch使用基于安装目录的相对路径来配置结点的路径,安装目录由属性path.home显示,在home path下,ElasticSearch自动创建config,data,logs和plugins目录,一般情况下不需要对结点路径单独配置。结点的文件路径配置项:

  • path.config 设置ElasticSearch的配置文件保存的目录;
  • path.data 设置ElasticSearch结点的索引数据保存的目录,多个数据文件使用逗号隔开,例如,path.data: /path/to/data1,/path/to/data2;
  • path.logs 设置ElasticSearch结点的日志文件保存的目录;
  • path.plugins  设置ElasticSearch插件安装的目录;
  • path.work 设置ElasticSearch的临时文件保存的目录;

4,单个ElasticSearch实例的Java虚拟机(JVM)的堆内存限制

ElasticSearch实例必须运行在安装JDK的机器上,在安装完Java Runtime组件之后,必须创建系统变量JAVA_HOME,指定JDK安装的位置,比如:

JAVA_HOME: C:\Program Files\Java\jre1.8.0_121\bin

默认的JVM内存限制是 1GB,对小项目而言,1GB内存足够使用,不会出现问题,但是,对大项目而言,这个默认限制太小了,如果日志文件中经常出现OutOfMemoryError异常消息,就考虑重新设置 ES_HEAP_SIZE 环境变量,为JVM分配足够的内存,同时,必须为OS分配足够的内存,建议JVM使用的物理内存最大不应超过32GB,推荐分配机器物理内存的50%,在Windows中创建新的系统环境变量 ES_HEAP_SIZE,设置堆内存大小为5G:

环境变量
ElasticSearch 首先读取通用的JAVA_OPTS环境变量来获取JVM参数, 除了可以用JAVA_OPTS配置JVM参数外, 还可以通过ElasticSearch提供的ES_JAVA_OPTS环境变量配置JVM参数, ES_JAVA_OPTS会覆盖JAVA_OPTS中配置的相同参数, 官方建议是在JAVA_OPTS中配置通用的参数, 在ES_JAVA_OPTS中配置针对Elasticsearch需要调整的参数。环境变量最主要的作用是指定 -Xmx 最大堆大小和 -Xms 最小堆大小。

JAVA_OPTS ,是用来设置JVM相关运行参数的环境变量,例如:JAVA_OPTS="-server -Xms2048m -Xmx2048m"

  • -server:一定要作为第一个参数,在多个CPU时性能佳
  • -Xms:初始Heap大小,使用的最小内存,cpu性能高时此值应设的大一些
  • -Xmx:java heap最大值,使用的最大内存

上面两个值是分配JVM的最小和最大内存,取决于硬件物理内存的大小,建议均设为物理内存的一半。

三,索引配置

1,配置索引的分片(shard)和副本(replica)数量

默认的配置是把索引分为5个分片,每个分片1个副本,共10个结点:

index.number_of_shards: 5
index.number_of_replicas: 1

禁用索引的分布式特性,使索引只创建在本地主机上:

index.number_of_shards: 1
index.number_of_replicas: 0

2,当前节点锁住内存

当JVM做分页切换(swapping)时,ElasticSearch执行的效率会降低,推荐把ES_MIN_MEM和ES_MAX_MEM两个环境变量设置成同一个值,并且保证机器有足够的物理内存分配给ES,同时允许ElasticSearch进程锁住内存:

bootstrap.mlockall: true

3,断路器(Circuit Breaker)控制内存的使用量

断路器用于阻止产生OutOfMemoryError的操作,每一个断路器设置一个内存使用的上限,一旦操作达到该上限,ElasticSearch将阻止该操作继续使用内存。设置较多,一般不需要修改,保持默认值:

  • indices.breaker.total.limit: defaults to 70% of JVM heap
  • indices.breaker.request.limit: defaults to 60% of JVM heap
  • indices.breaker.request.overhead: defaults to 1
  • network.breaker.inflight_requests.limit: defaults to 100% of JVM heap
  • network.breaker.inflight_requests.overhead: defaults to 1
  • script.max_compilations_per_minute: defaults to 15

4,字段数据缓存

在对一个字段执行排序或聚合操作时,使用字段数据缓存(field data cache)将该字段的值加载到内存,以加快查询的速度。加载字段数据还原是IO密集的操作,推荐分配足够的内存,并且不分配过期时间:

  • indices.fielddata.cache.size,控制结点级别的字段数据缓存大小,默认值是无限大(unbounded),建议分配足够的物理内存;
  • indices.fielddata.cache.expire,控制字段数据缓存的过期时间,默认值是字段数据永不过期;
  • indices.breaker.fielddata.limit,断路器,默认值是JVM堆内存的 80%;当加载字段值所需要的内存超过JVM堆内存的80%,将引发异常;
  • indices.breaker.fielddata.overhead: 断路器,默认值是1.03;

5,结点查询缓存:

查询缓存用于缓存查询的结果,每一个节点都有一个查询缓存,缓存过期使用LRU淘汰策略(Least Recently Used eviction):当缓存满时,最近最少使用的数据被淘汰,从缓存中清除,以存储新的数据。

  • indices.queries.cache.enabled,默认值是true;
  • indices.queries.cache.size,默认值是10%;

6,索引缓存

索引缓存用于存储最新的索引文档(newly indexed documents),当该缓存填充满时,缓存的文件将被写入到磁盘中的段(segment)中。 

  • indices.memory.index_buffer_size,默认值是10%,在单个结点上,所有索引的分片占用的最大内存大小,或占用JVM堆内存的百分比;
  • indices.memory.min_index_buffer_size,当indices.memory.index_buffer_size指定为百分比时,使用该选项配置绝对值,默认值是48MB;
  • indices.memory.max_index_buffer_size,当indices.memory.index_buffer_size指定为百分比时,使用该选项配置绝对值,默认值是无限大;

7,分片请求缓存:

  • indices.requests.cache.enable:默认值是true
  • indices.requests.cache.size: 默认值是1%

当对一个索引执行查找请求(Search Request)时,每一个相关的分片(involved shard)都在本地执行查询,返回查询结果(local result)给协调结点(coordinating node)进行组合(combine),将各个分片的结果合并为最终的结果集返回。分片请求缓存对每个分片的查询结果进行缓存。

8,索引的刷新频率

索引对象刷新的频率,刷新频率越低,文档对搜索操作可视的时间越长:

  • index.refresh_interval,在索引级别指定索引的刷新频率,默认值是1s

9,段(Segment)合并

每个索引分为多个段(Segment),一个段写入硬盘后,就不能再被更新,因此,被删除文档的信息存储在一个单独的文件中。ElasticSearch支持文档的更新,在底层,实际上是删除旧文档,再把更新内存的文档编入索引。在查找时,需要从返回结果中过滤掉已删除的文档。

如果数据更新或删除的数据比较多,那么每个段中的有效数据密度会变低,降低了查询的性能,通过段合并,将已删除的数据从段(Segment)中物理删除,能够提高搜索性能,合并的过程是:底层的Lucene库获取若干段,从段中过滤已删除的数据,保存到一个新的段上;段合并完成之后,将源段从硬盘上物理删除;段合并操作的CPU和IO的消耗是非常高的,必须控制段合并执行的频率和时机。

段合并的策略控制段合并的时机,在适当的条件下进行段合并:

  • index.merge.policy.type,有效值:tiered,log_byte_size,log_doc
    • tiered:默认的合并策略,合并尺寸大致相似的段
    • log_byte_size:根据文档的字节数量进行合并
    • log_doc:根据文档的数量进行合并

段合并的线程控制,以串行或并发方式执行段合并:

  • index.merge.scheduler.type,有效值:serial(串行),concurrent(并发)

合并因子控制段合并的频率,合并因子越小,合并的频率越高:

  • index.merge.policy.merge_factor,默认值是10

段合并调节器,限制合并的速度:

  • indices.store.throttle.type,有效值:none,merge,all
  • indices.store.throttle.max_bytes_per_sec:默认值10mb

四,集群的网络配置

1,结点的IP地址配置

配置当前结点绑定的IP地址,默认为0.0.0.0

network.bind_host: 192.168.0.1

设置其它结点和该结点交互的ip地址,如果不设置它会自动判断,值必须是个真实的ip地址

network.publish_host: 192.168.0.1

同时设置bind_host和publish_host两个参数

network.host: 192.168.0.1

2,设置结点间交互的TCP端口,默认是9300

transport.tcp.port: 9300

3,设置对外服务的HTTP端口,默认为9200

http.port: 9200

4,设置是否压缩TCP传输时的数据,默认为false,不压缩

transport.tcp.compress: true

5,设置HTTP包内容的最大容量,默认100mb

http.max_content_length: 100mb

6,是否启用HTTP协议对外提供服务,默认为true,开启

http.enabled: true

五,集群的发现机制(Discovery)

ElasticSearch使用zen发现来寻找结点和选举主结点,组建集群;zen发现默认使用多播,但是,单播发现比较安全,推荐使用单播发现。

1,配置多播(multicast)

多播(multicast)是zen发现的默认方法,主要有以下配置选项:

  • discovery.zen.ping.multicast.group:用于多播请求的群组地址,默认值是224.2.2.4;
  • discovery.zen.ping.multicast.port:设置多播通信的端口,默认值是54328;
  • discovery.zen.ping.multicast.ttl;设置多播请求被认为有效的时间,默认值是3s;
  • discovery.zen.ping.multicast.address:设置ElasticSearch绑定的网络接口,默认为null,意味着ElasticSearch尝试绑定所有网络接口;
  • discovery.zen.ping.multicast.enable:启用或禁用多播;

2,配置单播(unicast)

使用单播时,总是禁用多播,单播的配置选项:

discovery.zen.ping.unicast. hosts:指定接收单播请求的主机IP地址,推荐包含组成集群的所有主机,主机之间用逗号隔开;

discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]

3,结点之间的状态检测

ping是结点之间发送的心跳信号,用于检测其他结点是否健康运行,如果其他结点无法响应该信号,那么集群认为该结点发生故障,配置ping信号的属性:

  • discovery.zen.fd.ping_interval:默认为1s,设置结点之间互相ping信号的时间间隔;
  • discovery.zen.fd.ping_timeout:默认为30s,设置当前结点发送ping信号后等待目标结点响应的时间,超过该时间,当前结点认为目标结点无法响应;
  • discovery.zen.fd.ping_retries:默认为3次,设置重试次数,超过此次数之后,判定目标结点出现故障,停止工作;
  • discovery.zen.ping.timeout: 默认值是3s,设置当前结点等待其他结点的ping信号的超时时间;

六,本地网关(Gateway)和还原(Recovery)

网关用于持久化存储集群的数据,包括集群的状态,索引和索引里面的数据,索引的类型映射和索引级别的配置信息等元数据,网关相当于关系型DB的事务日志。每一次集群数据的改变,ElasticSearch都将集群的数据存储到网关中。当集群重新启动时,它将会从网关中读取集群的数据,还原到上一次停机时的状态。

1,网关的类型

目前只支持local类型(本地网关),在本地文件系统中存储索引及其元数据;

gateway.type: local

2,还原控制

还原控制的选项有:

  • gateway.recover_after_nodes:3,表示当集群中有3个结点之后,允许执行还原进程;
  • gateway.recover_after_time:10m,表示当集群满足gateway.recover_after_nodes属性(集群中至少有3个结点)之后,ElasticSearch在10分钟之后开始执行还原进程;
  • gateway.expected_nodes:5,设置集群期望的结点数量,当集群中结点的数目等于5,并且满足gateway.recover_after_nodes属性(集群中至少有3个结点)时,ElasticSearch将立即执行还原进程;
  • 网关对数据结点和主结点的还原控制:
    • gateway.recover_after_master_nodes 和 gateway.expected_master_nodes
    • gateway.recover_after_data_nodes 和 gateway.expected_data_nodes

3,保护动作

默认情况下,ElasticSearch自动创建索引,推荐关闭该选项

action.disable_close_all_indices: true
action.disable_delete_all_indices: true
action.disable_shutdown: true
action.auto_create_index: false

4,还原限制(Recovery Throttling)

  • cluster.routing.allocation.node_initial_primaries_recoveries: 4,在初始化还原期间,控制单个结点中执行并行还原进程的数量;
  • cluster.routing.allocation.node_concurrent_recoveries: 2,在增加/移除结点,重平衡期间,控制单个结点中执行并行还原进程的数量;
  • indices.recovery.max_bytes_per_sec: 100mb,在还原时,吞吐量的上限,默认值是20MB;
  • indices.recovery.concurrent_streams: 5,在还原分片时,设置开启的并发流的上限;

七,线程池配置

Elasticsearch公开两种类型的线程池:

  • cache:无限制的线程池,为每个请求创建一个线程;
  • fixed:固定大小的线程池,大小由size属性指定,

对于固定大小的线程池类型,必须为ElasticSearch指定一个请求队列(queue)用来保存请求,请求被存储到队列中,直到有一个空闲的线程来执行请求;如果队列满了,ElasticSearch无法把请求存放到队列中,该请求将被拒绝;

  • threadpool.index.type: fixed
  • threadpool.index.size: 100
  • threadpool.index.queue_size: 500

附言:

ElasticSearch引擎的配置选项非常多,并且要真正理解这些配置选项对引擎的影响,也不是一件容易的事,不过,不用担心,在使用ElasticSearch时,真正需要修改配置选项的情况少之又少,所以,通常使用默认的配置就能满足生产环境的要求,ElasticSearch重要配置示例:

复制代码
## cluster
cluster.name: "my-es-cluster"

## node
node.name: "node-1"
node.master: true
node.data: true

## index
index.number_of_shards: 1
index.number_of_replicas: 0

## memory
bootstrap.mlockall: true

## network
network.host: 192.168.0.1
transport.tcp.port: 9300
http.port: 9200

## discovery
discovery.zen.minimum_master_nodes: 1
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]

## protection
action.auto_create_index: false
## scripting script.inline: true script.indexed: true
复制代码

索引--------------------------------

ElasticSearch是文档型数据库,索引(Index)定义了文档的逻辑存储和字段类型,每个索引可以包含多个文档类型,文档类型是文档的集合,文档以索引定义的逻辑存储模型,比如,指定分片和副本的数量,配置刷新频率,分配分析器等,存储在索引中的海量文档分布式存储在ElasticSearch集群中。

ElasticSearch是基于Lucene框架的全文搜索引擎,将所有文档的信息写入到倒排索引(Inverted Index)的数据结构中,倒排索引建立的是索引中词和文档之间的映射关系,在倒排索引中,数据是面向词(Term)而不是面向文档的。

ElasticSearch的对象模型,跟关系型数据库模型相比:

  • 索引(Index):相当于数据库,用于定义文档类型的存储;在同一个索引中,同一个字段只能定义一个数据类型;
  • 文档类型(Type):相当于关系表,用于描述文档中的各个字段的定义;不同的文档类型,能够存储不同的字段,服务于不同的查询请求;
  • 文档(Document):相当于关系表的数据行,存储数据的载体,包含一个或多个存有数据的字段;
    • 字段(Field):文档的一个Key/Value对;
    • 词(Term):表示文本中的一个单词;
    • 标记(Token):表示在字段中出现的词,由该词的文本、偏移量(开始和结束)以及类型组成;

索引是由段(Segment)组成的,段存储在硬盘(Disk)文件中,段不是实时更新的,这意味着,段在写入磁盘后,就不再被更新。ElasticSearch引擎把被删除的文档的信息存储在一个单独的文件中,在搜索数据时,ElasticSearch引擎首先从段中查询,再从查询结果中过滤被删除的文档,这意味着,段中存储着“被删除”的文档,这使得段中含有”正常文档“的密度降低。多个段可以通过段合并(Segment Merge)操作把“已删除”的文档将从段中物理删除,把未删除的文档合并到一个新段中,新段中没有”已删除文档“,因此,段合并操作能够提高索引的查找速度,但段合并是IO密集型的操作,需要消耗大量的硬盘IO。

一,创建索引

在创建索引之前,首先了解RESTful API的调用风格,在管理和使用ElasticSearch服务时,常用的HTTP动词有下面五个:

  • GET 请求:获取服务器中的对象
    • 相当于SQL的Select命令
    • GET /blogs:列出所有博客
  • POST 请求:在服务器上更新对象
    • 相当于SQL的Update命令
    • POST /blogs/ID:更新指定的博客
  • PUT 请求:在服务器上创建对象
    • 相当于SQL的Create命令
    • PUT /blogs/ID:新建一个博客  
  • DELETE 请求:删除服务器中的对象
    • 相当于SQL的Delete命令
    • DELETE /blogs/ID:删除指定的博客
  • HEAD 请求:仅仅用于获取对象的基础信息

1,禁用自动创建索引

推荐设置:在全局配置文件 elasticsearch.yml 中,禁用自动创建索引:

action.auto_create_index:false

2,手动创建索引

创建索引的语法是:PUT http://host:port/index_name/  + index_configuration

其中,index_name是创建的索引的名字,indiex_configuration 是向ElasticSearch服务器传递的请求负载的主体,数据格式json,用于定义索引的配置信息:映射节(mappings)和配置节(settings)。

在创建索引时,需要精心设计索引的映射节(mappings)和配置节(settings),本例创建blog索引和articles文档类型,创建索引的语法是:

PUT http://localhost:9200/blog/

下文详细介绍ElasticSearch索引的映射(Mapping)配置,详细信息请参考《Elasticsearch Reference [2.4] » Mapping》。注意,ElasticSearch引擎是大小写敏感的,强制性要求索引名和文档类型小写,对于字段名,ElasticSearch引擎会将首字母小写,建议在配置索引,文档类型和字段名时,都使用小写字母。

二,索引映射节(mappings)

1,索引结构

索引是由文档类型构成的,在mappings字段中定义索引的文档类型,示例代码中为blog索引定义了三个文档类型:articles,followers和comments

复制代码
{  
   "mappings":{  
      "articles":{ },
      "followers":{ },
      "comments":{ }
   }
}
复制代码

2,文档属性

文档属性定义了文档类型的共用属性,适用于文档的所有字段:

  • dynamic_date_formats属性:该属性定义可以识别的日期格式列表;
  • dynamic属性:默认值为true,允许动态地向文档类型中加入新的字段。推荐设置为false,禁止向文档中添加字段,这样,文档类型的所有字段必须在索引映射的properties属性中显式定义,在properties字段中未定义的字段都将会ElasticSearch忽略。
    • dynamic设置为ture:默认值,新增加的字段被添加到索引映射中;
    • dynamic设置为false:新增加的字段会被忽略;
    • dynamic设置为strict:当向文档中新增字段时,ElasticSearch引擎抛出异常;
复制代码
{  
   "mappings":{  
      "articles":{  "dynamic":false,
         "dynamic_date_formats":["yyyy-MM-dd hh:mm:ss", "yyyy-MM-dd" ],
         "properties":{  
            "id":{},
            "title":{},
            "author":{},
            "content":{},
            "postedat":{}
         }
      }
   }
}
复制代码

三,文档的字段属性

1,字段的数据类型

字段的数据类型由字段的属性type指定,ElasticSearch支持的基础数据类型主要有:

  • 字符串类型:string;
  • 数值类型:字节(byte)、2字节(short)、4字节(integer)、8字节(long)、float、double;
  • 布尔类型:boolean,值是true或false;
  • 时间/日期类型:date,用于存储日期和时间;
  • 二进制类型:binary;
  • IP地址类型:ip,以字符串形式存储IPv4地址;
  • 特殊数据类型:token_count,用于存储索引的字数信息

在文档类型的properties属性中,定义字段的type属性,指定字段的数据类型,属性properties 用于定义文档类型的字段属性,或字段对象的属性:

 "properties":{  
            "id":{"type":"long"},

2,字段的公共属性

  • index:该属性控制字段是否编入索引被搜索,该属性共有三个有效值:analyzed、no和not_analyzed:
    • analyzed:表示该字段被分析,编入索引,产生的token能被搜索到;
    • not_analyzed:表示该字段不会被分析,使用原始值编入索引,在索引中作为单个词;
    • no:不编入索引,无法搜索该字段;
    • 其中analyzed是分析,分解的意思,默认值是analyzed,表示将该字段编入索引,以供搜索。
  • store:指定是否将字段的原始值写入索引,默认值是no,字段值被分析,能够被搜索,但是,字段值不会存储,这意味着,该字段能够被查询,但是不会存储字段的原始值。
  • boost:字段级别的助推,默认值是1,定义了字段在文档中的重要性/权重;
  • include_in_all:该属性指定当前字段是否包括在_all字段中,默认值是ture,所有的字段都会包含_all字段中;如果index=no,那么属性include_in_all无效,这意味着当前字段无法包含在_all字段中。
  • copy_to:该属性指定一个字段名称,ElasticSearch引擎将当前字段的值复制到该属性指定的字段中;
  • doc_values:文档值是存储在硬盘上的索引时(indexing time)数据结构,对于not_analyzed字段,默认值是true,analyzed string字段不支持文档值;
  • fielddata:字段数据是存储在内存中的查询时(querying time)数据结构,只支持analyzed string字段;
  • null_value:该属性指定一个值,当字段的值为NULL时,该字段使用null_value代替NULL值;在ElasticSearch中,NULL 值不能被索引和搜索,当一个字段设置为NULL值,ElasticSearch引擎认为该字段没有任何值,使用该属性为NULL字段设置一个指定的值,使该字段能够被索引和搜索。

3,字符串类型常用的其他属性

  • analyzer:该属性定义用于建立索引和搜索的分析器名称,默认值是全局定义的分析器名称,该属性可以引用在配置结点(settings)中自定义的分析器;
  • search_analyzer:该属性定义的分析器,用于处理发送到特定字段的查询字符串;
  • ignore_above:该属性指定一个整数值,当字符串字段(analyzed string field)的字节数量大于该数值之后,超过长度的部分字符数据将不能被analyzer处理,不能被编入索引;对于 not analyzed string字段,超过长度的部分字符将被忽略,不会被编入索引。默认值是0,禁用该属性;
  • position_increment_gap:该属性指定在相同词的位置上增加的gap,默认值是100;
  • index_options:索引选项控制添加到倒排索引(Inverted Index)的信息,这些信息用于搜索(Search)和高亮显示:
    • docs:只索引文档编号(Doc Number)
    • freqs:索引文档编号和词频率(term frequency)
    • positions:索引文档编号,词频率和词位置(序号)
    • offsets:索引文档编号,词频率,词偏移量(开始和结束位置)和词位置(序号)
    • 默认情况下,被分析的字符串(analyzed string)字段使用positions,其他字段使用docs; 

分析器(analyzer)把analyzed string 字段的值,转换成标记流(Token stream),例如,字符串"The quick Brown Foxes",可能被分解成的标记(Token)是:quick,brown,fox。这些词(term)是该字段的索引值,这使用对索引文本的查找更有效率。字段的属性 analyzer 用于指定在index-time和search-time时,ElasticSearch引擎分解字段值的分析器名称。

4,数值类型的其他属性

  • precision_step:该属性指定为数值字段每个值生成的term数量,值越低,产生的term数量越高,范围查询越快,索引越大,默认值是4;
  • ignore_malformed:忽略格式错误的数值,默认值是false,不忽略错误格式,对整个文档不处理,并且抛出异常;
  • coerce:默认值是true,尝试将字符串转换为数值,如果字段类型是整数,那么将小数取整;

5,日期类型的其他属性

  • format:指定日期的格式,例如:“yyyy-MM-dd hh:mm:ss”
  • precision_step:该属性指定数值字段每隔多少数值,生成一个词(term);step值越低,产生的词数量越高,范围查询越快,索引越大,占用存储空间越大;
  • ignore_malformed:忽略错误格式,默认值是false,不忽略错误格式;

6,多字段(fields)

在fields属性中定义一个或多个字段,该字段的值和当前字段值相同,可以设置一个字段用于搜索,一个字段用于排序等。

"properties":
{  
    "id":{  "type":"long",
         "fields":{  "id2":{"type":"long","index":"not_analyzed"} }
      },

7,文档值(doc_values) 

默认情况下,多数字段都被一起编入索引,用户使用倒排索引(Inverted Index)可以搜索到相应的词(Term),倒排索引支持在唯一的有序词列表中查找特定词,或检查文档中是否包含某个词,但是,对于排序(Sort),聚合和在脚本中访问特定字段的值(Field value),这三个操作需要执行不同的数据访问模式,即单字段数据访问:在文档中查找特定的字段,检查该字段是否包含指定的词。

文档值(doc_values)属性指定将字段的值写入到硬盘上的列式结构,实现了单个字段的数据访问模式,能够高效执行排序和聚合搜索。使用文档值的字段将有专属的字段数据缓存实例,无需像普通字段一样倒排。是存储在硬盘上的数据结构,在文档索引时创建。文档值数据存在硬盘上,在文档索引时创建,存储的数据和字段存储在_source 字段的数据相同,文档值支持所有的字段类型,除了analyzed string 字段之外。

默认情况下,所有的字段都支持文档值,默认是启用的(enabled),如果不需要在单个字段上执行排序或聚合操作,或者从脚本中访问指定字段的值,那么,可以禁用文档值,字段的值将不会存储在硬盘空间中。

复制代码
"properties": {
    "status_code": { 
        "type":       "string",
        "index":      "not_analyzed"
        "doc_values": true
    },
    "session_id": { 
        "type":       "string",
        "index":      "not_analyzed",
        "doc_values": false
    }
}
复制代码

8,字段数据(Fielddata)

字段数据(Fielddata)是存储在内存中的查询时数据结构,只支持analyzed string字段。该数据结构在字段第一次执行聚合,排序或被脚本访问时创建。创建的过程是:在读取整个倒排索引(Inverted Index)时,ElasticSearch从硬盘上加载倒排索引的每个段(Segment),倒转词(Term)和文档的关系,并将其存储在JVM堆内存中。加载字段数据的过程是非常消耗IO资源的,一旦被加载,就被存储在内存中,直到段的生命周期结束。

对于analyzed string字段,fielddata字段是默认启用的,

"text":{  
   "type":"string",
   "fielddata":{  "loading":"lazy"
   }
}

详细信息,请参考Mapping parameters » fielddata

Analyzed strings use a query-time data structure called fielddata. This data structure is built on demand the first time that a field is used for aggregations, sorting, or is accessed in a script. It is built by reading the entire inverted index for each segment from disk, inverting the term ↔︎ document relationship, and storing the result in memory, in the JVM heap.

9,存储(store)

存储(store)属性指定是否将字段的原始值写入索引,默认值是no,字段值被分析,能够被搜索,但是,字段的原始值不会存储,这意味着,该字段能够被查询,但是无法获取字段的原始值。默认情况下,该字段的值会被存储到_source字段中,如果想要获取单个或多个字段的值,而不是整个_source字段,可以使用 source filtering 来实现;但是在特定的条件下,只存储一个字段的值是有意义的(make sense),例如,一个article文档包含:title,postdate和content字段,从文档中只获取title和postdate字段,并且使_source 字段包含content字段,必须通过store属性来控制:

复制代码
"mappings": {
    "my_type": {
      "properties": {
        "title": {
          "type": "string",
          "store": true 
        },
        "date": {
          "type": "date",
          "store": true 
        },
        "content": {
          "type": "string",
"store": false } } } }
复制代码

10,位置增加间隔(position_increment_gap)

对于analyzed string字段,都会考虑把词的位置信息,用于支持位置和短语匹配查询(proximity or phrase queries),例如,有一个字符串字段,该字段中存在多个词“fake”,ElasticSearch引擎会在每个值之间增加一个gap,以防止短语匹配或位置匹配查询出现跨越多个词的异常,这个gap的值就是属性position_increment_gap,默认值是100;

四,元字段

在索引的映射中,元字段(Meta-field)是以下划线开头的字段,部分元字段可以配置,部分元字段不可配置,只能用于返回信息。

1,_all 字段,可以配置

ElasticSearch使用_all字段存储其他字段的数据以便搜索,默认情况下,_all字段是启用的,包含了索引中所有字段的数据,然而这一字段使索引变大,如果不需要,请禁用该字段,或排除某些字段。为了在_all字段中不包括某个特定字段,在字段中设置“include_in_all”属性为false。

禁用_all字段,需要修改映射配置:

复制代码
{  
   "articles":{  "_all":{  
         "enabled":false
      }
   }
}
复制代码

2,_source 字段,可以配置

_source字段表示在生成索引的过程中,存储发送到ElasticSearch的原始JSON文档,默认情况下,该字段会被启用,因为索引的局部更新功能依赖该字段。

复制代码
{  
   "articles":{  
      "_source":{  
         "enabled":true
      }
   }
}

{  
   "articles":{  
      "_source":{  
         "excludes":["Content","Comments"],
         "includes":["author"]
      }
   }
}
复制代码

3,_routing 字段,可以配置

路由字段,将一个文档值进行哈希映射,并将该文档路由到指定的分片,路由的公式是:

shard_num = hash(_routing) % num_primary_shards

在ElasticSearch 2.4 版本中,path参数被废弃,使用的默认字段是_id,设置required为true,表示路由字段在进行索引的CRUD操作时必需显式赋值。

复制代码
{  
   "articles":{  
      "_routing":{  
         "required":true
      }
   }
}
复制代码

在put 命令中,使用自定义的路由字段,以下示例使用 user1字段作为路由字段更新和查询文档:

PUT my_index/my_type/1?routing=user1 
{
  "title": "This is a document"
}
GET my_index/my_type/1?routing=user1

4,不可配置的元字段

  • _index:返回文档所属的索引
  • _uid:返回文档的type和id
  • _type:返回文档类型(type)
  • _id:返回文档的ID;
  • _size:返回文档的_source字段中函数的字节数量;
  • _field_names:返回文档中不包含null值的字段名称;

详细信息,请参考:Mapping » Meta-Fields

五,索引配置节(settings)

1,配置索引的分片和副本数量

ElasticSearch索引是有一个或多个分片组成的,每个分片是索引的一个水平分区,包含了文档数据的一部分;每个分片有0,1或多个副本,分片的副本和分片存储相同的数据。

示例代码,为索引创建5个分片,分片没有副本:

"settings":{
    "number_of_shards":5,
    "number_of_replicas":0,

2,配置分析器(analyzer)

在配置结点的analysis属性中配置分析器,参考官方文档了解更多,

分词器(tokenizer)是系统预定义的,常用的分词器是:

  • standard:默认值,用于大多数欧洲语言的标准分词器
  • simple:基于非字母字符来分词,并将其转化为小写形式
  • whitespace:基于空格来分词
  • stop:除了simple的所有功能,还能基于停用词(stop words)过滤数据;
  • pattern:使用正则表达式分词;
  • snowball:除了standard提供的分词功能之外,还提供词干提取功能;

过滤器是系统预定义的,常用的过滤器是:

  • asciifolding
  • lowercase
  • kstem

在配置结点中,自定义分析器(analyzer)示例代码:

复制代码
{  
   "settings":{  
      "index":{  
         "analysis":{  
            "analyzer":{  
               "myanalyzer_name":{  
                  "tokenizer":"standard",
                  "filter":[  
                     "asciifolding",
                     "lowercase",
                     "ourEnglishFilter"
                  ]
               }
            },
            "filter":{  
               "ourEnglishFilter":{  
                  "type":"kstem"
               }
            }
         }
      }
   }
}
复制代码

六,删除索引

删除索引的语法是: DELETE http://localhost:9200/blog

七,更新索引

索引的更新分为逐个文档的更新和批量文档更新:

1,单个文档(Individual Document)的更新

单个文档更新的语法是:POST http://localhost:9200/blog/articles/1 +文档对象的JSON数据

POST http://localhost:9200/blog/articles/1

文档对象的JSON数据示例如下:

复制代码
{  
   "id":1,
   "title":"Elasticsearch index",
   "Author":"悦光阴",
   "content":"xxxxxxxxxxx",
   "postedat":"2017-03-14"
}
复制代码

2,批量文档的更新(Bluk)

批量文档更新的语法是:POST http://localhost:9200/_bulk + 批量文档对象的JSON数据,在_bulk 端进行批量更新操作。

在传递的请求主体中,每一个请求分为两个JSON数据,第一个JSON数据包含操作说明的描述信息,第二个JSON数据包含文档对象:

{  
   "index":{  
      "_index":"blog",
      "_type":"ariticles",
      "_id":1
   }
}
{  
   "id":1,
   "title":"Elasticsearch index",
   "Author":"悦光阴",
   "content":"xxxxxxxxxxx",
   "postedat":"2017-03-14"
}
{  
   "index":{  
      "_index":"blog",
      "_type":"ariticles",
      "_id":2
   }
}
{  
   "id":2,
   "title":"Elasticsearch index",
   "Author":"悦光阴",
   "content":"xxxxxxxxxxx",
   "postedat":"2017-03-14"
}
复制代码
{  
   "index":{  
      "_index":"blog",
      "_type":"ariticles",
      "_id":1
   }
}
{  
   "id":1,
   "title":"Elasticsearch index",
   "Author":"悦光阴",
   "content":"xxxxxxxxxxx",
   "postedat":"2017-03-14"
}
{  
   "index":{  
      "_index":"blog",
      "_type":"ariticles",
      "_id":2
   }
}
{  
   "id":2,
   "title":"Elasticsearch index",
   "Author":"悦光阴",
   "content":"xxxxxxxxxxx",
   "postedat":"2017-03-14"
}
复制代码

八,搜索索引

在_search端对索引数据进行搜索,ES查询的语法非常复杂,总体来说,ElasticSearch支持聚合查询和简单查询。

1,按照路由搜索

路由可以控制文档和查询转发的目的分片,ElasticSearch计算路由字段的哈希值,对于相同的路由值,将产生相同的哈希值,分配到特定的分片上;如果在查询时,指定路由值,那么只需要搜索单个分片而不是整个索引,就能获取查询结果。

路由字段由文档类型的_routing属性定义,在查询时,使用routing参数来查找特定路由的文档:

GET http://localhost:9200/blog/_search?routing=1235&q=article_id=100

2,聚合和简单查询

附:索引的配置文档

{  
   "settings":{  
      "number_of_shards":5,
      "number_of_replicas":0
   },
   "mappings":{  
      "articles":{  
         "_routing":{  
            "required":false
         },
         "_all":{  
            "enabled":false
         },
         "_source":{  
            "enabled":true
         },
         "dynamic_date_formats":[  
            "yyyy-MM-dd",
            "yyyyMMdd"
         ],
         "dynamic":"false",
         "properties":{  
            "articleid":{  
               "type":"long",
               "store":true,
               "index":"not_analyzed",
               "doc_values":true,
               "ignore_malformed":true,
               "include_in_all":true,
               "null_value":0,
               "precision_step":16
            },
            "title":{  
               "type":"string",
               "store":true,
               "index":"analyzed",
               "doc_values":false,
               "ignore_above":0,
               "include_in_all":true,
               "index_options":"positions",
               "position_increment_gap":100,
               "fields":{  
                  "title":{  
                     "type":"string",
                     "store":true,
                     "index":"not_analyzed",
                     "doc_values":true,
                     "ignore_above":0,
                     "include_in_all":false,
                     "index_options":"docs",
                     "position_increment_gap":100
                  }
               }
            },
            "author":{  
               "type":"string",
               "store":true,
               "index":"analyzed",
               "doc_values":false,
               "ignore_above":0,
               "include_in_all":true,
               "index_options":"positions",
               "position_increment_gap":100,
               "fields":{  
                  "author":{  
                     "type":"string",
                     "index":"not_analyzed",
                     "include_in_all":false,
                     "doc_values":true
                  }
               }
            },
            "content":{  
               "type":"string",
               "store":true,
               "index":"analyzed",
               "doc_values":false,
               "ignore_above":0,
               "include_in_all":false,
               "index_options":"positions",
               "position_increment_gap":100
            },
            "postat":{  
               "type":"date",
               "store":true,
               "doc_values":true,
               "format":[  
                  "yyyy-MM-dd",
                  "yyyyMMdd"
               ],
               "index":"not_analyzed",
               "ignore_malformed":true,
               "include_in_all":true,
               "null_value":"2000-01-01",
               "precision_step":16
            }
         }
      }
   }
}
复制代码
{  
   "settings":{  
      "number_of_shards":5,
      "number_of_replicas":0
   },
   "mappings":{  
      "articles":{  
         "_routing":{  
            "required":false
         },
         "_all":{  
            "enabled":false
         },
         "_source":{  
            "enabled":true
         },
         "dynamic_date_formats":[  
            "yyyy-MM-dd",
            "yyyyMMdd"
         ],
         "dynamic":"false",
         "properties":{  
            "articleid":{  
               "type":"long",
               "store":true,
               "index":"not_analyzed",
               "doc_values":true,
               "ignore_malformed":true,
               "include_in_all":true,
               "null_value":0,
               "precision_step":16
            },
            "title":{  
               "type":"string",
               "store":true,
               "index":"analyzed",
               "doc_values":false,
               "ignore_above":0,
               "include_in_all":true,
               "index_options":"positions",
               "position_increment_gap":100,
               "fields":{  
                  "title":{  
                     "type":"string",
                     "store":true,
                     "index":"not_analyzed",
                     "doc_values":true,
                     "ignore_above":0,
                     "include_in_all":false,
                     "index_options":"docs",
                     "position_increment_gap":100
                  }
               }
            },
            "author":{  
               "type":"string",
               "store":true,
               "index":"analyzed",
               "doc_values":false,
               "ignore_above":0,
               "include_in_all":true,
               "index_options":"positions",
               "position_increment_gap":100,
               "fields":{  
                  "author":{  
                     "type":"string",
                     "index":"not_analyzed",
                     "include_in_all":false,
                     "doc_values":true
                  }
               }
            },
            "content":{  
               "type":"string",
               "store":true,
               "index":"analyzed",
               "doc_values":false,
               "ignore_above":0,
               "include_in_all":false,
               "index_options":"positions",
               "position_increment_gap":100
            },
            "postat":{  
               "type":"date",
               "store":true,
               "doc_values":true,
               "format":[  
                  "yyyy-MM-dd",
                  "yyyyMMdd"
               ],
               "index":"not_analyzed",
               "ignore_malformed":true,
               "include_in_all":true,
               "null_value":"2000-01-01",
               "precision_step":16
            }
         }
      }
   }
}
复制代码

在head插件中,打开"Any Request"窗体,输入索引名称:blog;在操作列表中选择PUT,并将配置文档作为请求body,点击下方的“Request”按钮,向Elasticsearch引擎发起请求,当右边面板中出现"acknowledged":true 时,说明索引blog创建成功。

在测试阶段,可以禁用路由(_routing)和_all字段,启用源(_source)字段,以便更好的观察索引的行为。

当启用dynamic属性时,推荐所有字段的名称都使用小写,

复合数据类型——数组,对象和嵌套-----------------------------

在ElasticSearch中,使用JSON结构来存储数据,一个Key/Value对是JSON的一个字段,而Value可以是基础数据类型,也可以是数组,文档(也叫对象),或文档数组,因此,每个JSON文档都内在地具有层次结构。复合数据类型是指数组类型,对象类型和嵌套类型,各个类型的特点分别是:

  • 数组字段是指一个字段有多个值,每个值都是该数组字段的一个元素;元素的类型可以是基础类型,也可以是文档类型;
  • 对象类型是指字段的值是一个JSON文档;
  • 嵌套字段是指对象类型的一个特殊版本,ElasticSearch引擎在内部把嵌套字段索引成单个文档。如果在嵌套字段中定义对象数组,那么对象数组中的每个元素(文档)都被索引成单个文档,每个文档都能被独立地查询。

一,对象类型

JSON文档是有层次结构的,一个文档可能包含其他文档,如果一个文档包含其他文档,那么该文档值是对象类型,其数据类型是对象,ElasticSearch默认把文档的属性type设置为object,即"type":"object"。

例如,在创建索引映时,定义name字段为对象类型,不需要显式定义type属性值,其默认值是object:

复制代码
"manager":{  
   "properties":{  
      "age":{ "type":"integer"},
      "name":{  
         "properties":{  
            "first":{"type":"string"},
            "last":{ "type":"string"}
         }
      }
   }
}
复制代码

默认情况下,上述文档类型被索引为以点号命名的数据结构,把层次结构展开之后,数据结构是由扁平的key/value对构成:

{
  "manager.age":        30,
  "manager.name.first": "John",
  "manager.name.last":  "Smith"
}

二,开箱即用的数组类型

在ElasticSearch中,没有专门的数组(Array)数据类型,但是,在默认情况下,任意一个字段都可以包含0或多个值,这意味着每个字段默认都是数组类型,只不过,数组类型的各个元素值的数据类型必须相同。在ElasticSearch中,数组是开箱即用的(out of box),不需要进行任何配置,就可以直接使用。

1,数组类型

在同一个数组中,数组元素的数据类型是相同的,ElasticSearch不支持元素为多个数据类型:[ 10, "some string" ],常用的数组类型是:

  • 字符数组: [ "one", "two" ]
  • 整数数组: productid:[ 1, 2 ]
  • 对象(文档)数组: "user":[ { "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }],ElasticSearch内部把对象数组展开为 {"user.name": ["Mary", "John"], "user.age": [12,10]}

对于文档数组,每个元素都是结构相同的文档,文档之间都不是独立的,在文档数组中,不能独立于其他文档而去查询单个文档,这是因为,一个文档的内部字段之间的关联被移除,各个文档共同构成对象数组。

对整数数组进行查询,例如,使用多词条(terms)查询类型,查询productid为1和2的文档:

复制代码
{  
   "query":{  
      "terms":{  
         "productid":[ 1, 2 ]
      }
   }
}
复制代码

2,对象数组

通过PUT动词,自动创建索引和文档类型,在文档中创建对象数组:

复制代码
PUT my_index/my_type/1
{
  "group" : "fans",
  "user" : [ 
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}
复制代码

ElasticSearch引擎内部把对象数组展开成扁平的数据结构,把上例的文档类型的数据结构展开之后,文档数据类似于:

{
  "group" :        "fans",
  "user.first" : [ "alice", "john" ],
  "user.last" :  [ "smith", "white" ]
}

字段 user.first 和 user.last 被展开成数组字段,但是,这样展开之后,单个文档内部的字段之间的关联就会丢失,在该例中,展开的文档数据丢失first和last字段之间的关联,比如,Alice 和 white 的关联就丢失了。

三,嵌套数据类型

嵌套数据类型是对象数据类型的特殊版本,它允许对象数组中的各个对象被索引,数组中的各个对象之间保持独立,能够对每一个文档进行单独查询,这就意味着,嵌套数据类型保留文档的内部之间的关联,ElasticSearch引擎内部使用不同的方式处理嵌套数据类型和对象数组的方式,对于嵌套数据类型,ElasticSearch把数组中的每一个嵌套文档(Nested Document)索引为单个文档,这些文档是隐藏(Hidden)的,文档之间是相互独立的,但是,保留文档的内部字段之间的关联,使用嵌套查询(Nested Query)能够独立于其他文档而去查询单个文档。在创建嵌套数据类型的字段时,需要设置字段的type属性为nested。

1,在索引映射中创建嵌套字段

设置user字段为嵌套数据类型,由于每个字段默认都可以是数组类型,因此,嵌套字段也可以是对象数组。

复制代码
"mappings":{  
   "my_type":{  
      "properties":{  
         "group":{ "type":"string"},
         "user":{  
            "type":"nested",
            "properties":{  
               "first":{ "type":"string"},
               "second":{  "type":"string"}
            }
         }
      }
   }
}
复制代码

2,为嵌套字段赋值

为嵌套字段赋予多个值,那么ElasticSearch自动把字段值转换为数组类型。

复制代码
PUT my_index/my_type/1
{
  "group" : "fans",
  "user" : [ 
    { "first" : "John", "last" :  "Smith"},
    { "first" : "Alice", "last" :  "White"}
  ]
}
复制代码

在ElasticSearch内部,嵌套的文档(Nested Documents)被索引为很多独立的隐藏文档(separate documents),这些隐藏文档只能通过嵌套查询(Nested Query)访问。每一个嵌套的文档都是嵌套字段(文档数组)的一个元素。嵌套文档的内部字段之间的关联被ElasticSearch引擎保留,而嵌套文档之间是相互独立的。在该例中,ElasticSearch引起保留Alice和White之间的关联,而John和White之间是没有任何关联的。

默认情况下,每个索引最多创建50个嵌套文档,可以通过索引设置选项:index.mapping.nested_fields.limit 修改默认的限制。

Indexing a document with 100 nested fields actually indexes 101 documents as each nested document is indexed as a separate document.

四,嵌套查询

嵌套查询用于查询嵌套对象,执行嵌套查询执行的条件是:嵌套对象被索引为单个文档,查询作用在根文档(Root Parent)上。嵌套查询由关键字“nested”指定:

"nested" : {
        "path" : "obj1",
"query" : {...}

1,必须赋值的参数:

  • path参数:指定嵌套字段的文档路径,根路径是顶层的文档,通过点号“.”来指定嵌套文档的路径;
  • query参数:在匹配路径(参数path)的嵌套文档上执行查询,query参数指定对嵌套文档执行的查询条件。

2,使用嵌套查询访问嵌套文档

复制代码
GET my_index/_search
{
  "query": {
    "nested": {
      "path": "user",
      "query": {
        "bool": {
          "must": [
            { "match": { "user.first": "Alice" }},
            { "match": { "user.last":  "White" }} 
          ]
        }
      }
    }
  }
}
复制代码

五,使用C#索引数组类型

1,创建ElasticSearch的索引映射

复制代码
{  
   "settings":{  
      "number_of_shards":5,
      "number_of_replicas":0
   },
   "mappings":{  
      "events":{  
        "dynamic":"false",
         "properties":{  
            "eventid":{  
               "type":"long",
               "store":true,
               "index":"not_analyzed"
            },
            "eventname":{  
               "type":"string",
               "store":true,
               "index":"analyzed",
               "fields":{  
                  "raw":{  
                     "type":"string",
                     "store":true,
                     "index":"not_analyzed"
                  }
               }
            },
            "topics":{  
               "type":"integer",
               "store":true,
               "index":"analyzed"
            }
         }
      }
   }
}
复制代码

对于topics字段,类型是integer,赋予其一组整数值[1,2,3],那么该字段就能存储数组。

"topics":{  
    "type":"integer",
    "store":true,
    "index":"analyzed"
}

2,创建数据模型(Data Model)

为数组字段定义为List类型,每个列表项的数据类型是int。

复制代码
public class EventBase
{
    public long eventid { get; set; }
}

public class EbrieEvents:EventBase
{
    public string eventname { get; set; }
    public List<int> topics { get; set; }
}
复制代码

3,为字段赋值

为List字段topics赋值,调用NEST对该文档进行索引

复制代码
EbrieEvents pb = new EbrieEvents();

//Topics List
List<string> strTopics = TableRow["Topics"].ToString().TrimEnd(',').Split(',').ToList();
List<int> topics = new List<int>();
foreach(string str in strTopics)
{
    topics.Add(int.Parse(str));
}
pb.topics = topics;  
复制代码

4,查询数组字段

复制代码
{  
   "query":{  
      "terms":{  
         "topics":[1001,487]
      }
   }
}

分析器-----------------------------

在全文搜索(Fulltext Search)中,词(Term)是一个搜索单元,表示文本中的一个词,标记(Token)表示在文本字段中出现的词,由词的文本、在原始文本中的开始和结束偏移量、以及数据类型等组成。ElasticSearch 把文档数据写到倒排索引(Inverted Index)的结构中,倒排索引建立词(Term)和文档之间的映射,索引中的数据是面向词,而不是面向文档的。分析器(Analyzer)的作用就是分析(Analyse),用于把传入Lucene的文档数据转化为倒排索引,把文本处理成可被搜索的词。分析器由一个分词器(Tokenizer)和零个或多个标记过滤器(TokenFilter)组成,也可以包含零个或多个字符过滤器(Character Filter)。

在ElasticSearch引擎中,分析器的任务是分析(Analyze)文本数据,分析是分词,规范化文本的意思,其工作流程是:

  • 首先,字符过滤器对分析(analyzed)文本进行过滤和处理,例如从原始文本中移除HTML标记,根据字符映射替换文本等,
  • 过滤之后的文本被分词器接收,分词器把文本分割成标记流,也就是一个接一个的标记,
  • 然后,标记过滤器对标记流进行过滤处理,例如,移除停用词,把词转换成其词干形式,把词转换成其同义词等,
  • 最终,过滤之后的标记流被存储在倒排索引中;
  • ElasticSearch引擎在收到用户的查询请求时,会使用分析器对查询条件进行分析,根据分析的结构,重新构造查询,以搜索倒排索引,完成全文搜索请求,

可见,分析器扮演的是处理索引数据和查询条件的重要角色。在2.4版本中,ElasticSearch 预定义了7个分析器,并且支持用户根据预定义的字符过滤器,分词器和标记过滤器创建自定义的分析器,以满足用户多样性的文本分析需求。

用户在创建索引时配置索引的分析,通过向ElasticSearch发送请求,在请求body的settings 配置节中设置索引的分析器,例如,为索引配置默认的分析器:

复制代码
"settings":{  
    "index":{
        "analysis":{
            "analyzer":{
                "default":{
                    "type":"standard"
                    ,"stopwords":"_english_"
                }
            }
        }
    }
}
复制代码

一,字符过滤器(Char Filter)

字符过滤器对未经分析的文本起作用,作用于被分析的文本字段(该字段的index属性为analyzed),字符过滤器在分词器之前工作,用于从文档的原始文本去除HTML标记(markup),或者把字符“&”转换为单词“and”。ElasticSearch 2.4版本内置3个字符过滤器,分别是:映射字符过滤器(Mapping Char Filter)、HTML标记字符过滤器(HTML Strip Char Filter)和模式替换字符过滤器(Pattern Replace Char Filter)。

1,映射字符过滤器

映射字符过滤器,类型是mapping,需要建立一个查找字符和替换字符的映射(Mapping),过滤器根据映射把文本中的字符替换成指定的字符。

复制代码
{
    "index" : {
        "analysis" : {
            "char_filter" : {
                "my_mapping" : {
                    "type" : "mapping",
                    "mappings" : [
                      "ph => f",
                      "qu => k"
                    ]
                }
            },
            "analyzer" : {
                "custom_with_char_filter" : {
                    "tokenizer" : "standard",
                    "char_filter" : ["my_mapping"]
                }
            }
        }
    }
}
复制代码

2,HTML标记字符过滤器

HTML标记字符过滤器,类型是html_strip,用于从原始文本中去除HTML标记。

3,模式替换字符过滤器

模式替换字符过滤器,类型是pattern_replace,它使用正则表达式(Regular Expression)匹配字符,把匹配到的字符替换为指定的替换字符串。

复制代码
PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "char_filter": [
            "my_char_filter"
          ]
        }
      },
      "char_filter": {
        "my_char_filter": {
          "type": "pattern_replace",
          "pattern": "(\\d+)-(?=\\d)",
          "replacement": "$1_"
        }
      }
    }
  }
}
复制代码

pattern参数:指定Java正则表达式;

replacement参数:指定替换字符串,把正则表达式匹配的字符串替换为replacement参数指定的字符串;

二,分词器(Tokenizer)

分词器在字符过滤器之后工作,用于把文本分割成多个标记(Token),一个标记基本上是词加上一些额外信息,分词器的处理结果是标记流,它是一个接一个的标记,准备被过滤器处理。ElasticSearch 2.4版本内置很多分词器,本节简单介绍常用的分词器。

1,标准分词器(Standard Tokenizer)

标准分词器类型是standard,用于大多数欧洲语言,使用Unicode文本分割算法对文档进行分词。

2,字母分词器(Letter Tokenizer)

字符分词器类型是letter,在非字母位置上分割文本,这就是说,根据相邻的词之间是否存在非字母(例如空格,逗号等)的字符,对文本进行分词,对大多数欧洲语言非常有用。

3,空格分词器(Whitespace Tokenizer)

空格分词类型是whitespace,在空格处分割文本

4,小写分词器(Lowercase Tokenizer)

小写分词器类型是lowercase,在非字母位置上分割文本,并把分词转换为小写形式,功能上是Letter Tokenizer和 Lower Case Token Filter的结合(Combination),但是性能更高,一次性完成两个任务。

5,经典分词器(Classic Tokenizer)

经典分词器类型是classic,基于语法规则对文本进行分词,对英语文档分词非常有用,在处理首字母缩写,公司名称,邮件地址和Internet主机名上效果非常好。

三,标记过滤器(Token Filter)

分析器包含零个或多个标记过滤器,标记过滤器在分词器之后工作,用来处理标记流中的标记。标记过滤从分词器中接收标记流,能够删除标记,转换标记,或添加标记。ElasticSearch 2.4版本内置很多标记过滤器,本节简单介绍常用的过滤器。

1,小写标记过滤器(Lowercase)

类型是lowercase,用于把标记转换为小写形式,通过language参数指定语言,小写标记过滤器支持的语言有:Greek, Irish, and Turkish

复制代码
index :
    analysis :
        analyzer :
            myAnalyzer2 :
                type : custom
                tokenizer : myTokenizer1
                filter : [myTokenFilter1, myGreekLowerCaseFilter]
                char_filter : [my_html]
        tokenizer :
            myTokenizer1 :
                type : standard
                max_token_length : 900
        filter :
            myTokenFilter1 :
                type : stop
                stopwords : [stop1, stop2, stop3, stop4]
            myGreekLowerCaseFilter :
                type : lowercase
                language : greek
        char_filter :
              my_html :
                type : html_strip
                escaped_tags : [xxx, yyy]
                read_ahead : 1024
复制代码

2,停用词标记过滤器(Stopwords)

类型是stop,用于从标记流中移除停用词。参数stopwords用于指定停用词,ElasticSearch 2.4版本提供的预定义的停用词列表:预定义的英语停用词是_english_,使用预定义的英语停用词列表是  “stopwords” :"_english_"

复制代码
PUT /my_index
{
    "settings": {
        "analysis": {
            "filter": {
                "my_stop": {
                    "type":       "stop",
                    "stopwords": ["and", "is", "the"]
                }
            }
        }
    }
}
复制代码

3,词干过滤器(Stemmer)

类型是stemmer,用于把词转换为其词根形式存储在倒排索引,能够减少标记。

复制代码
{
    "index" : {
        "analysis" : {
            "analyzer" : {
                "my_analyzer" : {
                    "tokenizer" : "standard",
                    "filter" : ["standard", "lowercase", "my_stemmer"]
                }
            },
            "filter" : {
                "my_stemmer" : {
                    "type" : "stemmer",
                    "name" : "english"
                }
            }
        }
    }
}
复制代码

4,同义词过滤器(Synonym)

类型是synonym,在分析阶段,基于同义词规则,把词转换为其同义词存储在倒排索引中

复制代码
{
    "index" : {
        "analysis" : {
            "analyzer" : {
                "synonym" : {
                    "tokenizer" : "whitespace",
                    "filter" : ["synonym"]
                }
            },
            "filter" : {
                "synonym" : {
                    "type" : "synonym",
                    "synonyms_path" : "analysis/synonym.txt"
                }
            }
        }
    }
}
复制代码

同义词文件的格式示例:

复制代码
# Blank lines and lines starting with pound are comments.

# Explicit mappings match any token sequence on the LHS of "=>"
# and replace with all alternatives on the RHS.  These types of mappings
# ignore the expand parameter in the schema.
# Examples:
i-pod, i pod => ipod,
sea biscuit, sea biscit => seabiscuit

# Equivalent synonyms may be separated with commas and give
# no explicit mapping.  In this case the mapping behavior will
# be taken from the expand parameter in the schema.  This allows
# the same synonym file to be used in different synonym handling strategies.
# Examples:
ipod, i-pod, i pod
foozball , foosball
universe , cosmos

# If expand==true, "ipod, i-pod, i pod" is equivalent
# to the explicit mapping:
ipod, i-pod, i pod => ipod, i-pod, i pod
# If expand==false, "ipod, i-pod, i pod" is equivalent
# to the explicit mapping:
ipod, i-pod, i pod => ipod

# Multiple synonym mapping entries are merged.
foo => foo bar
foo => baz
# is equivalent to
foo => foo bar, baz
复制代码

四,系统预定义的分析器

在创建索引映射时引用分析器,如果没有定义分析器,那么ElasticSearch将使用默认的分析器,用户可以通过API设置默认的分析器。

default 逻辑名称用于配置在索引和搜索时使用的分析器,default_search 逻辑名称用于配置在搜索时使用的分析器。

index :
  analysis :
    analyzer :
      default :
        tokenizer : keyword

1,标准分析器(Standard)

分析器类型是standard,由标准分词器(Standard Tokenizer),标准标记过滤器(Standard Token Filter),小写标记过滤器(Lower Case Token Filter)和停用词标记过滤器(Stopwords Token Filter)组成。参数stopwords用于初始化停用词列表,默认是空的。

2,简单分析器(Simple)

分析器类型是simple,实际上是小写标记分词器(Lower Case Tokenizer),在非字母位置上分割文本,并把分词转换为小写形式,功能上是Letter Tokenizer和 Lower Case Token Filter的结合(Combination),但是性能更高,一次性完成两个任务。

3,空格分析器(Whitespace)

分析器类型是whitespace,实际上是空格分词器(Whitespace Tokenizer)。

4,停用词分析器(Stopwords)

分析器类型是stop,由小写分词器(Lower Case Tokenizer)和停用词标记过滤器(Stop Token Filter)构成,配置参数stopwords 或 stopwords_path指定停用词列表。

5,雪球分析器(Snowball)

分析器类型是snowball,由标准分词器(Standard Tokenizer),标准过滤器(Standard Filter),小写过滤器(Lowercase Filter),停用词过滤器(Stop Filter)和雪球过滤器(Snowball Filter)构成。参数language用于指定语言。

复制代码
{
    "index" : {
        "analysis" : {
            "analyzer" : {
                "my_analyzer" : {
                    "type" : "snowball",
                    "language" : "English"
                }
            }
        }
    }
}
复制代码

6,自定义分析器

分析器类型是custom,允许用户定制分析器。参数tokenizer 用于指定分词器,filter用于指定过滤器,char_filter用于指定字符过滤器。

复制代码
index :
    analysis :
        analyzer :
            myAnalyzer2 :
                type : custom
                tokenizer : myTokenizer1
                filter : [myTokenFilter1, myTokenFilter2]
                char_filter : [my_html]
                position_increment_gap: 256
        tokenizer :
            myTokenizer1 :
                type : standard
                max_token_length : 900
        filter :
            myTokenFilter1 :
                type : stop
                stopwords : [stop1, stop2, stop3, stop4]
            myTokenFilter2 :
                type : length
                min : 0
                max : 2000
        char_filter :
              my_html :
                type : html_strip
                escaped_tags : [xxx, yyy]
                read_ahead : 1024
复制代码

五,查询分析

在分析(_ayalyze)端点上执行分析查询,用于对查询参数进行分析,并返回分析的结果

1,使用默认的分析器执行查询分析

例如,在索引ebrite上执行分析查询,分析字符“After School”,从返回的结果中,可以看到两个标记(Token):“after”和“school”,类型(type)是字符数字类型(<ALPHANUM>),偏移量(offset)从1开始计数,位置(position)从0开始计数。

POST myindex/_analyze -d
"After School"

2,指定分析器

POST myindex/_analyze?analyzer=standard -d
"After School"

3,指定分词器和过滤器

POST myindex/_analyze?tokenizer=standard&filters=lowercase -d
"After School"

4,在特定的字段上执行分析查询

POST myindex/_analyze?field=doc_field&tokenizer=standard&filters=lowercase -d
"After School"

 

附,在创建索引时,指定默认的分析器

示例代码,使用PUT动词,在创建索引时指定默认的分析器,ElasticSearch引擎在索引文档时,使用默认的分析器对index属性为analyzed的文本字段执行分析操作,而非分析字段,将不会应用分析操作。

{  
   "settings":{  
      "number_of_shards":5,
      "number_of_replicas":0,
      "index":{
        "analysis":{
            "analyzer":{
                "default":{
                    "type":"standard"
                    ,"stopwords":"_english_"
                }
            }
        }
      }
   },
   "mappings":{  
      "events":{  
         "dynamic":"false",
         "properties":{  
            "eventid":{  
               "type":"long",
               "store":false,
               "index":"not_analyzed"
            },
            "eventname":{  
               "type":"string",
               "store":false,
               "index":"analyzed",
               "fields":{  
                  "raw":{  
                     "type":"string",
                     "store":false,
                     "index":"not_analyzed"
                  }
               }
            }
         }
      }
   }
}

存储-----------------------------

在ElasticSearch 2.4版本中,文档存储的介质分为内存和硬盘:内存速度快,但是容量有限;硬盘速度较慢,但是容量很大。同时,ElasticSearch进程自身的运行也需要内存空间,必须保证ElasticSearch进程有充足的运行时内存。为了使ElasticSearch引擎达到最佳性能,必须合理分配有限的内存和硬盘资源。

一,倒排索引(Inverted Index)

ElasticSearch引擎把文档数据写入到倒排索引(Inverted Index)的数据结构中,倒排索引建立的是分词(Term)和文档(Document)之间的映射关系,在倒排索引中,数据是面向词(Term)而不是面向文档的。

举个例子,文档和词条之间的关系如下图:

字段值被分析之后,存储在倒排索引中,倒排索引存储的是分词(Term)和文档(Doc)之间的关系,简化版的倒排索引如下图:

从图中可以看出,倒排索引有一个词条的列表,每个分词在列表中是唯一的,记录着词条出现的次数,以及包含词条的文档。实际上,ElasticSearch引擎创建的倒排索引比这个复杂得多。

1,段是倒排索引的组成部分

倒排索引是由段(Segment)组成的,段存储在硬盘(Disk)文件中。索引段不是实时更新的,这意味着,段在写入硬盘之后,就不再被更新。在删除文档时,ElasticSearch引擎把已删除的文档的信息存储在一个单独的文件中,在搜索数据时,ElasticSearch引擎首先从段中执行查询,再从查询结果中过滤被删除的文档,这意味着,段中存储着被删除的文档,这使得段中含有”正常文档“的密度降低。多个段可以通过段合并(Segment Merge)操作把“已删除”的文档将从段中物理删除,把未删除的文档合并到一个新段中,新段中没有”已删除文档“,因此,段合并操作能够提高索引的查找速度,但是,段合并是IO密集型操作,需要消耗大量的硬盘IO。

在ElasticSearch中,大多数查询都需要从硬盘文件(索引的段数据存储在硬盘文件中)中获取数据,因此,在全局配置文件elasticsearch.yml 中,把结点的路径(Path)配置为性能较高的硬盘,能够提高查询性能。默认情况下,ElasticSearch使用基于安装目录的相对路径来配置结点的路径,安装目录由属性path.home显示,在home path下,ElasticSearch自动创建config,data,logs和plugins目录,一般情况下不需要对结点路径单独配置。结点的文件路径配置项:

  • path.data:设置ElasticSearch结点的索引数据保存的目录,多个数据文件使用逗号隔开,例如,path.data: /path/to/data1,/path/to/data2;
  • path.work:设置ElasticSearch的临时文件保存的目录;

2,分词和原始文本的存储

映射参数index决定ElasticSearch引擎是否对文本字段执行分析操作,也就是说分析操作把文本分割成一个一个的分词,也就是标记流(Token Stream),把分词编入索引,使分词能够被搜索到:

  • 当index为analyzed时,该字段是分析字段,ElasticSearch引擎对该字段执行分析操作,把文本分割成分词流,存储在倒排索引中,使其支持全文搜索;
  • 当index为not_analyzed时,该字段不会被分析,ElasticSearch引擎把原始文本作为单个分词存储在倒排索引中,不支持全文搜索,但是支持词条级别的搜索;也就是说,字段的原始文本不经过分析而存储在倒排索引中,把原始文本编入索引,在搜索的过程中,查询条件必须全部匹配整个原始文本;
  • 当index为no时,该字段不会被存储到倒排索引中,不会被搜索到;

字段的原始值是否被存储到倒排索引,是由映射参数store决定的,默认值是false,也就是,原始值不存储到倒排索引中。

映射参数index和store的区别在于:

  • store用于获取(Retrieve)字段的原始值,不支持查询,可以使用投影参数fields,对stroe属性为true的字段进行过滤,只获取(Retrieve)特定的字段,减少网络负载;
  • index用于查询(Search)字段,当index为analyzed时,对字段的分词执行全文查询;当index为not_analyzed时,字段的原始值作为一个分词,只能对字段的原始文本执行词条查询;

3,单个分词的最大长度

如果设置字段的index属性为not_analyzed,原始文本将作为单个分词,其最大长度跟UTF8 编码有关,默认的最大长度是32766Bytes,如果字段的文本超过该限制,那么ElasticSearch将跳过(Skip)该文档,并在Response中抛出异常消息:

operation[607]: index returned 400 _index: ebrite _type: events _id: 76860 _version: 0 error: Type: illegal_argument_exception Reason: "Document contains at least one immense term in field="event_raw" (whose UTF8 encoding is longer than the max length 32766), all of which were skipped. Please correct the analyzer to not produce such terms. The prefix of the first immense term is: '[112, 114,... 115]...', original message: bytes can be at most 32766 in length; got 35100" CausedBy:Type: max_bytes_length_exceeded_exception Reason: "bytes can be at most 32766 in length; got 35100"

可以在字段中设置ignore_above属性,该属性值指的是字符数量,而不是字节数量;由于一个UTF8字符最多占用3个字节,因此,可以设置

“ignore_above”:10000

这样,超过30000字节之后的字符将会被分析器忽略,单个分词(Term)的最大长度是30000Bytes。

The value for ignore_above is the character count, but Lucene counts bytes. If you use UTF-8 text with many non-ASCII characters, you may want to set the limit to 32766 / 3 = 10922 since UTF-8 characters may occupy at most 3 bytes.

二,列式存储(doc_values)

默认情况下,大多数字段被索引之后,能够被搜索到。倒排索引是由一个有序的词条列表构成的,每一个词条在列表中都是唯一存在的,通过这种数据存储模式,你可以很快查找到包含某一个词条的文档列表。但是,排序和聚合操作采用相反的数据访问模式,这两种操作不是查找词条以发现文档,而是查找文档,以发现字段中包含的词条。ElasticSearch使用列式存储实现排序和聚合查询。

文档值(doc_values)是存储在硬盘上的数据结构,在索引时(index time)根据文档的原始值创建,文档值是一个列式存储风格的数据结构,非常适合执行存储和聚合操作,除了字符类型的分析字段之外,其他字段类型都支持文档值存储。默认情况下,字段的文档值存储是启用的,除了字符类型的分析字段之外。如果不需要对字段执行排序或聚合操作,可以禁用字段的文档值,以节省硬盘空间。

复制代码
"mappings": {
    "my_type": {
        "properties": {
        "status_code": { 
            "type":       "string",
            "index":      "not_analyzed"
        },
        "session_id": { 
            "type":       "string",
            "index":      "not_analyzed",
            "doc_values": false
        }
        }
    }
}
复制代码

三,顺排索引(fielddata)

字符类型的分析字段,不支持文档值(doc_values),但是,支持fielddata数据结构,fielddata数据结构存储在JVM的堆内存中。相比文档值(数据存储在硬盘上),fielddata字段(数据存储在内存中)的查询性能更高。默认情况下,ElasticSearch引擎在第一次对字段执行聚合或排序查询时((query-time)),创建fielddata数据结构;在后续的查询请求中,ElasticSearch引擎使用fielddata数据结构以提高聚合和排序的查询性能。

在ElasticSearch中,倒排索引的各个段(segment)的数据存储在硬盘文件上,从整个倒排索引的段中读取字段数据之后,ElasticSearch引擎首先反转词条和文档之间的关系,创建文档和词条之间的关系,即创建顺排索引,然后把顺排索引存储在JVM的堆内存中。把倒排索引加载到fielddata结构是一个非常消耗硬盘IO资源的过程,因此,数据一旦被加载到内存,最好保持在内存中,直到索引段(segment)的生命周期结束。默认情况下,倒排索引的每个段(segment),都会创建相应的fielddata结构,以存储字符类型的分析字段值,但是,需要注意的是,分配的JVM堆内存是有限的,Fileddata把数据存储在内存中,会占用过多的JVM堆内存,甚至耗尽JVM赖以正常运行的内存空间,反而会降低ElasticSearch引擎的查询性能。

1,format属性

fielddata会消耗大量的JVM内存,因此,尽量为JVM设置大的内存,不要为不必要的字段启用fielddata存储。通过format参数控制是否启用字段的fielddata特性,字符类型的分析字段,fielddata的默认值是paged_bytes,这就意味着,默认情况下,字符类型的分析字段启用fielddata存储。一旦禁用fielddata存储,那么字符类型的分析字段将不再支持排序和聚合查询。

复制代码
"mappings": {
    "my_type": {
      "properties": {
        "text": {
          "type": "string",
          "fielddata": {
            "format": "disabled" 
          }
        }
      }
    }
  }
复制代码

2,加载属性(loading)

loading属性控制fielddata加载到内存的时机,可能的值是lazy,eager和eager_global_ordinals,默认值是lazy。

  • lazy:fielddata只在需要时加载到内存,默认情况下,在第一次搜索时,fielddata被加载到内存中;但是,如果查询一个非常大的索引段(Segment),lazy加载方式会产生较大的时间延迟。
  • eager:在倒排索引的段可用之前,其数据就被加载到内存,eager加载方式能够减少查询的时间延迟,但是,有些数据可能非常冷,以至于没有请求来查询这些数据,但是冷数据依然被加载到内存中,占用紧缺的内存资源。
  • eager_global_ordinals:按照global ordinals积极把fielddata加载到内存。

四,JVM进程使用的内存和堆内存

1,配置ElasticSearch使用的内存

ElasticSearch使用JAVA_OPTS环境变量(Environment Variable)启动JVM进程,在JAVA_OPTS中,最重要的配置是:-Xmx参数控制分配给JVM进程的最大内存,-Xms参数控制分配给JVM进程的最小内存。通常情况下,使用默认的配置就能满足工程需要。

ES_HEAP_SIZE 环境变量控制分配给JVM进程的堆内存(Heap Memory)大小,顺排索引(fielddata)的数据存储在堆内存(Heap Memory)中。

2,内存锁定

大多数应用程序尝试使用尽可能多的内存,并尽可能把未使用的内存换出,但是,内存换出会影响ElasticSearch引擎的查询性能,推荐启用内存锁定,禁用ElasticSearch内存的换进换出。

在全局配置文档 elasticsearch.yml中,设置 bootstrap.memory_lock为ture,这将锁定ElasticSearch进程的内存地址空间,阻止ElasticSearch内存被OS换出(Swap out)。

正则表达式-----------------------------

ElasticSearch 2.4版本支持Java正则表达式查询,但是,在对大段的文本(Text Block)进行挖掘之前,必须了解正则表达式查询的特殊之处。由于分析器会对文本字段进行分词,移除停用词,小写转换等操作,最终存储在倒转索引中的是小写的标记流(Token Stream),默认情况下,每一个标记是一个分词(Term),这无法满足正则表达式查询的一般要求,这就是说,正则表达式查询的是原始文本,需要注意的是,ElasticSearch引擎都是从原始文本的第一个字符开始执行正则表达式匹配。

在ElasticSearch 2.4版本中启用正则表达式查询之前,需要考虑两个问题:分词吗?大小写敏感吗?

一,分词还是不分词?

通常情况下,ElasticSearch引擎对文本字段进行分词,移除停用词,转换成小写,这是全文搜索的标准配置,在这种设置下,正则表达式只能匹配文本字段的单个分词,而无法对原始文本执行正则表达式查询,为了实现正则表达式查询,必须设置文本字段不被分词,也就是是设置该字段的index属性为not_analyzed。在实际的产品环境中,对一个字段同时执行正则表达式查询和全文搜索的情况是经常存在的,ElasticSearch 2.4版本提供的映射参数 fields 能够满足该需求。

fields 参数:多元字段(multi-fields),用不同的处理方式,把一个相同的字段编入索引,以实现不同的目的。多元字段使用相同的数据派生新的字段,例如,一个字段field被编入索引作为分析字段(analyzed field)以执行全文搜索,把该字段设置为多元字段,那么ElasticSearch引擎派生一个新的字段field .raw,该字段文本作为一个词被编入索引,只对该字段执行排序或聚合操作。

注:多元字段(multi-fields)不同于多值字段(multi-values field),字段的多值是ElasticSearch内在支持的特性,“开箱即用”,不需要做任何配置。每个字段都能存储多个数据值,这就是意味着,每个字段都是数组类型,只不过在字段中存储的数据,其数据类型都是相同的。

1,多元字段使用示例

在示例索引映射中,eventdescription是一个多元字段,其index属性是analyzed,表示该字段是分析字段,ElasticSearch引擎把该字段的文本分析成分词流,编入索引,以执行全文搜索,这就意味着,倒排索引中存储的不是该字段的原始文本,而是分割的单个分词;而eventdescription.raw是多元字段的派生字段,其index属性是not_analyzed,表示该派生字段不会被分词,整个文本字段整体作为一个分词被编入索引,这就意味着,倒排索引中存储的是派生字段原始的文本值。

复制代码
 "eventdescription":{  
    "type":"string",
"index":"analyzed", "fields":{ "raw":{ "type":"string",
"index":"not_analyzed" } } }
复制代码

2,对原始文本执行正则表达式查询

ElasticSearch引擎在处理分析字段(analyzed field)时,使用指定的分析器执行分析操作,包括分词,移除停用词,转换大小写等,如果一个字段不是分析字段,那么ElasticSearch引擎不会对其执行任何分析工作。

映射参数index:决定ElasticSearch引擎是否对文本字段执行分析操作,也就是说分析操作将分割文本,把分词编入索引,并使分词能够被搜索到

  • 当参数值为analyzed时,该字段是分析字段,ElasticSearch引擎对该字段执行分析操作,把文本分割成分词流,存储在倒排索引中,使其支持全文搜索;
  • 当参数值为not_analyzed时,该字段不会被分析,ElasticSearch引擎把原始文本作为单个分词存储在倒排索引中,不支持全文搜索,但是支持词条级别的搜索;也就是说,字段的原始文本不经过分析而存储在倒排索引中,使用原始文本编入索引,在搜索的过程中,查询条件必须全部匹配整个原始文本;
  • 当参数值为no时,该字段不会被存储到倒排索引中,也不会被搜索到;

 这也就意味着,要对原始文本执行正则表达式查询,必须设置index属性为not_analyzed,这也就意味着,保留文本的原始形式,例如大小写,空格等。

3,单个分词的最大长度

如果设置字段的index属性为not_analyzed,原始文本将作为单个分词,其最大长度跟UTF8 编码有关,默认的最大长度是32766Bytes,如果字段的文本超过该限制,那么ElasticSearch将跳过(Skip)该文档,并在Response中抛出异常消息:

operation[607]: index returned 400 _index: ebrite _type: events _id: 76860 _version: 0 error: Type: illegal_argument_exception Reason: "Document contains at least one immense term in field="event_raw" (whose UTF8 encoding is longer than the max length 32766), all of which were skipped. Please correct the analyzer to not produce such terms. The prefix of the first immense term is: '[112, 114,... 115]...', original message: bytes can be at most 32766 in length; got 35100" CausedBy:Type: max_bytes_length_exceeded_exception Reason: "bytes can be at most 32766 in length; got 35100"

可以在字段中设置ignore_above属性,该属性值指的是字符数量,而不是字节数量;由于一个UTF8字符最多占用3个字节,因此,可以设置

“ignore_above”:10000

这样,超过30000字节之后的字符将会被分析器忽略,单个分词(Term)的最大长度是30000Bytes。

The value for ignore_above is the character count, but Lucene counts bytes. If you use UTF-8 text with many non-ASCII characters, you may want to set the limit to 32766 / 3 = 10922 since UTF-8 characters may occupy at most 3 bytes.

二,大小写敏感?

正则表达式查询一般是区分大小写的,有时,我们可能会希望,正则表达式查询忽略大小写,在这种情况下,多元字段(fields)就无法满足需求了,多元字段不能执行文本大小写转换。为了解决这个问题,我们可以新建一个字段,在更新索引时,把原始文本导入到分析字段,把相同的数据转换成小写形式导入到另一个字段中,这样做以后,分析字段及其派生字段,用于支持全文搜索和大小写敏感的正则表达式查询,另外一个字段用于忽略大小写的正则表达式搜索。

复制代码
 "eventdescription":{  
    "type":"string",
"index":"analyzed", "fields":{ "raw":{ "type":"string",
"index":"not_analyzed" } } }, "eventdescription_lowcase":{ "type":"string",
"index":"not_analyzed" } }
复制代码

三,存储控制

为了实现正则表达式查询,上例为一个数据创建三个字段,eventdescription、eventdescription.raw和 eventdescription_lowcase,这三个字段都需要存储在倒排索引中,ElasticSearch引擎是否使用3倍的容量来存储这个数据?

默认情况下,一旦字段值被编入索引,该字段能够被搜索,但是,字段的原始值没有存储到倒排索引中,这就意味着,该字段能够被搜索,却不能从倒排索引中获取该字段的原始值。通常情况下,这样设计能够节省硬盘存储空间,不会对应用程序有什么影响,实际上,ElasticSearch引擎把该字段的原始值存储在_source元字段中,默认情况下,_source元字段是存储的。

1,存储属性(store)

字段的原始值是否被存储到倒排索引,是由映射参数store决定的,默认值是false,也就是,原始值不存储到倒排索引中。在特定的情况下,存储字段的原始值是有意义的。例如,为了存储一篇博客,文档必须有title,date和一个非常大的正文字段(content),如果仅仅是获取title和date,而不用获取正文字段,那么你可以存储title和date,并把content字段的store属性设置为false。

复制代码
"title":{  
    "type":"string",
    "store":true,
    "index":"analyzed"
},
 "date":{  
    "type":"date",
    "store":true,
    "index":"not_analyzed"
},
 "content":{  
    "type":"string",
    "store":false,
    "index":"analyzed"
}
复制代码

映射参数index和store的区别在于:

  • store用于获取(Retrieve)字段的原始值,不支持查询,可以使用投影参数fields,对stroe属性为true的字段进行过滤,只获取(Retrieve)特定的字段,减少网络负载;
  • index用于查询(Search)字段,当index为analyzed时,对字段的分词执行全文查询;当index为not_analyzed时,字段的原始值作为一个分词,只能对字段的原始文本执行词条查询;

2,源字段(_source)

当把原始的JSON文档传递到ElasticSearch引擎时,ElasticSearch引擎使用_source字段存储最原始的JSON文档。_source字段本身不会被索引,也不会被搜索,但是,该字段会存储在倒排索引中,用于返回查询的结果。

_source字段会导致索引存储空间的增加,因此,可以禁用_source字段,但是,在禁用_source字段之前,请认真阅读官方文档《_source field》。

复制代码
"mappings": {
    "tweet": {
      "_source": {
        "enabled": false
      }
    }
  }
复制代码

一般情况下,不要禁用_source字段,当需要考虑占用的Disk空间时,请有限考虑压缩存储,提高压缩等级。在配置文档 中,压缩选项是 index.codec,默认值是LZ4压缩,设置best_compression 能够提供更高的压缩率,代价是降低数据存储的性能。

四,一个字段包含所有文本?

为了实现正则表达式查询,上例为一个数据创建三个字段,eventdescription、eventdescription.raw和 eventdescription_lowcase,这三个字段都需要存储在倒排索引中,通常做法是,同时对这三个字段执行正则表达式查询,但是在ElasticSearch中,在编码上,可以更简单。元字段 _all是一个特殊的“包罗万象”(catch-all)的字段,把其他字段的值拼接成一个大的字符串,字段值之间使用空格分隔。ElasticSearch引擎先分析_all字段,然后编入索引,但是,默认情况下,不会存储字段的原始值,这就意味着,_all字段能够被搜索,但是不会返回原始值。_all 字段把所有字段的原始值,都视为字符类型,并把字段的原始值通过分隔符空格拼接在一起。

注意,添加到_all字段的是原始值,而不是字段分析之后的词条(term)。字段的原始值是否包含到_all字段,是由该字段的属性 include_in_all控制的,默认值是true。启用_all字段是需要付出代价的,_all字段会消耗额外的CPU时钟周期和更多的硬盘空间,如果不是必需,推荐把_all字段禁用掉。

"content": { 
     "type": "string",
     "include_in_all": false
},

当把_all字段禁用之后,用户可以创建自定义的"_all"字段:新建一个数据类型为string的字段,并在需要拼接的字段中设置属性“copy_to”。

在ElasticSearch中,每个索引只有一个_all字段,通过字段的属性copy_to能够创建自定义的"_all"字段。例如,字段 first_name和 last_name能够通过分隔符空格被拼接到一起,作为full_name字段的值。

复制代码
{
  "mappings": {
    "mytype": {
      "properties": {
        "first_name": {
          "type":    "string",
          "copy_to": "full_name" 
        },
        "last_name": {
          "type":    "string",
          "copy_to": "full_name" 
        },
        "full_name": {
          "type":    "string"
        }
      }
    }
  }
}
PUT myindex/mytype/1
{
  "first_name": "John",
  "last_name": "Smith"
}

GET myindex/_search
{
  "query": {
    "match": {
      "full_name": "John Smith"
    }
  }
}
复制代码

默认情况下,_all字段不会存储_source字段的值,也不会存储原始值,这是因为_all字段是其他字段结合在一起组成的,存储_all字段会占用大量的硬盘存储空间,如果设置_all字段的属性store为true,那么ElasticSearch引擎将会存储_all字段的原始值,其原始值也能够被获取到。 

五,示例

综上所述,为了实现正则表达式查询,为了实现正则表达式的查询,有两个设计思路

示例1,原始文本和小写文本各使用一个字段

通过bool查询的should子句,对多个字段执行正则表达式查询,当字段较多,或者字段的文本特别大时,使用该方式,节省硬盘空间,但是要编写更多的查询代码:

复制代码
"eventdescription":{  
    "type":"string",
    "index":"analyzed",
    "fields":{  
        "raw":{  
            "type":"string",
            "index":"not_analyzed",
            "ignore_above":10000
        }
    }
},
"eventdescription_lowcase":{  
    "type":"string",
    "index":"not_analyzed",
    "ignore_above":10000
    }
}
复制代码

示例2,添加冗余字段

在冗余字段上执行正则表达式查询,当在多个字段上执行相同的正则表达式时,使用该方式,便于编程,但是要注意,拼接字段的大小不能超过限制(32766Bytes):

复制代码
"eventdescription":{  
    "type":"string",
    "index":"analyzed",
    "copy_to":"eventdescription_regexp"
},
"eventdescription_lowcase":{  
    "type":"string",
    "index":"not_analyzed",
    "copy_to":"eventdescription_regexp"
},
"eventdescription_regexp":{
    "type":"string",
    "index":"not_analyzed"
}

 

posted @ 2022-04-21 20:20  hanease  阅读(509)  评论(0编辑  收藏  举报