热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ElasticSearch启动时加载Analyzer源码分析

ElasticSearch启动时加载Analyzer源码分析本文介绍ElasticSearch启动时如何创建、加载Analyzer,主要的参考资料是Lucene中关于Analyzer官方文

ElasticSearch 启动时加载 Analyzer 源码分析

本文介绍 ElasticSearch启动时如何创建、加载Analyzer,主要的参考资料是Lucene中关于Analyzer官方文档介绍、ElasticSearch6.3.2源码中相关类:AnalysisModule、AnalysisPlugin、AnalyzerProvider、各种Tokenizer类和它们对应的TokenizerFactory。另外还参考了一个具体的基于ElasticSearch采用HanLP进行中文分词的插件:elasticsearch-analysis-hanlp

这篇文章的主要目的是搞懂:AnalysisModule、AnalysisPlugin、AnalyzerProvider、某个具体的Tokenizer,比如HanLPStandardAnalyzer、和TokenizerFactory 之间的关系。这里面肯定是用过了某个(某些)设置模式的。搞懂了这个自己也能照葫芦画瓢,开发自定义的Plugin了。

分词插件

1 Tokenizer

对比HanLP中文分词器和ElasticSearch中内置的标准分词器(StandardTokenizer),发现elasticsearch-analysis-hanlp的实现方法和ElasticSearch中实现的标准分词插件二者几乎是一个套路。

HanLP提供了各种各样的中文分词方式,比如:标准分词、索引分词、NLP分词……因此,HanLPTokenizerFactory implements TokenizerFactory,实现了create()方法,负责创建各类分词器。

这种写法和ElasticSearch源码里面的StandardTokenizerFactory写法如出一辙。

2 Analyzer

把Analyzer想象成一部生产Token的机器,输入Text,输出Token。

An Analyzer builds TokenStreams, which analyze text. It thus represents a policy for extracting index terms from text.

这部机器可以以不同的方式生产Token。比如:对于英文,一般以文本中的空格作为分隔符,输入Text,输出Token。

对于中文,中文文本没有空格了,因此需要借助一些中文分词算法,输入Text,输出Token。

对于HTML这样的文本,那就需要根据HTML标签作为分隔符,输入Text,输出Token。

TokenStreamComponents内部类封装了生产Token的方式,看源码注释This class encapsulates the outer components of a token stream.It provides access to the source Tokenizer and .... 。主要是封装了Tokenizer

  /**
   * This class encapsulates the outer components of a token stream. It provides
   * access to the source ({@link Tokenizer}) and the outer end (sink), an
   * instance of {@link TokenFilter} which also serves as the
   * {@link TokenStream} returned by
   * {@link Analyzer#tokenStream(String, Reader)}.
   */
  public static class TokenStreamComponents {
    /**
     * Original source of the tokens.
     */
    protected final Tokenizer source;
    /**
     * Sink tokenstream, such as the outer tokenfilter decorating
     * the chain. This can be the source if there are no filters.
     */
    protected final TokenStream sink;

若要自定义Analyzer,只需继承Analyzer类,重写createComponents()方法,提供一个Tokenizer就可以了。比如:HanLPStandardAnalyzer重写的方法如下:

    @Override
    protected Analyzer.TokenStreamComponents createComponents(String fieldName) {
//  AccessController.doPrivileged((PrivilegedAction) () -> HanLP.Config.Normalization = true);
    Tokenizer tokenizer = new HanLPTokenizer(HanLP.newSegment(), configuration);
        return new Analyzer.TokenStreamComponents(tokenizer);
    }

另外,也可参考ElasticSearch中提供的StandardAnalyzer.java,它实现了ElasticSearch查询分析过程中的标准分词,它继承了StopwordAnalyzerBase.java,这样可以在生产Token的时候,过滤掉 stop words。

3 AnalyzerProvider

AnalyzerProvider封装了Analyzer,它的构造方法实例化一个Analyzer,并为Analyzer 提供了一些名称、版本相关的信息:

public class HanLPAnalyzerProvider extends AbstractIndexAnalyzerProvider {

    private final Analyzer analyzer;

AbstractIndexAnalyzerProvider 里面有 name 和 Version信息(Constructs a new analyzer component, with the index name and its settings and the analyzer name.)

public abstract class AbstractIndexAnalyzerProvider extends AbstractIndexComponent implements AnalyzerProvider {

    private final String name;

    protected final Version version;

4 AnalysisPlugin

AnalysisHanLPPlugin负责注册各种各样的分词器。在定义索引的时候需要指定某个字段的Analyzer名称,比如下面 name 字段中的文本在都使用名称为hanlp_standard分词器分词后,写入ElasticSearch索引。

        "name": {
          "type": "text",
          "analyzer": "hanlp_standard",
          "fields": {
            "raw": {
              "type": "keyword"
            }
          }
        },

AnalysisPlugin主要是下面三个方法,用来获取:CharFilter、TokenFilter、Tokenizer。关于这三个的区别可参考下节:索引分析过程。

    /**
     * Override to add additional {@link CharFilter}s. See {@link #requriesAnalysisSettings(AnalysisProvider)}
     * how to on get the configuration from the index.
     */
    default Map> getCharFilters() {
        return emptyMap();
    }

    /**
     * Override to add additional {@link TokenFilter}s. See {@link #requriesAnalysisSettings(AnalysisProvider)}
     * how to on get the configuration from the index.
     */
    default Map> getTokenFilters() {
        return emptyMap();
    }

    /**
     * Override to add additional {@link Tokenizer}s. See {@link #requriesAnalysisSettings(AnalysisProvider)}
     * how to on get the configuration from the index.
     */
    default Map> getTokenizers() {
        return emptyMap();
    }

ElasticSearch如何加载Analyzer插件

这里主要参考ElasticSearch启动过程中相关源代码。在创建PluginService过程中初始化各种Analyzer, Node.java

//加载 modules 和 plugins 目录下的内容
this.pluginsService = new PluginsService(tmpSettings, environment.configFile(), environment.modulesFile(), environment.pluginsFile(), classpathPlugins);

貌似是通过创建的ClassLoader,不管是module还是plugin都视为bundle,以SPI方式接入底层Lucene,PluginService.java

 // load modules
        if (modulesDirectory != null) {
                Set modules = getModuleBundles(modulesDirectory);
                for (Bundle bundle : modules) {
                    modulesList.add(bundle.plugin);
                }
                seenBundles.addAll(modules);
        }

        // now, find all the ones that are in plugins/
        if (pluginsDirectory != null) {
              List plugins = findBundles(pluginsDirectory, "plugin");
                    for (final BundleCollection plugin : plugins) {
                        final Collection bundles = plugin.bundles();
                        for (final Bundle bundle : bundles) {
                            pluginsList.add(bundle.plugin);
                        }
                        seenBundles.addAll(bundles);
                        pluginsNames.add(plugin.name());
        }

加载 module/plugin jar文件:

            try (DirectoryStream jarStream = Files.newDirectoryStream(dir, "*.jar")) {
                for (Path jar : jarStream) {
                    // normalize with toRealPath to get symlinks out of our hair
                    URL url = jar.toRealPath().toUri().toURL();
                    if (urls.add(url) == false) {
                        throw new IllegalStateException("duplicate codebase: " + url);
                    }
                }
            }

//...
        // create a child to load the plugin in this bundle
        ClassLoader parentLoader = PluginLoaderIndirection.createLoader(getClass().getClassLoader(), extendedLoaders);
        ClassLoader loader = URLClassLoader.newInstance(bundle.urls.toArray(new URL[0]), parentLoader);

当PluginService载入了所有的plugin后,过滤出与Analysis相关的Plugin,创建AnalysisModule

//从plugin service 中过滤出 与Analysis相关的plugin
AnalysisModule analysisModule = new AnalysisModule(this.environment, pluginsService.filterPlugins(AnalysisPlugin.class));

注册各种分词器、filters、analyzer的名称:(这样在创建索引的时候,为某个索引字段指定分词器,就是用的这里的注册了的名称)

NamedRegistry> charFilters = setupCharFilters(plugins);
        NamedRegistry> tokenFilters = setupTokenFilters(plugins, hunspellService);
        NamedRegistry> tokenizers = setupTokenizers(plugins);
        NamedRegistry>> analyzers = setupAnalyzers(plugins);

//....
    private NamedRegistry>> setupAnalyzers(List plugins) {
        NamedRegistry>> analyzers = new NamedRegistry<>("analyzer");
        analyzers.register("default", StandardAnalyzerProvider::new);
        analyzers.register("standard", StandardAnalyzerProvider::new);
        
        //....
            public StandardAnalyzerProvider(IndexSettings indexSettings, Environment env, String name, Settings settings) {
        //....
        standardAnalyzer = new StandardAnalyzer(stopWords);
        standardAnalyzer.setVersion(version);
    }

引用一段《An Introduction to Information Retrieval》中关于 token、type、term、dictionary概念的解释:(这里的type和ElasticSearch索引中的type是不一样的,ElasticSearch索引中的type以后版本将不支持了)

A token is an instance of a sequence of characters in some particular document that are grouped together as a useful semantic unit for processing. A type is the class of all tokens containing the same character sequence. A term is a (perhaps normalized) type that is included in the IR system's dictionary.

For example, if the document to be indexed is to sleep perchance to dream, then there are 5 tokens, but only 4 types (since there are 2 instances of to). However, if to is omitted from the index (as a stop word) then there will be only 3 terms: sleep, perchance, and dream.

索引分析过程

个人觉得Tokenization和Analysis过程有交叉的地方。Lucene中定义的Analysis是指:将字符串转化成Tokens的过程,Analysis主要有四个方面:

The analysis package provides the mechanism to convert Strings and Readers into tokens that can be indexed by Lucene. There are four main classes in the package from which all analysis processes are derived. These are:

  1. Analyzer
  2. CharFilter
  3. Tokenizer
  4. TokenFilter

这四个的区别如下:(以中文处理举例)

比如一句中文:“这是一篇关于ElasticSearch Analyzer的文章”,CharFilter过滤其中的某个字。Tokenizer是将这句话进行中文分词:这是、一篇、关于、ElasticSearch、Analyzer、的、文章;分词得到的结果就是一个个的Token。TokenFilter则是过滤某些Token。

The Analyzer is a factory for analysis chains. Analyzers don't process text, Analyzers construct CharFilters, Tokenizers, and/or TokenFilters that process text. An Analyzer has two tasks: to produce TokenStreams that accept a reader and produces tokens, and to wrap or otherwise pre-process Reader objects.

具体可参考:Lucene7.6.0。在Lucene中,Analyzer不处理文本,它只是构建CharFilters、Tokenizer、TokenFilters, 然后让它们来处理文本。

参考资料

lucene7.6.0 Analysis官方文档

ElasticSearch6.3.2源码

HanLP进行中文分词的插件:elasticsearch-analysis-hanlp

原文:https://www.cnblogs.com/hapjin/p/10151887.html


推荐阅读
  • Spring框架中枚举参数的正确使用方法与技巧
    本文详细阐述了在Spring Boot框架中正确使用枚举参数的方法与技巧,旨在帮助开发者更高效地掌握和应用枚举类型的数据传递,适合对Spring Boot感兴趣的读者深入学习。 ... [详细]
  • 本文探讨了如何利用Java代码获取当前本地操作系统中正在运行的进程列表及其详细信息。通过引入必要的包和类,开发者可以轻松地实现这一功能,为系统监控和管理提供有力支持。示例代码展示了具体实现方法,适用于需要了解系统进程状态的开发人员。 ... [详细]
  • 在Android应用开发中,实现与MySQL数据库的连接是一项重要的技术任务。本文详细介绍了Android连接MySQL数据库的操作流程和技术要点。首先,Android平台提供了SQLiteOpenHelper类作为数据库辅助工具,用于创建或打开数据库。开发者可以通过继承并扩展该类,实现对数据库的初始化和版本管理。此外,文章还探讨了使用第三方库如Retrofit或Volley进行网络请求,以及如何通过JSON格式交换数据,确保与MySQL服务器的高效通信。 ... [详细]
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 属性类 `Properties` 是 `Hashtable` 类的子类,用于存储键值对形式的数据。该类在 Java 中广泛应用于配置文件的读取与写入,支持字符串类型的键和值。通过 `Properties` 类,开发者可以方便地进行配置信息的管理,确保应用程序的灵活性和可维护性。此外,`Properties` 类还提供了加载和保存属性文件的方法,使其在实际开发中具有较高的实用价值。 ... [详细]
  • 优化Vite 1.0至2.0升级过程中遇到的某些代码块过大问题解决方案
    本文详细探讨了在将项目从 Vite 1.0 升级到 2.0 的过程中,如何解决某些代码块过大的问题。通过具体的编码示例,文章提供了全面的解决方案,帮助开发者有效优化打包性能。 ... [详细]
  • 在C#编程中,设计流畅的用户界面是一项重要的任务。本文分享了实现Fluent界面设计的技巧与方法,特别是通过编写领域特定语言(DSL)来简化字符串操作。我们探讨了如何在不使用`+`符号的情况下,通过方法链式调用来组合字符串,从而提高代码的可读性和维护性。文章还介绍了如何利用静态方法和扩展方法来实现这一目标,并提供了一些实用的示例代码。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • 本文介绍了如何利用ObjectMapper实现JSON与JavaBean之间的高效转换。ObjectMapper是Jackson库的核心组件,能够便捷地将Java对象序列化为JSON格式,并支持从JSON、XML以及文件等多种数据源反序列化为Java对象。此外,还探讨了在实际应用中如何优化转换性能,以提升系统整体效率。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 本文详细探讨了MySQL数据库实例化参数的优化方法及其在实例查询中的应用。通过具体的源代码示例,介绍了如何高效地配置和查询MySQL实例,为开发者提供了有价值的参考和实践指导。 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
author-avatar
等待1314578
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有