Hibernate Search 中文操作指南

17. Lucene backend

17.1. Basic configuration

Lucene 后端的所有配置属性都是可选的,但并非所有人都适合默认值。特别是,您可能想要 set the location of your indexes in the filesystem

其他配置属性在本文档的相关部分中提到。您可以在 the Lucene backend configuration properties appendix 中找到可用属性的完整参考。

17.2. Index storage (Directory)

在 Lucene 中负责索引存储的组件是 org.apache.lucene.store.Directory。目录的实现确定了索引的存储位置:在文件系统,在 JVM 的堆栈中,…​

默认情况下,Lucene 后端在文件系统中存储索引,位于 JVM 的工作目录中。

可以按如下方式配置目录类型:

# To configure the defaults for all indexes:
hibernate.search.backend.directory.type = local-filesystem
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.directory.type = local-filesystem

有以下一些目录类型可用:

  1. local-filesystem:将索引存储在本地文件系统上。有关详细信息和配置选项,请参阅 Local filesystem storage

  2. local-heap :将索引存储在本地 JVM 堆中。关闭 JVM 时,本地堆目录和所有包含的索引都将丢失。有关详细信息和配置选项,请参阅 Local heap storage

17.2.1. Local filesystem storage

local-filesystem 目录类型会将每个索引存储在配置的文件系统目录的一个子目录下。

本地文件系统目录真正设计为仅供一台服务器和一个应用程序使用。

特别是,它们不应该在多个 Hibernate Search 实例之间共享。即使网络共享允许共享索引的原始内容,从多个 Hibernate Search 使用相同的索引文件也需要更多内容:非独占锁,从一个节点向另一个节点路由写入请求…​ 这些附加功能在 local-filesystem 目录中根本不可用。

如果您需要在多个 Hibernate Search 实例之间共享索引,则 Elasticsearch 后端将是更好的选择。有关详细信息,请参阅 Architecture

Index location

每个索引会在根目录下分配一个子目录。

默认情况下,根目录是 JVM 的工作目录。可以按如下方式配置:

# To configure the defaults for all indexes:
hibernate.search.backend.directory.root = /path/to/my/root
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.directory.root = /path/to/my/root

例如,使用上述配置,名为 Orderentity type 将被索引到目录 /path/to/my/root/Order/ 中。如果明确地为该实体分配了索引名称 orders(请参见 Entity/index mapping 中的 @Indexed(index = …​)),则会将其索引到目录 /path/to/my/root/orders/ 中。

Filesystem access strategy

根据操作系统和架构自动决定访问文件系统的默认策略。在大多数情况下,它都应当运行良好。

在需要其他文件系统访问策略的情况下,Hibernate Search 会公开一个配置属性:

# To configure the defaults for all indexes:
hibernate.search.backend.directory.filesystem_access.strategy = auto
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.directory.filesystem_access.strategy = auto

允许的值有:

  1. auto:让 Lucene 根据操作系统和体系结构选择最合适的实现。这是该属性的默认值。

  2. mmap:将 mmap 用于读取,将 FSDirectory.FSIndexOutput 用于写入。请参见 org.apache.lucene.store.MMapDirectory

  3. nio:使用 java.nio.channels.FileChannel 的位置读取进行并发读取,并使用 FSDirectory.FSIndexOutput 进行写入。请参见 org.apache.lucene.store.NIOFSDirectory

在更改此设置之前,请务必参考这些 Directory 实现的 Javadoc。提供更好性能的实现也会带来它们自身的问题。

Other configuration options

local-filesystem 目录还允许配置 locking strategy

17.2.2. Local heap storage

local-heap 目录类型会将索引存储在本地 JVM 堆中。

因此,关闭 JVM 时,包含在 local-heap 目录中的索引将丢失。

仅在测试配置中使用小型索引和低并发的情况下,才提供此目录类型,在这种情况下,它可以稍微提高性能。在需要使用较大索引和/或高并发的情况下, filesystem-based directory 将达到更好的性能。

locking strategy 之外,local-heap 目录不会提供任何特定选项。

17.2.3. Locking strategy

为了写入索引,Lucene 需要获取一个锁以确保没有其他应用程序实例同时写入同一索引。每个目录类型都带有默认锁定策略,在大多数情况下都足够好。

对于需要其他锁定策略的那些(非常)罕见的情况,Hibernate Search 公开了一个配置属性:

# To configure the defaults for all indexes:
hibernate.search.backend.directory.locking.strategy = native-filesystem
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.directory.locking.strategy = native-filesystem

有以下策略可用:

  1. simple-filesystem :通过创建标记文件并检查这些文件,从而锁定索引,然后执行写入操作。此实现非常简单,基于 Java 的文件 API。如果由于某种原因应用程序突然终止,标记文件将保留在文件系统上,并且需要手动将其移除。

此策略仅适用于基于文件系统的目录。

参见 org.apache.lucene.store.SimpleFSLockFactory

  1. native-filesystem :类似于 simple-filesystem ,通过创建标记文件从而锁定索引,但使用本机操作系统的文件锁而不是 Java 的文件 API,以便在应用程序突然终止时清除锁。

这是 local-filesystem 目录类型的默认策略。

该实现已知存在 NFS 问题:应该避免在网络共享上使用。

此策略仅适用于基于文件系统的目录。

参见 org.apache.lucene.store.NativeFSLockFactory

  1. single-instance :使用 JVM 堆中持有的 Java 对象从而锁定。由于该锁只能通过相同的 JVM 访问,因此只有当已知只有一个应用程序会尝试访问这些索引时,此策略才正常工作。

这是 local-heap 目录类型的默认策略。

请参阅 org.apache.lucene.store.SingleInstanceLockFactory

  1. none :不使用任何锁。来自其他应用程序的并发写入会导致索引损坏。仔细测试您的应用程序,确保您了解这意味着什么。

请参阅 org.apache.lucene.store.NoLockFactory

17.3. Sharding

17.3.1. Basics

有关分片的初步介绍,包括它在 Hibernate Search 中的工作方式以及它的局限性是什么,请参阅 Sharding and routing

在 Lucene 后端中,分片默认处于禁用状态,但可以通过选择分片策略来启用它。有多个策略可用:

hash

# To configure the defaults for all indexes: hibernate.search.backend.sharding.strategy = hash hibernate.search.backend.sharding.number_of_shards = 2 # To configure a specific index: hibernate.search.backend.indexes.<index-name>.sharding.strategy = hash hibernate.search.backend.indexes.<index-name>.sharding.number_of_shards = 2_The _hash 策略需要通过 number_of_shards 属性设置指定数量的分片。

此策略将设置一个显式配置的分片数,从 0 到所选号码减一(例如,对于 2 个分片,将有分片“0”和分片“1”)。

在路由时,将对路由健进行哈希以将其分配给某个分片。如果路由键为 null,则将使用文档 ID。

当没有明确的路由键 configured in the mapping,或者当路由键具有大量需要减少到较小数量的可能值(例如,“所有整数”),此策略适用。

explicit

# To configure the defaults for all indexes: hibernate.search.backend.sharding.strategy = explicit hibernate.search.backend.sharding.shard_identifiers = fr,en,de # To configure a specific index: hibernate.search.backend.indexes.<index-name>.sharding.strategy = explicit hibernate.search.backend.indexes.<index-name>.sharding.shard_identifiers = fr,en,de_The _explicit 策略需要通过 shard_identifiers 属性设置分片标识符列表。标识符必须作为包含以逗号分隔的多 分片标识符的字符串提供,或者作为包含分片标识符的 Collection<String> 提供。分片标识符可以是任何字符串。

此策略将为每个配置的分片标识符设置一个分片。

在路由时,将验证路由键以确保它与分片标识符完全匹配。如果匹配,则文档将被路由到该分片。如果不匹配,则将引发异常。路由键不能为 null,并且将忽略文档 ID。

当存在明确的路由键 configured in the mapping,且该路由键具有在启动应用程序之前已知的有限数量的可能值时,此策略适用。

17.3.2. Per-shard configuration

在某些情况下,特别是当使用 explicit 分片策略时,可能需要以稍有不同的方式配置某些分片。例如,其中一个分片可能包含大量但很少访问的数据,这些数据应该存储在不同的驱动器上。

可以通过为特定分片添加配置属性来实现此目的:

# Default configuration for all shards an index:
hibernate.search.backend.indexes.<index-name>.directory.root = /path/to/fast/drive/
# Configuration for a specific shard:
hibernate.search.backend.indexes.<index-name>.shards.<shard-identifier>.directory.root = /path/to/large/drive/

并不是所有设置都可以按分片覆盖;例如,不能按每个分片覆盖分片策略。

按每个分片覆盖主要用于与 directoryI/O 相关的设置。

有效的片标识符取决于分片策略:

  1. 对于 hash 策略,每个分片都会分配一个正整数,从 0 分配到所选的分片数减一。

  2. 对于 explicit 策略,每个分片都会分配 shard_identifiers 属性中定义的一个标识符。

17.4. Index format compatibility

虽然 Hibernate Search 致力于提供向后兼容的 API,让您轻松将应用程序移植到较新版本,但它仍委托 Apache Lucene 处理索引写入和搜索。这会创建对 Lucene 索引格式的依赖关系。当然,Lucene 开发人员会尝试保持稳定的索引格式,但有时无法避免格式更改。在这些情况下,您要么必须重新索引所有数据,要么使用索引升级工具。有时,Lucene 也能够读取旧格式,因此您无需采取具体操作(除了对索引进行备份)。

虽然索引格式不兼容是一种罕见事件,但 Lucene 的分析器实现可能会稍微改变其行为。这可能导致一些文档不再匹配,尽管它们以前曾经匹配。

为了避免这种分析器不兼容性,Hibernate Search 允许您配置分析器和其他 Lucene 类应遵循其行为的 Lucene 版本。

此配置属性在后端级别设置:

hibernate.search.backend.lucene_version = LUCENE_8_1_1

根据您使用的 Lucene 的特定版本,您可能可以使用不同的选项:请参阅 lucene-core.jar 中包含的 org.apache.lucene.util.Version,以获取允许值的列表。

当未设置此选项时,Hibernate Search 将指示 Lucene 使用最新版本,这通常是新项目的最佳选择。不过,建议在配置中明确定义您正在使用的版本,以便在您进行升级时,Lucene 分析器不会改变行为。然后,您可以在以后更新此值,例如当您有机会从头开始重建索引时。

在使用 Hibernate Search API 时,此设置将得到一致的应用,但是如果您也通过绕过 Hibernate Search 使用 Lucene(例如,在您自己实例化一个分析器时),请务必使用相同的值。

有关可以升级到的 Hibernate Search 版本(同时保持与给定版本 Lucene API 的向后兼容性)的信息,请参阅 compatibility policy

17.5. Schema

Lucene 实际上没有集中式架构的概念来指定每个字段的数据类型和功能,而 Hibernate Search 保存在内存中维护这样的架构,以便记住可应用于每个字段的谓词/投影/排序。

在大多数情况下,模式由 the mapping configured through Hibernate Search’s mapping APIs 推断出来,它们是通用的并独立于 Lucene。

本节中说明了特定于 Lucene 后端的方面。

17.5.1. Field types

Available field types

某些类型不受 Lucene 后端直接支持,但仍然可以正常工作,因为它们由映射器“桥接”。例如,实体模型中的 java.util.Date “桥接”到 java.time.Instant,后者受 Lucene 后端支持。有关更多信息,请参阅 Supported property types

不在此列表中的字段类型仍然可以使用,但需要多做一些工作:

如果实体模型中的属性具有不受支持的类型,但可以转换为受支持的类型,则需要桥接。请参见 Binding and bridges

如果您需要 Hibernate Search 不支持的特定类型的索引字段,您需要一个定义本机字段类型的桥梁。请参阅 Index field type DSL extensions

表 11. Lucene 后端支持的字段类型

Field type

Limitations

java.lang.String

-

java.lang.Byte

-

java.lang.Short

-

java.lang.Integer

-

java.lang.Long

-

java.lang.Double

-

java.lang.Float

-

java.lang.Boolean

-

java.math.BigDecimal

-

java.math.BigInteger

-

java.time.Instant

Lower range/resolution

java.time.LocalDate

Lower range/resolution

java.time.LocalTime

Lower range/resolution

java.time.LocalDateTime

Lower range/resolution

java.time.ZonedDateTime

Lower range/resolution

java.time.OffsetDateTime

Lower range/resolution

java.time.OffsetTime

Lower range/resolution

java.time.Year

Lower range/resolution

java.time.YearMonth

Lower range/resolution

java.time.MonthDay

-

org.hibernate.search.<wbr>engine.<wbr>spatial.<wbr>GeoPoint

Lower resolution

日期/时间字段的范围和解析度日期/时间类型不支持 java.time 类型中可表示的全部年份范围:

_java.time_可以表示从 _-999.999.999_到 _999.999.999_的年份。

Lucene 后端支持从年份 _-292.275.054_到年份 _292.278.993_的日期。

超出范围的值会触发索引失败。

时间类型的解析度也较低:

java.time 支持纳秒精度。

Lucene 后端支持毫秒级分辨率。

索引时,毫秒精度以上的精度会丢失。

GeoPoint 字段的范围和解析度 GeoPoint_s are indexed as _LatLonPoint_s in the Lucene backend. According to _LatLonPoint 的 javadoc 中,在对值编码时会出现精度损失:

值以从原始 double 值中损失一些精度的方式编入索引(纬度组件为 4.190951585769653E-8 ,经度组件为 8.381903171539307E-8 )。

值以从原始 double 值中损失一些精度的方式编入索引(纬度组件为 4.190951585769653E-8 ,经度组件为 8.381903171539307E-8 )。

实际上,这意味着在最坏的情况下,索引点可能会偏差约 13 厘米(5.2 英寸)。

Index field type DSL extensions

并非所有 Lucene 字段类型都在 Hibernate Search 中具有内置支持。不过,依然可以通过利用“native”字段类型来使用不受支持的字段类型。使用此字段类型,可以直接创建 Lucene IndexableField 实例,从而可以访问 Lucene 所能提供的一切。

以下是如何使用 Lucene “原生”类型的示例。

示例 423. 使用 Lucene “本机”类型

public class PageRankValueBinder implements ValueBinder { (1)
    @Override
    public void bind(ValueBindingContext<?> context) {
        context.bridge(
                Float.class,
                new PageRankValueBridge(),
                context.typeFactory() (2)
                        .extension( LuceneExtension.get() ) (3)
                        .asNative( (4)
                                Float.class, (5)
                                (absoluteFieldPath, value, collector) -> { (6)
                                    collector.accept( new FeatureField( absoluteFieldPath, "pageRank", value ) );
                                    collector.accept( new StoredField( absoluteFieldPath, value ) );
                                },
                                field -> (Float) field.numericValue() (7)
                        )
        );
    }

    private static class PageRankValueBridge implements ValueBridge<Float, Float> {
        @Override
        public Float toIndexedValue(Float value, ValueBridgeToIndexedValueContext context) {
            return value; (8)
        }

        @Override
        public Float fromIndexedValue(Float value, ValueBridgeFromIndexedValueContext context) {
            return value; (8)
        }
    }
}
@Entity
@Indexed
public class WebPage {

    @Id
    private Integer id;

    @NonStandardField( (1)
            valueBinder = @ValueBinderRef(type = PageRankValueBinder.class) (2)
    )
    private Float pageRank;

    // Getters and setters
    // ...

}

17.5.2. Multi-tenancy

根据在当前会话中定义的租户 ID,多租户功能得到支持并且会以透明的方式处理:

  1. 文档将会通过相应的值进行索引,允许稍后进行过滤;

  2. 查询将适当过滤结果。

如果在映射器中启用了多租户,则在后端中会自动启用多租户,例如,如果 a multi-tenancy strategy is selected in Hibernate ORM,或者如果 multi-tenancy is explicitly configured in the Standalone POJO mapper

但是,可以手动启用多租户功能。

多租户策略是在后端级别设置的:

hibernate.search.backend.multi_tenancy.strategy = none

有关可用策略的详细信息,请参阅以下小节。

none: single-tenancy

none 策略(默认策略)完全禁用多租户功能。

尝试设置租户 ID 会导致索引编制失败。

discriminator: type name mapping using the index name

使用 discriminator 策略,来自所有租户的所有文档都存储在同一个索引中。

索引编制时,会为每个文档透明地填充一个保存租户 ID 的鉴别器字段。

搜索时,会将针对租户 ID 字段的筛选器透明地添加到搜索查询中,以便仅返回当前租户的搜索结果。

17.6. Analysis

17.6.1. Basics

Analysis 是由分析器执行的文本处理,包括在索引编制(文档处理)时和在搜索(查询处理)时。

Lucene 后端会自带一些 default analyzers,但也可以明确配置分析。

要在 Lucene 后端中配置分析,你需要:

  • 定义一个实现 _org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurer_接口的类。

  • 通过将配置属性 hibernate.search.backend.analysis.configurer_设置为指向实现的 bean reference(例如 _class:com.mycompany.MyAnalysisConfigurer),将后端配置为使用该实现。

Hibernate Search 将在启动时调用此实现的 configure 方法,配置器将能够利用 DSL 来定义 analyzers and normalizers,甚至(对于更高级的用法)定义 similarity。请参见下面的示例。

17.6.2. Built-in analyzers

开箱即用的内置分析器不需要显式配置。如有必要,通过用相同名称定义自己的分析器可以覆盖它们。

Lucene 后端附带一系列内置分析器;其名称在 org.hibernate.search.engine.backend.analysis.AnalyzerNames 中的常量中列出:

default

@FullTextField 默认使用的分析器。

默认实现:org.apache.lucene.analysis.standard.StandardAnalyzer

默认行为:首先,使用标准标记化器进行标记化,该标记化器遵循 Unicode 文本分段算法的单词分隔规则,如 Unicode Standard Annex #29中指定的那样。然后,将每个标记小写。

standard

默认实现:org.apache.lucene.analysis.standard.StandardAnalyzer

默认行为:首先,使用标准标记化器进行标记化,该标记化器遵循 Unicode 文本分段算法的单词分隔规则,如 Unicode Standard Annex #29中指定的那样。然后,将每个标记小写。

simple

默认实现: org.apache.lucene.analysis.core.SimpleAnalyzer

默认行为:首先在非字母字符处拆分文本。然后将每个单词转换为小写。

whitespace

默认实现: org.apache.lucene.analysis.core.WhitespaceAnalyzer

默认行为:在空格字符处拆分文本。不更改单词。

stop

默认实现: org.apache.lucene.analysis.core.StopAnalyzer

默认行为:首先在非字母字符处拆分文本。然后将每个单词转换为小写。最后,删除英语停用词。

keyword

默认实现: org.apache.lucene.analysis.core.KeywordAnalyzer

默认行为:不以任何方式更改文本。

通过这个分析器,全文字段的行为将类似于关键字字段,但功能更少:例如,没有词组聚合。

请考虑改用 @KeywordField

17.6.3. Built-in normalizers

Lucene 后端不提供任何内置规范化器。

17.6.4. Custom analyzers and normalizers

Referencing components by name

传递给配置器的上下文采用了 DSL 来定义分析器和规范化器:

示例 424. 实现并使用分析配置器,在 Lucene 后端定义分析器和规范化器

package org.hibernate.search.documentation.analysis;

import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurationContext;
import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurer;

public class MyLuceneAnalysisConfigurer implements LuceneAnalysisConfigurer {
    @Override
    public void configure(LuceneAnalysisConfigurationContext context) {
        context.analyzer( "english" ).custom() (1)
                .tokenizer( "standard" ) (2)
                .charFilter( "htmlStrip" ) (3)
                .tokenFilter( "lowercase" ) (4)
                .tokenFilter( "snowballPorter" ) (4)
                        .param( "language", "English" ) (5)
                .tokenFilter( "asciiFolding" );

        context.normalizer( "lowercase" ).custom() (6)
                .tokenFilter( "lowercase" )
                .tokenFilter( "asciiFolding" );

        context.analyzer( "french" ).custom() (7)
                .tokenizer( "standard" )
                .charFilter( "htmlStrip" )
                .tokenFilter( "lowercase" )
                .tokenFilter( "snowballPorter" )
                        .param( "language", "French" )
                .tokenFilter( "asciiFolding" );
    }
}
(1)
hibernate.search.backend.analysis.configurer = class:org.hibernate.search.documentation.analysis.MyLuceneAnalysisConfigurer

要了解有哪些字符筛选器、分词器和单词筛选器可用,可以在传递给分析配置器的上下文中调用 context.availableTokenizers()context.availableTokenizers()context.availableTokenFilters();这会返回所有有效名称的集合。

要深入了解这些字符过滤器、标记化器和标记过滤器,请浏览 Lucene Javadoc,特别是查看 common analysis components的各个包,或阅读 Solr Wiki上的相应部分(无需 Solr 即可使用这些分析器,只是 Lucene 本身没有文档页面)。

在 Lucene Javadoc 中,每个工厂类的描述都包含后跟字符串常量的“SPI 名称”。在定义分析器时,应将此名称传递给该工厂以供使用。

Referencing components by factory class

你可以传递 Lucene 工厂类,而不是名称,以引用特定的分词器、字符过滤器或分词过滤器实现。这些类会扩展 org.apache.lucene.analysis.TokenizerFactoryorg.apache.lucene.analysis.TokenFilterFactoryorg.apache.lucene.analysis.CharFilterFactory

这会避免在代码中使用字符串常量,代价是直接编译时依赖 Lucene。

示例 425. 使用 Lucene factory 类实现分析配置器

context.analyzer( "english" ).custom()
        .tokenizer( StandardTokenizerFactory.class )
        .charFilter( HTMLStripCharFilterFactory.class )
        .tokenFilter( LowerCaseFilterFactory.class )
        .tokenFilter( SnowballPorterFilterFactory.class )
                .param( "language", "English" )
        .tokenFilter( ASCIIFoldingFilterFactory.class );

context.normalizer( "lowercase" ).custom()
        .tokenFilter( LowerCaseFilterFactory.class )
        .tokenFilter( ASCIIFoldingFilterFactory.class );

要了解有哪些字符过滤器、标记化器和标记过滤器可用,请浏览 Lucene Javadoc,特别是查看 common analysis components的各个包,或阅读 Solr Wiki上的相应部分(无需 Solr 即可使用这些分析器,只是 Lucene 本身没有文档页面)。

Assigning names to analyzer instances

也可以给一个分析器实例指定一个名称:

示例 426. 在 Lucene 后端命名分析器实例

context.analyzer( "my-standard" ).instance( new StandardAnalyzer() );

17.6.5. Overriding the default analyzer

使用 @FullTextField 但未明确指定分析器时的默认分析器名为 default

像任何其他 built-in analyzer 一样,可以通过定义具有相同名称的 custom analyzer 来覆盖默认分析器:

示例 427. 覆盖 Lucene 后端的默认分析器
package org.hibernate.search.documentation.analysis;

import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurationContext;
import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurer;
import org.hibernate.search.engine.backend.analysis.AnalyzerNames;

public class DefaultOverridingLuceneAnalysisConfigurer implements LuceneAnalysisConfigurer {
    @Override
    public void configure(LuceneAnalysisConfigurationContext context) {
        context.analyzer( AnalyzerNames.DEFAULT ) (1)
                .custom() (2)
                .tokenizer( "standard" )
                .tokenFilter( "lowercase" )
                .tokenFilter( "snowballPorter" )
                        .param( "language", "French" )
                .tokenFilter( "asciiFolding" );
    }
}
(1)
hibernate.search.backend.analysis.configurer = class:org.hibernate.search.documentation.analysis.DefaultOverridingLuceneAnalysisConfigurer

17.6.6. Similarity

搜索时,将根据在索引时间记录的统计信息使用特定公式为文档分配分数。这些统计信息和公式由一个称为“相似性”的单个组件定义,实现 Lucene 的 org.apache.lucene.search.similarities.Similarity 接口。

默认情况下,Hibernate Search 使用 BM25Similarity 及其默认参数 (k1 = 1.2, b = 0.75)。这应该可以在大多数情况下提供令人满意的评分。

如有高级需求,可以在分析配置器中设置自定义 Similarity,如下所示。

请记住,还要从您的配置属性中引用分析配置器,如 Custom analyzers and normalizers 中所述。

示例 428. 实现分析配置器以使用 Lucene 后端更改相似性
public class CustomSimilarityLuceneAnalysisConfigurer implements LuceneAnalysisConfigurer {
    @Override
    public void configure(LuceneAnalysisConfigurationContext context) {
        context.similarity( new ClassicSimilarity() ); (1)

        context.analyzer( "english" ).custom() (2)
                .tokenizer( "standard" )
                .tokenFilter( "lowercase" )
                .tokenFilter( "asciiFolding" );
    }
}

有关 Similarity 、其各种实现以及每种实现的利弊的更多信息,请参阅 Similarity 和 Lucene 源代码的 javadoc。

还可以在网上找到有用的资源,例如在 Elasticsearch 的文档中。

17.7. Threads

Lucene 后端依赖于内部线程池在索引上执行写操作。

默认情况下,此池包含的线程数恰好等于引导时 JVM 可用的处理器数。可以使用配置属性更改此设置:

hibernate.search.backend.thread_pool.size = 4

每索引这个数字都是 per backend,而不是每个索引。添加更多索引不会添加更多线程。

在此线程池中发生的运算包括阻塞 I/O,因此将其大小提高到 JVM 可用处理器核心数之上可能是合理的,还能提升性能。

17.8. Indexing queues

在 Hibernate Search 在索引上执行的所有写操作中,预计会出现许多“索引”操作来创建/更新/删除特定文档。我们通常希望在请求与同一文档相关时保留这些请求的相对顺序。

出于这个原因,Hibernate Search 会将这些操作推入内部队列并批处理应用这些操作。每个索引维护 10 个队列,每个队列最多包含 1000 个元素。这些队列会独立运行(并行),但每个队列都会依次执行一个操作,因此在任意给定时间,每个索引最多可以应用 10 批索引请求。

相对于同一文档 ID 的索引操作始终会被推送到同一队列。

可以自定义队列以减少资源消耗,或者相反,改善吞吐量。这是通过以下配置属性来实现的:

# To configure the defaults for all indexes:
hibernate.search.backend.indexing.queue_count = 10
hibernate.search.backend.indexing.queue_size = 1000
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.indexing.queue_count = 10
hibernate.search.backend.indexes.<index-name>.indexing.queue_size = 1000
  1. indexing.queue_count 定义队列数。需要严格为正整数。此属性的默认值为 10

更高的值将导致更多索引操作并行执行,如果在索引时 CPU 能力是瓶颈,这可能会提高索引吞吐量。

请注意,将此数字提高到 number of threads 以上永远没有用,因为线程数限制了可以并行处理多少个队列。

  1. indexing.queue_size 定义每个队列能容纳的最大元素数。需要严格为正整数。此属性的默认值为 1000

较低的值可能会导致较低的内存使用率,尤其是当有多个队列时,但过低的值会增加 application threads blocking 的可能性,因为队列已满,这可能导致较低的索引编制吞吐量。

当队列已满时,任何请求索引的尝试都会阻塞,直到该请求可以放入队列。

为了达到合理的性能水平,务必将队列的大小设置为足够高的数字,以便仅在应用程序负载非常高时才会发生此类阻塞。

启用 sharding 时,将为每个分片分配自己的队列集。

如果你使用基于文档 ID(而不是提供的路由键)的 hash 分区策略,请务必将队列数设置为与分片数没有公因子的数字;否则,某些队列的使用率可能远低于其他队列。

例如,如果你将分片数设为 8,并将队列数设为 4,则最终存储在分片 0 中的文档将始终存储在该分片队列 0 中。这是因为路由到分片和路由到队列都会对文档 ID 进行哈希运算,然后对该哈希应用模运算,并且 <some hash> % 8 == 0 (路由到分片 0)意味着 <some hash> % 4 == 0 (路由到分片 0 的队列 0)。同样,这仅在你依靠文档 ID 而不是提供的用于分区的路由键时才成立。

17.9. Writing and reading

17.9.1. Commit

有关在 Hibernate

在 Lucene 术语中,@{24} 是当缓存在索引编写器中的更改被推送到索引本身时,以便崩溃或断电不再会导致数据丢失。

某些操作非常重要,并且总是在被认为完成之前提交。对于由 listener-triggered indexing 触发的更改(除非 configured otherwise),以及对于大规模操作(例如 purge)来说,情况就是这样。当遇到此类操作时,将立即执行提交,以保证仅在所有更改都安全地存储在磁盘上后才将该操作视为完成。

但是, mass indexer 贡献的更改等其他操作或在 indexing 使用 async synchronization strategy 时,不一定预期立即提交。

就性能而言,提交可能是一项昂贵的操作,这就是 Hibernate Search 尝试不要太频繁地提交的原因。默认情况下,当不立即提交的更改应用于索引时,Hibernate Search 将延迟提交一秒钟。如果在那一秒钟内应用了其他更改,则它们将包含在相同的提交中。这极大地减少了在写入密集型场景(例如 mass indexing)中的提交数量,从而导致了更好的性能。

可以通过设置提交间隔(以毫秒为单位)来控制 Hibernate Search 执行提交的频率:

# To configure the defaults for all indexes:
hibernate.search.backend.io.commit_interval = 1000
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.io.commit_interval = 1000

此属性的默认值为 1000

将提交间隔设置为 0 将强制 Hibernate Search 在每次更改批处理后提交,这可能会导致吞吐量大幅下降,尤其是对于 explicit or listener-triggered indexing,对 mass indexing 更是如此。

记住,单独写入操作可能会强制进行提交,而这可能会抵消因设置较高的提交间隔而带来的潜在性能提升。

默认情况下,提交间隔可能只会提升 mass indexer 的吞吐量。如果你希望由 explicitly or by listeners 触发的更改也能从中受益,则需要选择非默认 synchronization strategy ,以免在每次更改后都需要进行提交。

17.9.2. Refresh

有关在 Hibernate

在 Lucene 术语中,当打开一个新索引阅读器时,refresh 会进行操作,这样可以是下一个搜索查询考虑索引的最新更改。

在性能方面,刷新可能是一项昂贵的操作,这就是 Hibernate Search 尝试不要过频繁地刷新的原因。索引读取器在每个搜索查询时都会刷新,但前提是自上次刷新以来发生了写操作。

在写密集型场景中,每次写操作后刷新仍然过于频繁,这时可以降低刷新频率,从而通过设置以毫秒为单位的刷新间隔提高读取吞吐量。当将其设置为大于 0 的值时,将不再在每个搜索查询时刷新索引读取器:如果搜索查询开始时刷新在 X 毫秒之前发生,则即使索引读取器可能已过时,也不会刷新它。

刷新间隔可以通过以下方式设置:

# To configure the defaults for all indexes:
hibernate.search.backend.io.refresh_interval = 0
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.io.refresh_interval = 0

此属性的默认值为 0

17.9.3. IndexWriter settings

Hibernate Search 用来写索引的 Lucene’s IndexWriter,公开了多个设置,可以对其进行微调以更好地适配你的应用程序,最终获得更好的性能。

Hibernate Search 通过索引级别的具有 io.writer. 前缀的配置属性公开这些设置。

以下是所有索引作者设置的列表。它们都可以通过配置属性以类似的方式设置;例如,io.writer.ram_buffer_size 可以像这样设置:

# To configure the defaults for all indexes:
hibernate.search.backend.io.writer.ram_buffer_size = 32
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.io.writer.ram_buffer_size = 32

表 12. IndexWriter 的配置属性

Property

Description

[…​].io.writer.max_buffered_docs

在将最大文档数冲洗到目录之前,能在内存中进行缓冲的最大文档数。较大的值意味着更快的索引,但使用更多的 RAM。当与 ram_buffer_size 一起使用时,先发生的任何事件都会发生冲洗。

[…​].io.writer.ram_buffer_size

在将添加的文档和删除内容冲洗到目录之前,可用于缓冲的最大 RAM 大小。较大的值意味着更快的索引,但使用更多的 RAM。通常来说,为了获得更快的索引性能,最好使用此设置,而不是 max_buffered_docs。当与 max_buffered_docs 一起使用时,先发生的任何事件都会发生冲洗。

[…​].io.writer.infostream

启用关于 Lucene 内部组件的低级别跟踪信息;truefalse。日志将会附加到 org.hibernate.search.backend.lucene.infostream 中的 TRACE 级记录中。即使记录忽略了 TRACE 级别,也可能导致显著的性能下降,因此只应将其用于故障排除。默认情况下禁用。

请参阅 Lucene 文档,特别是 IndexWriterConfig 的 javadoc 和源代码获取更多关于设置及其默认值的信息。

17.9.4. Merge settings

Lucene 索引不会存储在单个连续的文件中。相反,每次刷新索引会生成一个小文件,包含加入到索引中的所有文档。该文件被称为“段”。在包含过多段的索引上,搜索可能会更慢,所以 Lucene 会定期合并小段以创建更少且更大的段。

Lucene 的合并行为通过 MergePolicy 控制。Hibernate Search 使用 LogByteSizeMergePolicy,它公开了多个设置,可以对其进行微调以更好地适配你的应用程序,最终获得更好的性能。

以下是所有合并设置的列表。它们都可以通过配置属性以类似的方式设置;例如,io.merge.factor 可以像这样设置:

# To configure the defaults for all indexes:
hibernate.search.backend.io.merge.factor = 10
# To configure a specific index:
hibernate.search.backend.indexes.<index-name>.io.merge.factor = 10

表 13. 与合并相关的配置属性

Property

Description

[…​].io.merge.max_docs

在合并之前,某个段可拥有的最大文档数。拥有超出此文档数的段不会被合并。较小的值在频繁更改的索引中表现得更好,较大的值在索引不频繁更改时能提供更好的搜索性能。

[…​].io.merge.factor

一次合并的段数。对于较小的值, 合并会更频繁地发生, 从而使用更多的资源, 但平均而言, 段的总数会更低, 从而提高读取性能。因此, 较大的值(&gt; 10) 最适合 mass indexing, 较小的值(&lt; 10) 最适合 explicit or listener-triggered indexing。该值不得低于 2

[…​].io.merge.min_size

用于后台合并的段的最小目标大小(以 MB 为单位)。小于此大小的段会更主动地合并。将此值设置得太大可能会导致昂贵的合并操作,即使它们不那么频繁也是如此。

[…​].io.merge.max_size

针对后台合并, 以 MB 为单位的最大段大小。大于此大小的段绝不会在后台合并。将此值设置得更低有助于减少内存需求, 避免某些合并操作, 但会牺牲最佳搜索速度。当 forcefully merging 一个索引时, 将忽略此值, 而使用 max_forced_size(见下文)。

[…​].io.merge.max_forced_size

针对强制合并, 以 MB 为单位的最大段大小。这等同于 forceful mergesio.merge.max_size。通常, 您会想要将其设置为与 max_size 相同或更低的, 但如果设置得太低, 会 degrade search performance as documents are deleted

[…​].io.merge.calibrate_by_deletes

是否要考虑索引中已删除文档的数量;truefalse。启用时,Lucene 会认为,包含 100 个文档且其中 50 个被删除的某个段实际上包含 50 个文档。禁用时,Lucene 会认为,这样的段包含 100 个文档。将 calibrate_by_deletes 设置为 false 将导致由 io.merge.max_docs 引起的更频繁合并,但将更主动地合并包含了许多已删除文档的段,从而提高搜索性能。

请参阅 Lucene 文档,特别是 LogByteSizeMergePolicy 的 javadoc 和源代码获取更多关于设置及其默认值的信息。

选项 io.merge.max_sizeio.merge.max_forced_size 不会直接定义所有段文件允许的最大大小。

首先,考虑合并段是指将段与另一个现有段组合在一起形成一个更大的段。 io.merge.max_size 是合并之前的段允许的最大大小,因此新合并后的段可能达到其两倍大小。

其次,合并选项不会影响索引写入程序在合并之前创建的段的初始大小。此大小可以用设置 io.writer.ram_buffer_size 来限制,但 Lucene 依靠估算值来实施此限制;当这些估算值出现偏差时,新创建的段可能会略大于 io.writer.ram_buffer_size

因此,例如,为了相当自信地确保没有文件增长到大于 15MB,请使用类似这样的设置:

hibernate.search.backend.io.writer.ram_buffer_size = 10 hibernate.search.backend.io.merge.max_size = 7 hibernate.search.backend.io.merge.max_forced_size = 7 hibernate.search.backend.io.writer.ram_buffer_size = 10 hibernate.search.backend.io.merge.max_size = 7 hibernate.search.backend.io.merge.max_forced_size = 7

使用 Lucene 后端进行搜索依赖于 same APIs as any other backend

此章节详细介绍与搜索相关的 Lucene 特有配置。

17.10.1. Low-level hit caching

此特性意味着应用程序代码直接依赖 Lucene API。

即使是针对 bug 修复(微)版本,升级 Hibernate Search 也可能需要升级 Lucene,这可能会导致 Lucene 中中断 API 更改。

如果出现此情况,您将需要更改应用程序代码来应对这些更改。

Lucene 支持缓存低级别命中,即缓存与给定 org.apache.lucene.search.Query 在给定索引段中匹配的文档列表。

在读密集型场景中,该缓存很有用,在这种场景中,相同的查询会在同一索引上执行很频繁,而且很少会写到索引中。

要在 Lucene 后端中配置缓存,您需要:

  • 定义一个实现 _org.hibernate.search.backend.lucene.cache.QueryCachingConfigurer_接口的类。

  • 通过将配置属性 hibernate.search.backend.query.caching.configurer_设置为指向实现的 bean reference(例如 _class:com.mycompany.MyQueryCachingConfigurer),将后端配置为使用该实现。

Hibernate Search 会在启动时调用此实现的 configure 方法,配置人员将能够利用 DSL 来定义 org.apache.lucene.search.QueryCacheorg.apache.lucene.search.QueryCachingPolicy