elasticsearch分享

1.小技巧

1.1多节点安装

修改yaml文件,修改master, slave, 以不同的端口启动。

题外话: kibana dashboard介绍

1.2动态mapping迁移

用 scroll 从旧的索引检索批量文档 , 然后用 bulk API 把文档推送到新的索引中手动复制数据(es官方推荐)

reindex api

建立一个新的索引,并把数据导入(注意只能导入存在的字段,不一致的字段是无法导入的)
POST _reindex
{
  "source": {
    "index": "customer_1212"
  },
  "dest": {
    "index": "customer_1213"
  }
}
POST /_aliases
{
    "actions": [
        { "remove": { "index": "customer_1212", "alias": "customer" }},
        { "add":    { "index": "customer_1213", "alias": "customer" }}
    ]
}

(演示一个分词查询将string换成另外一种)

2.一些高级查询(一些平常不太注意的地方)

2.1 查询评分

TF/IDF
 TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜寻引擎应用,作为文件与用户查询之间相关程度的度量或评级。

在一份给定的文件里,词频 (term frequency, TF) 指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(分子一般小于分母),以防止它偏向长的文件。同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否。

逆向文件频率 (inverse document frequency, IDF) 是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。总文件数目除以包含该词语之文件的数目: 假如一个词在所有文件中都出现,那么这个商就接近1,log后的值接近0,重要度接近0.如果一个词就在很少的文件中出现,那么这个商值很大,就是重要性也很大 ,TF-IDF倾向于过滤掉常见的词语,保留重要的词语

这儿我们可以举个例子(点融,新的金融)

2.2 查询方式

QUERY_THEN_FETCH,QUERY_AND_FEATCH,DFS_QUERY_THEN_FEATCH和DFS_QUERY_AND_FEATCH这四种查询方式

1.  query and fetch
    向索引的所有分片发出查询请求, 各分片返回的时候把元素文档 (document)和计算后的排名信息一起返回。这种搜索方式是最快的。 因为相比下面的几种搜索方式, 这种查询方法只需要去shard查询一次。 但是各个 shard 返回的结果的数量之和可能是用户要求的 size 的 n 倍。
  优点:这种搜索方式是最快的。因为相比后面的几种es的搜索方式,这种查询方法只需要去shard查询一次。
  缺点:返回的数据量不准确,可能返回(N*分片数量)的数据,并且数据排名也不准确,同时各个shard返回的结果的数量之和可能是用户要求的size的n倍。

2.  query then fetch
    (es 默认的搜索方式),在spring-data-es中默认也是这个,这种搜索方式第一步先向所有的 shard 发出请求, 各分片只返回文档 id(注意, 不包括文档document)和排名相关的信息(也就是文档对应的分值), 然后按照各分片返回的文档的分数进行重新排序和排名, 取前 size个数据,第二步, 根据文档 id 去相关的 shard 取 document。
    优点:数量与用户要求的大小是相等的。   
    缺点:性能一般,并且数据排名不准确。

3.  DFS query and fetch   
      这种方式比第一种方式多了一个 DFS步骤,有这一步,可以更精确控制搜索打分和排名。也就是在进行查询之前, 先对所有分片发送请求,把所有分片中的词频和文档频率等打分依据全部汇总到一块, 再执行后面的操作 
      优点:数据排名准确   
      缺点:性能一般,返回的数据量不准确, 可能返回(N*分片数量)的数据

4.  DFS query then fetch   
    比第 2 种方式多了一个 DFS 步骤. 也就是在进行查询之前,先对所有分片发送请求, 把所有分片中的词频和文档频率等打分依据全部汇总到一块, 再执行后面的操作、
    优点:返回的数据量准确, 数据排名准确   
    缺点:性能最差

点融财富kpi举例,上海和成都贷款数目

2.2 filter过滤器原理(bitset)

过滤器的速度会快于普通搜索,同时过滤器会缓存,在结构化搜索中,尽量使用filter代替match,这儿saas-crm做的不好.
内部过滤器的操作编辑在内部,Elasticsearch 会在运行非评分查询的时执行多个操作:
查找匹配文档.创建 bitset.(一个包含 0 和 1 的数组),它描述了哪个文档会包含该 term 。匹配文档的标志位是 1 。本例中,bitset 的值为 [1,0,0,0] 。在内部,它表示成一个 "roaring bitmap",可以同时对稀疏或密集的集合进行高效编码。
一旦为每个查询生成了 bitsets ,Elasticsearch 就会循环迭代 bitsets 从而找到满足所有过滤条件的匹配文档的集合。执行顺序是启发式的,但一般来说先迭代稀疏的 bitset (因为它可以排除掉大量的文档)。
Elasticsearch 会为每个索引跟踪保留查询使用的历史状态。如果查询在最近的 256 次查询中会被用到,那么它就会被缓存到内存中。这些小的段(<10000文档)缓存后会迅速删除,另外缓存使用LRU机制淘汰.

2.3 Ngrams在匹配中的应用

单纯的n-grams对于匹配单词中的某一部分是实用的,在复合单词的ngrams中我们会用到它。然而,对于即时搜索。我们使用了一种特殊的n-grams。被称为边缘n-grams(Edge n-grams)。
边缘n-grams会将起始点放在单词的开头处。
单词quick的边缘n-gram例如以下所看到的:
q
qu
qui
quic
quick
你或许注意到它遵循了用户在搜索"quick"时的输入形式。主要用于即使搜索,自动补全功能。(这儿可以参考google instant)

第一步是配置一个自己定义的edge_ngram词条过滤器,我们将它称为autocomplete_filter:
{
    "filter": {
        "autocomplete_filter": {
            "type":     "edge_ngram",
            "min_gram": 1,
            "max_gram": 20
        }
    }
}
以上配置的作用是,对于此词条过滤器接受的不论什么词条,它都会产生一个最小长度为1。最大长度为20的边缘ngram(Edge ngram)。

然后我们将该词条过滤器配置在自己定义的解析器中,该解析器名为autocomplete。
{
    "analyzer": {
        "autocomplete": {
            "type":      "custom",
            "tokenizer": "standard",
            "filter": [
                "lowercase",
                "autocomplete_filter" 
            ]
        }
    }
}
(示范展示一个分析)

3.ES部分原理(为什么ES检索速度快)

3.1 索引储存

倒排索引是基于每个字段的,每个字段都有对应的倒排索引。以人名为例Carla,Sara这些叫做 term,而[1,3]就是posting list。Posting list就是一个int的数组,存储了所有符合某个term的文档id。

假设我们有很多个term,比如:Carla,Sara,Elin,Ada,Patty,Kate,Selena

如果按照这样的顺序排列,找出某个特定的term一定很慢,因为term没有排序,需要全部过滤一遍才能找出特定的term。

排序之后就变成了:Ada,Carla,Elin,Kate,Patty,Sara,Selena

这样我们可以用二分查找的方式,比全遍历更快地找出目标的term。这个就是 term dictionary。有了term dictionary之后,可以用 logN 次磁盘查找得到目标。但是磁盘的随机读操作仍然是非常昂贵的(一次random access大概需要10ms的时间)。所以尽量少的读磁盘,有必要把一些数据缓存到内存里。但是整个term dictionary本身又太大了,无法完整地放到内存里。于是就有了term index。term index有点像一本字典的大的章节表。比如:

A开头的term ……………. Xxx页

C开头的term ……………. Xxx页

E开头的term ……………. Xxx页

如果所有的term都是英文字符的话,可能这个term index就真的是26个英文字符表构成的了。但是实际的情况是,term未必都是英文字符,term可以是任意的byte数组。而且26个英文字符也未必是每一个字符都有均等的term,比如x字符开头的term可能一个都没有,而s开头的term又特别多。实际的term index是一棵trie 树:

一个trie树

例子是一个包含 "A", "to", "tea", "ted", "ten", "i", "in", 和 "inn" 的 trie 树。这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。再加上一些压缩技术term index 的尺寸可以只有所有term的尺寸的几十分之一,使得用内存缓存整个term index变成可能。整体上来说就是这样的效果。

term index图

3.2 Posting list合并

Posting list

以上是三个posting list。我们现在需要把它们用AND的关系合并,得出posting list的交集。首先选择最短的posting list,然后从小到大遍历。遍历的过程可以跳过一些元素,比如我们遍历到绿色的13的时候,就可以跳过蓝色的3了,因为3比13要小。


最后得出的交集是[13,98],所需的时间比完整遍历三个posting list要快得多。但是前提是每个list需要指出Advance这个操作,快速移动指向的位置。

3.2 DocValue

按照行存储

按照列存储

按列存储的话会把一个文件分成多个文件,每个列一个。对于每个文件,都是按照docid排序的。这样一来,只要知道docid,就可以计算出这个docid在这个文件里的偏移量。也就是对于每个docid需要一次随机读操作。

那么这种排列是如何让随机读更快的呢?秘密在于Lucene底层读取文件的方式是基于memory mapped byte buffer的,也就是mmap。这种文件访问的方式是由操作系统去缓存这个文件到内存里。这样在内存足够的情况下,访问文件就相当于访问内存。那么随机读操作也就不再是磁盘操作了,而是对内存的随机读。

那么为什么按行存储不能用mmap的方式呢?因为按行存储的方式一个文件里包含了很多列的数据,这个文件尺寸往往很大,超过了操作系统的文件缓存的大小。而按列存储的方式把不同列分成了很多文件,可以只缓存用到的那些列,而不让很少使用的列数据浪费内存。

4.调优分析

4.1 索引和分片的选择

当单个索引变得非常大的时候,索引越来越大,单个 shard 也很巨大,查询速度也越来越慢。这时候,是选择分索引还是更多的shards.
实践过程中,更多的 shards 会带来额外的索引压力,即 IO 压力。分索引通常会带来更好的效果。比如主要的大租户分为一个索引,其他的小租户另外一个索引。(实际并没有这么做,理想状态)
http://localhost:9200/big_tenant,small_tenant/_search?routing=customer_id。
在分片策略上,选择合适的分片数和副本数。默认情况下,ES会为每个索引创建5个分片,如果过度分配,就增大了Lucene在合并分片查询结果时的复杂度,使用尽量少的分片,分片数最好不要大于节点数量
然而在索引已创建好的前提下,首先能做的是调整分片分配器的类型,具体是在elasticsearch.yml中设置cluster.routing.allocation.type属性,共有两种分片器even_shard,balanced(默认)。even_shard是尽量保证每个节点都具有相同数量的分片,balanced是基于可控制的权重进行分配,相对于前一个分配器,它更暴漏了一些参数而引入调整分配过程的能力。

4.2 routing

默认情况下,Elasticsearch使用文档的ID(类似于关系数据库中的自增ID,当然,如果不指定ID的话,Elasticsearch使用的是随机值)将文档平均的分布于所有的分片上,这导致了Elasticsearch不能确定文档的位置,所以它必须将这个请求广播到所有的100个分片上去执行。这同时也解释了为什么主分片的数量在索引创建的时候是固定下来的,并且永远不能改变。因为如果分片的数量改变了,所有先前的路由值就会变成非法了,文档相当于丢失了。

而自定义的Routing模式,可以使我们的查询更具目的性。我们不必盲目地去广播查询请求,取而代之的是:我们要告诉Elasticsearch我们的数据在哪个分片上。

原来的查询语句:“Tenant1的文档数量一共有多少”
使用自定义Routing(在TenantId上)后的查询语句:“Tenant1的文档数量一共有多少,它就在第1个分片上”
举个例子 http://localhost:9200/small_tenant/_search?routing=tenant1。

4.3 内存

ElasticSearch本质上是个Java程序,所以配置JVM垃圾回收器本身也是一个很有意义的工作。我们使用JVM的Xms和Xmx参数来提供指定内存大小,本质上提供的是JVM的堆空间大小,和其他java程序一样,通过ElasticSearch上的GC日志,使用jstat,内存Dump分析,网上大部分是人云亦云.

4.4 一个优秀的索引例子

{
  "500px.photos-2016-05-06-20-09": {
    "aliases": {
      "500px.photos": {}
    },
    "mappings": {
      "photo": {
        "_all": {
          "enabled": false },
        "_routing": {
          "required": true,
          "path": "user_id" },
        "properties": {
          "camera": { "type": "string", "fields": { "exact": { "type": "string", "analyzer": "exact" }, "not_analyzed": { "type": "string", "index": "not_analyzed" }, "prefix": { "type": "string", "index_analyzer": "prefix", "search_analyzer": "prefix_search" } } },
          "category": { "type": "integer" },
          "collections_count": { "type": "long" },
          "comments_count": { "type": "integer" },
1.500px.photos-2016-05-06-20-09 通过具体日期来命名索引名称,来记录某次索引修改的日期,索引设置别名:500px.photos
2.参数 “_all”: {
“enabled”: false
},有效的阻止进行全文系统在索引阶段对CPU和存储空间资源的开销

3.设置_routing,均以user_id为路由路径,可以使每个用户的图片都会存储到相同的shard中,当进行用户个人图片相关搜索,都会提高搜索效率;
4.针对camera进行查询,设置了三种不同的analyzer,1.exact 2.not_analyzed 3.prefix,分别对应精确查找,前缀查询;
4.设置多条能判断图片质量好坏的计数,如collections_count,comments_count,favorites_count,context_tags_tags_count,sales_count,votes_count
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值