查询数据网格缓存
Red Hat Data Grid
Data Grid 是一个高性能分布式内存数据存储。
- 无架构数据结构
- 将不同对象存储为键值对的灵活性。
- 基于网格的数据存储
- 旨在在集群中分发和复制数据。
- 弹性扩展
- 动态调整节点数量,以便在不中断服务的情况下满足需求。
- 数据互操作性
- 从不同端点在网格中存储、检索和查询数据。
Data Grid 文档
红帽客户门户网站中提供了 Data Grid 的文档。
Data Grid 下载
访问 红帽客户门户上的 Data Grid 软件下载。
您必须有一个红帽帐户才能访问和下载数据中心软件。
使开源包含更多
红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。我们从这四个术语开始:master、slave、黑名单和白名单。由于此项工作十分艰巨,这些更改将在即将推出的几个发行版本中逐步实施。有关更多详情,请参阅我们的首席技术官 Chris Wright 提供的消息。
第 1 章 索引数据网格缓存
Data Grid 可在您的缓存中创建值索引,以提高查询性能,从而提供比非索引查询更快地的结果。索引还允许您在查询中使用全文本搜索功能。
Data Grid 使用 Apache Lucene 技术来索引缓存中的值。
1.1. 将 Data Grid 配置为索引缓存
在缓存配置中启用索引,并指定在创建索引时应包含哪些实体数据网格。
在使用查询时,您应该始终将 Data Grid 配置为索引缓存。索引为您的查询提供了显著的性能,允许您更快地了解您的数据。
流程
在缓存配置中启用索引。
<distributed-cache> <indexing> <!-- Indexing configuration goes here. --> </indexing> </distributed-cache>
提示在您的
配置中添加
indexing 元素可启用索引,而无需包含enabled=true
属性。对于远程缓存,添加此元素也会隐式将编码配置为 ProtoStream。
使用
indexed-entity
元素指定要索引的实体。<distributed-cache> <indexing> <indexed-entities> <indexed-entity>...</indexed-entity> </indexed-entities> </indexing> </distributed-cache>
protobuf 消息
指定 schema 中声明的消息作为
indexed-entity
元素的值,例如:<distributed-cache> <indexing> <indexed-entities> <indexed-entity>org.infinispan.sample.Car</indexed-entity> <indexed-entity>org.infinispan.sample.Truck</indexed-entity> </indexed-entities> </indexing> </distributed-cache>
此配置使用
book_sample
软件包名称对模式中的书签消息进行索引。package book_sample; /* @Indexed */ message Book { /* @Field(store = Store.YES, analyze = Analyze.YES) */ optional string title = 1; /* @Field(store = Store.YES, analyze = Analyze.YES) */ optional string description = 2; optional int32 publicationYear = 3; // no native Date type available in Protobuf repeated Author authors = 4; } message Author { optional string name = 1; optional string surname = 2; }
Java 对象
-
指定包含
@Indexed
注释的每个类的完全限定名称(FQN)。
XML
<distributed-cache> <indexing> <indexed-entities> <indexed-entity>book_sample.Book</indexed-entity> </indexed-entities> </indexing> </distributed-cache>
ConfigurationBuilder
import org.infinispan.configuration.cache.*; ConfigurationBuilder config=new ConfigurationBuilder(); config.indexing().enable().storage(FILESYSTEM).path("/some/folder").addIndexedEntity(Book.class);
1.1.1. 索引配置
Data Grid 配置控制索引的存储和构建方式。
1.1.1.1. 索引存储
您可以配置 Data Grid 如何存储索引:
- 在主机文件系统中,这是默认值并在重启之间保留索引。
-
在 JVM 堆内存中,这意味着索引不会在重启后。
您应该仅将索引存储在 JVM 堆内存中,以用于小型数据集。
File system
<distributed-cache> <indexing storage="filesystem" path="${java.io.tmpdir}/baseDir"> <!-- Indexing configuration goes here. --> </indexing> </distributed-cache>
JVM 堆内存
<distributed-cache> <indexing storage="local-heap"> <!-- Additional indexing configuration goes here. --> </indexing> </distributed-cache>
1.1.1.2. 索引读取器
索引读取器是一个内部组件,提供对索引的访问来执行查询。随着索引内容的变化,Data Grid 需要刷新读取器,以便搜索结果为最新版本。您可以为索引读取器配置刷新间隔。默认情况下,如果索引自上次刷新后改变,Data Grid 会在每个查询前读取索引。
<distributed-cache> <indexing storage="filesystem" path="${java.io.tmpdir}/baseDir"> <!-- Sets an interval of one second for the index reader. --> <index-reader refresh-interval="1000"/> <!-- Additional indexing configuration goes here. --> </indexing> </distributed-cache>
1.1.1.3. 索引写入器
索引写入程序是一个内部组件,它构造由一个或多个片段(子索引)组成的索引,可随着时间的推移合并以提高性能。较少的片段通常意味着在查询期间较少的开销,因为索引读取器操作需要考虑所有片段。
数据中心在内部使用 Apache Lucene,并在两个层(内存和存储)中索引条目。新条目首先进入内存索引,然后在发生清除时进入配置的索引存储。定期提交操作发生,从之前清除的数据创建片段,并使所有索引更改持久。
index-writer
配置是可选的。对于大多数情况和自定义配置,默认值应该只用于调整性能。
<distributed-cache> <indexing storage="filesystem" path="${java.io.tmpdir}/baseDir"> <index-writer commit-interval="2000" low-level-trace="false" max-buffered-entries="32" queue-count="1" queue-size="10000" ram-buffer-size="400" thread-pool-size="2"> <index-merge calibrate-by-deletes="true" factor="3" max-entries="2000" min-size="10" max-size="20"/> </index-writer> <!-- Additional indexing configuration goes here. --> </indexing> </distributed-cache>
属性 | 描述 |
---|---|
| 索引在内存中缓冲的索引更改的时间(以毫秒为单位)会刷新到索引存储中,并执行提交。由于操作非常昂贵,因此应该避免较小的值。默认值为 1000 毫秒(1 秒)。 |
|
在将内存刷新到索引存储前可缓冲的最大条目数。较大的值会导致索引更快,但使用更多内存。当与 |
|
在将添加的条目刷新到索引存储前,可用于缓冲添加条目的最大内存量。较大的值会导致索引更快,但使用更多内存。为加快索引性能,您应该设置此属性而不是 |
| 对索引执行写入操作的线程数量。 |
|
用于每个索引类型的内部队列数量。每个队列都包含一系列应用于索引和队列的修改,这些修改并行处理。增加队列数量将导致索引吞吐量增加,但只有瓶颈是 CPU 时。对于最佳结果,请不要为大于 |
|
每个队列可以保留的最大元素数。增加 |
| 为索引操作启用低级追踪信息。启用此属性可显著降低性能。您应该只使用这个低级追踪作为故障排除的最后资源。 |
要配置 Data Grid 如何合并索引片段,您可以使用 index-merge
子元素。
属性 | 描述 |
---|---|
| 索引片段在合并前可以具有的最大条目数。超过此条目的片段不会合并。较小的值在频繁更改索引时更好地执行,如果索引经常没有改变,较大的值可以提供更好的搜索性能。 |
| 同时合并的片段数。使用较小的值时,合并会更频繁地使用更多资源,但片段总数会降低,从而增加搜索性能。较大的值(同意大于 10)最适合大量编写场景。 |
| 最小片段的目标大小(以 MB 为单位)用于后台合并。小于这个大小的片段会更积极地合并。设置太大的值可能会导致昂贵的合并操作,即使它们的频率较低。 |
|
后台合并的最大片段大小(以 MB 为单位)。大于这个大小的片段不会在后台合并。把它设置为较低值有助于降低内存要求,并避免在最佳搜索速度成本上出现一些合并操作。当强制合并索引和 |
|
强制合并并覆盖 |
|
在计算片段中的条目时,是否应该考虑索引中删除条目的数量。设置 |
其他资源
1.2. 索引注解
当您在缓存中启用索引时,您可以将 Data Grid 配置为创建索引。您还需要为 Data Grid 提供缓存中实体的结构化表示,以便它实际上可以索引它们。
有两个注解控制 Data Grid 索引的实体和字段:
@indexed
- 指明数据网格索引的实体或 Protobuf 消息类型。
@field
指明 Data Grid 索引并具有以下属性的字段:
属性 描述 值 index
控制 Data Grid 是否包含索引中的字段。
index.YES
或Index.NO
Store
允许 Data Grid 将字段存储在索引中,以便您可以将它们用于预测。
store.YES
或Store.NO
.对于需要用于排序的字段,使用Store.YES
和 setsortable = true
。分析
包括全文本搜索中的字段。
analyze.NO
或指定分析器定义
远程缓存
您可以通过两种方式为远程缓存提供索引注解:
-
使用
@ProtoDoc ("@Indexed")
和@ProtoDoc ("@Field (…) ")
直接注解您的 Java 类。
然后,在将 Protobuf 模式.proto
文件上传到 Data Grid Server 之前,生成 Protobuf 模式。 直接使用
@Indexed
和@Field (…)
直接注解 Protobuf 模式。
然后,将 Protobuf 模式上传到 Data Grid Server。例如,以下模式使用
@Field
注释:/** * @Field(analyze = Analyze.YES, store = Store.YES, sortable = true) */ required string street = 1;
通过在
@Field
注释中包含store.YES
和sortable = true
,您可以使用street
字段排序查询,而无需遇到警告消息或意外的结果。
嵌入式缓存
对于嵌入式缓存,您可以根据 Data Grid 如何存储您的条目,为 Java 类添加索引注解。
使用 @Indexed
和 @Field
注释,以及其他 Hibernate 搜索注释,如 @FullTextField
。
1.3. 重建索引
重建索引从缓存中存储的数据重建它。当您更改索引类型或分析器的定义等内容时,您应该重建索引。同样,出于某种原因,您可以在删除索引后重建索引。
重建索引可能需要很长时间才能完成,因为网格中的所有数据需要很长时间。在重新构建操作进行时,查询可能会返回较少的结果。
流程
使用以下方法之一重建索引:
调用
reindexCache ()
方法,以编程方式从 Hot Rod Java 客户端重建索引:remoteCacheManager.administration().reindexCache("MyCache");
提示对于远程缓存,您还可以从 Data Grid 控制台重建索引。
调用
index.run
() 方法为嵌入式缓存重建索引,如下所示:Indexer indexer = Search.getIndexer(cache); CompletionStage<Void> future = index.run();
1.4. 非索引查询
Data Grid 建议对缓存进行索引缓存,以获得最佳性能。但是,您可以查询非索引的缓存。
- 对于嵌入式缓存,您可以对 Plain Old Java 对象(POJO)执行非索引查询。
-
对于远程缓存,您必须使用带有
application/x-protostream
介质类型的 ProtoStream 编码来执行非索引查询。
第 2 章 创建 Ickle 查询
Data Grid 提供了一个 Ickle 查询语言,可让您创建关系和全文本查询。
2.1. Ickle queries
要使用 API,首先获取 QueryFactory 到缓存,然后调用 .create ()
方法,传递要在查询中使用的字符串。每个 QueryFactory
实例都绑定到与 Search
相同的 缓存
实例,但它是一个无状态和线程安全对象,可用于并行创建多个查询。
例如:
// Remote Query, using protobuf QueryFactory qf = org.infinispan.client.hotrod.Search.getQueryFactory(remoteCache); Query<Transaction> q = qf.create("from sample_bank_account.Transaction where amount > 20"); // Embedded Query using Java Objects QueryFactory qf = org.infinispan.query.Search.getQueryFactory(cache); Query<Transaction> q = qf.create("from org.infinispan.sample.Book where price > 20"); // Execute the query QueryResult<Book> queryResult = q.execute();
查询始终以单个实体类型为目标,并评估单个缓存的内容。不支持对多个缓存运行查询或创建以多种实体类型(加入)为目标的查询。
执行查询并获取结果非常简单,就像调用 Query
对象的 execute ()
方法一样。执行后,在同一实例上调用 execute ()
将重新执行查询。
2.1.1. 分页
您可以使用 Query.maxResults (int maxResults)
来限制返回的结果数量。这可以与 Query.startOffset (long startOffset)
一起使用,以实现结果集的分页。
// sorted by year and match all books that have "clustering" in their title // and return the third page of 10 results Query<Book> query = queryFactory.create("FROM org.infinispan.sample.Book WHERE title like '%clustering%' ORDER BY year").startOffset(20).maxResults(10)
2.1.2. 命中数
QueryResult
对象具有 .hitCount ()
方法,可返回查询结果总数,而不考虑任何分页参数。因性能原因,点击数仅适用于索引查询。
2.1.3. iteration
Query
对象具有 .iterator ()
方法,用于获取结果 lazily。它返回一个 CloseableIterator
实例,实例必须在使用量后关闭。
当前限制了对 Remote Queries 的迭代支持,因为它将在迭代前首先向客户端获取所有条目。
2.1.4. 命名的查询参数
也可以为每个执行构建新的 Query 对象,而是在查询中包含命名的参数,可在执行前替换为实际值。这允许定义一次查询,并多次高效地执行查询。参数只能在 Operator 右侧使用,并在通过提供由 org.infinispan.query.dsl.Expression.param (String paramName)
方法生成的对象创建时定义。定义了参数后,可以通过调用 Query.setParameter (parameterName, value)
或 Query.setParameters (parameterMap)
来设置这些参数,如下例所示。
QueryFactory queryFactory = Search.getQueryFactory(cache); // Defining a query to search for various authors and publication years Query<Book> query = queryFactory.create("SELECT title FROM org.infinispan.sample.Book WHERE author = :authorName AND publicationYear = :publicationYear").build(); // Set actual parameter values query.setParameter("authorName", "Doe"); query.setParameter("publicationYear", 2010); // Execute the query List<Book> found = query.execute().list();
或者,您可以提供一个实际参数值映射来一次性设置多个参数:
一次性设置多个命名参数
Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("authorName", "Doe"); parameterMap.put("publicationYear", 2010); query.setParameters(parameterMap);
查询解析、验证和执行规划工作的一个主要部分是在首次执行带有参数的查询过程中执行的。与使用恒定值而非查询参数相比类似的查询相比,这个工作不会在后续执行过程中重复。
2.1.5. 查询执行
Query
API 提供了在缓存上执行 Ickle 查询的方法:
-
query.execute ()
运行 SELECT 语句并返回结果。 -
query.executeStatement ()
运行 DELETE 语句并修改数据。
您应该始终调用 executeStatement ()
来修改数据并调用 execute ()
来获取查询的结果。
2.2. Ickle 查询语言语法
Ickle 查询语言是 JPQL 查询语言的子集,一些用于全文本的扩展。
解析器语法有一些值得注意的规则:
- 空格并不重要。
- 字段名称不支持通配符。
- 必须始终指定字段名称或路径,因为没有默认字段。
-
在
全文本和 JPA predicates 中,接受 &
amp;& 和||
| 而不是AND
或OR
。 -
可以使用
!
而不是 not。
-
缺少布尔值运算符解释为
OR
。 - 字符串术语必须用单引号或双引号括起来。
- Fuzziness 和 boosting 不会被任意顺序接受;模糊始终首先是第一个。
-
!=
被接受,而不是 <>
。 -
提升无法应用到 > ,
&
gt;=,
<
,<
;= operators。范围可用于实现相同的结果。
2.2.1. 过滤 Operator
Ickle 支持可用于索引和非索引字段的许多过滤运算符。
Operator | 描述 | 示例 |
---|---|---|
| 检查左侧操作对象是否等于集合以参数形式给出的其中一个元素。 |
|
| 检查 left 参数(预期为字符串)是否与 JPA 规则后面的通配符模式匹配。 |
|
| 检查 left 参数是否与给定值完全匹配。 |
|
| 检查 left 参数是否与给定值不同。 |
|
| 检查 left 参数是否大于给定值。 |
|
| 检查 left 参数是否大于或等于给定值。 |
|
| 检查 left 参数是否小于给定值。 |
|
| 检查 left 参数是否小于或等于给定值。 |
|
| 检查 left 参数是否在给定范围限制之间。 |
|
2.2.2. 布尔值条件
将多个属性条件与逻辑组合(和
)和解除关联(或
)运算符相结合,以便演示下例中更复杂的条件。布尔值运算符的已知 Operator 优先级规则应用在此处,因此运算符的顺序无关。这里的 operator 仍然具有更高的优先级,即使
先调用。或被
# match all books that have "Data Grid" in their title # or have an author named "Manik" and their description contains "clustering" FROM org.infinispan.sample.Book WHERE title LIKE '%Data Grid%' OR author.name = 'Manik' AND description like '%clustering%'
布尔值在逻辑运算符之间具有最高优先级,仅适用于下一个简单的属性条件。
# match all books that do not have "Data Grid" in their title and are authored by "Manik" FROM org.infinispan.sample.Book WHERE title != 'Data Grid' AND author.name = 'Manik'
2.2.3. 嵌套条件
使用括号更改逻辑运算符的优先级:
# match all books that have an author named "Manik" and their title contains # "Data Grid" or their description contains "clustering" FROM org.infinispan.sample.Book WHERE author.name = 'Manik' AND ( title like '%Data Grid%' OR description like '% clustering%')
2.2.4. 使用 SELECT 语句进行预测
在某些情况下,如果应用程序实际使用一小部分属性,则返回整个域对象的用例会被过度使用,特别是在域实体已嵌入实体时。查询语言允许您指定属性子集(或属性路径)来返回 - 投射。如果使用 projections,则 QueryResult.list ()
将不会返回整个域实体,而是会返回 Object[]
列表
,每个插槽都与投射属性对应。
# match all books that have "Data Grid" in their title or description # and return only their title and publication year SELECT title, publicationYear FROM org.infinispan.sample.Book WHERE title like '%Data Grid%' OR description like '%Data Grid%'
排序
根据一个或多个属性或属性路径对结果进行排序,使用 ORDER BY
子句完成。如果指定了多个排序条件,则顺序将决定其优先级。
# match all books that have "Data Grid" in their title or description # and return them sorted by the publication year and title FROM org.infinispan.sample.Book WHERE title like '%Data Grid%' ORDER BY publicationYear DESC, title ASC
2.2.5. 分组和聚合
Data Grid 可根据一组分组字段和构建来自每个组中结果的聚合来对查询结果进行分组,方法是将聚合功能应用到每个组中的一组值。分组和聚合只能应用到投射查询(与 SELECT 子句中的一个或多个字段无关)。
支持的聚合有:vg、sum
、count
、max
、和 min
。
使用 GROUP BY
子句指定一组分组字段,用于定义分组字段的顺序不相关。投射中选择的所有字段都必须是分组字段,或者必须使用下面描述的分组函数之一来聚合它们。可聚合投射字段,用于同时分组。仅选择分组字段但没有聚合字段的查询。示例:按作者分组书签并计数它们。
SELECT author, COUNT(title) FROM org.infinispan.sample.Book WHERE title LIKE '%engine%' GROUP BY author
所有所选字段都应用了聚合函数的投射查询,并且不允许使用任何字段分组。在这种情况下,聚合将全局计算,就像只有一个全局组一样。
聚合
您可以将以下聚合功能应用到一个字段中:
聚合功能 | 描述 |
---|---|
|
计算一组数字的平均值。接受的值是 |
|
计算非空行的数量并返回 |
|
返回找到的最大值。接受的值必须是 |
|
返回找到的最小值。接受的值必须是 |
|
计算一组数字的总和。如果没有非空值,则结果为 |
字段类型 | 返回类型 |
---|---|
Integrationl (除 bigInteger 以外) | Long |
float 或 Double | å�Œ |
BigInteger | BigInteger |
BigDecimal | BigDecimal |
使用分组和聚合评估查询
聚合查询可以包含过滤条件,如常见的查询。可以在两个阶段执行过滤:分组操作之前和之后。在执行分组操作前,将应用调用 groupBy ()
方法前定义的所有过滤器条件,直接发送到缓存条目(而不是最终投射)。这些过滤器条件可以引用查询实体类型的任何字段,并旨在限制将要作为分组阶段输入的数据集。调用 groupBy ()
方法后定义的所有过滤器条件将应用到来自投射和分组操作的预测。这些过滤器条件可以引用任何 groupBy ()
字段或聚合字段。允许引用在 select 子句中指定的聚合字段;但是,禁止引用非集合和非组字段。在此阶段过滤将根据其属性减少组数量。也可以指定类似于常见查询的排序。排序操作是在分组操作后执行的,并可引用任何 groupBy ()
字段或聚合字段。
2.2.6. DELETE 语句
您可以使用以下语法从 Data Grid 缓存中删除实体:
DELETE FROM <entityName> [WHERE condition]
-
仅引用具有 <
entityName&
gt; 的单一实体。DELETE 查询无法使用加入。 - WHERE 条件是可选的。
DELETE 查询无法使用以下任一操作:
- 使用 SELECT 语句进行预测
- 分组和聚合
- ORDER BY 子句
调用 Query.executeStatement ()
方法来执行 DELETE 语句。
2.3. 全文本查询
您可以使用 Ickle 查询语言执行全文本搜索。
2.3.1. fuzzy 查询
要执行模糊查询 add ~
和一个整数,代表术语后所使用的术语的距离。例如
FROM sample_bank_account.Transaction WHERE description : 'cofee'~2
2.3.2. 范围查询
要执行范围查询,请在一对括号内定义给定边界,如下例所示:
FROM sample_bank_account.Transaction WHERE amount : [20 to 50]
2.3.3. 短语查询
可以通过引号中的引号来搜索一组单词,如下例所示:
FROM sample_bank_account.Transaction WHERE description : 'bus fare'
2.3.4. 代理查询
要执行代理查询,请在特定距离中查找两个术语,请在短语后添加一个 ~
和距离。例如,以下示例会找到取消和费用的词语,只要它们没有超过 3 个单词:
FROM sample_bank_account.Transaction WHERE description : 'canceling fee'~3
2.3.5. 通配符查询
要搜索 "text" 或 "test",请使用 ?
单字符通配符搜索:
FROM sample_bank_account.Transaction where description : 'te?t'
要搜索 "test", "tests", 或 "tester",请使用 sVirt
多字符通配符搜索:
FROM sample_bank_account.Transaction where description : 'test*'
2.3.6. 正则表达式查询
正则表达式查询可以通过指定 /
之间的模式来执行。Ickle 使用 Lucene 的正则表达式语法,因此可以使用以下内容搜索单词 moat
或 boat
:
FROM sample_library.Book where title : /[mb]oat/
2.3.7. 提升查询
在术语后添加 ^
来增加给定查询的适用程度,可以提高术语的提高因素。例如,要搜索包含 beer 和 wine 的标题,它们对 beer 的适用程度高,可以使用以下内容:
FROM sample_library.Book WHERE title : beer^3 OR wine
第 3 章 查询远程缓存
您可以在 Data Grid 服务器上索引和查询远程缓存。
3.1. 从 Hot Rod Java 客户端查询缓存
Data Grid 可让您通过 Hot Rod 端点,以编程方式从 Java 客户端查询远程缓存。此流程解释了如何索引查询存储书签实例的远程缓存。
先决条件
-
将 ProtoStream 处理器添加到
pom.xml
中。
Data Grid 为 @ProtoField
和 @ProtoDoc
注解提供此处理器,以便您可以生成 Protobuf 模式并执行查询。
<dependencyManagement> <dependencies> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-bom</artifactId> <version>${version.infinispan}</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.infinispan.protostream</groupId> <artifactId>protostream-processor</artifactId> <scope>provided</scope> </dependency> </dependencies>
流程
在您的类中添加索引注解,如下例所示:
Book.java
import org.infinispan.protostream.annotations.ProtoDoc; import org.infinispan.protostream.annotations.ProtoFactory; import org.infinispan.protostream.annotations.ProtoField; @ProtoDoc("@Indexed") public class Book { @ProtoDoc("@Field(index=Index.YES, analyze = Analyze.YES, store = Store.NO)") @ProtoField(number = 1) final String title; @ProtoDoc("@Field(index=Index.YES, analyze = Analyze.YES, store = Store.NO)") @ProtoField(number = 2) final String description; @ProtoDoc("@Field(index=Index.YES, analyze = Analyze.YES, store = Store.NO)") @ProtoField(number = 3, defaultValue = "0") final int publicationYear; @ProtoFactory Book(String title, String description, int publicationYear) { this.title = title; this.description = description; this.publicationYear = publicationYear; } // public Getter methods omitted for brevity }
在新类中实施
SerializationContextInitializer
接口,然后添加@AutoProtoSchemaBuilder
注释。-
使用
includeClasses
参数引用包含@ProtoField
和@ProtoDoc
注解的类。 -
使用
schemaFileName
和schemaFilePath
参数定义您生成的和文件系统路径的 Protobuf 模式的名称。 使用
schemaPackageName
参数指定 Protobuf 模式的软件包名称。RemoteQueryInitializer.java
import org.infinispan.protostream.SerializationContextInitializer; import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; @AutoProtoSchemaBuilder( includeClasses = { Book.class }, schemaFileName = "book.proto", schemaFilePath = "proto/", schemaPackageName = "book_sample") public interface RemoteQueryInitializer extends SerializationContextInitializer { }
-
使用
编译您的项目。
此流程中的代码示例生成
proto/book.proto
模式和注释Book
类的RemoteQueryInitializerImpl.java
实现。
后续步骤
创建一个远程缓存,将数据仓库配置为索引您的实体。例如,以下远程缓存会索引您在上一步中生成的 book.proto
模式中的书签实体:
<replicated-cache name="books"> <indexing> <indexed-entities> <indexed-entity>book_sample.Book</indexed-entity> </indexed-entities> </indexing> </replicated-cache>
以下 RemoteQuery
类执行以下操作:
-
将
RemoteQueryInitializerImpl
serialization 上下文注册到 Hot Rod Java 客户端。 -
将 Protobuf 模式
书.proto
注册到 Data Grid Server。 -
将两个书签
实例
添加到远程缓存中。 - 执行与标题中的关键字匹配的全文本查询。
RemoteQuery.java
package org.infinispan; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.Search; import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; import org.infinispan.query.dsl.Query; import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants; public class RemoteQuery { public static void main(String[] args) throws Exception { ConfigurationBuilder clientBuilder = new ConfigurationBuilder(); // RemoteQueryInitializerImpl is generated clientBuilder.addServer().host("127.0.0.1").port(11222) .security().authentication().username("user").password("user") .addContextInitializers(new RemoteQueryInitializerImpl()); RemoteCacheManager remoteCacheManager = new RemoteCacheManager(clientBuilder.build()); // Grab the generated protobuf schema and registers in the server. Path proto = Paths.get(RemoteQuery.class.getClassLoader() .getResource("proto/book.proto").toURI()); String protoBufCacheName = ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME; remoteCacheManager.getCache(protoBufCacheName).put("book.proto", Files.readString(proto)); // Obtain the 'books' remote cache RemoteCache<Object, Object> remoteCache = remoteCacheManager.getCache("books"); // Add some Books Book book1 = new Book("Infinispan in Action", "Learn Infinispan with using it", 2015); Book book2 = new Book("Cloud-Native Applications with Java and Quarkus", "Build robust and reliable cloud applications", 2019); remoteCache.put(1, book1); remoteCache.put(2, book2); // Execute a full-text query QueryFactory queryFactory = Search.getQueryFactory(remoteCache); Query<Book> query = queryFactory.create("FROM book_sample.Book WHERE title:'java'"); List<Book> list = query.execute().list(); // Voila! We have our book back from the cache! } }
其他资源
- Marshalling 和 Encoding Data,以了解有关创建序列化上下文和注册 Protobuf 模式的更多信息。
-
ProtoStream 注解,以了解有关
@ProtoField
、@ProtoDoc
和@AutoProtoSchemaBuilder
注解的更多信息。
3.2. 从 Data Grid 控制台和 CLI 查询缓存
Data Grid Console 和 Data Grid 命令行界面(CLI)可让您查询索引和非索引的远程缓存。您还可以使用任何 HTTP 客户端通过 REST API 索引和查询缓存。
此流程解释了如何对存储 Person
实例的远程缓存进行索引和查询。
先决条件
- 至少有一个正在运行的 Data Grid 服务器实例。
- 具有创建权限的 Data Grid 凭证。
流程
在 Protobuf 模式中添加索引注解,如下例所示:
package org.infinispan.example; /* @Indexed */ message Person { /* @Field(index=Index.YES, store = Store.NO, analyze = Analyze.NO) */ optional int32 id = 1; /* @Field(index=Index.YES, store = Store.YES, analyze = Analyze.NO) */ required string name = 2; /* @Field(index=Index.YES, store = Store.YES, analyze = Analyze.NO) */ required string surname = 3; /* @Field(index=Index.YES, store = Store.YES, analyze = Analyze.NO) */ optional int32 age = 6; }
在 Data Grid CLI 中,使用带有
--upload=
参数的schema
命令,如下所示:schema --upload=person.proto person.proto
创建名为 ProtoStream 编码的人员 的缓存,并将 Data Grid 配置为索引您的 Protobuf 模式中声明的实体。
以下缓存会索引上一步中的
Person
实体:<distributed-cache name="people"> <encoding media-type="application/x-protostream"/> <indexing> <indexed-entities> <indexed-entity>org.infinispan.example.Person</indexed-entity> </indexed-entities> </indexing> </distributed-cache>
在 CLI 中,使用带有
--file=
参数的create cache
命令,如下所示:create cache --file=people.xml people
在缓存中添加条目。
要查询远程缓存,需要包含一些数据。在本例中,创建使用以下 JSON 值的条目:
PersonOne
{ "_type":"org.infinispan.example.Person", "id":1, "name":"Person", "surname":"One", "age":44 }
PersonTwo
{ "_type":"org.infinispan.example.Person", "id":2, "name":"Person", "surname":"Two", "age":27 }
PersonThree
{ "_type":"org.infinispan.example.Person", "id":3, "name":"Person", "surname":"Three", "age":35 }
在 CLI 中,使用
put
命令和--file=
参数来添加每个条目,如下所示:put --encoding=application/json --file=personone.json personone
提示在 Data Grid Console 中,在使用自定义 类型为 JSON 格式添加值时,您必须为 Value 内容类型 字段选择 Custom Type。
查询您的远程缓存。
通过 CLI,从远程缓存的上下文中使用
query
命令。query "from org.infinispan.example.Person p WHERE p.name='Person' ORDER BY p.age ASC"
查询会返回所有带有以升序与
Person
匹配的条目。
其他资源
3.3. 使用带有远程缓存的分析器
分析器将输入数据转换为您可以索引和查询的术语。您可以使用 Java 类中的 @Field
注释或直接在 Protobuf 模式中指定分析器定义。
流程
-
包含
analyzee.YES
属性,以指示分析属性。 -
使用
@Analyzer
注释指定分析器定义。
protobuf 模式
/* @Indexed */ message TestEntity { /* @Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "keyword")) */ optional string id = 1; /* @Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "simple")) */ optional string name = 2; }
Java 类
@ProtoDoc("@Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = \"keyword\"))") @ProtoField(1) final String id; @ProtoDoc("@Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = \"simple\"))") @ProtoField(2) final String description;
3.3.1. 默认分析器定义
Data Grid 提供了一组默认的分析器定义。
定义 | 描述 |
---|---|
| 将文本字段分成令牌,将空格和分页视为分隔符。 |
| Tokenize input 流通过非字母限制,然后将所有字母转换为小写字符。空格和非字母字符将被丢弃。 |
| 在空格上分割文本流,并将非空格字符的序列返回为令牌。 |
| 将整个文本字段视为单个令牌。 |
| 使用 Snowball Porter 过滤器的英语词。 |
| 生成默认大小为 3 个 gram 的 n-gram 令牌。 |
|
将文本字段分成大于 |
这些分析器定义基于 Apache Lucene,并提供了"as-is"。有关令牌化器、过滤器和 CharFilters 的更多信息,请参阅适当的 Lucene 文档。
3.3.2. 创建自定义分析器定义
创建自定义分析器定义并将其添加到 Data Grid Server 安装中。
先决条件
如果 Data Grid Server 正在运行,则停止它。
Data Grid Server 仅在启动时加载类。
流程
-
实施
ProgrammaticSearchMappingProvider
API。 在以下文件中使用完全限定类(FQN)在 JAR 中打包您的实现:
META-INF/services/org.infinispan.query.spi.ProgrammaticSearchMappingProvider
-
将 JAR 文件复制到您的 Data Grid Server 安装的
server/lib
目录中。 - 启动 Data Grid Server。
ProgrammaticSearchMappingProvider
示例
import org.apache.lucene.analysis.core.LowerCaseFilterFactory; import org.apache.lucene.analysis.core.StopFilterFactory; import org.apache.lucene.analysis.standard.StandardFilterFactory; import org.apache.lucene.analysis.standard.StandardTokenizerFactory; import org.hibernate.search.cfg.SearchMapping; import org.infinispan.Cache; import org.infinispan.query.spi.ProgrammaticSearchMappingProvider; public final class MyAnalyzerProvider implements ProgrammaticSearchMappingProvider { @Override public void defineMappings(Cache cache, SearchMapping searchMapping) { searchMapping .analyzerDef("standard-with-stop", StandardTokenizerFactory.class) .filter(StandardFilterFactory.class) .filter(LowerCaseFilterFactory.class) .filter(StopFilterFactory.class); } }
第 4 章 查询嵌入式缓存
当您将 Data Grid 作为库添加到自定义应用程序时,请使用嵌入式查询。
内嵌查询不需要 protobuf 映射。索引和查询都在 Java 对象之上完成。
4.1. 查询嵌入式缓存
本节介绍如何使用名为"books"的示例缓存查询嵌入式缓存,该缓存存储了索引的书签实例。
在本例中,每个书签实例都定义了索引哪些属性,并使用 Hibernate Search 注解指定一些高级索引选项,如下所示:
Book.java
package org.infinispan.sample; import java.time.LocalDate; import java.util.HashSet; import java.util.Set; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.*; // Annotate values with @Indexed to add them to indexes // Annotate each fields according to how you want to index it @Indexed public class Book { @FullTextField String title; @FullTextField String description; @KeywordField String isbn; @GenericField LocalDate publicationDate; @IndexedEmbedded Set<Author> authors = new HashSet<Author>(); }
Author.java
package org.infinispan.sample; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; public class Author { @FullTextField String name; @FullTextField String surname; }
流程
配置 Data Grid 以索引"books"缓存,并将
org.infinispan.sample.Book
指定为索引的实体。<distributed-cache name="books"> <indexing path="${user.home}/index"> <indexed-entities> <indexed-entity>org.infinispan.sample.Book</indexed-entity> </indexed-entities> </indexing> </distributed-cache>
获取缓存。
import org.infinispan.Cache; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; EmbeddedCacheManager manager = new DefaultCacheManager("infinispan.xml"); Cache<String, Book> cache = manager.getCache("books");
对存储在 Data Grid 缓存中的书签实例中执行查询,如下例所示:
// Get the query factory from the cache QueryFactory queryFactory = org.infinispan.query.Search.getQueryFactory(cache); // Create an Ickle query that performs a full-text search using the ':' operator on the 'title' and 'authors.name' fields // You can perform full-text search only on indexed caches Query<Book> fullTextQuery = queryFactory.create("FROM org.infinispan.sample.Book b WHERE b.title:'infinispan' AND b.authors.name:'sanne'"); // Use the '=' operator to query fields in caches that are indexed or not // Non full-text operators apply only to fields that are not analyzed Query<Book> exactMatchQuery=queryFactory.create("FROM org.infinispan.sample.Book b WHERE b.isbn = '12345678' AND b.authors.name : 'sanne'"); // You can use full-text and non-full text operators in the same query Query<Book> query=queryFactory.create("FROM org.infinispan.sample.Book b where b.authors.name : 'Stephen' and b.description : (+'dark' -'tower')"); // Get the results List<Book> found=query.execute().list();
4.2. 实体映射注解
在您的 Java 类中添加注解,将实体映射到索引。
Hibernate Search API
Data Grid 使用 Hibernate Search API 定义在实体级别索引的精细配置。此配置包括注解哪些字段、应该使用分析器、如何映射嵌套对象等。
以下小节提供了适用于用于 Data Grid 的实体映射注解的信息。
有关这些注解的完整详情,您应该参考 Hibernate Search manual。
@DocumentId
与 Hibernate Search 不同,使用 @DocumentId
将字段标记为标识符不适用于 Data Grid 值;在 Data Grid 中,所有 @Indexed
对象的标识符都是用于存储值的键。您仍然可以使用 @Transformable
、自定义类型和自定义 FieldBridge
实现的组合来自定义如何索引密钥。
@Transformable key
每个值的键还需要进行索引,而键实例必须在 String
中转换。Data Grid 包括一些用于编码常见原语的默认转换例程,但要使用自定义密钥,您必须提供 org.infinispan.query.Transformer
的实现。
通过注解注册密钥转换
您可以使用 org.infinispan.query.Transformable
注解密钥类,您的自定义转换器实现会被自动获取:
@Transformable(transformer = CustomTransformer.class) public class CustomKey { ... } public class CustomTransformer implements Transformer { @Override public Object fromString(String s) { ... return new CustomKey(...); } @Override public String toString(Object customType) { CustomKey ck = (CustomKey) customType; return ... } }
通过缓存索引配置注册密钥转换
在嵌入式和服务器配置中使用 key-transformers
xml 元素:
<replicated-cache name="test"> <indexing auto-config="true"> <key-transformers> <key-transformer key="com.mycompany.CustomKey" transformer="com.mycompany.CustomTransformer"/> </key-transformers> </indexing> </replicated-cache>
或者,使用 Java 配置 API (embedded 模式):
ConfigurationBuilder builder = ... builder.indexing().enable() .addKeyTransformer(CustomKey.class, CustomTransformer.class);
4.3. 以编程方式映射实体
您可以将实体作为注解 Java 类的替代选择将实体映射到索引。
在以下示例中,我们映射要存储在网格中的对象 Author
,并在两个属性上搜索:
import org.apache.lucene.search.Query; import org.hibernate.search.cfg.Environment; import org.hibernate.search.cfg.SearchMapping; import org.hibernate.search.query.dsl.QueryBuilder; import org.infinispan.Cache; import org.infinispan.configuration.cache.Configuration; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.cache.Index; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.query.CacheQuery; import org.infinispan.query.Search; import org.infinispan.query.SearchManager; import java.io.IOException; import java.lang.annotation.ElementType; import java.util.Properties; SearchMapping mapping = new SearchMapping(); mapping.entity(Author.class).indexed() .property("name", ElementType.METHOD).field() .property("surname", ElementType.METHOD).field(); Properties properties = new Properties(); properties.put(Environment.MODEL_MAPPING, mapping); properties.put("hibernate.search.[other options]", "[...]"); Configuration infinispanConfiguration = new ConfigurationBuilder() .indexing().index(Index.NONE) .withProperties(properties) .build(); DefaultCacheManager cacheManager = new DefaultCacheManager(infinispanConfiguration); Cache<Long, Author> cache = cacheManager.getCache(); SearchManager sm = Search.getSearchManager(cache); Author author = new Author(1, "Manik", "Surtani"); cache.put(author.getId(), author); QueryBuilder qb = sm.buildQueryBuilderForClass(Author.class).get(); Query q = qb.keyword().onField("name").matching("Manik").createQuery(); CacheQuery cq = sm.getQuery(q, Author.class); assert cq.getResultSize() == 1;
第 5 章 创建持续查询
应用程序可以注册监听程序,以接收与查询过滤器匹配的缓存条目的持续更新。
5.1. 持续查询
持续查询为应用程序提供有关查询过滤的数据的实时通知。当条目与查询数据平面将更新的数据发送到任何监听程序时,这提供了事件流,而不是需要执行查询的应用程序。
持续查询可以通知应用程序有关传入匹配项、用于已加入集合的值;针对已修改并继续匹配的匹配值,请针对已设置的值进行传出匹配。
例如,持续查询可以通知应用程序所有:
-
18 到 25 之间的年龄,假设
Person
实体具有age
属性,并由用户应用程序更新。 - 交易大于 $2000 的交易。
- F1 竞争器的 lap 速度小于 1:45.00 秒,假设缓存包含 Lap 条目,并在竞争期间输入 laps。
持续查询可以使用除分组、聚合和排序操作之外的所有查询功能。
持续查询的工作方式
持续查询会通知客户端监听程序,并带有以下事件:
join
- 缓存条目与查询匹配。
Update(更新)
- 与查询匹配的缓存条目已更新,仍然与查询匹配。
leave
- 缓存条目不再与查询匹配。
当客户端注册持续查询监听程序时,它会立即接收与查询匹配的任何条目的 加入
事件。每次缓存操作修改与查询匹配的条目时,客户端监听程序都会接收后续的事件。
Data Grid 决定何时向客户端监听程序发送 Join
、Update
或 Leave
事件,如下所示:
- 如果旧值和新值的查询不匹配,Data Grid 不会发送事件。
-
如果旧值上的查询不匹配,但新值确实会发送
加入
事件。 -
如果旧值和新值的查询都匹配,Data Grid 会发送
Update
事件。 -
如果旧值的查询匹配但新值不匹配,Data Grid 会发送
Leave
事件。 -
如果对旧值的查询匹配,且条目随后被删除或过期,Data Grid 会发送
Leave
事件。
5.1.1. 持续查询和数据平面性能
持续查询为应用程序提供恒定的更新流,生成大量事件。Data Grid 会临时为它生成的每个事件分配内存,这可能会导致内存压力,并可能导致 OutOfMemoryError
异常,特别是远程缓存。因此,您应该仔细设计您的持续查询以避免任何性能影响。
Data Grid 强烈建议您将持续查询的范围限制为您需要的最小信息量。要达到此目的,您可以使用 projections 和 predicates。例如,以下语句只提供有关与条件匹配而不是整个条目的字段子集的结果:
SELECT field1, field2 FROM Entity WHERE x AND y
确保您创建的每个 ContinuousQueryListener
都可以快速处理所有接收的事件,而不阻断线程。要达到此目的,您应该避免任何不必要的生成事件的缓存操作。
5.2. 创建持续查询
您可以为远程和嵌入式缓存创建持续查询。
流程
-
创建
Query
对象。 通过调用适当的方法来获取缓存的
ContinuousQuery
对象:-
远程缓存:
org.infinispan.client.hotrod.Search.getContinuousQuery (RemoteCache<K, V> cache)
-
嵌入式缓存:
org.infinispan.query.Search.getContinuousQuery (Cache<K, V> cache)
-
远程缓存:
注册查询和
ContinuousQueryListener
对象,如下所示:continuousQuery.addContinuousQueryListener(query, listener);
当您不再需要持续查询时,按如下所示删除监听程序:
continuousQuery.removeContinuousQueryListener(listener);
持续查询示例
以下代码示例演示了带有嵌入式缓存的简单持续查询。
在本例中,监听器会在 21 年龄下的任何 Person
实例添加到缓存中时收到通知。这些 Person
实例也添加到"匹配"映射中。当从缓存中删除条目或其年龄大于或等于 21 时,它们会从 "matches" 映射中删除。
注册持续查询
import org.infinispan.query.api.continuous.ContinuousQuery; import org.infinispan.query.api.continuous.ContinuousQueryListener; import org.infinispan.query.Search; import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.dsl.Query; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; [...] // We have a cache of Person objects. Cache<Integer, Person> cache = ... // Create a ContinuousQuery instance on the cache. ContinuousQuery<Integer, Person> continuousQuery = Search.getContinuousQuery(cache); // Define a query. // In this example, we search for Person instances under 21 years of age. QueryFactory queryFactory = Search.getQueryFactory(cache); Query query = queryFactory.create("FROM Person p WHERE p.age < 21"); final Map<Integer, Person> matches = new ConcurrentHashMap<Integer, Person>(); // Define the ContinuousQueryListener. ContinuousQueryListener<Integer, Person> listener = new ContinuousQueryListener<Integer, Person>() { @Override public void resultJoining(Integer key, Person value) { matches.put(key, value); } @Override public void resultUpdated(Integer key, Person value) { // We do not process this event. } @Override public void resultLeaving(Integer key) { matches.remove(key); } }; // Add the listener and the query. continuousQuery.addContinuousQueryListener(query, listener); [...] // Remove the listener to stop receiving notifications. continuousQuery.removeContinuousQueryListener(listener);
第 6 章 监控和调优数据平面查询
Data Grid 会公开查询的统计信息,并提供您可以调整的属性以提高查询性能。
6.1. 获取查询统计信息
收集统计信息,以收集有关索引和查询性能的信息,包括索引类型和查询完成的平均时间等信息。
流程
执行以下操作之一:
-
调用
getSearchStatistics ()
或getClusteredSearchStatistics ()
方法进行嵌入式缓存。 -
使用
GET
请求从 REST API 获取远程缓存的统计信息。
嵌入式缓存
// Statistics for the local cluster member SearchStatistics statistics = Search.getSearchStatistics(cache); // Consolidated statistics for the whole cluster CompletionStage<SearchStatisticsSnapshot> statistics = Search.getClusteredSearchStatistics(cache)
远程缓存
GET /v2/caches/{cacheName}/search/stats
6.2. 调整查询性能
使用以下指南帮助您提高索引操作和查询的性能。
检查索引用量统计
针对部分索引缓存查询返回较慢的结果。例如,如果没有注解模式中的一些字段,则生成的索引不包括这些字段。
通过检查每种查询运行所需的时间,开始调整查询性能。如果您的查询似乎较慢,您应该确保查询正在使用索引缓存,并且所有实体和字段映射都被索引。
调整索引的提交间隔
索引可以降低 Data Grid 集群的写入吞吐量。commit-interval
属性定义在内存中缓冲的索引更改要刷新到索引存储和执行提交之间的间隔(以毫秒为单位)。
此操作非常昂贵,因此您应该避免配置太小的时间间隔。默认值为 1000 毫秒(1 秒)。
为查询调整刷新间隔
refresh-interval
属性定义索引读取器刷新的时间间隔(以毫秒为单位)。
默认值为 0,
它会在将查询写入缓存后立即返回查询中的数据。
大于 0
的值会导致一些过时的查询结果,但会显著提高吞吐量,特别是在写密集型场景中。如果您不需要在查询被写入后马上返回数据,您应该调整刷新间隔以提高查询性能。