Lucene索引的一个特点就filed,索引以field组合。这一特点为索引和搜索提供了很大的灵活性。elasticsearch则在Lucene的基础上更近一步,它可以是 no scheme。实现这一功能的秘密就Mapping。Mapping是对索引各个字段的一种预设,包括索引与分词方式,是否存储等,数据根据字段名在Mapping中找到对应的配置,建立索引。这里将对Mapping的实现结构简单分析,Mapping的放置、更新、应用会在后面的索引fenx中进行说明。

首先看一下Mapping的实现关系结构,如下图所示:

这只是Mapping中的一部分内容。Mapping扩展了lucene的filed,定义了更多的field类型既有Lucene所拥有的string,number等字段又有date,IP,byte及geo的相关字段,这也是es的强大之处。如上图所示,可以分为两类,mapper与documentmapper,前者是所有mapper的父接口。而DocumentMapper则是Mapper的集合,它代表了一个索引的mapper定义。

Mapper的有三类,第一类就是核心field结构FileMapper—>AbstractFieldMapper—>StringField这种核心数据类型,它代表了一类数据类型,如字符串类型,int类型这种;第二类是Mapper—>ObjectMapper—>RootObjectMapper,object类型的Mapper,这也是elasticsearch对lucene的一大改进,不想lucene之支持基本数据类型;最后一类是Mapper—>RootMapper—>IndexFieldMapper这种类型,只存在于根Mapper中的一种Mapper,如IdFieldMapper及图上的IndexFieldMapper,它们类似于index的元数据,只可能存在于某个index内部。

Mapper中一个比较重要的方法就是parse(ParseContext context),Mapper的子类对这个方法都有各自的实现。它的主要功能是通过解析ParseContext获取到对应的field,这个方法主要用于建立索引时。索引数据被继续成parsecontext,每个field解析parseContext构建对应的lucene Field。它在AbstractFieldMapper中的实现如下所示:

    public void parse(ParseContext context) throws IOException {
final List<Field> fields = new ArrayList<>(2);
try {
parseCreateField(context, fields);//实际Filed解析方法
for (Field field : fields) {
if (!customBoost()) {//设置boost
field.setBoost(boost);
}
if (context.listener().beforeFieldAdded(this, field, context)) {
context.doc().add(field);//将解析完成的Field加入到context中
}
}
} catch (Exception e) {
throw new MapperParsingException("failed to parse [" + names.fullName() + "]", e);
}
multiFields.parse(this, context);//进行mutiFields解析,MultiFields作用是对同一个field做不同的定义,如可以进行不同分词方式的索引这样便于通过各种方式查询
if (copyTo != null) {
copyTo.parse(context);
}
}

这里的parseCreateField是一个抽象方法,每种数据类型都有自己的实现,如string的实现方式如下所示:

protected void parseCreateField(ParseContext context, List<Field> fields) throws IOException {
ValueAndBoost valueAndBoost = parseCreateFieldForString(context, nullValue, boost);//解析成值和boost
if (valueAndBoost.value() == null) {
return;
}
if (ignoreAbove > 0 && valueAndBoost.value().length() > ignoreAbove) {
return;
}
if (context.includeInAll(includeInAll, this)) {
context.allEntries().addText(names.fullName(), valueAndBoost.value(), valueAndBoost.boost());
} if (fieldType.indexed() || fieldType.stored()) {//构建LuceneField
Field field = new Field(names.indexName(), valueAndBoost.value(), fieldType);
field.setBoost(valueAndBoost.boost());
fields.add(field);
}
if (hasDocValues()) {
fields.add(new SortedSetDocValuesField(names.indexName(), new BytesRef(valueAndBoost.value())));
}
if (fields.isEmpty()) {
context.ignoredValue(names.indexName(), valueAndBoost.value());
}
}

//解析出字段的值和boost
public static ValueAndBoost parseCreateFieldForString(ParseContext context, String nullValue, float defaultBoost) throws IOException {
if (context.externalValueSet()) {
return new ValueAndBoost((String) context.externalValue(), defaultBoost);
}
XContentParser parser = context.parser();
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return new ValueAndBoost(nullValue, defaultBoost);
}
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
XContentParser.Token token;
String currentFieldName = null;
String value = nullValue;
float boost = defaultBoost;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else {
if ("value".equals(currentFieldName) || "_value".equals(currentFieldName)) {
value = parser.textOrNull();
} else if ("boost".equals(currentFieldName) || "_boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else {
throw new ElasticsearchIllegalArgumentException("unknown property [" + currentFieldName + "]");
}
}
}
return new ValueAndBoost(value, boost);
}
return new ValueAndBoost(parser.textOrNull(), defaultBoost);
}

以上就是Mapper如何将一个值解析成对应的Field的过程,这里只是简单介绍,后面会有详细分析。

DocumentMapper是一个索引所有Mapper的集合,它表述了一个索引所有field的定义,可以说是lucene的Document的定义,同时它还包含以下index的默认值,如index和search时默认分词器。它的部分Field如下所示:

    private final DocumentMapperParser docMapperParser;

    private volatile ImmutableMap<String, Object> meta;

    private volatile CompressedString mappingSource;

    private final RootObjectMapper rootObjectMapper;

    private final ImmutableMap<Class<? extends RootMapper>, RootMapper> rootMappers;
private final RootMapper[] rootMappersOrdered;
private final RootMapper[] rootMappersNotIncludedInObject; private final NamedAnalyzer indexAnalyzer; private final NamedAnalyzer searchAnalyzer;
private final NamedAnalyzer searchQuoteAnalyzer;

DocumentMapper的功能也体现在parse方法上,它的作用是解析整条数据。之前在Mapper中看到了Field是如何解析出来的,那其实是在DocumentMapper解析之后。index请求发过来的整条数据在这里被解析出Field,查找Mapping中对应的Field设置,交给它去解析。如果没有且运行动态添加,es则会根据值自动创建一个Field同时更新Mapping。方法代码如下所示:

    public ParsedDocument parse(SourceToParse source, @Nullable ParseListener listener) throws MapperParsingException {
ParseContext.InternalParseContext context = cache.get(); if (source.type() != null && !source.type().equals(this.type)) {
throw new MapperParsingException("Type mismatch, provide type [" + source.type() + "] but mapper is of type [" + this.type + "]");
}
source.type(this.type); XContentParser parser = source.parser();
try {
if (parser == null) {
parser = XContentHelper.createParser(source.source());
}
if (sourceTransforms != null) {
parser = transform(parser);
}
context.reset(parser, new ParseContext.Document(), source, listener); // will result in START_OBJECT
int countDownTokens = 0;
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new MapperParsingException("Malformed content, must start with an object");
}
boolean emptyDoc = false;
token = parser.nextToken();
if (token == XContentParser.Token.END_OBJECT) {
// empty doc, we can handle it...
emptyDoc = true;
} else if (token != XContentParser.Token.FIELD_NAME) {
throw new MapperParsingException("Malformed content, after first object, either the type field or the actual properties should exist");
}
// first field is the same as the type, this might be because the
// type is provided, and the object exists within it or because
// there is a valid field that by chance is named as the type.
// Because of this, by default wrapping a document in a type is
// disabled, but can be enabled by setting
// index.mapping.allow_type_wrapper to true
if (type.equals(parser.currentName()) && indexSettings.getAsBoolean(ALLOW_TYPE_WRAPPER, false)) {
parser.nextToken();
countDownTokens++;
} for (RootMapper rootMapper : rootMappersOrdered) {
rootMapper.preParse(context);
} if (!emptyDoc) {
rootObjectMapper.parse(context);
} for (int i = 0; i < countDownTokens; i++) {
parser.nextToken();
} for (RootMapper rootMapper : rootMappersOrdered) {
rootMapper.postParse(context);
}
} catch (Throwable e) {
// if its already a mapper parsing exception, no need to wrap it...
if (e instanceof MapperParsingException) {
throw (MapperParsingException) e;
} // Throw a more meaningful message if the document is empty.
if (source.source() != null && source.source().length() == 0) {
throw new MapperParsingException("failed to parse, document is empty");
} throw new MapperParsingException("failed to parse", e);
} finally {
// only close the parser when its not provided externally
if (source.parser() == null && parser != null) {
parser.close();
}
}
// reverse the order of docs for nested docs support, parent should be last
if (context.docs().size() > 1) {
Collections.reverse(context.docs());
}
// apply doc boost
if (context.docBoost() != 1.0f) {
Set<String> encounteredFields = Sets.newHashSet();
for (ParseContext.Document doc : context.docs()) {
encounteredFields.clear();
for (IndexableField field : doc) {
if (field.fieldType().indexed() && !field.fieldType().omitNorms()) {
if (!encounteredFields.contains(field.name())) {
((Field) field).setBoost(context.docBoost() * field.boost());
encounteredFields.add(field.name());
}
}
}
}
} ParsedDocument doc = new ParsedDocument(context.uid(), context.version(), context.id(), context.type(), source.routing(), source.timestamp(), source.ttl(), context.docs(), context.analyzer(),
context.source(), context.mappingsModified()).parent(source.parent());
// reset the context to free up memory
context.reset(null, null, null, null);
return doc;
}

将整条数据解析成ParsedDocument,解析后的数据才能进行后面的Field解析建立索引。

总结:以上就是Mapping的结构和相关功能概括,Mapper赋予了elasticsearch索引的更强大功能,使得索引和搜索可以支持更多数据类型,灵活性更高。

elasticsearch index 之 Mapping的更多相关文章

  1. ElasticSearch Index API && Mapping

    ElasticSearch  NEST Client 操作Index var indexName="twitter"; var deleteIndexResponse = clie ...

  2. elasticsearch index 之 put mapping

    elasticsearch index 之 put mapping   mapping机制使得elasticsearch索引数据变的更加灵活,近乎于no schema.mapping可以在建立索引时设 ...

  3. elasticsearch中的mapping映射配置与查询典型案例

    elasticsearch中的mapping映射配置与查询典型案例 elasticsearch中的mapping映射配置示例比如要搭建个中文新闻信息的搜索引擎,新闻有"标题".&q ...

  4. ES 11 - 配置Elasticsearch的映射 (mapping)

    目录 1 映射的相关概念 1.1 什么是映射 1.2 映射的组成 1.3 元字段 1.4 字段的类型 2 如何配置mapping 2.1 创建mapping 2.2 更新mapping 2.3 查看m ...

  5. 第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理

    第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理 1.映射(mapping)介绍 映射:创建索引的时候,可以预先定义字 ...

  6. Elasticsearch:Dynamic mapping

    Elasticsearch最重要的功能之一是它试图摆脱你的方式,让你尽快开始探索你的数据. 要索引文档,您不必首先创建索引,定义映射类型和定义字段 - 您只需索引文档,那么index,type和fie ...

  7. ElasticSearch Index操作源码分析

    ElasticSearch Index操作源码分析 本文记录ElasticSearch创建索引执行源码流程.从执行流程角度看一下创建索引会涉及到哪些服务(比如AllocationService.Mas ...

  8. elasticsearch篇之mapping

    2018年05月17日 18:01:37 lyzkks 阅读数:444更多 个人分类: Elastic stack   版权声明:文章内容来自于网络和博主自身学习体会,转载请注明出处,欢迎留言大家一起 ...

  9. 【ElasticSearch】:Mapping相关

    Mapping 类似数据库中的表结构定义,主要作用如下: 定义Index下的字段名(Field Name). 定义字段类型,例如数值型.字符串型.布尔型等. 定义倒排索引相关配置,比如是否索引.记录p ...

随机推荐

  1. Strings are immutable

    It is tempting to use the [] operator on the left side of an assignment, with the intention of chang ...

  2. CUDA笔记(七)

    今天集中时间找程序的问题.于是发现: 首先,程序里的kernel想要调试,必须用nsight. 于是一堆找.http://www.nvidia.com/object/nsight.html http: ...

  3. PostgreSQL Replication之第二章 理解PostgreSQL的事务日志(3)

    2.3 理解一致性和数据丢失 挖掘PostgreSQL事务日志而不考虑一致性是不可能的.在本章的第一部分,我们已经大体上解释了事务日志的基本思想.您已经知道,无需事先的日志改变的能力,使数据处于一种好 ...

  4. PostgreSQL备份与还原

    物理备份:整个数据库的数据目录及文件做备份:备份整个数据库的文件系统. 物理恢复:恢复整个数据库的文静系统. 物理备份方法: 开启归档 select pg_start_backup('backup—— ...

  5. <Sicily>Fibonacci

    一.题目描述 In the Fibonacci integer sequence, F0 = 0, F1 = 1, and Fn = Fn-1 + Fn-2 for n ≥ 2. For exampl ...

  6. 洛谷3933 Chtholly Nota Seniorious 二分答案+贪心

    题目链接 题意 给你一个N*M的矩阵 (N,M <=2000)  把他分成两部分 使两部分的极差较大的一个最小  求这个最小值.然后分矩阵的要求是:每个部分内部的方块之间,可以通过上下左右相互到 ...

  7. 【Educational Codeforces Round 37 B】 Tea Queue

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 用一个队列来模拟排队就好. 队列放三元组(x,y,z) x表示人的下标,y和z分别表示进入和退出时间. 然后枚举时间从1到5000 ...

  8. Perl Learning 5 Hash

    [本文原创,未经同意请勿转载] 哈希是一种数据结构,它和数组的相似之处在于能够容纳随意多的值并能按需取用,而它和数组的不同在于索引方式,数组是以数字来索引.哈希则以名字来索引.也就是说.哈希的索引值, ...

  9. lightoj--1008--Fibsieve`s Fantabulous Birthday(水题)

    Fibsieve`s Fantabulous Birthday Time Limit: 500MS   Memory Limit: 32768KB   64bit IO Format: %lld &a ...

  10. javascript 左侧菜单

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...