elaseticsearch

elaseticsearch

elastic搜索排序问题,如果人为的影响score得分结果?

Elasticsearchzz_hello 回复了问题 • 4 人关注 • 3 个回复 • 100 次浏览 • 13 小时前 • 来自相关话题

安装search guard后,使用客户端代码连接ES。ES版本是6.5.1。

Elasticsearchzqc0512 回复了问题 • 3 人关注 • 4 个回复 • 146 次浏览 • 15 小时前 • 来自相关话题

ES集群多实例分片不能恢复;

Elasticsearchzqc0512 回复了问题 • 3 人关注 • 6 个回复 • 110 次浏览 • 1 天前 • 来自相关话题

Day 7 - Elasticsearch中数据是如何存储的

Adventweizijun 发表了文章 • 2 个评论 • 528 次浏览 • 5 天前 • 来自相关话题

前言

很多使用Elasticsearch的同学会关心数据存储在ES中的存储容量,会有这样的疑问:xxTB的数据入到ES会使用多少存储空间。这个问题其实很难直接回答的,只有数据写入ES后,才能观察到实际的存储空间。比如同样是1TB的数据,写入ES的存储空间可能差距会非常大,可能小到只有300~400GB,也可能多到6-7TB,为什么会造成这么大的差距呢?究其原因,我们来探究下Elasticsearch中的数据是如何存储。文章中我以Elasticsearch 2.3版本为示例,对应的lucene版本是5.5,Elasticsearch现在已经来到了6.5版本,数字类型、列存等存储结构有些变化,但基本的概念变化不多,文章中的内容依然适用。

Elasticsearch索引结构

Elasticsearch对外提供的是index的概念,可以类比为DB,用户查询是在index上完成的,每个index由若干个shard组成,以此来达到分布式可扩展的能力。比如下图是一个由10个shard组成的index。

elasticsearch_store_arc.png

shard是Elasticsearch数据存储的最小单位,index的存储容量为所有shard的存储容量之和。Elasticsearch集群的存储容量则为所有index存储容量之和。

一个shard就对应了一个lucene的library。对于一个shard,Elasticsearch增加了translog的功能,类似于HBase WAL,是数据写入过程中的中间数据,其余的数据都在lucene库中管理的。

所以Elasticsearch索引使用的存储内容主要取决于lucene中的数据存储。

lucene数据存储

下面我们主要看下lucene的文件内容,在了解lucene文件内容前,大家先了解些lucene的基本概念。

lucene基本概念

  • segment : lucene内部的数据是由一个个segment组成的,写入lucene的数据并不直接落盘,而是先写在内存中,经过了refresh间隔,lucene才将该时间段写入的全部数据refresh成一个segment,segment多了之后会进行merge成更大的segment。lucene查询时会遍历每个segment完成。由于lucene* 写入的数据是在内存中完成,所以写入效率非常高。但是也存在丢失数据的风险,所以Elasticsearch基于此现象实现了translog,只有在segment数据落盘后,Elasticsearch才会删除对应的translog。
  • doc : doc表示lucene中的一条记录
  • field :field表示记录中的字段概念,一个doc由若干个field组成。
  • term :term是lucene中索引的最小单位,某个field对应的内容如果是全文检索类型,会将内容进行分词,分词的结果就是由term组成的。如果是不分词的字段,那么该字段的内容就是一个term。
  • 倒排索引(inverted index): lucene索引的通用叫法,即实现了term到doc list的映射。
  • 正排数据:搜索引擎的通用叫法,即原始数据,可以理解为一个doc list。
  • docvalues :Elasticsearch中的列式存储的名称,Elasticsearch除了存储原始存储、倒排索引,还存储了一份docvalues,用作分析和排序。

lucene文件内容

lucene包的文件是由很多segment文件组成的,segments_xxx文件记录了lucene包下面的segment文件数量。每个segment会包含如下的文件。

Name Extension Brief Description
Segment Info .si segment的元数据文件
Compound File .cfs, .cfe 一个segment包含了如下表的各个文件,为减少打开文件的数量,在segment小的时候,segment的所有文件内容都保存在cfs文件中,cfe文件保存了lucene各文件在cfs文件的位置信息
Fields .fnm 保存了fields的相关信息
Field Index .fdx 正排存储文件的元数据信息
Field Data .fdt 存储了正排存储数据,写入的原文存储在这
Term Dictionary .tim 倒排索引的元数据信息
Term Index .tip 倒排索引文件,存储了所有的倒排索引数据
Frequencies .doc 保存了每个term的doc id列表和term在doc中的词频
Positions .pos Stores position information about where a term occurs in the index
全文索引的字段,会有该文件,保存了term在doc中的位置
Payloads .pay Stores additional per-position metadata information such as character offsets and user payloads
全文索引的字段,使用了一些像payloads的高级特性会有该文件,保存了term在doc中的一些高级特性
Norms .nvd, .nvm 文件保存索引字段加权数据
Per-Document Values .dvd, .dvm lucene的docvalues文件,即数据的列式存储,用作聚合和排序
Term Vector Data .tvx, .tvd, .tvf Stores offset into the document data file
保存索引字段的矢量信息,用在对term进行高亮,计算文本相关性中使用
Live Documents .liv 记录了segment中删除的doc

测试数据示例

下面我们以真实的数据作为示例,看看lucene中各类型数据的容量占比。

写100w数据,有一个uuid字段,写入的是长度为36位的uuid,字符串总为3600w字节,约为35M。

数据使用一个shard,不带副本,使用默认的压缩算法,写入完成后merge成一个segment方便观察。

使用线上默认的配置,uuid存为不分词的字符串类型。创建如下索引:

PUT test_field
{
  "settings": {
    "index": {
      "number_of_shards": "1",
      "number_of_replicas": "0",
      "refresh_interval": "30s"
    }
  },
  "mappings": {
    "type": {
      "_all": {
        "enabled": false
      }, 
      "properties": {
        "uuid": {
          "type": "string",
          "index": "not_analyzed"
        }
      }
    }
  }
}

首先写入100w不同的uuid,使用磁盘容量细节如下:


health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    122.7mb        122.7mb 

-rw-r--r--  1 weizijun  staff    41M Aug 19 21:23 _8.fdt
-rw-r--r--  1 weizijun  staff    17K Aug 19 21:23 _8.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 19 21:23 _8.fnm
-rw-r--r--  1 weizijun  staff   494B Aug 19 21:23 _8.si
-rw-r--r--  1 weizijun  staff   265K Aug 19 21:23 _8_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff    44M Aug 19 21:23 _8_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   340K Aug 19 21:23 _8_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 19 21:23 _8_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 19 21:23 _8_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   195B Aug 19 21:23 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 19 21:20 write.lock

可以看到正排数据、倒排索引数据,列存数据容量占比几乎相同,正排数据和倒排数据还会存储Elasticsearch的唯一id字段,所以容量会比列存多一些。

35M的uuid存入Elasticsearch后,数据膨胀了3倍,达到了122.7mb。Elasticsearch竟然这么消耗资源,不要着急下结论,接下来看另一个测试结果。

我们写入100w一样的uuid,然后看看Elasticsearch使用的容量。

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0     13.2mb         13.2mb 

-rw-r--r--  1 weizijun  staff   5.5M Aug 19 21:29 _6.fdt
-rw-r--r--  1 weizijun  staff    15K Aug 19 21:29 _6.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 19 21:29 _6.fnm
-rw-r--r--  1 weizijun  staff   494B Aug 19 21:29 _6.si
-rw-r--r--  1 weizijun  staff   309K Aug 19 21:29 _6_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   7.0M Aug 19 21:29 _6_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   195K Aug 19 21:29 _6_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff   244K Aug 19 21:29 _6_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   252B Aug 19 21:29 _6_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   195B Aug 19 21:29 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 19 21:26 write.lock

这回35M的数据Elasticsearch容量只有13.2mb,其中还有主要的占比还是Elasticsearch的唯一id,100w的uuid几乎不占存储容积。

所以在Elasticsearch中建立索引的字段如果基数越大(count distinct),越占用磁盘空间。

我们再看看存100w个不一样的整型会是如何。

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0     13.6mb         13.6mb 

-rw-r--r--  1 weizijun  staff   6.1M Aug 28 10:19 _42.fdt
-rw-r--r--  1 weizijun  staff    22K Aug 28 10:19 _42.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 28 10:19 _42.fnm
-rw-r--r--  1 weizijun  staff   503B Aug 28 10:19 _42.si
-rw-r--r--  1 weizijun  staff   2.8M Aug 28 10:19 _42_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   2.2M Aug 28 10:19 _42_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff    83K Aug 28 10:19 _42_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff   2.5M Aug 28 10:19 _42_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   228B Aug 28 10:19 _42_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   196B Aug 28 10:19 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 28 10:16 write.lock

从结果可以看到,100w整型数据,Elasticsearch的存储开销为13.6mb。如果以int型计算100w数据的长度的话,为400w字节,大概是3.8mb数据。忽略Elasticsearch唯一id字段的影响,Elasticsearch实际存储容量跟整型数据长度差不多。

我们再看一下开启最佳压缩参数对存储空间的影响:

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    107.2mb        107.2mb 

-rw-r--r--  1 weizijun  staff    25M Aug 20 12:30 _5.fdt
-rw-r--r--  1 weizijun  staff   6.0K Aug 20 12:30 _5.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 20 12:31 _5.fnm
-rw-r--r--  1 weizijun  staff   500B Aug 20 12:31 _5.si
-rw-r--r--  1 weizijun  staff   265K Aug 20 12:31 _5_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff    44M Aug 20 12:31 _5_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   322K Aug 20 12:31 _5_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 20 12:31 _5_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 20 12:31 _5_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   224B Aug 20 12:31 segments_4
-rw-r--r--  1 weizijun  staff     0B Aug 20 12:00 write.lock

结果中可以发现,只有正排数据会启动压缩,压缩能力确实强劲,不考虑唯一id字段,存储容量大概压缩到接近50%。

我们还做了一些实验,Elasticsearch默认是开启_all参数的,_all可以让用户传入的整体json数据作为全文检索的字段,可以更方便的检索,但在现实场景中已经使用的不多,相反会增加很多存储容量的开销,可以看下开启_all的磁盘空间使用情况:


health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    162.4mb        162.4mb 

-rw-r--r--  1 weizijun  staff    41M Aug 18 22:59 _20.fdt
-rw-r--r--  1 weizijun  staff    18K Aug 18 22:59 _20.fdx
-rw-r--r--  1 weizijun  staff   777B Aug 18 22:59 _20.fnm
-rw-r--r--  1 weizijun  staff    59B Aug 18 22:59 _20.nvd
-rw-r--r--  1 weizijun  staff    78B Aug 18 22:59 _20.nvm
-rw-r--r--  1 weizijun  staff   539B Aug 18 22:59 _20.si
-rw-r--r--  1 weizijun  staff   7.2M Aug 18 22:59 _20_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   4.2M Aug 18 22:59 _20_Lucene50_0.pos
-rw-r--r--  1 weizijun  staff    73M Aug 18 22:59 _20_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   832K Aug 18 22:59 _20_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 18 22:59 _20_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 18 22:59 _20_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   196B Aug 18 22:59 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 18 22:53 write.lock

开启_all比不开启多了40mb的存储空间,多的数据都在倒排索引上,大约会增加30%多的存储开销。所以线上都直接禁用。

然后我还做了其他几个尝试,为了验证存储容量是否和数据量成正比,写入1000w数据的uuid,发现存储容量基本为100w数据的10倍。我还验证了数据长度是否和数据量成正比,发现把uuid增长2倍、4倍,存储容量也响应的增加了2倍和4倍。在此就不一一列出数据了。

lucene各文件具体内容和实现

lucene数据元信息文件

文件名为:segments_xxx

该文件为lucene数据文件的元信息文件,记录所有segment的元数据信息。

该文件主要记录了目前有多少segment,每个segment有一些基本信息,更新这些信息定位到每个segment的元信息文件。

lucene元信息文件还支持记录userData,Elasticsearch可以在此记录translog的一些相关信息。

文件示例

elasticsearch_store_segments.png

具体实现类

public final class SegmentInfos implements Cloneable, Iterable<SegmentCommitInfo> {
  // generation是segment的版本的概念,从文件名中提取出来,实例中为:2t/101
  private long generation;     // generation of the "segments_N" for the next commit

  private long lastGeneration; // generation of the "segments_N" file we last successfully read
                               // or wrote; this is normally the same as generation except if
                               // there was an IOException that had interrupted a commit

  /** Id for this commit; only written starting with Lucene 5.0 */
  private byte[] id;

  /** Which Lucene version wrote this commit, or null if this commit is pre-5.3. */
  private Version luceneVersion;

  /** Counts how often the index has been changed.  */
  public long version;

  /** Used to name new segments. */
  // TODO: should this be a long ...?
  public int counter;

  /** Version of the oldest segment in the index, or null if there are no segments. */
  private Version minSegmentLuceneVersion;

  private List<SegmentCommitInfo> segments = new ArrayList<>();

  /** Opaque Map&lt;String, String&gt; that user can specify during IndexWriter.commit */
  public Map<String,String> userData = Collections.emptyMap();
}

/** Embeds a [read-only] SegmentInfo and adds per-commit
 *  fields.
 *
 *  @lucene.experimental */
public class SegmentCommitInfo {

  /** The {@link SegmentInfo} that we wrap. */
  public final SegmentInfo info;

  // How many deleted docs in the segment:
  private int delCount;

  // Generation number of the live docs file (-1 if there
  // are no deletes yet):
  private long delGen;

  // Normally 1+delGen, unless an exception was hit on last
  // attempt to write:
  private long nextWriteDelGen;

  // Generation number of the FieldInfos (-1 if there are no updates)
  private long fieldInfosGen;

  // Normally 1+fieldInfosGen, unless an exception was hit on last attempt to
  // write
  private long nextWriteFieldInfosGen; //fieldInfosGen == -1 ? 1 : fieldInfosGen + 1;

  // Generation number of the DocValues (-1 if there are no updates)
  private long docValuesGen;

  // Normally 1+dvGen, unless an exception was hit on last attempt to
  // write
  private long nextWriteDocValuesGen; //docValuesGen == -1 ? 1 : docValuesGen + 1;

  // TODO should we add .files() to FieldInfosFormat, like we have on
  // LiveDocsFormat?
  // track the fieldInfos update files
  private final Set<String> fieldInfosFiles = new HashSet<>();

  // Track the per-field DocValues update files
  private final Map<Integer,Set<String>> dvUpdatesFiles = new HashMap<>();

  // Track the per-generation updates files
  @Deprecated
  private final Map<Long,Set<String>> genUpdatesFiles = new HashMap<>();

  private volatile long sizeInBytes = -1;
}

segment的元信息文件

文件后缀:.si

每个segment都有一个.si文件,记录了该segment的元信息。

segment元信息文件中记录了segment的文档数量,segment对应的文件列表等信息。

文件示例

elasticsearch_store_si.png

具体实现类

/**
 * Information about a segment such as its name, directory, and files related
 * to the segment.
 *
 * @lucene.experimental
 */
public final class SegmentInfo {

  // _bl
  public final String name;

  /** Where this segment resides. */
  public final Directory dir;

  /** Id that uniquely identifies this segment. */
  private final byte[] id;

  private Codec codec;

  // Tracks the Lucene version this segment was created with, since 3.1. Null
  // indicates an older than 3.0 index, and it's used to detect a too old index.
  // The format expected is "x.y" - "2.x" for pre-3.0 indexes (or null), and
  // specific versions afterwards ("3.0.0", "3.1.0" etc.).
  // see o.a.l.util.Version.
  private Version version;

  private int maxDoc;         // number of docs in seg

  private boolean isCompoundFile;

  private Map<String,String> diagnostics;

  private Set<String> setFiles;

  private final Map<String,String> attributes;
}

fields信息文件

文件后缀:.fnm

该文件存储了fields的基本信息。

fields信息中包括field的数量,field的类型,以及IndexOpetions,包括是否存储、是否索引,是否分词,是否需要列存等等。

文件示例

elasticsearch_store_fnm.png

具体实现类

/**
 *  Access to the Field Info file that describes document fields and whether or
 *  not they are indexed. Each segment has a separate Field Info file. Objects
 *  of this class are thread-safe for multiple readers, but only one thread can
 *  be adding documents at a time, with no other reader or writer threads
 *  accessing this object.
 **/
public final class FieldInfo {
  /** Field's name */
  public final String name;

  /** Internal field number */
  //field在内部的编号
  public final int number;

  //field docvalues的类型
  private DocValuesType docValuesType = DocValuesType.NONE;

  // True if any document indexed term vectors
  private boolean storeTermVector;

  private boolean omitNorms; // omit norms associated with indexed fields 

  //index的配置项
  private IndexOptions indexOptions = IndexOptions.NONE;

  private boolean storePayloads; // whether this field stores payloads together with term positions 

  private final Map<String,String> attributes;

  // docvalues的generation
  private long dvGen;
}

数据存储文件

文件后缀:.fdx, .fdt

索引文件为.fdx,数据文件为.fdt,数据存储文件功能为根据自动的文档id,得到文档的内容,搜索引擎的术语习惯称之为正排数据,即doc_id -> content,es的_source数据就存在这

索引文件记录了快速定位文档数据的索引信息,数据文件记录了所有文档id的具体内容。

文件示例

elasticsearch_store_fdt.png

具体实现类

/**
 * Random-access reader for {@link CompressingStoredFieldsIndexWriter}.
 * @lucene.internal
 */
public final class CompressingStoredFieldsIndexReader implements Cloneable, Accountable {
  private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(CompressingStoredFieldsIndexReader.class);

  final int maxDoc;

  //docid索引,快速定位某个docid的数组坐标
  final int[] docBases;

  //快速定位某个docid所在的文件offset的startPointer
  final long[] startPointers;

  //平均一个chunk的文档数
  final int[] avgChunkDocs;

  //平均一个chunk的size
  final long[] avgChunkSizes;

  final PackedInts.Reader[] docBasesDeltas; // delta from the avg

  final PackedInts.Reader[] startPointersDeltas; // delta from the avg
}

/**
 * {@link StoredFieldsReader} impl for {@link CompressingStoredFieldsFormat}.
 * @lucene.experimental
 */
public final class CompressingStoredFieldsReader extends StoredFieldsReader {

  //从fdt正排索引文件中获得
  private final int version;

  // field的基本信息
  private final FieldInfos fieldInfos;

  //fdt正排索引文件reader
  private final CompressingStoredFieldsIndexReader indexReader;

  //从fdt正排索引文件中获得,用于指向fdx数据文件的末端,指向numChunks地址4
  private final long maxPointer;

  //fdx正排数据文件句柄
  private final IndexInput fieldsStream;

  //块大小
  private final int chunkSize;

  private final int packedIntsVersion;

  //压缩类型
  private final CompressionMode compressionMode;

  //解压缩处理对象
  private final Decompressor decompressor;

  //文档数量,从segment元数据中获得
  private final int numDocs;

  //是否正在merge,默认为false
  private final boolean merging;

  //初始化时new了一个BlockState,BlockState记录下当前正排文件读取的状态信息
  private final BlockState state;
  //chunk的数量
  private final long numChunks; // number of compressed blocks written

  //dirty chunk的数量
  private final long numDirtyChunks; // number of incomplete compressed blocks written

  //是否close,默认为false
  private boolean closed;
}

倒排索引文件

索引后缀:.tip,.tim

倒排索引也包含索引文件和数据文件,.tip为索引文件,.tim为数据文件,索引文件包含了每个字段的索引元信息,数据文件有具体的索引内容。

5.5.0版本的倒排索引实现为FST tree,FST tree的最大优势就是内存空间占用非常低 ,具体可以参看下这篇文章:http://www.cnblogs.com/bonelee/p/6226185.html

http://examples.mikemccandless.com/fst.py?terms=&cmd=Build+it 为FST图实例,可以根据输入的数据构造出FST图

输入到 FST 中的数据为:
String inputValues[] = {"mop","moth","pop","star","stop","top"};
long outputValues[] = {0,1,2,3,4,5};

生成的 FST 图为:

elasticsearch_store_tip1.png

elasticsearch_store_tip2.png

文件示例

elasticsearch_store_tip3.png

具体实现类

public final class BlockTreeTermsReader extends FieldsProducer {
  // Open input to the main terms dict file (_X.tib)
  final IndexInput termsIn;
  // Reads the terms dict entries, to gather state to
  // produce DocsEnum on demand
  final PostingsReaderBase postingsReader;
  private final TreeMap<String,FieldReader> fields = new TreeMap<>();

  /** File offset where the directory starts in the terms file. */
  /索引数据文件tim的数据的尾部的元数据的地址
  private long dirOffset;
  /** File offset where the directory starts in the index file. */

  //索引文件tip的数据的尾部的元数据的地址
  private long indexDirOffset;

  //semgent的名称
  final String segment;

  //版本号
  final int version;

  //5.3.x index, we record up front if we may have written any auto-prefix terms,示例中记录的是false
  final boolean anyAutoPrefixTerms;
}

/**
 * BlockTree's implementation of {@link Terms}.
 * @lucene.internal
 */
public final class FieldReader extends Terms implements Accountable {

  //term的数量
  final long numTerms;

  //field信息
  final FieldInfo fieldInfo;

  final long sumTotalTermFreq;

  //总的文档频率
  final long sumDocFreq;

  //文档数量
  final int docCount;

  //字段在索引文件tip中的起始位置
  final long indexStartFP;

  final long rootBlockFP;

  final BytesRef rootCode;

  final BytesRef minTerm;

  final BytesRef maxTerm;

  //longs:metadata buffer, holding monotonic values
  final int longsSize;

  final BlockTreeTermsReader parent;

  final FST<BytesRef> index;
}

倒排链文件

文件后缀:.doc, .pos, .pay

.doc保存了每个term的doc id列表和term在doc中的词频

全文索引的字段,会有.pos文件,保存了term在doc中的位置

全文索引的字段,使用了一些像payloads的高级特性才会有.pay文件,保存了term在doc中的一些高级特性

文件示例

elasticsearch_store_doc.png

具体实现类

/**
 * Concrete class that reads docId(maybe frq,pos,offset,payloads) list
 * with postings format.
 *
 * @lucene.experimental
 */
public final class Lucene50PostingsReader extends PostingsReaderBase {
  private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Lucene50PostingsReader.class);
  private final IndexInput docIn;
  private final IndexInput posIn;
  private final IndexInput payIn;
  final ForUtil forUtil;
  private int version;

  //不分词的字段使用的是该对象,基于skiplist实现了倒排链
  final class BlockDocsEnum extends PostingsEnum {
  }

  //全文检索字段使用的是该对象
  final class BlockPostingsEnum extends PostingsEnum {
  }

  //包含高级特性的字段使用的是该对象
  final class EverythingEnum extends PostingsEnum {
  }
}

列存文件(docvalues)

文件后缀:.dvm, .dvd

索引文件为.dvm,数据文件为.dvd。

lucene实现的docvalues有如下类型:

  • 1、NONE 不开启docvalue时的状态
  • 2、NUMERIC 单个数值类型的docvalue主要包括(int,long,float,double)
  • 3、BINARY 二进制类型值对应不同的codes最大值可能超过32766字节,
  • 4、SORTED 有序增量字节存储,仅仅存储不同部分的值和偏移量指针,值必须小于等于32766字节
  • 5、SORTED_NUMERIC 存储数值类型的有序数组列表
  • 6、SORTED_SET 可以存储多值域的docvalue值,但返回时,仅仅只能返回多值域的第一个docvalue
  • 7、对应not_anaylized的string字段,使用的是SORTED_SET类型,number的类型是SORTED_NUMERIC类型

其中SORTED_SET 的 SORTED_SINGLE_VALUED类型包括了两类数据 : binary + numeric, binary是按ord排序的term的列表,numeric是doc到ord的映射。

文件示例

elasticsearch_store_dvd.png

具体实现类

/** reader for {@link Lucene54DocValuesFormat} */
final class Lucene54DocValuesProducer extends DocValuesProducer implements Closeable {
  //number类型的field的列存列表
  private final Map<String,NumericEntry> numerics = new HashMap<>();

  //字符串类型的field的列存列表
  private final Map<String,BinaryEntry> binaries = new HashMap<>();

  //有序字符串类型的field的列存列表
  private final Map<String,SortedSetEntry> sortedSets = new HashMap<>();

  //有序number类型的field的列存列表
  private final Map<String,SortedSetEntry> sortedNumerics = new HashMap<>();

  //字符串类型的field的ords列表
  private final Map<String,NumericEntry> ords = new HashMap<>();

  //docId -> address -> ord 中field的ords列表
  private final Map<String,NumericEntry> ordIndexes = new HashMap<>();

  //field的数量
  private final int numFields;

  //内存使用量
  private final AtomicLong ramBytesUsed;

  //数据源的文件句柄
  private final IndexInput data;

  //文档数
  private final int maxDoc;
  // memory-resident structures
  private final Map<String,MonotonicBlockPackedReader> addressInstances = new HashMap<>();
  private final Map<String,ReverseTermsIndex> reverseIndexInstances = new HashMap<>();
  private final Map<String,DirectMonotonicReader.Meta> directAddressesMeta = new HashMap<>();

  //是否正在merge
  private final boolean merging;
}

/** metadata entry for a numeric docvalues field */
  static class NumericEntry {
    private NumericEntry() {}
    /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
    long missingOffset;

    /** offset to the actual numeric values */
    //field的在数据文件中的起始地址
    public long offset;

    /** end offset to the actual numeric values */
    //field的在数据文件中的结尾地址
    public long endOffset;

    /** bits per value used to pack the numeric values */
    public int bitsPerValue;

    //format类型
    int format;
    /** count of values written */
    public long count;
    /** monotonic meta */
    public DirectMonotonicReader.Meta monotonicMeta;

    //最小的value
    long minValue;

    //Compressed by computing the GCD
    long gcd;

    //Compressed by giving IDs to unique values.
    long table[];
    /** for sparse compression */
    long numDocsWithValue;
    NumericEntry nonMissingValues;
    NumberType numberType;
  }

  /** metadata entry for a binary docvalues field */
  static class BinaryEntry {
    private BinaryEntry() {}
    /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
    long missingOffset;
    /** offset to the actual binary values */
    //field的在数据文件中的起始地址
    long offset;
    int format;
    /** count of values written */
    public long count;

    //最短字符串的长度
    int minLength;

    //最长字符串的长度
    int maxLength;
    /** offset to the addressing data that maps a value to its slice of the byte[] */
    public long addressesOffset, addressesEndOffset;
    /** meta data for addresses */
    public DirectMonotonicReader.Meta addressesMeta;
    /** offset to the reverse index */
    public long reverseIndexOffset;
    /** packed ints version used to encode addressing information */
    public int packedIntsVersion;
    /** packed ints blocksize */
    public int blockSize;
  }

参考资料

lucene source code

lucene document

lucene字典实现原理——FST

分片综合评分问题

Elasticsearchmedcl 回复了问题 • 3 人关注 • 1 个回复 • 69 次浏览 • 6 天前 • 来自相关话题

SparkStreaming 写 ES可能遇到的问题

Elasticsearchrochy 回复了问题 • 2 人关注 • 2 个回复 • 72 次浏览 • 6 天前 • 来自相关话题

ES集群分片rebalance问题;

Elasticsearchzz_hello 回复了问题 • 3 人关注 • 2 个回复 • 109 次浏览 • 2018-12-05 15:07 • 来自相关话题

Day 4 - PB级规模数据的Elasticsearch分库分表实践

Adventouyangchucai 发表了文章 • 2 个评论 • 657 次浏览 • 2018-12-04 20:50 • 来自相关话题

从2018年7月在开始在某阿里云数据中心部署Elasticsearch软件,到2018年12月共创建了15个集群,服务于客户的文档检索、交通视频检索、地理信息检索、日志安全审计等业务。其中数据规模最大的一个业务,共有800张表,7万亿条数据,每天新增500亿条记录,数据要求存储半年,单条记录大小1KB左右,存储规模约10PB,需要支持1000并发查询。

一、数据存储空间规划。

数据中心能用于搭建Elasticsearch集群的SSD盘共700TB,SATA盘共50PB。根据业务类型、时间范围划分热数据和冷数据,一部分重要数据存储在SSD盘的热数据集群,其它数据存储在SATA盘的冷数据集群。热数据集群主要存储各类实体信息,包括人员、物品、事件、地址、组织数据,以及最新轨迹数据。冷数据集群主要存储历史轨迹信息。热数据和冷数据按照业务拆分多个小集群,每个集群规模保持在50个节点左右,单个集群最大不超过200个节点。利用阿里云平台弹性伸缩的能力,每个Elasticsearch集群可以先从小规模创建,根据资源使用情况来弹性扩展节点规模。

Elasticsearch集群节点配置

pb001.jpg

二、索引设计。

1.索引别名(alias)。每类数据根据数据源表名建立索引(index),索引中只包含一个类型(type)。配置索引别名(alias),业务上根据别名写入、查询数据,索引重建等数据维护操作可以通过别名切换对业务透明。

2.按时间分表。轨迹类数据按时间(日/月)拆分,每个索引存储数据量保持在1TB(10亿)左右,索引名带上日期/月份后缀,拆分后的索引配置别名区分冷热数据。配置索引模板,指定索引分片数和副本数、字段类型、分词器。配置Linux crontab定时任务,通过shell脚本创建索引。

3.分片(shard)设置。索引按照单个分片10-40GB数据大小设计分片数,数据量少于10GB(1000万)的索引设置1个分片即可,数据量大于1TB(10亿)的索引设置分片数为集群节点数整数倍(例如50个节点的集群配置50个分片)。

4.副本(replica)设置。数据首次批量导入时索引副本数设置为0,快速写入数据。生产环境索引副本数设置为1,避免集群节点故障数据丢失。

三、索引mapping设计。

1.精心设计索引字段类型。在开发环境配置Elasticsearch允许自动创建索引,从数据源每张表取1000条记录批量写入Elasticsearch,自动创建索引mapping,然后再根据业务需要修改mapping配置合适的字段类型,指定字段索引分词器、是否存储、是否索引、是否合并至全文检索字段。 对于数据量大的表尤其要精心设计字段类型,尽量减少索引存储空间占用。在生产环境中建议配置不允许自动创建索引。

2.配置全文检索字段。如果业务需要全文检索,可以配置开启全文字段,同时需要占用更多存储空间;如果业务上只是按字段查询,可以配置禁用全文字段,减少存储空间。Elasticsearch5.X及之前的版本默认启用_all字段,合并所有字段的值。Elasticsearch6.X及之后的版本默认禁用_all字段,可以通过copy_to将多个字段值合并成一个全文字段。对于数据查全率要求高的业务场景,建议对全文字段配置cjk分词器(Elasticsearch和Lucene中自带,对中日韩文进行二元分词的分词器)。

3.通用字段统一命名。各个索引中的姓名、证件号码、时间(开始时间、结束时间)、地点(始发地、目的地)等常用字段统一命名。用户指定证件号、时间范围等精确字段查询条件时,可以使用统一的查询条件并行查询多个索引。

四、分词设置。

1.选择合适的分词器。Elasticsearch中内置了很多分词器:standard、cjk、nGram等,也可以安装ik、pinyin等开源分词器, 可以根据业务场景选择合适的分词器。 常用分词器: standard:Elasticsearch默认分词,英文按空格切分,中文按单个汉字切分。 cjk:根据二元索引(两个相邻的字作为一个词条)对中日韩文分词,可以保证查全率。 NGram:可以将英文按照字母切分,结合Elasticsearch的短语搜索(match_phrase)使用。 ik:比较热门的中文分词,能按照中文语义切分,可以自定义词典。 pinyin:可以让用户输入拼音,就能查找到相关的关键词。 对于查全率要求较高的场景,建议使用cjk分词,同时能支持比较快的响应速度。对于查准率要求较高的场景,建议使用ik分词。

CJK分词和IK分词对比(测试环境:Elasticsearch5.5.3,8335万条人员档案信息,10节点集群,单节点16核CPU、64G内存、2T SSD盘,1个线程批量写入,1个并发查询)

pb002.jpg

测试分词效果: curl -XPOST "http://localhost:9200/_analyze" -H 'Content-Type: application/json' -d' { "analyzer": "ik_max_word", "text": "南京市长江大桥" }'

2.NGram分词。对于像车牌号之类数字和字母连在一起的字符,默认会被切成一个完整词条,但是业务上又需要支持前缀、后缀模糊匹配,可以根据业务需求进行分词。车牌号建议增加一个分词字段,配置NGram分词器,切分1元至7元的组合。身份证号码建议增加分词字段,根据业务需要切分18位完整词条、前2位(省)、前4位(省市)、前6位(省市区县)、后4位、出生年月日、出生年份、出生年月、出生月日等组合。

3.单字分词。对于像姓名类字段,业务上需要支持完整匹配,又需要支持单字查询。可以配置1个keyword字段(不分词);1个text字段(分词),分词器选择Elasticsearch默认分词器standard,按单个汉字切分。

五、数据写入策略。

1.批量离线数据导入。各类业务数据源主要在数据仓库MaxCompute(原ODPS),为了把表数据从MaxCompute表导入到ElasticSearch集群中, 我们基于MaxCompute MapReduce开发了MaxCompute到ElasticSearch的数据导出作业,通过简单的配置就可以把数据导入到ElasticSearch中。 数据源在关系数据库RDS或者NoSQL的数据,可以通过配置DataWorks(dataX企业版)导入Elasticsearch集群。

2.实时数据导入。实时数据源主要是流式数据服务DataHub, 配置DataHub任务即可同步至Elasticsearch集群。也可以自己开发程序调用DataHub的SDK获取实时数据,经过业务处理后,调用ES Rest Client SDK批量写入Elasticsearch。

3.冷热数据自动迁移。轨迹类实时数据默认先写入热数据集群(SSD盘Elasticsearch集群),对于热数据集群过期的索引(例如1个月前的索引)需要迁移到冷数据集群(SATA盘Elasticsearch)。为了实现数据跨集群迁移,我们开发了snapshot插件将索引备份到对象存储服务OSS或分布式文件系统盘古。配置定时任务,将热数据集群索引备份后,从冷数据集群恢复,然后再删除热集群中的过期索引,保持热数据集群只存储较小规模数据。冷数据集群的索引如果超过半年,则关闭索引,减少JVM堆内存占用。

4.配置索引主键字段。为了保证Elasticsearch集群和数据源记录的一致性,建议所有索引配置主键字段,而不是让Elasticsearch自动生成主键。配置数据业务主键字段作为Elasticsearch主键字段。如果没有主键字段,则将原始数据能确定记录惟一性的几个字段合并为主键,或者将所有字段值合并起来计算MD5值作为主键。

5.配置写入路由。如果业务上需要经常根据某个字段查询,例如用户ID、车牌号等的字段,写入时可以指定路由字段。

6.写入参数调优。调整数据写入任务参数,避免写入操作占用过多磁盘IO和CPU。使用批量请求,配置合理的写入线程数,调大索引刷新时间间隔refresh interval,调整事务日志translog同步策略。

六、数据查询策略。

1.冷热库异步查询。用户输入关键词查询时,优先从热数据集群查询,有结果立即返回,并估算命中记录条数。热数据集群命中结果集不足时,再查询冷数据集群。

2.跨集群搜索。业务上需要多个Elasticsearch集群一起参与检索时,可以通过Cross Cluster Search同时对多个集群发起检索请求合并检索结果。单独创建一个5节点的Cross Cluster,设置远程集群节点信息,用于跨集群搜索,不存储业务数据。

3.快速返回和超时设置。查询请求中设置参数teminate_after指定每个分片(shard)最多匹配N条记录后返回(例如10000),设置查询超时时间timeout(例如10s),避免查询一些宽泛的条件时耗费过多系统资源。

4.查询语法解析。解析用户查询条件,识别用户的查询类型,例如用户输入车牌号、证件号、年龄段等条件时,查询条件改写为字段精确匹配,无法识别的查询条件默认从全文字段匹配。

5.查询条件调优。查询结果不需要相关度排序时使用过滤器(filter),尽量使用路由(routing),设置较少的查询读取记录条数和字段,避免前缀模糊匹配,设置search_after规避深度翻页性能问题。

七、数据写入、查询性能测试。

SSD盘集群写入性能测试(测试环境:Elasticsearch6.3.2集群,单节点16核CPU、64G内存、2T SSD盘,写入10亿条记录,单条记录1KB,副本数为0,1台写入服务器):

pb003.jpg

SSD盘集群查询性能测试

pb004.jpg

SATA盘集群写入性能测试(测试环境:Elasticsearch5.5.3集群,单节点56核CPU、128G内存、12块 6T SATA盘,分别写入1亿、3亿、5亿、30亿、300亿条记录,单条记录1KB,0副本,50台写入服务器):

pb005.jpg

SATA盘集群查询性能测试

pb006.jpg

参考文档:

  1. 阿里云Elasticsearch帮助文档 https://help.aliyun.com/product/57736.html
  2. Elasticsearch参考 https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
  3. 《Elasticsearch: 权威指南》 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
  4. 《深入理解Elasticsearch》https://detail.tmall.com/item.htm?id=551001166567
  5. 《死磕Elasticsearch方法论》https://blog.csdn.net/laoyang360/article/details/79293493
  6. Elasticsearch索引别名和零停机 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index-aliases.html
  7. Elasticsearch自动按天创建索引脚本 https://blog.csdn.net/reblue520/article/details/80553317
  8. Elasticsearch NGram分词器 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html
  9. Elasticsearch开源权限管理认证插件Search Guard https://github.com/floragunncom/search-guard
  10. Elasticsearch开源可视化管理插件cerebro https://github.com/lmenezes/cerebro
  11. Elasticsearch开源SQL插件 https://github.com/NLPchina/elasticsearch-sql
  12. Elasticsearch快照及恢复 https://help.aliyun.com/document_detail/65675.html

Elasticsearch技术交流钉钉群

dingdingpng.png

【 报名开启】2018 Elastic & 袋鼠云 & 阿里云技术沙龙(杭州)

ElasticsearchAllwang 发表了文章 • 1 个评论 • 559 次浏览 • 2018-12-02 20:40 • 来自相关话题

互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!
互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!

Day 2 - ES 6.x拼音分词高亮爬坑记

Adventabia 发表了文章 • 8 个评论 • 295 次浏览 • 2018-12-02 16:29 • 来自相关话题

大家好,我是来自尚德机构ES平台的负责人,白凡,今天为大家分享一些在6.x版本中拼音分词高亮问题爬坑的心路历程~,其实问题不复杂,主要介绍下思路。 首先简单讲下背景~可能在很多公司的很多部门,都有使用到ES。包括在尚德,由于很多部门的业务都涉及到ES,于是我们为了统一管理及维护,专门成立了ES平台部门,主要扮演的是类似于op dba角色,帮助业务部门部署维护ES集群,并根据业务需求提供解决方案。当然,除此之外,我们也会在公司内部推荐业务方去尝试除了日志和搜索以外的应用场景,比如分布式计算存储、监控、安全等方面。毕竟ES相比于其他组建,搭建部署更加方便,更轻量级,查询方式更丰富。所以,现如今在尚德机构,ES平台不仅用于了传统的日志和搜索领域,也在分布式数据存储和计算方面有很多应用。当然,这里只是为大家提供一些ES应用场景及其团队构建的思路。主要还是ES这个工具确实好用。 广告先做到这,回到正文。所以,前段日子,我们接收了一个新的业务部门需求,大致是:他们之前使用的自己搭建ES 2.x集群,现在接入到我们6.x的平台上来。我们帮忙设计了mapping,数据写入及同步方案之后,数据就慢慢接入进来。但问题也随即出现,原来在2.x上使用正常的拼音高亮mapping,在6.x上只能检索但无法高亮了? 2.x field如下: "index" : { "analysis" : { "analyzer" : { "pinyin_analyzer" : { "tokenizer" : "my_pinyin" } }, "tokenizer" : { "my_pinyin" : { "type" : "pinyin", "keep_full_pinyin" : false, "limit_first_letter_length" : 16, "lowercase" : true, "remove_duplicated_term":true, "keep_first_letter":true, "keep_separate_first_letter" :true } } } } POST /medcl/doc/_mapping { "properties": { "name":{ "analyzer": "pinyin_analyzer", "type": "string" } } } 可以从上面例子看出,这个analyzer并没有问题,但是在搜索时,能得到结果,却无法高亮,即分词结果中start_offset及end_offset为0,这个如何解决呢? 回到medcl的拼音分词项目: https://github.com/medcl/elast ... inyin 其中,有个配置项引起了我们的注意:
图片1.png
没跑了,应该是要将这个参数设置为false。 并且查看了源码,在PinyinTokenizer这个类下面,看到了这一行:
图片2.png
确定了我们的思路,于是乎,在tokenizer中将此参数设为false,如下: "tokenizer" : { "my_pinyin" : { "type" : "pinyin", "keep_full_pinyin" : true, "keep_original" : false, "limit_first_letter_length" : 16, "lowercase" : true, "remove_duplicated_term":true, "ignore_pinyin_offset": false, "keep_first_letter":true, "keep_separate_first_letter" :true } } 写入一条数据,高亮没问题了,问题“看似”解决了。 当然,没有那么简单。因为在批量写入一部分数据后,总会报如下异常: startOffset must be non-negative, and endOffset must be >= startOffset 这个异常,导致数据无法写入集群。 这里又是为什么呢? 这个问题,我也搞了一段时间,始终没找到很好的解决方案,此处只能先@medcl。 只是猜测在end()或者reset()方法内,需要lastOffset置0或者offsetAtt清空。但尝试了几次,依然报错。。。 这就比较头疼了,不过好在条条道路通罗马。在某次蹲坑过程中,灵感如尿崩。 如果Tokenizer解决不了,为何不仅用filter就行了呢?可以先用其他分词器,按我们业务的需求进行分词,再用filter,将分词过滤为拼音呢? 大致思路如下: 目前我们这个业务,需要如对于“尚德机构”这个词,搜索“shang”,“shangde”,“deji”时,能返回结果并高亮。 所以我们先用ngram分词,将“尚德机构”这个词分为“尚”,“尚德”,“徳机”,“德机构”等等。。 再用pinyin filter将各分词过滤为拼音,即“shang”,“shangde”,“deji”等。 并在搜索时,采用standard分词。 Mapping如下: { "settings": { "analysis": { "analyzer": { "pinyin_analyzer": { "tokenizer": "my_ngram", "filter": [ "pinyin_filter" ] } }, "tokenizer": { "my_ngram": { "type": "ngram", "min_gram": 1, "max_gram": 50, "token_chars": [ "letter", "digit", "punctuation", "symbol" ] } }, "filter": { "pinyin_filter": { "type": "pinyin", "keep_full_pinyin": false, "keep_joined_full_pinyin": true, "keep_none_chinese_in_joined_full_pinyin": true, "none_chinese_pinyin_tokenize": false, "remove_duplicated_term": true } } } }, "mappings": { "abia321": { "properties": { "name": { "type": "text", "analyzer": "pinyin_analyzer", "search_analyzer": "standard", "term_vector": "with_positions_offsets" } } } } } 最后,高亮问题解决,数据写入问题同样解决。 当然有朋友肯定还会需要搜索拼音首字母进行搜索,如搜“s”,“sd”,“dj”,也返回结果。 其实,只需要再专门设置个field,并调整pinyin filter参数, 搜索时用bool查询,逻辑should查询,同时对完整拼音field和拼音首字母field进行搜索即可。 在此就不做过多赘述。 当然,这里仅仅只是提供一种ES在选择analyzer,tokenizer,filter解决需求的思路。拼音分词这个问题,还是需要等待后续修复 最后,这里有较为完整的issue: https://github.com/medcl/elast ... s/169

es传输json数据出现的问题

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 77 次浏览 • 2018-11-30 15:58 • 来自相关话题

ES5.0两台集群在50 query并发量的性能已经较低了,性能怎么去提升呢?

Elasticsearchweizijun 回复了问题 • 3 人关注 • 2 个回复 • 206 次浏览 • 2018-11-29 12:44 • 来自相关话题

es怎么实现图片的搜索,大佬有什么好的建议吗

ElasticsearchYu Tao 回复了问题 • 5 人关注 • 2 个回复 • 143 次浏览 • 2018-11-29 11:37 • 来自相关话题

Elastic日报 第462期 (2018-11-28)

Elastic日报sterne vencel 发表了文章 • 0 个评论 • 164 次浏览 • 2018-11-28 16:58 • 来自相关话题

Elastic日报 第462期 (2018-11-28) 1. 让天下没有难用的搜索 阿里搜索如何成长为贴心“暖男” http://t.cn/E2UjZ6d 2.Elasticsearcharch索引生命周期管理探索 http://t.cn/RDUxF3t 3. 实例展示Elasticsearch集群生态,分片以及水平扩展 http://t.cn/RzIQzrR 编辑:江水 归档:http://elasticsearch.cn/article/6160 订阅:https://tinyletter.com/elastic-daily
Elastic日报 第462期 (2018-11-28) 1. 让天下没有难用的搜索 阿里搜索如何成长为贴心“暖男” http://t.cn/E2UjZ6d 2.Elasticsearcharch索引生命周期管理探索 http://t.cn/RDUxF3t 3. 实例展示Elasticsearch集群生态,分片以及水平扩展 http://t.cn/RzIQzrR 编辑:江水 归档:http://elasticsearch.cn/article/6160 订阅:https://tinyletter.com/elastic-daily

关于ES中Lucene Merge 线程问题;

Elasticsearchrochy 回复了问题 • 3 人关注 • 1 个回复 • 159 次浏览 • 2018-11-28 11:50 • 来自相关话题

【 报名开启】2018 Elastic & 袋鼠云 & 阿里云技术沙龙(杭州)

ElasticsearchAllwang 发表了文章 • 1 个评论 • 559 次浏览 • 2018-12-02 20:40 • 来自相关话题

互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!
互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!

elastic搜索排序问题,如果人为的影响score得分结果?

回复

Elasticsearchzz_hello 回复了问题 • 4 人关注 • 3 个回复 • 100 次浏览 • 13 小时前 • 来自相关话题

安装search guard后,使用客户端代码连接ES。ES版本是6.5.1。

回复

Elasticsearchzqc0512 回复了问题 • 3 人关注 • 4 个回复 • 146 次浏览 • 15 小时前 • 来自相关话题

ES集群多实例分片不能恢复;

回复

Elasticsearchzqc0512 回复了问题 • 3 人关注 • 6 个回复 • 110 次浏览 • 1 天前 • 来自相关话题

分片综合评分问题

回复

Elasticsearchmedcl 回复了问题 • 3 人关注 • 1 个回复 • 69 次浏览 • 6 天前 • 来自相关话题

SparkStreaming 写 ES可能遇到的问题

回复

Elasticsearchrochy 回复了问题 • 2 人关注 • 2 个回复 • 72 次浏览 • 6 天前 • 来自相关话题

ES集群分片rebalance问题;

回复

Elasticsearchzz_hello 回复了问题 • 3 人关注 • 2 个回复 • 109 次浏览 • 2018-12-05 15:07 • 来自相关话题

es传输json数据出现的问题

回复

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 77 次浏览 • 2018-11-30 15:58 • 来自相关话题

ES5.0两台集群在50 query并发量的性能已经较低了,性能怎么去提升呢?

回复

Elasticsearchweizijun 回复了问题 • 3 人关注 • 2 个回复 • 206 次浏览 • 2018-11-29 12:44 • 来自相关话题

es怎么实现图片的搜索,大佬有什么好的建议吗

回复

ElasticsearchYu Tao 回复了问题 • 5 人关注 • 2 个回复 • 143 次浏览 • 2018-11-29 11:37 • 来自相关话题

关于ES中Lucene Merge 线程问题;

回复

Elasticsearchrochy 回复了问题 • 3 人关注 • 1 个回复 • 159 次浏览 • 2018-11-28 11:50 • 来自相关话题

es terms聚合个别字段特别慢

回复

Elasticsearchrochy 回复了问题 • 5 人关注 • 1 个回复 • 129 次浏览 • 2018-11-26 17:19 • 来自相关话题

请问elasticsearch 搜索优先级怎么设置?

回复

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 121 次浏览 • 2018-11-21 20:24 • 来自相关话题

elastalert开启钉钉通知

回复

Elasticsearchdoctor 回复了问题 • 2 人关注 • 1 个回复 • 184 次浏览 • 2018-11-20 17:54 • 来自相关话题

按条件提取index里面指定的数据,然后在转存到新的index里面。

回复

Elasticsearchzz_hello 回复了问题 • 2 人关注 • 1 个回复 • 58 次浏览 • 2018-11-19 16:02 • 来自相关话题

mysql数据导入es,多值字段如何实现

回复

Elasticsearchhaliaddel 回复了问题 • 1 人关注 • 1 个回复 • 199 次浏览 • 2018-11-19 10:02 • 来自相关话题

Day 7 - Elasticsearch中数据是如何存储的

Adventweizijun 发表了文章 • 2 个评论 • 528 次浏览 • 5 天前 • 来自相关话题

前言

很多使用Elasticsearch的同学会关心数据存储在ES中的存储容量,会有这样的疑问:xxTB的数据入到ES会使用多少存储空间。这个问题其实很难直接回答的,只有数据写入ES后,才能观察到实际的存储空间。比如同样是1TB的数据,写入ES的存储空间可能差距会非常大,可能小到只有300~400GB,也可能多到6-7TB,为什么会造成这么大的差距呢?究其原因,我们来探究下Elasticsearch中的数据是如何存储。文章中我以Elasticsearch 2.3版本为示例,对应的lucene版本是5.5,Elasticsearch现在已经来到了6.5版本,数字类型、列存等存储结构有些变化,但基本的概念变化不多,文章中的内容依然适用。

Elasticsearch索引结构

Elasticsearch对外提供的是index的概念,可以类比为DB,用户查询是在index上完成的,每个index由若干个shard组成,以此来达到分布式可扩展的能力。比如下图是一个由10个shard组成的index。

elasticsearch_store_arc.png

shard是Elasticsearch数据存储的最小单位,index的存储容量为所有shard的存储容量之和。Elasticsearch集群的存储容量则为所有index存储容量之和。

一个shard就对应了一个lucene的library。对于一个shard,Elasticsearch增加了translog的功能,类似于HBase WAL,是数据写入过程中的中间数据,其余的数据都在lucene库中管理的。

所以Elasticsearch索引使用的存储内容主要取决于lucene中的数据存储。

lucene数据存储

下面我们主要看下lucene的文件内容,在了解lucene文件内容前,大家先了解些lucene的基本概念。

lucene基本概念

  • segment : lucene内部的数据是由一个个segment组成的,写入lucene的数据并不直接落盘,而是先写在内存中,经过了refresh间隔,lucene才将该时间段写入的全部数据refresh成一个segment,segment多了之后会进行merge成更大的segment。lucene查询时会遍历每个segment完成。由于lucene* 写入的数据是在内存中完成,所以写入效率非常高。但是也存在丢失数据的风险,所以Elasticsearch基于此现象实现了translog,只有在segment数据落盘后,Elasticsearch才会删除对应的translog。
  • doc : doc表示lucene中的一条记录
  • field :field表示记录中的字段概念,一个doc由若干个field组成。
  • term :term是lucene中索引的最小单位,某个field对应的内容如果是全文检索类型,会将内容进行分词,分词的结果就是由term组成的。如果是不分词的字段,那么该字段的内容就是一个term。
  • 倒排索引(inverted index): lucene索引的通用叫法,即实现了term到doc list的映射。
  • 正排数据:搜索引擎的通用叫法,即原始数据,可以理解为一个doc list。
  • docvalues :Elasticsearch中的列式存储的名称,Elasticsearch除了存储原始存储、倒排索引,还存储了一份docvalues,用作分析和排序。

lucene文件内容

lucene包的文件是由很多segment文件组成的,segments_xxx文件记录了lucene包下面的segment文件数量。每个segment会包含如下的文件。

Name Extension Brief Description
Segment Info .si segment的元数据文件
Compound File .cfs, .cfe 一个segment包含了如下表的各个文件,为减少打开文件的数量,在segment小的时候,segment的所有文件内容都保存在cfs文件中,cfe文件保存了lucene各文件在cfs文件的位置信息
Fields .fnm 保存了fields的相关信息
Field Index .fdx 正排存储文件的元数据信息
Field Data .fdt 存储了正排存储数据,写入的原文存储在这
Term Dictionary .tim 倒排索引的元数据信息
Term Index .tip 倒排索引文件,存储了所有的倒排索引数据
Frequencies .doc 保存了每个term的doc id列表和term在doc中的词频
Positions .pos Stores position information about where a term occurs in the index
全文索引的字段,会有该文件,保存了term在doc中的位置
Payloads .pay Stores additional per-position metadata information such as character offsets and user payloads
全文索引的字段,使用了一些像payloads的高级特性会有该文件,保存了term在doc中的一些高级特性
Norms .nvd, .nvm 文件保存索引字段加权数据
Per-Document Values .dvd, .dvm lucene的docvalues文件,即数据的列式存储,用作聚合和排序
Term Vector Data .tvx, .tvd, .tvf Stores offset into the document data file
保存索引字段的矢量信息,用在对term进行高亮,计算文本相关性中使用
Live Documents .liv 记录了segment中删除的doc

测试数据示例

下面我们以真实的数据作为示例,看看lucene中各类型数据的容量占比。

写100w数据,有一个uuid字段,写入的是长度为36位的uuid,字符串总为3600w字节,约为35M。

数据使用一个shard,不带副本,使用默认的压缩算法,写入完成后merge成一个segment方便观察。

使用线上默认的配置,uuid存为不分词的字符串类型。创建如下索引:

PUT test_field
{
  "settings": {
    "index": {
      "number_of_shards": "1",
      "number_of_replicas": "0",
      "refresh_interval": "30s"
    }
  },
  "mappings": {
    "type": {
      "_all": {
        "enabled": false
      }, 
      "properties": {
        "uuid": {
          "type": "string",
          "index": "not_analyzed"
        }
      }
    }
  }
}

首先写入100w不同的uuid,使用磁盘容量细节如下:


health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    122.7mb        122.7mb 

-rw-r--r--  1 weizijun  staff    41M Aug 19 21:23 _8.fdt
-rw-r--r--  1 weizijun  staff    17K Aug 19 21:23 _8.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 19 21:23 _8.fnm
-rw-r--r--  1 weizijun  staff   494B Aug 19 21:23 _8.si
-rw-r--r--  1 weizijun  staff   265K Aug 19 21:23 _8_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff    44M Aug 19 21:23 _8_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   340K Aug 19 21:23 _8_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 19 21:23 _8_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 19 21:23 _8_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   195B Aug 19 21:23 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 19 21:20 write.lock

可以看到正排数据、倒排索引数据,列存数据容量占比几乎相同,正排数据和倒排数据还会存储Elasticsearch的唯一id字段,所以容量会比列存多一些。

35M的uuid存入Elasticsearch后,数据膨胀了3倍,达到了122.7mb。Elasticsearch竟然这么消耗资源,不要着急下结论,接下来看另一个测试结果。

我们写入100w一样的uuid,然后看看Elasticsearch使用的容量。

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0     13.2mb         13.2mb 

-rw-r--r--  1 weizijun  staff   5.5M Aug 19 21:29 _6.fdt
-rw-r--r--  1 weizijun  staff    15K Aug 19 21:29 _6.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 19 21:29 _6.fnm
-rw-r--r--  1 weizijun  staff   494B Aug 19 21:29 _6.si
-rw-r--r--  1 weizijun  staff   309K Aug 19 21:29 _6_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   7.0M Aug 19 21:29 _6_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   195K Aug 19 21:29 _6_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff   244K Aug 19 21:29 _6_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   252B Aug 19 21:29 _6_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   195B Aug 19 21:29 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 19 21:26 write.lock

这回35M的数据Elasticsearch容量只有13.2mb,其中还有主要的占比还是Elasticsearch的唯一id,100w的uuid几乎不占存储容积。

所以在Elasticsearch中建立索引的字段如果基数越大(count distinct),越占用磁盘空间。

我们再看看存100w个不一样的整型会是如何。

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0     13.6mb         13.6mb 

-rw-r--r--  1 weizijun  staff   6.1M Aug 28 10:19 _42.fdt
-rw-r--r--  1 weizijun  staff    22K Aug 28 10:19 _42.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 28 10:19 _42.fnm
-rw-r--r--  1 weizijun  staff   503B Aug 28 10:19 _42.si
-rw-r--r--  1 weizijun  staff   2.8M Aug 28 10:19 _42_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   2.2M Aug 28 10:19 _42_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff    83K Aug 28 10:19 _42_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff   2.5M Aug 28 10:19 _42_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   228B Aug 28 10:19 _42_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   196B Aug 28 10:19 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 28 10:16 write.lock

从结果可以看到,100w整型数据,Elasticsearch的存储开销为13.6mb。如果以int型计算100w数据的长度的话,为400w字节,大概是3.8mb数据。忽略Elasticsearch唯一id字段的影响,Elasticsearch实际存储容量跟整型数据长度差不多。

我们再看一下开启最佳压缩参数对存储空间的影响:

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    107.2mb        107.2mb 

-rw-r--r--  1 weizijun  staff    25M Aug 20 12:30 _5.fdt
-rw-r--r--  1 weizijun  staff   6.0K Aug 20 12:30 _5.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 20 12:31 _5.fnm
-rw-r--r--  1 weizijun  staff   500B Aug 20 12:31 _5.si
-rw-r--r--  1 weizijun  staff   265K Aug 20 12:31 _5_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff    44M Aug 20 12:31 _5_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   322K Aug 20 12:31 _5_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 20 12:31 _5_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 20 12:31 _5_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   224B Aug 20 12:31 segments_4
-rw-r--r--  1 weizijun  staff     0B Aug 20 12:00 write.lock

结果中可以发现,只有正排数据会启动压缩,压缩能力确实强劲,不考虑唯一id字段,存储容量大概压缩到接近50%。

我们还做了一些实验,Elasticsearch默认是开启_all参数的,_all可以让用户传入的整体json数据作为全文检索的字段,可以更方便的检索,但在现实场景中已经使用的不多,相反会增加很多存储容量的开销,可以看下开启_all的磁盘空间使用情况:


health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    162.4mb        162.4mb 

-rw-r--r--  1 weizijun  staff    41M Aug 18 22:59 _20.fdt
-rw-r--r--  1 weizijun  staff    18K Aug 18 22:59 _20.fdx
-rw-r--r--  1 weizijun  staff   777B Aug 18 22:59 _20.fnm
-rw-r--r--  1 weizijun  staff    59B Aug 18 22:59 _20.nvd
-rw-r--r--  1 weizijun  staff    78B Aug 18 22:59 _20.nvm
-rw-r--r--  1 weizijun  staff   539B Aug 18 22:59 _20.si
-rw-r--r--  1 weizijun  staff   7.2M Aug 18 22:59 _20_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   4.2M Aug 18 22:59 _20_Lucene50_0.pos
-rw-r--r--  1 weizijun  staff    73M Aug 18 22:59 _20_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   832K Aug 18 22:59 _20_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 18 22:59 _20_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 18 22:59 _20_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   196B Aug 18 22:59 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 18 22:53 write.lock

开启_all比不开启多了40mb的存储空间,多的数据都在倒排索引上,大约会增加30%多的存储开销。所以线上都直接禁用。

然后我还做了其他几个尝试,为了验证存储容量是否和数据量成正比,写入1000w数据的uuid,发现存储容量基本为100w数据的10倍。我还验证了数据长度是否和数据量成正比,发现把uuid增长2倍、4倍,存储容量也响应的增加了2倍和4倍。在此就不一一列出数据了。

lucene各文件具体内容和实现

lucene数据元信息文件

文件名为:segments_xxx

该文件为lucene数据文件的元信息文件,记录所有segment的元数据信息。

该文件主要记录了目前有多少segment,每个segment有一些基本信息,更新这些信息定位到每个segment的元信息文件。

lucene元信息文件还支持记录userData,Elasticsearch可以在此记录translog的一些相关信息。

文件示例

elasticsearch_store_segments.png

具体实现类

public final class SegmentInfos implements Cloneable, Iterable<SegmentCommitInfo> {
  // generation是segment的版本的概念,从文件名中提取出来,实例中为:2t/101
  private long generation;     // generation of the "segments_N" for the next commit

  private long lastGeneration; // generation of the "segments_N" file we last successfully read
                               // or wrote; this is normally the same as generation except if
                               // there was an IOException that had interrupted a commit

  /** Id for this commit; only written starting with Lucene 5.0 */
  private byte[] id;

  /** Which Lucene version wrote this commit, or null if this commit is pre-5.3. */
  private Version luceneVersion;

  /** Counts how often the index has been changed.  */
  public long version;

  /** Used to name new segments. */
  // TODO: should this be a long ...?
  public int counter;

  /** Version of the oldest segment in the index, or null if there are no segments. */
  private Version minSegmentLuceneVersion;

  private List<SegmentCommitInfo> segments = new ArrayList<>();

  /** Opaque Map&lt;String, String&gt; that user can specify during IndexWriter.commit */
  public Map<String,String> userData = Collections.emptyMap();
}

/** Embeds a [read-only] SegmentInfo and adds per-commit
 *  fields.
 *
 *  @lucene.experimental */
public class SegmentCommitInfo {

  /** The {@link SegmentInfo} that we wrap. */
  public final SegmentInfo info;

  // How many deleted docs in the segment:
  private int delCount;

  // Generation number of the live docs file (-1 if there
  // are no deletes yet):
  private long delGen;

  // Normally 1+delGen, unless an exception was hit on last
  // attempt to write:
  private long nextWriteDelGen;

  // Generation number of the FieldInfos (-1 if there are no updates)
  private long fieldInfosGen;

  // Normally 1+fieldInfosGen, unless an exception was hit on last attempt to
  // write
  private long nextWriteFieldInfosGen; //fieldInfosGen == -1 ? 1 : fieldInfosGen + 1;

  // Generation number of the DocValues (-1 if there are no updates)
  private long docValuesGen;

  // Normally 1+dvGen, unless an exception was hit on last attempt to
  // write
  private long nextWriteDocValuesGen; //docValuesGen == -1 ? 1 : docValuesGen + 1;

  // TODO should we add .files() to FieldInfosFormat, like we have on
  // LiveDocsFormat?
  // track the fieldInfos update files
  private final Set<String> fieldInfosFiles = new HashSet<>();

  // Track the per-field DocValues update files
  private final Map<Integer,Set<String>> dvUpdatesFiles = new HashMap<>();

  // Track the per-generation updates files
  @Deprecated
  private final Map<Long,Set<String>> genUpdatesFiles = new HashMap<>();

  private volatile long sizeInBytes = -1;
}

segment的元信息文件

文件后缀:.si

每个segment都有一个.si文件,记录了该segment的元信息。

segment元信息文件中记录了segment的文档数量,segment对应的文件列表等信息。

文件示例

elasticsearch_store_si.png

具体实现类

/**
 * Information about a segment such as its name, directory, and files related
 * to the segment.
 *
 * @lucene.experimental
 */
public final class SegmentInfo {

  // _bl
  public final String name;

  /** Where this segment resides. */
  public final Directory dir;

  /** Id that uniquely identifies this segment. */
  private final byte[] id;

  private Codec codec;

  // Tracks the Lucene version this segment was created with, since 3.1. Null
  // indicates an older than 3.0 index, and it's used to detect a too old index.
  // The format expected is "x.y" - "2.x" for pre-3.0 indexes (or null), and
  // specific versions afterwards ("3.0.0", "3.1.0" etc.).
  // see o.a.l.util.Version.
  private Version version;

  private int maxDoc;         // number of docs in seg

  private boolean isCompoundFile;

  private Map<String,String> diagnostics;

  private Set<String> setFiles;

  private final Map<String,String> attributes;
}

fields信息文件

文件后缀:.fnm

该文件存储了fields的基本信息。

fields信息中包括field的数量,field的类型,以及IndexOpetions,包括是否存储、是否索引,是否分词,是否需要列存等等。

文件示例

elasticsearch_store_fnm.png

具体实现类

/**
 *  Access to the Field Info file that describes document fields and whether or
 *  not they are indexed. Each segment has a separate Field Info file. Objects
 *  of this class are thread-safe for multiple readers, but only one thread can
 *  be adding documents at a time, with no other reader or writer threads
 *  accessing this object.
 **/
public final class FieldInfo {
  /** Field's name */
  public final String name;

  /** Internal field number */
  //field在内部的编号
  public final int number;

  //field docvalues的类型
  private DocValuesType docValuesType = DocValuesType.NONE;

  // True if any document indexed term vectors
  private boolean storeTermVector;

  private boolean omitNorms; // omit norms associated with indexed fields 

  //index的配置项
  private IndexOptions indexOptions = IndexOptions.NONE;

  private boolean storePayloads; // whether this field stores payloads together with term positions 

  private final Map<String,String> attributes;

  // docvalues的generation
  private long dvGen;
}

数据存储文件

文件后缀:.fdx, .fdt

索引文件为.fdx,数据文件为.fdt,数据存储文件功能为根据自动的文档id,得到文档的内容,搜索引擎的术语习惯称之为正排数据,即doc_id -> content,es的_source数据就存在这

索引文件记录了快速定位文档数据的索引信息,数据文件记录了所有文档id的具体内容。

文件示例

elasticsearch_store_fdt.png

具体实现类

/**
 * Random-access reader for {@link CompressingStoredFieldsIndexWriter}.
 * @lucene.internal
 */
public final class CompressingStoredFieldsIndexReader implements Cloneable, Accountable {
  private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(CompressingStoredFieldsIndexReader.class);

  final int maxDoc;

  //docid索引,快速定位某个docid的数组坐标
  final int[] docBases;

  //快速定位某个docid所在的文件offset的startPointer
  final long[] startPointers;

  //平均一个chunk的文档数
  final int[] avgChunkDocs;

  //平均一个chunk的size
  final long[] avgChunkSizes;

  final PackedInts.Reader[] docBasesDeltas; // delta from the avg

  final PackedInts.Reader[] startPointersDeltas; // delta from the avg
}

/**
 * {@link StoredFieldsReader} impl for {@link CompressingStoredFieldsFormat}.
 * @lucene.experimental
 */
public final class CompressingStoredFieldsReader extends StoredFieldsReader {

  //从fdt正排索引文件中获得
  private final int version;

  // field的基本信息
  private final FieldInfos fieldInfos;

  //fdt正排索引文件reader
  private final CompressingStoredFieldsIndexReader indexReader;

  //从fdt正排索引文件中获得,用于指向fdx数据文件的末端,指向numChunks地址4
  private final long maxPointer;

  //fdx正排数据文件句柄
  private final IndexInput fieldsStream;

  //块大小
  private final int chunkSize;

  private final int packedIntsVersion;

  //压缩类型
  private final CompressionMode compressionMode;

  //解压缩处理对象
  private final Decompressor decompressor;

  //文档数量,从segment元数据中获得
  private final int numDocs;

  //是否正在merge,默认为false
  private final boolean merging;

  //初始化时new了一个BlockState,BlockState记录下当前正排文件读取的状态信息
  private final BlockState state;
  //chunk的数量
  private final long numChunks; // number of compressed blocks written

  //dirty chunk的数量
  private final long numDirtyChunks; // number of incomplete compressed blocks written

  //是否close,默认为false
  private boolean closed;
}

倒排索引文件

索引后缀:.tip,.tim

倒排索引也包含索引文件和数据文件,.tip为索引文件,.tim为数据文件,索引文件包含了每个字段的索引元信息,数据文件有具体的索引内容。

5.5.0版本的倒排索引实现为FST tree,FST tree的最大优势就是内存空间占用非常低 ,具体可以参看下这篇文章:http://www.cnblogs.com/bonelee/p/6226185.html

http://examples.mikemccandless.com/fst.py?terms=&cmd=Build+it 为FST图实例,可以根据输入的数据构造出FST图

输入到 FST 中的数据为:
String inputValues[] = {"mop","moth","pop","star","stop","top"};
long outputValues[] = {0,1,2,3,4,5};

生成的 FST 图为:

elasticsearch_store_tip1.png

elasticsearch_store_tip2.png

文件示例

elasticsearch_store_tip3.png

具体实现类

public final class BlockTreeTermsReader extends FieldsProducer {
  // Open input to the main terms dict file (_X.tib)
  final IndexInput termsIn;
  // Reads the terms dict entries, to gather state to
  // produce DocsEnum on demand
  final PostingsReaderBase postingsReader;
  private final TreeMap<String,FieldReader> fields = new TreeMap<>();

  /** File offset where the directory starts in the terms file. */
  /索引数据文件tim的数据的尾部的元数据的地址
  private long dirOffset;
  /** File offset where the directory starts in the index file. */

  //索引文件tip的数据的尾部的元数据的地址
  private long indexDirOffset;

  //semgent的名称
  final String segment;

  //版本号
  final int version;

  //5.3.x index, we record up front if we may have written any auto-prefix terms,示例中记录的是false
  final boolean anyAutoPrefixTerms;
}

/**
 * BlockTree's implementation of {@link Terms}.
 * @lucene.internal
 */
public final class FieldReader extends Terms implements Accountable {

  //term的数量
  final long numTerms;

  //field信息
  final FieldInfo fieldInfo;

  final long sumTotalTermFreq;

  //总的文档频率
  final long sumDocFreq;

  //文档数量
  final int docCount;

  //字段在索引文件tip中的起始位置
  final long indexStartFP;

  final long rootBlockFP;

  final BytesRef rootCode;

  final BytesRef minTerm;

  final BytesRef maxTerm;

  //longs:metadata buffer, holding monotonic values
  final int longsSize;

  final BlockTreeTermsReader parent;

  final FST<BytesRef> index;
}

倒排链文件

文件后缀:.doc, .pos, .pay

.doc保存了每个term的doc id列表和term在doc中的词频

全文索引的字段,会有.pos文件,保存了term在doc中的位置

全文索引的字段,使用了一些像payloads的高级特性才会有.pay文件,保存了term在doc中的一些高级特性

文件示例

elasticsearch_store_doc.png

具体实现类

/**
 * Concrete class that reads docId(maybe frq,pos,offset,payloads) list
 * with postings format.
 *
 * @lucene.experimental
 */
public final class Lucene50PostingsReader extends PostingsReaderBase {
  private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Lucene50PostingsReader.class);
  private final IndexInput docIn;
  private final IndexInput posIn;
  private final IndexInput payIn;
  final ForUtil forUtil;
  private int version;

  //不分词的字段使用的是该对象,基于skiplist实现了倒排链
  final class BlockDocsEnum extends PostingsEnum {
  }

  //全文检索字段使用的是该对象
  final class BlockPostingsEnum extends PostingsEnum {
  }

  //包含高级特性的字段使用的是该对象
  final class EverythingEnum extends PostingsEnum {
  }
}

列存文件(docvalues)

文件后缀:.dvm, .dvd

索引文件为.dvm,数据文件为.dvd。

lucene实现的docvalues有如下类型:

  • 1、NONE 不开启docvalue时的状态
  • 2、NUMERIC 单个数值类型的docvalue主要包括(int,long,float,double)
  • 3、BINARY 二进制类型值对应不同的codes最大值可能超过32766字节,
  • 4、SORTED 有序增量字节存储,仅仅存储不同部分的值和偏移量指针,值必须小于等于32766字节
  • 5、SORTED_NUMERIC 存储数值类型的有序数组列表
  • 6、SORTED_SET 可以存储多值域的docvalue值,但返回时,仅仅只能返回多值域的第一个docvalue
  • 7、对应not_anaylized的string字段,使用的是SORTED_SET类型,number的类型是SORTED_NUMERIC类型

其中SORTED_SET 的 SORTED_SINGLE_VALUED类型包括了两类数据 : binary + numeric, binary是按ord排序的term的列表,numeric是doc到ord的映射。

文件示例

elasticsearch_store_dvd.png

具体实现类

/** reader for {@link Lucene54DocValuesFormat} */
final class Lucene54DocValuesProducer extends DocValuesProducer implements Closeable {
  //number类型的field的列存列表
  private final Map<String,NumericEntry> numerics = new HashMap<>();

  //字符串类型的field的列存列表
  private final Map<String,BinaryEntry> binaries = new HashMap<>();

  //有序字符串类型的field的列存列表
  private final Map<String,SortedSetEntry> sortedSets = new HashMap<>();

  //有序number类型的field的列存列表
  private final Map<String,SortedSetEntry> sortedNumerics = new HashMap<>();

  //字符串类型的field的ords列表
  private final Map<String,NumericEntry> ords = new HashMap<>();

  //docId -> address -> ord 中field的ords列表
  private final Map<String,NumericEntry> ordIndexes = new HashMap<>();

  //field的数量
  private final int numFields;

  //内存使用量
  private final AtomicLong ramBytesUsed;

  //数据源的文件句柄
  private final IndexInput data;

  //文档数
  private final int maxDoc;
  // memory-resident structures
  private final Map<String,MonotonicBlockPackedReader> addressInstances = new HashMap<>();
  private final Map<String,ReverseTermsIndex> reverseIndexInstances = new HashMap<>();
  private final Map<String,DirectMonotonicReader.Meta> directAddressesMeta = new HashMap<>();

  //是否正在merge
  private final boolean merging;
}

/** metadata entry for a numeric docvalues field */
  static class NumericEntry {
    private NumericEntry() {}
    /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
    long missingOffset;

    /** offset to the actual numeric values */
    //field的在数据文件中的起始地址
    public long offset;

    /** end offset to the actual numeric values */
    //field的在数据文件中的结尾地址
    public long endOffset;

    /** bits per value used to pack the numeric values */
    public int bitsPerValue;

    //format类型
    int format;
    /** count of values written */
    public long count;
    /** monotonic meta */
    public DirectMonotonicReader.Meta monotonicMeta;

    //最小的value
    long minValue;

    //Compressed by computing the GCD
    long gcd;

    //Compressed by giving IDs to unique values.
    long table[];
    /** for sparse compression */
    long numDocsWithValue;
    NumericEntry nonMissingValues;
    NumberType numberType;
  }

  /** metadata entry for a binary docvalues field */
  static class BinaryEntry {
    private BinaryEntry() {}
    /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
    long missingOffset;
    /** offset to the actual binary values */
    //field的在数据文件中的起始地址
    long offset;
    int format;
    /** count of values written */
    public long count;

    //最短字符串的长度
    int minLength;

    //最长字符串的长度
    int maxLength;
    /** offset to the addressing data that maps a value to its slice of the byte[] */
    public long addressesOffset, addressesEndOffset;
    /** meta data for addresses */
    public DirectMonotonicReader.Meta addressesMeta;
    /** offset to the reverse index */
    public long reverseIndexOffset;
    /** packed ints version used to encode addressing information */
    public int packedIntsVersion;
    /** packed ints blocksize */
    public int blockSize;
  }

参考资料

lucene source code

lucene document

lucene字典实现原理——FST

Day 4 - PB级规模数据的Elasticsearch分库分表实践

Adventouyangchucai 发表了文章 • 2 个评论 • 657 次浏览 • 2018-12-04 20:50 • 来自相关话题

从2018年7月在开始在某阿里云数据中心部署Elasticsearch软件,到2018年12月共创建了15个集群,服务于客户的文档检索、交通视频检索、地理信息检索、日志安全审计等业务。其中数据规模最大的一个业务,共有800张表,7万亿条数据,每天新增500亿条记录,数据要求存储半年,单条记录大小1KB左右,存储规模约10PB,需要支持1000并发查询。

一、数据存储空间规划。

数据中心能用于搭建Elasticsearch集群的SSD盘共700TB,SATA盘共50PB。根据业务类型、时间范围划分热数据和冷数据,一部分重要数据存储在SSD盘的热数据集群,其它数据存储在SATA盘的冷数据集群。热数据集群主要存储各类实体信息,包括人员、物品、事件、地址、组织数据,以及最新轨迹数据。冷数据集群主要存储历史轨迹信息。热数据和冷数据按照业务拆分多个小集群,每个集群规模保持在50个节点左右,单个集群最大不超过200个节点。利用阿里云平台弹性伸缩的能力,每个Elasticsearch集群可以先从小规模创建,根据资源使用情况来弹性扩展节点规模。

Elasticsearch集群节点配置

pb001.jpg

二、索引设计。

1.索引别名(alias)。每类数据根据数据源表名建立索引(index),索引中只包含一个类型(type)。配置索引别名(alias),业务上根据别名写入、查询数据,索引重建等数据维护操作可以通过别名切换对业务透明。

2.按时间分表。轨迹类数据按时间(日/月)拆分,每个索引存储数据量保持在1TB(10亿)左右,索引名带上日期/月份后缀,拆分后的索引配置别名区分冷热数据。配置索引模板,指定索引分片数和副本数、字段类型、分词器。配置Linux crontab定时任务,通过shell脚本创建索引。

3.分片(shard)设置。索引按照单个分片10-40GB数据大小设计分片数,数据量少于10GB(1000万)的索引设置1个分片即可,数据量大于1TB(10亿)的索引设置分片数为集群节点数整数倍(例如50个节点的集群配置50个分片)。

4.副本(replica)设置。数据首次批量导入时索引副本数设置为0,快速写入数据。生产环境索引副本数设置为1,避免集群节点故障数据丢失。

三、索引mapping设计。

1.精心设计索引字段类型。在开发环境配置Elasticsearch允许自动创建索引,从数据源每张表取1000条记录批量写入Elasticsearch,自动创建索引mapping,然后再根据业务需要修改mapping配置合适的字段类型,指定字段索引分词器、是否存储、是否索引、是否合并至全文检索字段。 对于数据量大的表尤其要精心设计字段类型,尽量减少索引存储空间占用。在生产环境中建议配置不允许自动创建索引。

2.配置全文检索字段。如果业务需要全文检索,可以配置开启全文字段,同时需要占用更多存储空间;如果业务上只是按字段查询,可以配置禁用全文字段,减少存储空间。Elasticsearch5.X及之前的版本默认启用_all字段,合并所有字段的值。Elasticsearch6.X及之后的版本默认禁用_all字段,可以通过copy_to将多个字段值合并成一个全文字段。对于数据查全率要求高的业务场景,建议对全文字段配置cjk分词器(Elasticsearch和Lucene中自带,对中日韩文进行二元分词的分词器)。

3.通用字段统一命名。各个索引中的姓名、证件号码、时间(开始时间、结束时间)、地点(始发地、目的地)等常用字段统一命名。用户指定证件号、时间范围等精确字段查询条件时,可以使用统一的查询条件并行查询多个索引。

四、分词设置。

1.选择合适的分词器。Elasticsearch中内置了很多分词器:standard、cjk、nGram等,也可以安装ik、pinyin等开源分词器, 可以根据业务场景选择合适的分词器。 常用分词器: standard:Elasticsearch默认分词,英文按空格切分,中文按单个汉字切分。 cjk:根据二元索引(两个相邻的字作为一个词条)对中日韩文分词,可以保证查全率。 NGram:可以将英文按照字母切分,结合Elasticsearch的短语搜索(match_phrase)使用。 ik:比较热门的中文分词,能按照中文语义切分,可以自定义词典。 pinyin:可以让用户输入拼音,就能查找到相关的关键词。 对于查全率要求较高的场景,建议使用cjk分词,同时能支持比较快的响应速度。对于查准率要求较高的场景,建议使用ik分词。

CJK分词和IK分词对比(测试环境:Elasticsearch5.5.3,8335万条人员档案信息,10节点集群,单节点16核CPU、64G内存、2T SSD盘,1个线程批量写入,1个并发查询)

pb002.jpg

测试分词效果: curl -XPOST "http://localhost:9200/_analyze" -H 'Content-Type: application/json' -d' { "analyzer": "ik_max_word", "text": "南京市长江大桥" }'

2.NGram分词。对于像车牌号之类数字和字母连在一起的字符,默认会被切成一个完整词条,但是业务上又需要支持前缀、后缀模糊匹配,可以根据业务需求进行分词。车牌号建议增加一个分词字段,配置NGram分词器,切分1元至7元的组合。身份证号码建议增加分词字段,根据业务需要切分18位完整词条、前2位(省)、前4位(省市)、前6位(省市区县)、后4位、出生年月日、出生年份、出生年月、出生月日等组合。

3.单字分词。对于像姓名类字段,业务上需要支持完整匹配,又需要支持单字查询。可以配置1个keyword字段(不分词);1个text字段(分词),分词器选择Elasticsearch默认分词器standard,按单个汉字切分。

五、数据写入策略。

1.批量离线数据导入。各类业务数据源主要在数据仓库MaxCompute(原ODPS),为了把表数据从MaxCompute表导入到ElasticSearch集群中, 我们基于MaxCompute MapReduce开发了MaxCompute到ElasticSearch的数据导出作业,通过简单的配置就可以把数据导入到ElasticSearch中。 数据源在关系数据库RDS或者NoSQL的数据,可以通过配置DataWorks(dataX企业版)导入Elasticsearch集群。

2.实时数据导入。实时数据源主要是流式数据服务DataHub, 配置DataHub任务即可同步至Elasticsearch集群。也可以自己开发程序调用DataHub的SDK获取实时数据,经过业务处理后,调用ES Rest Client SDK批量写入Elasticsearch。

3.冷热数据自动迁移。轨迹类实时数据默认先写入热数据集群(SSD盘Elasticsearch集群),对于热数据集群过期的索引(例如1个月前的索引)需要迁移到冷数据集群(SATA盘Elasticsearch)。为了实现数据跨集群迁移,我们开发了snapshot插件将索引备份到对象存储服务OSS或分布式文件系统盘古。配置定时任务,将热数据集群索引备份后,从冷数据集群恢复,然后再删除热集群中的过期索引,保持热数据集群只存储较小规模数据。冷数据集群的索引如果超过半年,则关闭索引,减少JVM堆内存占用。

4.配置索引主键字段。为了保证Elasticsearch集群和数据源记录的一致性,建议所有索引配置主键字段,而不是让Elasticsearch自动生成主键。配置数据业务主键字段作为Elasticsearch主键字段。如果没有主键字段,则将原始数据能确定记录惟一性的几个字段合并为主键,或者将所有字段值合并起来计算MD5值作为主键。

5.配置写入路由。如果业务上需要经常根据某个字段查询,例如用户ID、车牌号等的字段,写入时可以指定路由字段。

6.写入参数调优。调整数据写入任务参数,避免写入操作占用过多磁盘IO和CPU。使用批量请求,配置合理的写入线程数,调大索引刷新时间间隔refresh interval,调整事务日志translog同步策略。

六、数据查询策略。

1.冷热库异步查询。用户输入关键词查询时,优先从热数据集群查询,有结果立即返回,并估算命中记录条数。热数据集群命中结果集不足时,再查询冷数据集群。

2.跨集群搜索。业务上需要多个Elasticsearch集群一起参与检索时,可以通过Cross Cluster Search同时对多个集群发起检索请求合并检索结果。单独创建一个5节点的Cross Cluster,设置远程集群节点信息,用于跨集群搜索,不存储业务数据。

3.快速返回和超时设置。查询请求中设置参数teminate_after指定每个分片(shard)最多匹配N条记录后返回(例如10000),设置查询超时时间timeout(例如10s),避免查询一些宽泛的条件时耗费过多系统资源。

4.查询语法解析。解析用户查询条件,识别用户的查询类型,例如用户输入车牌号、证件号、年龄段等条件时,查询条件改写为字段精确匹配,无法识别的查询条件默认从全文字段匹配。

5.查询条件调优。查询结果不需要相关度排序时使用过滤器(filter),尽量使用路由(routing),设置较少的查询读取记录条数和字段,避免前缀模糊匹配,设置search_after规避深度翻页性能问题。

七、数据写入、查询性能测试。

SSD盘集群写入性能测试(测试环境:Elasticsearch6.3.2集群,单节点16核CPU、64G内存、2T SSD盘,写入10亿条记录,单条记录1KB,副本数为0,1台写入服务器):

pb003.jpg

SSD盘集群查询性能测试

pb004.jpg

SATA盘集群写入性能测试(测试环境:Elasticsearch5.5.3集群,单节点56核CPU、128G内存、12块 6T SATA盘,分别写入1亿、3亿、5亿、30亿、300亿条记录,单条记录1KB,0副本,50台写入服务器):

pb005.jpg

SATA盘集群查询性能测试

pb006.jpg

参考文档:

  1. 阿里云Elasticsearch帮助文档 https://help.aliyun.com/product/57736.html
  2. Elasticsearch参考 https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
  3. 《Elasticsearch: 权威指南》 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
  4. 《深入理解Elasticsearch》https://detail.tmall.com/item.htm?id=551001166567
  5. 《死磕Elasticsearch方法论》https://blog.csdn.net/laoyang360/article/details/79293493
  6. Elasticsearch索引别名和零停机 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index-aliases.html
  7. Elasticsearch自动按天创建索引脚本 https://blog.csdn.net/reblue520/article/details/80553317
  8. Elasticsearch NGram分词器 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html
  9. Elasticsearch开源权限管理认证插件Search Guard https://github.com/floragunncom/search-guard
  10. Elasticsearch开源可视化管理插件cerebro https://github.com/lmenezes/cerebro
  11. Elasticsearch开源SQL插件 https://github.com/NLPchina/elasticsearch-sql
  12. Elasticsearch快照及恢复 https://help.aliyun.com/document_detail/65675.html

Elasticsearch技术交流钉钉群

dingdingpng.png

【 报名开启】2018 Elastic & 袋鼠云 & 阿里云技术沙龙(杭州)

ElasticsearchAllwang 发表了文章 • 1 个评论 • 559 次浏览 • 2018-12-02 20:40 • 来自相关话题

互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!
互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!

Day 2 - ES 6.x拼音分词高亮爬坑记

Adventabia 发表了文章 • 8 个评论 • 295 次浏览 • 2018-12-02 16:29 • 来自相关话题

大家好,我是来自尚德机构ES平台的负责人,白凡,今天为大家分享一些在6.x版本中拼音分词高亮问题爬坑的心路历程~,其实问题不复杂,主要介绍下思路。 首先简单讲下背景~可能在很多公司的很多部门,都有使用到ES。包括在尚德,由于很多部门的业务都涉及到ES,于是我们为了统一管理及维护,专门成立了ES平台部门,主要扮演的是类似于op dba角色,帮助业务部门部署维护ES集群,并根据业务需求提供解决方案。当然,除此之外,我们也会在公司内部推荐业务方去尝试除了日志和搜索以外的应用场景,比如分布式计算存储、监控、安全等方面。毕竟ES相比于其他组建,搭建部署更加方便,更轻量级,查询方式更丰富。所以,现如今在尚德机构,ES平台不仅用于了传统的日志和搜索领域,也在分布式数据存储和计算方面有很多应用。当然,这里只是为大家提供一些ES应用场景及其团队构建的思路。主要还是ES这个工具确实好用。 广告先做到这,回到正文。所以,前段日子,我们接收了一个新的业务部门需求,大致是:他们之前使用的自己搭建ES 2.x集群,现在接入到我们6.x的平台上来。我们帮忙设计了mapping,数据写入及同步方案之后,数据就慢慢接入进来。但问题也随即出现,原来在2.x上使用正常的拼音高亮mapping,在6.x上只能检索但无法高亮了? 2.x field如下: "index" : { "analysis" : { "analyzer" : { "pinyin_analyzer" : { "tokenizer" : "my_pinyin" } }, "tokenizer" : { "my_pinyin" : { "type" : "pinyin", "keep_full_pinyin" : false, "limit_first_letter_length" : 16, "lowercase" : true, "remove_duplicated_term":true, "keep_first_letter":true, "keep_separate_first_letter" :true } } } } POST /medcl/doc/_mapping { "properties": { "name":{ "analyzer": "pinyin_analyzer", "type": "string" } } } 可以从上面例子看出,这个analyzer并没有问题,但是在搜索时,能得到结果,却无法高亮,即分词结果中start_offset及end_offset为0,这个如何解决呢? 回到medcl的拼音分词项目: https://github.com/medcl/elast ... inyin 其中,有个配置项引起了我们的注意:
图片1.png
没跑了,应该是要将这个参数设置为false。 并且查看了源码,在PinyinTokenizer这个类下面,看到了这一行:
图片2.png
确定了我们的思路,于是乎,在tokenizer中将此参数设为false,如下: "tokenizer" : { "my_pinyin" : { "type" : "pinyin", "keep_full_pinyin" : true, "keep_original" : false, "limit_first_letter_length" : 16, "lowercase" : true, "remove_duplicated_term":true, "ignore_pinyin_offset": false, "keep_first_letter":true, "keep_separate_first_letter" :true } } 写入一条数据,高亮没问题了,问题“看似”解决了。 当然,没有那么简单。因为在批量写入一部分数据后,总会报如下异常: startOffset must be non-negative, and endOffset must be >= startOffset 这个异常,导致数据无法写入集群。 这里又是为什么呢? 这个问题,我也搞了一段时间,始终没找到很好的解决方案,此处只能先@medcl。 只是猜测在end()或者reset()方法内,需要lastOffset置0或者offsetAtt清空。但尝试了几次,依然报错。。。 这就比较头疼了,不过好在条条道路通罗马。在某次蹲坑过程中,灵感如尿崩。 如果Tokenizer解决不了,为何不仅用filter就行了呢?可以先用其他分词器,按我们业务的需求进行分词,再用filter,将分词过滤为拼音呢? 大致思路如下: 目前我们这个业务,需要如对于“尚德机构”这个词,搜索“shang”,“shangde”,“deji”时,能返回结果并高亮。 所以我们先用ngram分词,将“尚德机构”这个词分为“尚”,“尚德”,“徳机”,“德机构”等等。。 再用pinyin filter将各分词过滤为拼音,即“shang”,“shangde”,“deji”等。 并在搜索时,采用standard分词。 Mapping如下: { "settings": { "analysis": { "analyzer": { "pinyin_analyzer": { "tokenizer": "my_ngram", "filter": [ "pinyin_filter" ] } }, "tokenizer": { "my_ngram": { "type": "ngram", "min_gram": 1, "max_gram": 50, "token_chars": [ "letter", "digit", "punctuation", "symbol" ] } }, "filter": { "pinyin_filter": { "type": "pinyin", "keep_full_pinyin": false, "keep_joined_full_pinyin": true, "keep_none_chinese_in_joined_full_pinyin": true, "none_chinese_pinyin_tokenize": false, "remove_duplicated_term": true } } } }, "mappings": { "abia321": { "properties": { "name": { "type": "text", "analyzer": "pinyin_analyzer", "search_analyzer": "standard", "term_vector": "with_positions_offsets" } } } } } 最后,高亮问题解决,数据写入问题同样解决。 当然有朋友肯定还会需要搜索拼音首字母进行搜索,如搜“s”,“sd”,“dj”,也返回结果。 其实,只需要再专门设置个field,并调整pinyin filter参数, 搜索时用bool查询,逻辑should查询,同时对完整拼音field和拼音首字母field进行搜索即可。 在此就不做过多赘述。 当然,这里仅仅只是提供一种ES在选择analyzer,tokenizer,filter解决需求的思路。拼音分词这个问题,还是需要等待后续修复 最后,这里有较为完整的issue: https://github.com/medcl/elast ... s/169

Elastic日报 第462期 (2018-11-28)

Elastic日报sterne vencel 发表了文章 • 0 个评论 • 164 次浏览 • 2018-11-28 16:58 • 来自相关话题

Elastic日报 第462期 (2018-11-28) 1. 让天下没有难用的搜索 阿里搜索如何成长为贴心“暖男” http://t.cn/E2UjZ6d 2.Elasticsearcharch索引生命周期管理探索 http://t.cn/RDUxF3t 3. 实例展示Elasticsearch集群生态,分片以及水平扩展 http://t.cn/RzIQzrR 编辑:江水 归档:http://elasticsearch.cn/article/6160 订阅:https://tinyletter.com/elastic-daily
Elastic日报 第462期 (2018-11-28) 1. 让天下没有难用的搜索 阿里搜索如何成长为贴心“暖男” http://t.cn/E2UjZ6d 2.Elasticsearcharch索引生命周期管理探索 http://t.cn/RDUxF3t 3. 实例展示Elasticsearch集群生态,分片以及水平扩展 http://t.cn/RzIQzrR 编辑:江水 归档:http://elasticsearch.cn/article/6160 订阅:https://tinyletter.com/elastic-daily

CentOS 7.4 下安装 ES 6.5.1 搜索集群

Elasticsearchrochy 发表了文章 • 2 个评论 • 351 次浏览 • 2018-11-23 00:02 • 来自相关话题

一、准备安装

1、修改系统 hosts

vi /etc/hosts   # 修改 hosts 文件,添加下面的内容

192.168.11.1    sky-00
192.168.11.2    sky-01
192.168.11.3    sky-02
192.168.11.4    sky-03
192.168.11.5    sky-04
192.168.11.6    sky-05
192.168.11.7    sky-06

2、角色分配

主机名 角色 内存分配
sky-00 Master 4G
sky-01 Master 8G
sky-02 Master+Data 12G
sky-03 Data 12G
sky-04 Data 12G
sky-05 Data 12G
sky-06 Data 12G

3、创建 ES 用户

adduser elastic  # 新增用户
passwd elastic   # 修改用户密码

4、创建 ES 数据和日志目录

cd /data/
mkdir elastic
cd elastic
mkdir data      # 创建数据目录
mkdir log       # 创建日志目录
chown -R elastic /data/elastic/  # 修改拥有着

5、调整文件句柄数以及可用进程数

Elasticsearch 要求其可用的文件句柄至少为 65536,同时要求其进程数限制至少为 2048,可用按照下面的指令进行修改。

分别对应以下两个报错信息:

  • max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
  • max number of threads [1024] for user [es] is too low, increase to at least [2048]
vi /etc/security/limits.conf

*     soft   nofile  100001
*     hard   nofile  100002
*     soft   nproc   4096
*     hard   nproc   8192
elastic soft memlock unlimited
elastic hard memlock unlimited

6、设置内核交换

为了避免不必要的磁盘和内存交换,影响效率,需要将 vm.swappiness 修改为 1(进行最少量的交换,而不禁用交换)或者 10(当系统存在足够内存时,推荐设置为该值以提高性能),其默认值为 60。

此外需要修改最大虚拟内存 vm.max_map_count 防止启动时报错:max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]

vi /etc/sysctl.conf

vm.swappiness = 1
vm.max_map_count = 262144

7、下载安装文件

mkdir /opt/downloads/
mkdir /opt/soft/
cd /opt/downloads/

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.1.tar.gz
wget https://artifacts.elastic.co/downloads/kibana/kibana-6.5.1-linux-x86_64.tar.gz
wget http://download.oracle.com/otn/java/jdk/xxxxxx/jdk-8u191-linux-x64.tar.gz

tar -zxvf elasticsearch-6.5.1.tar.gz -C /opt/soft/
tar -zxvf jdk-8u191-linux-x64.tar.gz -C /opt/soft/
tar -zxvf kibana-6.5.1-linux-x86_64.tar.gz -C /opt/soft/

chown -R elastic /opt/soft/elasticsearch-6.5.1/
chown -R elastic /opt/soft/kibana-6.5.1/

二、开始安装

1、配置 Java 环境

su elastic             #切换到 elastic 用户
vi ~/.bashrc          #只修改 elastic 用户自己的环境变量

export JAVA_HOME=/opt/soft/jdk1.8.0_191
export JRE_HOME=/opt/soft/jdk1.8.0_191/jre
export CLASSPATH=.:/opt/soft/jdk1.8.0_191/lib:/opt/soft/jdk1.8.0_191/jre/lib
export PATH=$PATH:/opt/soft/jdk1.8.0_191/bin:/opt/soft/jdk1.8.0_191/jre/bin

2、配置 ES 内存占用

cd /opt/soft/elasticsearch-6.5.1/config/
vi jvm.options 

-Xms4g      # 请根据自己机器配置调整
-Xmx4g

3、配置 Elasticsearch

下面的配置已经过多个生产环境验证,具体设置值仅供参考,请务必根据实际情况进行调整。

# ---------------------------------- Cluster -----------------------------------
#
# 设置集群名
cluster.name: cluster-name
#
# ------------------------------------ Node ------------------------------------
#
# 设置节点名
node.name: node01

# 设置角色
node.master: true   
node.data: false
node.ingest: true

# 设置机架信息
#node.attr.rack: r1
#
# ----------------------------------- Paths ------------------------------------
#
# 设置数据路径
path.data: /data/elastic/data

# 设置日志路径
path.logs: /data/elastic/log
#
# ----------------------------------- Memory -----------------------------------
#
# 设置内存锁定
bootstrap.memory_lock: true
bootstrap.system_call_filter: false
#
# ---------------------------------- Network -----------------------------------
#
# 设置ip和端口
network.bind_host: sky-00
network.publish_host: 0.0.0.0
http.port: 9200

# 设置跨域访问
http.cors.enabled: true
http.cors.allow-origin: "*"
http.max_content_length: 500mb

# --------------------------------- Discovery ----------------------------------

# 设置zen发现范围(只需要填写主节点的 ip 即可)
discovery.zen.ping.unicast.hosts: ["sky-00", "sky-01", "sky-02"]

discovery.zen.no_master_block: write
discovery.zen.fd.ping_timeout: 10s

# 设置最小主节点个数,一般为:(master_node_count+1)/2
discovery.zen.minimum_master_nodes: 2

# ---------------------------------- Gateway -----------------------------------
#
# 设置在有4个节点后进行数据恢复
gateway.recover_after_nodes: 4
gateway.expected_nodes: 7
gateway.recover_after_time: 1m
#
# ---------------------------------- Various -----------------------------------
# 禁止通配符模式删除索引
action.destructive_requires_name: true

indices.recovery.max_bytes_per_sec: 200mb
indices.memory.index_buffer_size: 20%

# 默认开启全部类型脚本,可以通过下面配置进行限制
#script.allowed_types: inline
#script.allowed_contexts: search, update

# 关闭xpack的安全校验
xpack.security.enabled: false

# 开启 monitoring
xpack.monitoring.enabled: true
xpack.monitoring.collection.enabled: true

# 设置 monitoring 写入信息
xpack.monitoring.exporters:
  sky:
    type: http
    host: ["sky-02", "sky-03", "sky-04", "sky-05", "sky-06"]
    # 设置 monitoring 索引格式,默认是 YYYY-MM-DD(按天新建)
    index.name.time_format: YYYY-MM
    headers:
      # 设置 Basic 认证信息(详见插件安装部分说明)
      Authorization: "Basic XXXXXXXXXXXXXXX"

三、安装插件

1、安装插件

推荐安装的插件有:

  • IK 中文分词插件
  • Readonlyrest 安全认证插件
  • elasticsearch-head 集群监控管理插件(chrome 插件)

插件下载链接: https://pan.baidu.com/s/1r_322unsIjoWlhY8u7pkBA 提取码: aupq

使用下面命令即可安装。

$ES_HOME/bin/elasticsearch-plugin -install file:///data/downloads/elasticsearch-analysis-ik-6.5.1.zip
$ES_HOME/bin/elasticsearch-plugin -install file:///data/downloads/readonlyrest-1.16.29_es6.5.1.zip

2、配置 Readonlyrest 安全认证

下面只简单介绍 Readonlyrest 的 Basic 认证,更高级的用法可以去官方网站查看,在 ES 安装目录的 conf 目录下新建文件 readonlyrest.yml,并添加下面内容。

readonlyrest:
    access_control_rules:
    - name: "Require HTTP Basic Auth"
      type: allow
      auth_key: 用户名:密码

3、启动 ES

全部安装完成后,即可使用 elastic 用户启动 ES。

# 默认 ES 不支持 root 用户启动
su elastic
cd /opt/soft/elasticsearch-6.5.1/bin
./elasticsearch -d

四、在 Kibana 里面监控

在安装 ES 的时候,我们配置了 ES 的监控信息,这样我们就可以在 Kibana 中查看 ES 索引信息、node 信息等。

1、配置 Kibana

进入 Kibana 的解压目录下的 conf 文件夹,打开配置文件 kibana.yml

# 配置 kibana ui 的端口
server.port: 5601

# 配置 kibana 访问 ip
server.host: "0.0.0.0"

# 设置 ES 地址
elasticsearch.url: "http://sky-00:9200"

# dashboards. Kibana creates a new index if the index doesn't already exist.
#kibana.index: ".kibana"

# 打开 kibana 时默认页面
#kibana.defaultAppId: "home"

# ES Basic 认证信息
elasticsearch.username: "用户名"
elasticsearch.password: "密码"

# 设置时区信息
#i18n.locale: "en"

# 开启监控
xpack.monitoring.enabled: true

# 关闭 kibana 监控,默认为 true
xpack.monitoring.kibana.collection.enabled: false

2、对 Kibana 配置文件的说明

  • ES Basic 认证信息配置(在启动时对 Kibana 索引进行维护)完成后,登陆 kibana 时,依旧需要输入认证信息;
  • 由于 kibana 的 monitoring 无法设置新建的索引的索引名(无法配置 index.name.time_format),这样 kibana 每天会新建一个索引,由于 kibana 只是作为管理查看工具,因此关闭了 kibana 监控;
  • elasticsearch.url 该配置项无法设置多个 es 地址;如果你想实现类似负载均衡的功能,最简单的方法就是在 Kibana 机器上运行一个协调(Coordinating)节点。

3、监控界面

全部配置完成后,启动 kibana,打开 monitoring 即可开始监控 node、index 等。

使用 Kibana 监控节点状态

五、设置索引模板

具体请参考之前发布的文章基于 IK 分词器的 ES 通用索引模板


Any Code,Code Any!

扫码关注『AnyCode』,编程路上,一起前行。

ELK 使用小技巧(第 1 期)

Elasticsearchrochy 发表了文章 • 1 个评论 • 386 次浏览 • 2018-10-27 19:16 • 来自相关话题

ELK Tips 主要介绍一些 ELK 使用过程中的小技巧,内容主要来源为 Elastic 中文社区。

一、Logstash

1、Logstash 字符串分割(Split)

以下配置将 message 内容按照 \t 进行切分,为了使 \t 生效需要将 logstah.yml 中配置项 config.support_escapes 设置为 true,当设置为 true 时,带引号的字符串将处理转义字符,默认值为 false

filter {
  mutate {
    split => {"message" => "\t"}
    add_field => {
      "ftimeold" => "%{[message][0]}"
    }
  }
}

2、Logstash 按文件读取内容并存储到 ES

下面的配置将读取/home/txts/*下的文件,并读取整个文件内容,然后将文件内容存储到 test-text 索引中,同时该条记录的 _id 为文档的文件名。这里需要注意的是,想读取到文档末尾时,分隔符需设置为 EOF

input {
  file {
    path => ["/home/txts/*"]
    start_position => "beginning"
    mode => "read"
    delimiter => "EOF"
    file_completed_action => "log"
    file_completed_log_path => "/home/logs/file.log"
  }
}
output {
  elasticsearch {
    hosts => ["http://192.168.3.214:9200/"]
    index => "test-text"
    document_id => "%{path}"
  }
  stdout {}
}

3、ES Ingest Node 脚本案例

Ingest Node 可以使用多种过滤器对数据进行处理,其中 Script 脚本的功能非常强大,下面的案例实现了将一个 Json 结构进行了 Flat 化:

{
    "script" : {
      "lang" : "painless",
      "source" : "def dict = ['result': new HashMap()]; for (entry in ctx['json'].entrySet()) { dict['result'][entry.getKey()] = entry.getValue(); } ctx['osquery'] = dict; ctx.remove('json');"
    }
}

4、Logstash input file 插件中 sincedb 维护问题

  1. 如果不想保存 sincedb,可以使用下面配置:sincedb_path => "/dev/null"
  2. 如果希望被扫描的记录超过一段时间后自动被清除,可以使用 sincedb_clean_after => "2 weeks" 来实现,sincedb_clean_after 表示当一个文件在设定的时间内没有发生过任何变化,则关于这个文件的扫描记录将不会存储到 sincedb 里面,简单来说就是一条记录的过期时间。

二、Elasticsearch

1、ES 查询结果的一致性

为了保证用户每次查询结果的一致性(文档在结果中的顺序),可以在查询 url 里添加 preference=<some string> 这个参数,其中<some string>可以是用户的 session ID,这样某一个用户查询的时候,查询会被固定在某几个 shard。

2、同义词的扩展或收缩

  • 简单扩展,把同义词列表中的任意一个词扩展成同义词列表所有的词:jump,hop,leap
  • 简单收缩,把左边的多个同义词映射到了右边的单个词:leap,hop => jump
  • 类型扩展,完全不同于简单收缩或扩张,并不是平等看待所有的同义词,而是扩大了词的意义使被拓展的词更为通用:
    "cat    => cat,pet",
    "kitten => kitten,cat,pet",
    "dog    => dog,pet"
    "puppy  => puppy,dog,pet"

3、设置某个索引为只读状态

index.blocks.write 设置为 true 来禁止对索引的写操作,但索引的 metadatra 可以正常写。

PUT indexName/_settings
{
    "index.blocks.write": true
}

4、Failed to process cluster event (put-mapping) within 30s

这个是创建 mapping 的时候超时了,默认是 30s 应该是集群处理不过来了。索引文件太多,使得集群的状态数据过多过大,在每个小时新建索引和设置索引 mapping 的时候,就产生集群状态更新任务交给 master 处理,master 在变更状态数据的时候是单线程处理的,如果集群总的状态数据很大,master处理这些任务就容易出现超时。

解决办法:

  • 控制集群的总的索引数量,shard 数量;
  • 如果同时创建的索引非常多,最好避免通过写入数据自动创建索引;
  • 可以通过 cron 任务,预先顺序的创建好索引

5、Get 查询获取不到数据,但是用 _search 就可以查询到

这种情况一般在索引时候加入了路由字段(routing),那么在 get,delete,update 操作中都必须使用路由字段。

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

GET my_index/my_type/1?routing=user1

6、ES 5.X 版本多个 type 的数据迁移到 6.X

把 5.x 集群中的索引按不同 type 拆分 reindex 到 6.x 集群索引中,然后将拆分出来的多个索引使用别名进行组织;例如 5.x 集群中有索引 IndexA,该索引上有 typeA 和 typeB,reindex 到 6.x 集群IndexA_TypeAIndexB_TypeB,reindex 语句如下所示:

POST _reindex
{
  "source": {
    "index": "IndexA",
    "type": "TypeA",
    "size": 10000
  },
  "dest": {
    "index": "IndexA_TypeA"
  }
}

最后给 6.x 集群的IndexA_TypeAIndexB_TypeB添加别名 IndexA,用户查询时使用的索引名称就不用变化。

POST _aliases  
{
    "actions": [
        {"add": {"index": "IndexA_TypeA", "alias": "IndexA"}},
        {"add": {"index": "IndexA_TypeB", "alias": "IndexA"}}
    ]
}

7、reindex 将多个索引合并成一个索引,需要重新设置新索引的 mapping 吗?

需要在 reindex 之前为新索引重新设置 mapping ,reindex 只是通过类似 scroll 的方式把数据 bulk 到新的索引,不会自动同步原索引的 mappings 信息。

8、集群的 discovery.zen.ping.unicast.hosts 配置

只需要配置主节点(master)地址即可。

discovery.zen.ping.unicast.hosts:
   - 192.168.1.10:9300
   - 192.168.1.11 
   - seeds.mydomain.com 

9、ES 的 path.data 配置多个盘的路径,查询效率与单个存储盘的效率比,哪个效率高些?

想最大程度发挥磁盘读写 io,还是推荐 RAID0。

使用多路径不一定会提升读写速度,和集群 shard 的数量有关系;主要是因为一个 shard 对应的文件,只会放到其中一块磁盘上,不会跨磁盘存储。比如一个极端的场景,集群 shard 数量比较少,每个结点上就一个shard,那么读写只会有一块磁盘发挥作用,其他磁盘都空闲的。

多路径对读写有提升比较大的场景,是每个结点上 shard 数量至少比盘的数量多,并且 shard 大小也差别不太多;shard 数量比较少,shard 大小差别太大,可能产生读写热点问题,即有的磁盘磁盘很忙,有的很闲。

ES 不会将一个索引的主副分片分配到同一台机器,所以即使一台机器的 RAID0 坏了,不会导致数据丢失,仅仅是副本没有了。

用 RAID0 的负面影响主要是磁盘损坏的时候,需要恢复的数据比较多;多路径磁盘,坏一块只会丢一部分数据,恢复数据会比较快;但是他也有缺陷,比如容易出现读写热点问题以及磁盘空间使用不均匀问题。

10、查询索引分片(Shard)位置的接口

# 推荐
GET /_cat/shards/<index_name>?v

GET /_cluster/state/routing_table

11、multi_match 与 match_phrase 的区别

  • multi_match 是对 boolQuery().should(matchQuery(field, keyword)) 的一种简化,简单说就是一个关键词,匹配多个字段,匹配方式为 matchQuery,正常的全文匹配。
  • match_phrase 简单说就是要匹配一个短语,例如你输入的文本为:中国人,如果被分词为:中国/人,那么查找时候会在指定的字段先查找到 "中国" 这个 term,然后在 "中国" 这个 term 后面去查找 "人"这个term(有顺序要求),如果匹配到则认为匹配成功;所以更像是在匹配一个短语(连贯的句子)。

12、analyzer, tokenizer, token-filter 有什么区别

  • analyzer :分析器,analyzer = 1 个 tokenizer + 若干个 token-filter;
  • tokenizer :分词器,主要用于对文本进行切割;
  • token-filter :过滤器,主要对 tokenizer 切割后的 term 进行再次处理。

13、_source 字段的用途

简单来说:_source 字段用于存储最原始的 JSON 文档内容(创建索引时传递的),这个字段不能被搜索,它可以在 get 或者 search 请求阶段进行返回;此外它会参与字段高亮计算、文档的更新等操作,一般不推荐关闭 _source 字段。

三、Kibana

1、kibana 表格默认排序

在设计表格的时候直接点击需要排序的那一列,然后让它按照倒序或者正序排序,然后点击保存即可,这样这个表格默认就是按照这一列倒序或者正序排列的。

kibana 排序设置


Any Code,Code Any!

扫码关注『AnyCode』,编程路上,一起前行。

ES内存分配规划

Elasticsearchyayg2008 发表了文章 • 2 个评论 • 603 次浏览 • 2018-07-04 15:15 • 来自相关话题

阅读本文前,请先阅读ES内存分析。 ES默认配置下,heap是存在超卖情况的。

类目 默认占比 是否常驻 淘汰策略(在控制大小情况下) 控制参数
query cache 10% LRU indices.queries.cache.size
request cache 1% LRU indices.requests.cache.size
fielddata cache 无限制 LRU indices.fielddata.cache.size
segment memory 无限制 不能通过参数控制
common space 70% GC 通过熔断器 indices.breaker.total.limit 限制

common space(可GC)

子类目 默认占比 控制参数
indexing buffer 10% indices.memory.index_buffer_size
request agg data 60% indices.breaker.request.limit
in-flight data 100% network.breaker.inflight_requests.limit

通过上表可知,segment memory是非常重要,而且是不可通过参数干预的内存空间,而cache部分则可以提升性能,可以被清除。common space 是运行时的动态空间,可以被GC。

综上所述,需要保证segment memory+cache+common space不超过100%。由于熔断器是按整个heap大小来计算的,所以如果segment memory 过大,仍然可能会导致OOM。为了减少这种情况的发生,需要预留足够空间给segment。 优化

  1. 限制fielddata大小,fielddata是针对text类型进行排序、聚合才用到。正常应该避免这种情况发生。
  2. 限制request agg data大小,这个参数会影响聚合使用的内存,如果触发熔断,业务需要进行优化。

内存分配

                                                                                                                                                                                                                                                                                         
         
segment memory

       

         
预留10%
       
         
       
         
fielddata cache
       
         
限制在20%
       
         
       
         
query cache
       
         
限制10%
       
         
       
         
request cache
       
         
限制1%
       
         
       
         
indexing buffer
       
         
限制10%
       
         
       
         
request agg data
       
         
限制1%
       
         
父熔断器配置30%,扣除fielddata,agg剩余的就是in-flight
       
         
in-flight data
       
         
限制9%
       

参数设置

indices.fielddata.cache.size:1%--需要重启节点

PUT _cluster/settings
{
  "persistent": {
    "indices.breaker.fielddata.limit":"20%",
    "indices.breaker.request.limit":"1%",
    "indices.breaker.total.limit":"70%"

  }
}

ES内存使用分析及熔断器设置

Elasticsearchyayg2008 发表了文章 • 0 个评论 • 824 次浏览 • 2018-07-04 15:08 • 来自相关话题

内存占用

ES的JVM heap按使用场景分为可GC部分和常驻部分。 可GC部分内存会随着GC操作而被回收; 常驻部分不会被GC,通常使用LRU策略来进行淘汰; 内存占用情况如下图:

jvm.png

common space包括了indexing buffer和其他ES运行需要的class。indexing buffer由indices.memory.index_buffer_size参数控制, 默认最大占用10%,当full up后,该部分数据被刷入磁盘对应的Segments中。这部分空间是可以被回收反复利用的。

queryCache 是node级别的filter过滤器结果缓存,大小由indices.queries.cache.size 参数控制,默认10%。使用LRU淘汰策略。

requestCache是shard级别的query result缓存,通常 only requests of size 0 such as aggregations, counts and suggestions will be cached。使用LRU淘汰策略。通过indices.requests.cache.size参数控制,默认1%。设置后整个NODE都生效。

fieldDataCache,针对text字段,没有docValues属性(相当于列存储),当对text类型字段进行sort,agg时,需要将对应的字段内容全部加载到内存,这部分数据就放在fieldDataCache。通过indices.fielddata.cache.size 参数限制大小,默认不限制。这种情况下,占用内存会逐渐增多,直到触发熔断;新数据无法加载。

segmentsMemory ,缓存段信息,包括FST,Dimensional points for numeric range filters,Deleted documents bitset ,Doc values and stored fields codec formats等数据。这部分缓存是必须的,不能进行大小设置,通常跟index息息相关,close index、force merge均会释放部分空间。 可以通过命令

GET _cat/nodes?v&h=id,ip,port,r,ramPercent,ramCurrent,heapMax,heapCurrent,fielddataMemory,queryCacheMemory,requestCacheMemory,segmentsMemory

查看当前各块的使用情况。

熔断器

Elasticsearch 有一系列的断路器,它们都能保证内存不会超出限制:

  • indices.breaker.fielddata.limit fielddata 断路器默认设置堆的 60% 作为 fielddata 大小的上限。
  • indices.breaker.request.limit request 断路器估算需要完成其他请求部分的结构大小,例如创建一个聚合桶,默认限制是堆内存的 60%。它实际上是node level的一个统计值,统计的是这个结点上,各类查询聚合操作,需要申请的Bigarray的空间大小总和。 所以如果有一个聚合需要很大的空间,同时在执行的聚合可能也会被break掉。
  • indices.breaker.total.limit 父熔断,inflight、request(agg)和fielddata不会使用超过堆内存的 70%。
  • network.breaker.inflight requests.limit 限制当前通过HTTP等进来的请求使用内存不能超过Node内存的指定值。这个内存主要是限制请求内容的长度。 默认100%。
  • script.max_compilations_per_minute
  • 限制script并发执行数,默认值为15。

参考文档 https://www.elastic.co/guide/en/elasticsearch/reference/5.3/circuit-breaker.html#fielddata-circuit-breaker https://www.elastic.co/guide/cn/elasticsearch/guide/cn/_limiting_memory_usage.html http://zhengjianglong.leanote.com/post/ES%E8%AE%BE%E7%BD%AE

[杭州滨江] [PingPong 金融] 招聘 Java 高级工程师

求职招聘jeffreyji 发表了文章 • 0 个评论 • 610 次浏览 • 2018-06-26 11:49 • 来自相关话题

PingPong 金融是家极其低调又极具实力的独角兽公司,作为中国第一家获得欧洲支付牌照的民营公司,目前在跨境收款这个风口处于领先地位,公司继去年的 B 轮之后,现在又敲定 C 轮融资了。 我司已经自建 IDC 搭建大数据中心,微服务上线如火如荼,Spark, Hadoop, HBase, MongoDB, Dubbo, RocketMQ, Elasticsearch 等大公司用的技术,我们全部都有用,我们完全拥抱开源,18 年我们已经准备自研一些开源的 framework,也愿意为开源贡献力量。 我们招聘技术大牛了,先贴下公司福利: 最新版 MacBook Pro 或 Surface 随你挑 每天下午水果 + 晚上免费晚餐 每年 15 天年假 + 每月 2k 加班费 + 3 个月以上的年终 生日礼品 + 每月团建费 + 节日聚餐 + 买书报销 继续贴 JD: ============================================================ 高级 Java 工程师职位描述(薪资 25-40K): 岗位职责 1.带领团队完成产品开发,承担技术 leader 和项目经理角色; 2.负责系统后端核心 API 的编写; 3.负责公司各平台的对接工作; 4.根据业务需求合理设计和扩展; 岗位要求 1.Java 基础扎实,精通多线程、并发、集合、网络、IO 等基础知识,熟悉 Http、TCP/IP 等协议; 2.熟练使用 SpringBoot、Mybatis 等常用的框架并了解其工作原理; 3.熟悉 RocketMQ、Dubbo、Zookeeper 等开源技术的使用以及工作原理; 4.熟悉 MySql、HBase、Elasticsearch、Redis 等的运用以及原理,优秀的 SQL 编写能力以及调优能力; 5.思维清晰,能独立分析并解决遇到的问题,丰富的系统设计能力以及服务化设计能力; 6.具备良好的表达能力,善于团队合作、具备非常良好的责任心以及 Owner 意识,具备独立解决问题能力; 有意向的请发简历到邮箱: jiwg#pingpongx.com, 最后感谢各位的阅读.

关于同义词检索方案的一点实践经验

ElasticsearchJinyang Zhou 发表了文章 • 0 个评论 • 811 次浏览 • 2018-06-15 13:36 • 来自相关话题

最近一直在搞同义词搜索的问题,踩了一些坑,总结了一些经验,尤其是刚刚接触搜索和 ES,所以如果有不对的,或者不完备的地方也希望大家能提出改进意见。。。

下面是自己留下的文档记录:


需求

同义词检索也是搜索引擎必备的功能之一,例如,我们希望用户在搜索广东话的同时,也能找出和粤语有关的信息;用户在搜索苹果手机的同时,包含iPhone的内容也能被检索并呈现。

在现实生活中,相同语义的表述词汇往往有很多,而用户在检索的时候很难在一条 query 中将它们全部体现,所以识别和提供同义词检索显然可以获得更高的召回率。

需求剖析

在思考解决方案之前,我们不妨再来看看刚才提到的两个例子:

  1. 苹果手机iPhone
  2. 广东话粤语

我们先来看第一个,苹果手机iPhone

显然,这两个词是等价的,因为苹果公司发布的所有手机产品都叫 iPhone,而 iPhone 这个名字也没有被其他公司使用过。

于是,当用户搜索“苹果手机购买”的时候,我们也就有理由将它拆分成“苹果手机购买”和“iPhone购买”,分别进行检索,再将结果合并返回。


语言学中对这样的词组,称为是同义词中的相对同义词,或是等义词等义同义词。它们表达意思完全一致,在绝大多数语境中都可以相互替换,同时对上下文也不会产生影响。

这样的词组还有很多,例如:猫熊熊猫柚子文旦等等,这些等义词大抵来说有这样几种来源:

  1. 音节减缩形成:机枪机关枪坦克坦克车电扇电风扇
  2. 音译和音译形成:出租车的士维生素维他命
  3. 地域叫法不同,或新旧叫法:单车自行车脚踏车西红柿番茄马铃薯洋芋土豆黄瓜胡瓜
  4. 昵称代称:周杰伦周董
  5. 描述角度不同,学名方言差异:电脑计算机曲别针回形针

这些词组多以名词呈现,数量比较少,词组规模也较小,同时变化也很小。


接下来我们再来看第二组词:广东话粤语

广东话粤语这两个词代表的意思是相同的吗?它们也是可以相互替换的吗?

答案显然是否定的。

广东话从语义本身来说是一个比较粗糙的概念,它不仅指广东省内的粤语,还涵盖了潮汕话,客家话,雷州话等其他方言。而粤语却是一个非常严肃的概念,对语音语调都有非常详细的规定,不仅通用于广东省大部分地区,还包括广西、香港、澳门等地,甚至东南亚和北美。它们联系在于,大部分广东地区的人说的是粤语。

如此说来,给广东话粤语这样非常相似却又并不完全一致的词直接划上等号是有失偏颇的。当然,其实仔细考虑也不难发现,和广东话有相似之处却又不完全相同的词还有很多,例如:客家话广州话广府话等等。


语言学中把这样的词汇,称作是相对同义词,或是近义词。它们在意义上有一些相似之处,只能在特定的语境中进行替换。

它们的差别可能包括:

  1. 语义上:毁坏损坏(前者更严重),介绍说明(前者可以对人施加作用)
  2. 色彩上:团结勾结

对于这类相似却又不完全相同的近义词,在搜索的时候提供关联搜索是一个不错的方案。例如用户搜索“毁坏公物如何处罚”的时候,查询结果可以由90%的“损坏公物如何处罚”和10%的“毁坏公物如何处罚”查询结果合并后返回,从而获得更多的召回。

这些近义词以动词为主,不仅数量多,词组的规模也大,例如靠近的近义词可以是:靠拢逼近接近迫近等等。

解决思路

可替换的等义词问题中,我们可以直接使用 Elasticsearch 原生的 synonyms 功能来完成。虽说原生 synonyms 功能不支持热更新,而且需要将词典事先放进制定目录,不过好在这类等义词数量并不多,变化也并不大,尚且属于一劳永逸的任务。

对于不可直接替换的近义词问题,如果直接套用原生的 Synonyms 虽然可能会带来更多召回,但是查准率却骤降。

对于这类问题,我们期待的场景是,一旦发现用户 query 中的某个词有近义词,我们就将它拆分替换,成为多个 query 进行联合搜索。就像前面的例子:用户搜索“损坏公物如何处罚”的时候,查询结果可以由90%的“损坏公物如何处罚”和10%的“毁坏公物如何处罚”查询结果合并后返回。如此说来,使用 Elasticsearch 提供的 boosting_query 就成为了一个自然而然的想法。

不过稍加思考也不难发现,boosting_query 中 weight 的获得并不容易,也就是前面例子中的90%10%这组数字应该怎么设定,这也是近义词联合搜索中的重点。

先回到我们刚才的例子:当用户搜索“损坏公物如何处罚”的时候,我们本能地觉得用90%损坏10%毁坏合并在一起是“合理的”,这样的本能其实是来自于:我们主观地认为在检索“搞坏公物”这个事实的时候,90%的用户会使用毁坏来描述,10%的用户会使用损坏来描述。

简而言之,这组数字可以理解为用户群体描述同一个问题时,对词组选择的组成比例。再换个说法,也就是在当前这条 query 中,原词和近义词之间的可替换程度

再举一例,在“广东话入门”这条 query 中,从“表达学习语言”的语义上来看,广东话粤语差别并不大,这条 query 自然可以拆分成“广东话入门”和“粤语入门”,进行联合搜索,而且它们的 weight 甚至可以设置为 1:1 来获得更多合理的召回。

反过来,在“粤语歌曲推荐”这条 query 中,广东话的 weight 就需要慎重考虑,一方面是因为本身就没有“广东话歌曲”这种说法,另一方面也因为在广泛的语料中,歌曲广东话这两个词极少同时被提及。所以几遍是“粤语歌曲推荐”的拆分成分中有“广东话歌曲推荐”,weight 也需要被设置地非常低(倘若真的没有“粤语歌曲”相关的内容,推荐“广东话”的内容作为替补)。

说到这里,其实已经很明白了,语言模型是可以在这里被使用的,而语言模型的困惑度也与前面提到的 weight 一脉相承。

所以大致计算流程可以是:获得用户的query之后进行分词,在词组中寻找所有可能的同义词替换,将所有替换后的 query 分别放进语言模型中获得困惑度(或其他 metrics),依据它们来作为 boosting query 中的 weight。

graph TD;
广东话入门-->'广东话','入门';
'广东话','入门'-->_'广东话','入门'_;
'广东话','入门'-->_'粤语','入门'_;
_'广东话','入门'_-->语言模型;
_'粤语','入门'_-->语言模型;
语言模型-->PPL:0.65;
语言模型-->PPL:0.35;

对于这里语言模型的选择,可以使用传统的ngram,也可以使用双向的LSTM这样一些成熟的方案从语料中训练,也可以使用一些现成的方案:http://ai.baidu.com/tech/nlp/dnnlm_cn