
可视化数据结构并不容易,我相信我们展示在 JBoss World 2011 主旨演讲 中广受欢迎的演示的成功,很大程度上归功于多个大屏幕上展示的优秀的Web界面。这些Web应用有效地可视化了流动的推文、在标签云中突出的投票标签,以及动画化的Infinispan网格,当节点在一个理想化的hashweel上跳舞时,可视化节点间的数据分布。
因此,我相信在场的每个人都清楚地知道数据存储在Infinispan中,通过现场拔掉一台随机服务器,大家都能看到数据重新组织,这看起来是一种简单自然的方式处理大量数据。并不是所有的技术细节都得到了解释,因此在这篇和后续的博文中,我们将详细说明你 没有 看到的内容:数据是如何存储的,Drools如何过滤数据,所有可视化如何加载存储的网格数据,以及如何仍然在创纪录的时间内开发完成?
网格上的JPA
所有这些应用都只是使用了JPA:Java持久化API。想想这个名字:它显然是为了解决Java应用程序的所有持久化需求;实际上,虽然它传统上与JDBC数据库相关联,但我们刚刚展示了它并不一定局限于这些数据库:我们的应用程序运行的是Hibernate对象/网格映射器的早期预览: Hibernate OGM,简单的JPA模型映射到Infinispan,一个键/值存储。
收集需求
最初的计划不包括Hibernate OGM,因为它当时非常实验性,从未发布过,甚至没有打上标签。但很明确的是,我们想使用Infinispan:用于存储和搜索推文。Kevin Conner是负责设想技术需求的架构师,他成功推动每个参与的开发者完成各自的部分,并在创纪录的时间内将其整合成一个工作的应用程序;因此,他带着一个简单的需求列表找到了Emmanuel和我。
- 我们希望展示Infinispan。
- 我们希望实时存储大量来自Twitter直播流的推文。
- 我们需要能够按时间顺序遍历它们,回滚流并重新处理(如您在演示视频中看到的那样,我们有一个虚假的作弊者,希望在第二阶段应用更严格的规则来过滤无效的推文,而不会丢失最初收集的推文)。
- 我们需要知道哪些项目被投票最多:人们将通过在推文中使用话题标签来表达他们的偏好。
- 我们想知道谁投票最多。
- 它必须性能良好,可能在大量数据上。
使用Lucene。
因此,为了实现这些搜索需求,你必须考虑到,由于Infinispan是一个键/值存储,执行查询并不像在数据库上那么自然。Infinispan目前提供了两种主要方法:使用查询模块或定义一些简单的Map/Reduce任务。
此外,考虑到这些要求。使用SQL,我们该如何统计包含特定话题标签的所有推文,提取所有收集话题标签的计数,并按频率排序?在一个关系型数据库中,这将是一个非常低效的查询,至少涉及全表扫描,可能每个话题标签都要进行一次扫描,并且需要预先列出要查找的话题标签列表。我们希望提取最常提及的标签,我们实际上不知道要查找什么,因为人们可以自由投票。
一个完全不同的方法是使用倒排索引:每次保存推文时,你都会对其进行分词,提取所有术语,并保持一个包含指向包含推文的指针的术语列表,并存储频率。这正是全文搜索引擎如Lucene的工作方式;此外,Lucene能够应用最先进的优化、缓存和过滤功能。我们的Infinispan查询和Hibernate Search都提供了与Lucene的便捷集成(它们实际上使用的是同一个引擎,一个针对Infinispan用户,另一个针对Hibernate和JPA用户)。
统计谁投票最多的问题在技术上与统计词频的问题类似,因此,Lucene再次是完美的选择。按时间戳对所有数据进行排序不是引入Lucene的好理由,但它仍然能做得很好,因此Lucene确实解决了这个应用程序的所有查询需求。
Hibernate OGM与Hibernate Search
因此,Infinispan查询可能是一个很好的选择。但我们选择了Hibernate OGM与Search,因为它们将提供相同的索引功能,而且在JPA接口之上。我还必须承认,Hibernate OGM最初被摒弃,因为它缺少HQL查询解析器:是我的错,因为我晚于实现它,但在这种情况下,这不是问题,因为所有我们需要的查询都通过全文查询得到了更好的解决,这些查询不是通过HQL定义的。
模型
那么我们的模型是什么样的呢?非常简单,它是一个单一的JPA实体,增强了Hibernate Search注解。
@Indexed(index = "tweets") @Analyzer(definition = "english") @AnalyzerDef(name = "english", tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class), @TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = StopFilterFactory.class, params = { @Parameter(name = "words", value = "stoplist.properties"), @Parameter(name = "resource_charset", value = "UTF-8"), @Parameter(name = "ignoreCase", value = "true") }) }) @Entity public class Tweet { private String id; private String message = ""; private String sender = ""; private long timestamp = 0L; public Tweet() {} public Tweet(String message, String sender, long timestamp) { this.message = message; this.sender = sender; this.timestamp = timestamp; } @Id @GeneratedValue(generator = "uuid") @GenericGenerator(name = "uuid", strategy = "uuid2") public String getId() { return id; } public void setId(String id) { this.id = id; } @Field public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Field(index=Index.UN_TOKENIZED) public String getSender() { return sender; } public void setSender(String sender) { this.sender = sender; } @Field @NumericField public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } }
请注意标识符的 uuid 生成器:在分布式环境中,目前这是最有效的选择。除了标准的 @Entity 以外,@Indexed 启用了 Lucene 索引引擎,@AnalyzerDef 和 Analyzer 指定了我们想要应用于索引推文的文本清理方式,@Field 选择要索引的属性,@NumericField 确保数值排序会高效执行,将索引值真正视为数字而不是额外的关键词:始终记住 Lucene 专注于自然语言匹配。
示例查询
由于我正在扩展所有功能以提高清晰度,所以这看起来可能有点冗长
public List<Tweet> allTweetsSortedByTime() { //this is needed only once but we want to show it in this context: QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity( Tweet.class ).get(); //Define a Lucene query which is going to return all tweets: Query query = queryBuilder.all().createQuery(); //Make a JPA Query out of it: FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery( query ); //Currently needed to have Hibernate Search work with OGM: fullTextQuery.initializeObjectsWith( ObjectLookupMethod.PERSISTENCE_CONTEXT, DatabaseRetrievalMethod.FIND_BY_ID ); //Specify the desired sort: fullTextQuery.setSort( new Sort( new SortField( "timestamp", SortField.LONG ) ) ); //Run the query (or alternatively open a scrollable result): return fullTextQuery.getResultList(); }
下载它
要查看完整的示例,我已经将一个完整的 Maven 项目推送到 github。它包括所有查询的测试以及运行项目所需的所有详细信息,例如 Infinispan 和 JGroups 配置,以及启用 HibernateOGM 所需的 persistence.xml。
请克隆它以开始使用 OGM:https://github.com/Sanne/tweets-ogm
并在 IRC、Hibernate Search 论坛 或全新的 Hibernate OGM 论坛 上寻求任何解释。
实体如何在网格中持久化?
Emmanuel 将很快撰写关于此主题的博客,请关注博客!更新:已发布 OGM 博客