gitweixin
  • 首页
  • 小程序代码
    • 资讯读书
    • 工具类
    • O2O
    • 地图定位
    • 社交
    • 行业软件
    • 电商类
    • 互联网类
    • 企业类
    • UI控件
  • 大数据开发
    • Hadoop
    • Spark
    • Hbase
    • Elasticsearch
    • Kafka
    • Flink
    • 数据仓库
    • 数据挖掘
    • flume
    • Kafka
    • Hive
    • shardingsphere
    • solr
  • 开发博客
    • Android
    • php
    • python
    • 运维
    • 技术架构
    • 数据库
  • 程序员网赚
  • bug清单
  • 量化投资
  • 在线查询工具
    • 去行号
    • 在线时间戳转换工具
    • 免费图片批量修改尺寸在线工具
    • SVG转JPG在线工具

分类归档solr

精品微信小程序开发门户,代码全部亲测可用

  • 首页   /  大数据开发
  • 分类归档: "solr"
Elasticsearch, solr 10月 4,2024

Lucene、Elasticsearch和Solr在快速查询中的选择研究

第一章 相关理论

1.1 搜索引擎概述

1.1.1 搜索引擎的基本原理

搜索引擎的核心工作原理涉及一系列复杂的过程,从网页抓取到索引构建,再到查询处理和结果排序。这一流程确保了用户能够高效、准确地获取所需信息。搜索引擎通过爬虫程序自动抓取互联网上的网页内容,这些爬虫遵循特定的算法和规则,不断地遍历和更新网页数据。抓取到的网页数据随后被送入索引构建阶段,此阶段通过分词、建立倒排索引等技术手段,为后续的查询服务奠定基础。当用户输入查询关键词时,搜索引擎依据已建立的索引进行快速匹配,并结合相关性排序算法,将最符合用户需求的搜索结果呈现在用户面前]。

1.1.2 搜索引擎的分类

搜索引擎可根据其工作方式和特点分为多种类型,其中全文搜索引擎、目录搜索引擎和元搜索引擎是主要的三种。全文搜索引擎,如Google和Baidu,通过全面索引网页的文本内容来提供广泛的搜索服务。这类搜索引擎能够深入理解网页内容,并根据用户查询的关键词返回相关结果。目录搜索引擎,如Yahoo,则依赖人工编辑的分类目录来提供搜索结果,这种方式虽然覆盖范围有限,但往往能提供更精准、更专业的信息。而元搜索引擎则整合了多个搜索引擎的资源和服务,通过统一的查询接口为用户提供更全面的搜索结果[。

1.1.3 搜索引擎的发展历程

搜索引擎技术的发展经历了多个阶段,从最初的简单文本搜索到现在基于深度学习的语义搜索,每一步技术革新都为用户带来了更优质的搜索体验。早期的搜索引擎主要依赖关键词匹配和基本的排序算法来提供查询服务。随后,基于超链分析的PageRank算法的出现,极大地提高了搜索结果的准确性和相关性。近年来,随着深度学习技术的不断发展,搜索引擎开始融入语义理解、用户意图识别等高级功能,使得搜索结果更加智能化和个性化。这些技术进步不仅提升了搜索引擎的性能,也推动了整个信息检索领域的持续发展。

1.2 Lucene搜索引擎

1.2.1 Lucene的架构设计

Lucene,作为一款高性能、可扩展的信息检索(IR)库,以其灵活的架构设计和强大的功能吸引了众多开发者的关注。其架构设计采用了模块化思想,将不同功能划分为独立的模块,主要包括索引模块、查询模块和存储模块等。这种设计方式不仅提高了系统的可维护性,还为开发者提供了自定义扩展和优化的空间。索引模块负责构建和维护索引,是Lucene实现快速查询的核心;查询模块则提供了丰富的查询方式,满足用户多样化的查询需求;存储模块则负责数据的持久化存储,确保数据的安全性和可靠性。

在Lucene的架构中,各个模块之间通过明确定义的接口进行交互,降低了模块间的耦合度,提高了系统的整体稳定性。同时,Lucene还提供了丰富的API和文档,方便开发者快速上手并集成到自己的应用中。这些特点使得Lucene成为了众多搜索引擎和信息检索系统的首选方案。

1.2.2 Lucene的索引机制

Lucene的快速查询能力得益于其高效的索引机制。Lucene采用倒排索引技术来构建索引,这是一种将文档中的词汇与包含这些词汇的文档列表相关联的数据结构[。通过倒排索引,Lucene可以迅速定位到包含特定词汇的文档,从而实现快速查询。此外,Lucene还支持增量索引和批量索引,以适应不同规模的数据集。增量索引允许在原有索引的基础上添加新的文档,而无需重新构建整个索引;批量索引则适用于大规模数据的一次性索引构建,提高了索引构建的效率。

在构建倒排索引时,Lucene会对文档进行分词处理,将文档拆分为一个个独立的词汇。为了提高查询的准确性,Lucene还支持对词汇进行各种处理,如去除停用词、词形还原等。这些处理步骤有助于减少索引的大小,提高查询的效率和准确性。

1.2.3 Lucene的查询方式

Lucene提供了丰富的查询方式,以满足用户在不同场景下的查询需求。这些查询方式包括精确查询、短语查询、布尔查询、通配符查询和模糊查询等[。精确查询要求用户输入的查询词与文档中的词汇完全匹配;短语查询则允许用户输入一个短语,Lucene会返回包含该短语的文档;布尔查询允许用户使用布尔运算符(如AND、OR、NOT)来组合多个查询条件;通配符查询支持使用通配符(如*、?)来匹配文档中的词汇;模糊查询则允许用户输入一个近似的查询词,Lucene会返回与该词相似的文档。

这些多样化的查询方式为用户提供了极大的灵活性,使得他们可以根据具体需求选择合适的查询方式。同时,Lucene还提供了查询结果的排序功能,用户可以根据相关性、时间等因素对查询结果进行排序,以获取更符合需求的查询结果。这些特点使得Lucene在信息检索领域具有广泛的应用前景。

1.3 Elasticsearch搜索引擎

1.3.1 Elasticsearch的分布式架构

Elasticsearch是一个基于Lucene构建的分布式搜索引擎,其设计初衷就是为了解决大规模数据的实时搜索问题。它通过分布式架构,能够轻松地在多台服务器上并行处理数据,从而显著提高查询效率。这种架构不仅保证了系统的高可用性,还使得Elasticsearch能够轻松应对数据量的不断增长[。

在Elasticsearch的分布式架构中,数据被分散到多个节点上,每个节点都负责存储和处理一部分数据。这种设计方式不仅提高了数据的处理速度,还增强了系统的容错能力。当一个节点发生故障时,其他节点可以继续提供服务,保证搜索引擎的稳定运行。

1.3.2 Elasticsearch的索引机制

Elasticsearch继承了Lucene的索引机制,即采用倒排索引技术来构建索引。这种索引方式将文档中的词汇与包含这些词汇的文档列表相关联,从而实现了快速查询。在Elasticsearch中,索引被进一步分解为多个分片,每个分片都是一个独立的Lucene索引。这种设计方式使得Elasticsearch能够并行处理多个查询请求,提高了查询吞吐量[。

Elasticsearch还支持多种数据类型和复杂的查询操作。用户可以定义自己的映射规则,将不同类型的数据映射到不同的字段上。同时,Elasticsearch还提供了丰富的查询API,支持全文搜索、精确查询、范围查询等多种查询方式,满足了用户的多样化需求。

1.3.3 Elasticsearch的查询方式

Elasticsearch提供了多种灵活且强大的查询方式。其中,全文搜索是Elasticsearch最为核心的功能之一。它允许用户在整个数据集中进行关键词搜索,并且能够根据相关性对结果进行排序。此外,Elasticsearch还支持精确查询,即根据指定的字段值进行精确匹配;范围查询,即根据指定的范围条件进行筛选;以及布尔查询,即组合多个查询条件进行复杂查询等[。

除了基本的查询方式外,Elasticsearch还支持地理位置查询和正则表达式查询等高级功能。地理位置查询允许用户根据地理位置信息进行搜索,例如查找某个区域内的所有文档。正则表达式查询则允许用户使用正则表达式模式匹配文本内容,从而实现更为复杂的文本搜索需求。

1.3.4 Elasticsearch的扩展性

Elasticsearch具有良好的扩展性,能够在集群环境中轻松扩展以处理更大的数据集。它支持水平扩展和垂直扩展两种方式。水平扩展是指通过增加更多的节点来扩展集群的规模和处理能力;而垂直扩展则是指通过提升单个节点的性能来提高整个集群的处理能力[。

在Elasticsearch中,集群的扩展过程非常简单且灵活。用户只需要按照官方文档提供的步骤进行操作,即可轻松地将新的节点加入到集群中。同时,Elasticsearch还提供了丰富的监控和管理工具,帮助用户实时了解集群的状态和性能情况,以便及时进行调整和优化。

1.4 Solr搜索引擎

1.4.1 Solr的架构设计

Solr,一个基于Lucene构建的开源搜索服务器,以其丰富的搜索功能和管理界面在搜索引擎领域占据了一席之地。其架构设计特别注重可伸缩性和可扩展性,使得Solr能够轻松应对大规模数据集的搜索需求。通过支持分布式索引和查询,Solr能够在多台服务器上并行处理数据,从而显著提高查询效率[。

Solr的架构不仅灵活,而且易于扩展。它允许用户根据实际需求自定义扩展和优化,以满足各种复杂的搜索场景。这种模块化设计使得Solr能够轻松集成到各种应用系统中,提供高效、准确的搜索服务[。

1.4.2 Solr的索引机制

Solr的索引机制与Lucene紧密相关,它采用了倒排索引技术来构建索引。这种技术将文档中的词汇与包含这些词汇的文档列表相关联,从而实现快速、准确的查询。倒排索引的构建过程包括词汇分析、文档编号分配、倒排列表生成等步骤,这些步骤共同保证了Solr的高效查询性能[。

除了基本的倒排索引技术外,Solr还支持实时索引和增量索引。实时索引允许用户将新文档立即添加到索引中,使得新内容能够立即被搜索到。而增量索引则允许用户在现有索引的基础上逐步添加新文档,而无需重新构建整个索引。这些功能使得Solr能够满足用户对实时性的要求,同时保持高效的查询性能[。

1.4.3 Solr的查询方式

Solr提供了多种查询方式,以满足用户的不同需求。其中包括全文搜索、精确查询、范围查询和布尔查询等。全文搜索允许用户在整个文档集中搜索包含特定词汇的文档,而精确查询则要求搜索结果与查询条件完全匹配。范围查询允许用户指定一个范围来搜索符合条件的文档,而布尔查询则允许用户使用逻辑运算符来组合多个查询条件[。

Solr还支持高亮显示和分面搜索等高级功能。高亮显示能够将搜索结果中的关键词以醒目方式显示出来,提高用户的阅读体验。而分面搜索则允许用户根据文档的多个属性进行筛选和排序,从而快速找到符合需求的文档]。

1.4.4 Solr的特点

Solr以其强大的搜索功能和管理界面而著称。它提供了丰富的配置选项和工具,使得用户可以轻松部署和维护搜索服务器。同时,Solr还支持多种数据格式和协议,能够与其他系统进行无缝集成。这些特点使得Solr成为企业级搜索解决方案的首选之一[。

Solr的另一个显著特点是其可扩展性。通过支持分布式部署和水平扩展,Solr能够轻松应对不断增长的数据量和查询负载。用户可以根据需要增加或减少服务器节点,以保持搜索服务的高可用性和性能]。这种灵活性使得Solr能够适应各种规模和复杂度的搜索场景。

第二章 Lucene、Elasticsearch和Solr快速查询比较

2.1 查询速度比较

在对比Lucene、Elasticsearch和Solr的查询速度时,我们发现Elasticsearch和Solr通常表现出更优越的性能。这一优势主要源于它们在Lucene的核心技术上所做的优化和改进,从而提供了更高效的查询机制和算法。Elasticsearch和Solr不仅继承了Lucene强大的索引和搜索功能,还针对分布式环境和大规模数据处理进行了专门的优化,因此在处理复杂查询和大数据集时能够保持较高的响应速度。

查询速度并非仅由搜索引擎本身的技术特性决定,还受到多种外部因素的影响。例如,数据量的大小直接关系到索引的构建时间和查询效率。在数据量较小的情况下,Lucene、Elasticsearch和Solr之间的查询速度差异可能并不明显;但随着数据量的增加,Elasticsearch和Solr的分布式架构优势逐渐显现,能够更好地应对大规模数据的查询需求。

索引结构的设计也对查询速度产生重要影响。合理的索引结构能够显著提高查询效率,减少不必要的计算和数据扫描。Lucene提供了灵活的索引构建方式,但要求开发者具备一定的专业知识和经验;相比之下,Elasticsearch和Solr在索引管理方面提供了更为丰富的功能和工具,帮助用户更容易地创建和维护高效的索引结构。

查询复杂度是另一个不可忽视的因素。不同类型的查询(如精确查询、模糊查询、全文搜索等)对搜索引擎的性能要求各不相同。在某些特定类型的查询中,Lucene可能表现出与Elasticsearch和Solr相当甚至更好的性能。因此,在选择搜索引擎时,需要根据实际应用场景中的查询需求进行综合考虑。

虽然Elasticsearch和Solr在查询速度上通常优于Lucene,但具体性能仍然受到数据量、索引结构和查询复杂度等多种因素的共同影响。在实际应用中,我们需要根据具体需求和场景来选择合适的搜索引擎,以达到最佳的查询效果和性能表现。

为了更全面地评估Lucene、Elasticsearch和Solr在快速查询方面的性能,未来研究可以进一步探讨它们在不同数据集、索引策略和查询负载下的表现。通过实验数据和案例分析,我们可以为搜索引擎的选择和优化提供更具体的指导和建议。同时,随着技术的不断发展,我们也需要关注这些搜索引擎在应对新兴挑战(如、大规模实时数据处理多模态搜索等)方面的最新进展和趋势。

2.2 索引速度比较

Lucene、Elasticsearch和Solr在索引速度方面的表现均令人瞩目。作为底层引擎,Lucene凭借其高效的索引能力为信息检索领域奠定了坚实基础。Elasticsearch和Solr则在Lucene的基石上进行了进一步的扩展与优化,从而实现了索引速度的再度提升。

Lucene的索引速度得益于其精巧的架构设计以及优化的索引机制。通过采用倒排索引技术,Lucene能够迅速地将文档中的词汇与包含这些词汇的文档列表相关联,进而在构建索引时展现出卓越的性能。此外,Lucene还支持增量索引和批量索引,这使得它能够灵活应对不同规模的数据集,在保持高效索引的同时,也确保了数据的实时性。

Elasticsearch在继承Lucene索引机制的基础上,通过引入分布式架构进一步提升了索引速度。其分布式特性使得Elasticsearch能够在多台服务器上并行处理数据,从而显著提高了索引的创建和更新效率。同时,Elasticsearch还支持多种数据类型和复杂的查询操作,这使得它在处理大规模实时数据时能够游刃有余。

Solr同样在Lucene的基础上进行了优化,特别注重于提升索引的实时性和增量更新能力。通过采用与Lucene相似的倒排索引技术,并结合实时索引和增量索引的支持,Solr能够确保用户在对数据进行实时更新时,仍然能够保持高效的索引速度。这一特性对于需要频繁更新数据集的应用场景而言,无疑具有极大的吸引力。

尽管Lucene、Elasticsearch和Solr在索引速度方面均表现出色,但具体的索引速度仍然受到多种因素的影响。例如,硬件配置的高低将直接影响到索引的创建和更新效率。在高性能的硬件环境下,这些搜索引擎能够更充分地发挥其索引速度的优势。此外,数据量的大小以及索引策略的选择也会对索引速度产生显著影响。对于大规模数据集而言,合理的索引策略能够显著提高索引效率,降低索引过程中的时间消耗。

Lucene、Elasticsearch和Solr在索引速度方面的优异表现得益于其各自独特的架构设计和优化策略。在实际应用中,用户应根据具体需求和场景选择合适的搜索引擎,并结合硬件配置、数据量以及索引策略等因素进行综合考虑,以实现最佳的索引效果。

2.3 可扩展性比较

在搜索引擎技术中,可扩展性是一个至关重要的考量因素,尤其当面对日益增长的数据量和查询请求时。Elasticsearch和Solr,作为基于Lucene的搜索引擎,均展现出了在可扩展性方面的优势,这些优势主要体现在分布式架构、高可用性以及可配置性上。

Elasticsearch的分布式架构允许其在多台服务器上并行处理数据。这种架构不仅提高了系统的处理能力,还增强了可靠性。通过分片技术,Elasticsearch能够将索引分割成多个部分,并分散存储在不同的节点上,从而实现了数据的水平扩展。当需要增加处理能力时,只需简单地添加更多节点即可。此外,Elasticsearch还提供了丰富的API和插件支持,使得开发者能够根据需要灵活配置和扩展系统功能。

Solr同样具备出色的可扩展性。其架构设计注重可伸缩性和可扩展性,能够轻松应对大规模数据集的搜索需求。Solr支持分布式索引和查询,使得系统能够随着数据量的增长而平滑扩展。与Elasticsearch相似,Solr也提供了丰富的配置选项和插件支持,以满足不同场景下的搜索需求。Solr还具备强大的容错能力,能够在部分节点故障时保证系统的正常运行,进一步提高了其可用性。

Lucene作为底层的搜索引擎库,虽然提供了高性能的索引和查询功能,但在可扩展性方面稍显不足。Lucene本身并不直接支持分布式架构,需要开发者自行实现数据的分布式处理和索引的分片管理。这增加了开发复杂性和维护成本,也使得Lucene在面对超大规模数据集时可能面临挑战。

Lucene的可扩展性限制并不意味着它在所有场景下都不适用。对于中小型规模的数据集或特定领域的搜索需求,Lucene仍然是一个高效且灵活的选择。此外,通过合理的架构设计和优化,开发者也可以在Lucene基础上构建出具备良好可扩展性的搜索系统。

Elasticsearch和Solr在可扩展性方面相较于Lucene具有明显优势。这些优势主要体现在分布式架构、高可用性以及可配置性上,使得它们能够更好地应对日益增长的数据量和查询请求。在选择搜索引擎时,还需根据具体需求和场景进行综合考虑,以确保选择最适合的解决方案。

2.4 其他特性比较

Lucene、Elasticsearch和Solr在查询语法、全文搜索以及用户界面等方面展现出各自独特的特点。

Lucene,作为底层的搜索库,提供了基础的查询语法,如TermQuery、PhraseQuery等,这些语法允许用户进行精确匹配、短语搜索等操作。同时,Lucene的全文搜索功能也相当强大,它能够通过分词器将文本内容切分为单词或词组,并构建倒排索引以实现高效的全文检索。Lucene在用户界面方面相对简单,主要面向开发人员,需要一定的编程知识才能充分利用其功能。

Elasticsearch在Lucene的基础上进行了扩展,提供了更为丰富的查询类型和高级功能。Elasticsearch的查询DSL(领域特定语言)允许用户以JSON格式编写复杂的查询语句,支持多种查询类型的组合,如bool查询、range查询等。此外,Elasticsearch还支持地理位置查询、聚合查询等高级功能,这些功能使得Elasticsearch在处理复杂搜索需求时表现出色。在用户界面方面,Elasticsearch提供了Kibana这一可视化工具,用户可以通过Kibana轻松地构建仪表盘、监控集群状态以及进行搜索分析等操作。

Solr则提供了基于Lucene的丰富快速搜索查询功能方案和管理界面。Solr的查询语法与Lucene相似,但它在易用性和功能性上进行了增强。例如,Solr支持面搜索(faceted search),这是一种允许用户根据分类或属性对搜索结果进行过滤的功能,大大提高了搜索的灵活性和准确性。同时,Solr的管理界面非常友好,提供了丰富的配置选项和监控工具,使得用户可以轻松地部署和维护搜索服务。此外,Solr还支持多种数据格式和协议的导入,能够与其他系统进行无缝集成,从而满足用户在不同场景下的搜索需求。

Lucene、Elasticsearch和Solr在查询语法、全文搜索以及用户界面等方面各有千秋。Lucene提供了基础的搜索功能,适合作为底层库进行开发;Elasticsearch在Lucene的基础上增加了更多高级功能,适合处理复杂搜索需求;而Solr则注重易用性和管理性,适合作为企业级搜索解决方案。在选择时,用户应根据自身需求和场景进行权衡考虑。

第三章 基于Lucene、Elasticsearch和Solr的快速查询方案

3.1 Lucene快速查询方案

Lucene,作为一款高性能、可扩展的信息检索库,为开发者提供了构建高效搜索引擎的基础。在实现Lucene的快速查询方案时,我们需要从索引构建、查询优化以及性能评估等多个方面进行深入探讨。

在索引构建方面,首先,要明确索引的结构和内容。对于大规模的数据集,我们需要合理地划分索引的粒度,以保证索引的效率和查询的准确性。此外,利用Lucene的增量索引功能,可以实时地更新索引,从而确保查询结果的实时性。为了提高索引的效率,我们还可以考虑使用并行索引技术,将数据分散到多个索引中进行处理。

查询优化是提升Lucene查询速度的关键环节。我们可以从查询语句的构造、查询策略的选择以及查询结果的排序等方面进行优化。具体来说,优化查询语句可以减少不必要的词汇和短语,从而提高查询的精确性和效率;选择合适的查询策略,如布尔查询、短语查询等,可以根据实际需求获取最相关的结果;而合理的排序算法则能够确保用户在最短的时间内找到所需信息。

性能评估是确保快速查询方案有效性的重要手段。我们可以通过对比不同查询策略的执行时间、准确率和召回率等指标,来评估查询方案的优劣。此外,还可以利用Lucene提供的性能监测工具,实时监控查询过程的性能表现,从而及时发现并解决潜在的性能瓶颈。

基于Lucene的快速查询方案需要从索引构建、查询优化和性能评估等多个方面进行综合考虑。通过合理地设计索引结构、优化查询策略以及持续地进行性能评估,我们可以构建出高效、稳定的搜索引擎,为用户提供更加优质的查询体验。

3.2 Elasticsearch快速查询方案

Elasticsearch作为一款功能强大的分布式搜索引擎,其快速查询方案的设计与实施涉及多个关键环节。以下将详细介绍基于Elasticsearch的快速查询方案,涵盖集群配置、索引构建、查询优化等方面。

3.2.1 集群配置

Elasticsearch的集群配置是实现快速查询的基础。首先,需要合理规划集群的拓扑结构,确定主节点、数据节点和协调节点的数量和配置。主节点负责管理集群状态和元数据,数据节点负责存储和检索数据,而协调节点则负责接收客户端请求并协调其他节点完成查询操作。

在配置过程中,应充分考虑硬件资源、网络带宽和数据量等因素,以确保集群的稳定性和性能。此外,还可以通过设置合理的分片策略和副本策略来优化数据存储和查询性能。

3.2.2 索引构建

索引构建是Elasticsearch快速查询方案中的关键环节。为了提高查询效率,需要合理设计索引结构,包括字段类型、分析器和映射等。

在字段类型方面,应根据数据的实际特点选择合适的类型,如文本、关键字、日期等。同时,可以利用分析器对文本字段进行分词处理,以便更好地支持全文搜索功能。

在映射方面,需要定义索引中的字段及其属性,以确保数据的正确存储和检索。此外,还可以通过设置动态映射规则来自动处理新字段的映射问题。

3.2.3 查询优化

查询优化是Elasticsearch快速查询方案中的核心环节。为了提高查询性能,可以采取以下措施:

1、精确查询:尽量避免使用高开销的通配符查询和正则表达式查询,而是使用精确查询来获取特定字段的值。

2、利用过滤器:过滤器可以在不计算评分的情况下过滤文档,从而提高查询效率。在可能的情况下,应尽量使用过滤器而非查询来缩小结果集。

3、分页与滚动:对于大量数据的查询结果,可以采用分页或滚动的方式来逐步获取数据,以减少单次查询的负载。

4、缓存策略:合理利用Elasticsearch的缓存机制,如请求缓存、查询结果缓存等,可以减少重复查询的开销。

5、监控与调优:定期对Elasticsearch集群进行监控和调优,以确保其处于最佳性能状态。这包括检查硬件资源使用情况、调整配置参数、优化索引结构等。

基于Elasticsearch的快速查询方案需要从集群配置、索引构建和查询优化等多个方面进行综合考虑和实施。通过合理规划和设计,可以充分发挥Elasticsearch在快速查询方面的优势,满足用户的高效检索需求。

3.3 Solr快速查询方案

在构建基于Solr的快速查询方案时,我们需要综合考虑集群配置、索引构建、查询优化等多个方面,以确保系统能够满足高性能、高可扩展性和易用性的需求。

Solr支持分布式搜索,因此,集群配置是实现快速查询的关键。我们需要根据数据量和查询负载来合理规划集群的规模,包括节点数量、硬件配置等。同时,我们还需要配置SolrCloud模式,以实现数据的自动分片、冗余复制和负载均衡,提高系统的可用性和容错性。

在集群配置过程中,我们还需要关注网络延迟、数据一致性等问题,以确保集群的稳定性和性能。此外,我们还可以通过配置Solr的监控和日志系统,实时监控集群的状态和性能,及时发现并解决问题。

索引是Solr实现快速查询的基础。在构建索引时,我们需要根据数据的特征和查询需求来选择合适的字段类型、分析器和索引策略。例如,对于文本字段,我们可以选择使用全文搜索引擎来支持复杂的文本搜索;对于数值字段,我们可以选择使用范围查询来支持数值范围的搜索。

我们还需要关注索引的更新和维护问题。Solr支持实时索引和增量索引,我们可以根据数据的更新频率和查询需求来选择合适的索引更新策略。同时,我们还需要定期优化和重建索引,以提高索引的质量和查询性能。

查询优化是实现Solr快速查询的关键环节。在编写查询语句时,我们需要根据数据的特征和查询需求来选择合适的查询类型和语法,以提高查询的准确性和效率。例如,对于精确匹配的需求,我们可以选择使用精确查询;对于模糊匹配的需求,我们可以选择使用模糊查询或通配符查询。

除了查询语句的优化外,我们还可以通过配置Solr的查询缓存、结果高亮、分面搜索等高级功能来进一步提升查询的性能和用户体验。例如,通过配置查询缓存,我们可以缓存热门查询的结果,减少重复计算的开销;通过配置结果高亮,我们可以突出显示查询结果中的关键词,提高用户的阅读体验。

基于Solr的快速查询方案需要综合考虑集群配置、索引构建和查询优化等多个方面。通过合理的规划和优化,我们可以构建一个高性能、高可扩展性和易用的搜索引擎系统,满足用户的多样化查询需求。

作者 east
Elasticsearch, neo4j, solr 3月 22,2022

neo4j、solr、es数据同步和增量更新

一、Neo4j的数据增量更新:

图数据除了节点的增量更新还牵扯到边的增量更新,节点其实还好说,无论是新增节点还是新增节点属性或者是节点属性更新,实际上都是在节点表可以完成,不是很消耗资源;比较复杂且影响更新效率的其实边的增量更新。

1、节点更新

这个需求其实很普遍,比如我有一个节点:

(n:Person {id: 'argan', name: 'argan', age: 32})

然后用户又传递了一个人数据过来:

{id: 'argan', age: 30, sex: 'male', email: 'arganzheng@gmail.com'} 

可以看到更新了一个属性:年龄,新增了两个属性:性别和电子邮件。我们希望最后的结果是:

(n:Person {id: 'argan', name: 'argan', age: 30, sex: 'male', email: 'arganzheng@gmail.com'})。

需要注意的是name是没有传递的,但还是保留着的。如果要删除一个属性,需要把它的值显式的设置为空。

在Neo4j的要怎么做到呢?

Neo4j的提供了合并语句来实现这个功能。

与ON CREATE和ON MATCH合并

如果需要创建节点,请合并节点并设置属性。

MERGE (n:Person { id: 'argan' })
ON CREATE SET n.created = timestamp()
ON MATCH SET n.lastAccessed = timestamp()
RETURN n.name, n.created, n.lastAccessed

上面的例子可以这么写:

MERGE (n:Node {id: 'argan'}) SET n += {id: 'argan', age: 30, sex: 'male', email: 'arganzheng@gmail.com'} 
RETURN n 

因为这里采用了+=本身就是合并属性,所以区分不需要的英文ON CREATE还是ON MATCH。

同样关系也可以用合并保证只创建一次:

MATCH (n), (m) WHERE n.id = "argan" AND m.id = "magi" CREATE (n)-[:KNOWS]->(m)

写成这样子就可以保证唯一了:

MATCH (n:User {name: "argan"}), (m:User {name: "magi"}) MERGE (n)-[:KNOWS]->(m)

2、neo4j如何支持动态节点标签和关系类型?

上面的合并语句能够实现“存在更新,否则创建”的逻辑,但是还有一个问题没有解决,就是没有设置节点的标签。我们希望构建的节点数据完全是运行时根据用户提供的数据构造的,包括。标签比如用户提供如下数据:

:param batch: [{properties: {name: "argan", label: "Person", id: "1", age: 31}}, {properties: {name: "magi", label: "Person", id: "2", age: 28}}]

下面的暗号语句并没有设置节点的标签,虽然节点有一个叫做标签的属性:

UNWIND {batch} as row 
MERGE (n {id: row.id})
SET n += row.properties

那么我们能不能简单的指定标签呢?

UNWIND {batch} as row 
MERGE (n:row.properties.label {id: row.id})
SET n += row.properties

但是遗憾的是这个语句会报错,因为neo4j不支持动态的节点标签。把row.properties.label去掉或者改成一个固定的字符串就没有问题。

改成这样子也不行:

UNWIND {batch} as row   MERGE (n {id: row.id} )   SET n:row.properties.label,  n += row.properties

绑定变量也不行:

UNWIND {batch} as row   MERGE (n {id: row.id} )   SET n:{label},  n += row.properties

直接指定标签就可以了:

UNWIND {batch} as row   MERGE (n {id: row.id} )   SET n:Test,  n += row.properties

也就是说3.3.13.9。在节点上设置标签也并不支持动态标签..

笔记

neo4j的设置标签还有一个问题,就是它其实是新增标签,不是修改标签。要到更新的效果,你需要先删除掉,再新增..

MATCH (n) WHERE ID(n) = 14  REMOVE n:oldLabel SET n:newLabel

如果是单条数据更新,那其实很简单,我们只需要做字符串拼接就可以了:

String label = vertex.getLabel(); "MERGE (n:" + label + " {id: {id}} " + "SET n += {properties}"

但是关键是我们这里是在Neo4j的内部用开卷展开的服务端变量,如果它不允许动态变量,根本搞不定。难道真的要一条条的插入,那会非常慢的!Neo4j的的插入性能是众所周知的差。一种做法就是先批量插入数据,设置一个临时的标签,然后再批量的更新标签。不过需要两次操作,性能肯定至少慢两倍。

有没有什么方式呢?谷歌了很久,发现了也有人遇到这样的问题:功能请求:apoc支持MERGE节点和rels#271和是否可以使用数据驱动的节点或关系标签进行合并?。

原理跟单条数据插入一样,只是由于退绕是在服务端(Neo4j的)进行的,所以拼接也只能在服务端进行,怎么拼接的就是用?apoc.cypher.doIt拼接后让它在服务端执行:

UNWIND {batch} as row  WITH 'MERGE (n:' + row.properties.label + ' { id: row.id }) SET n += row.properties return n' AS cypher CALL apoc.cypher.doIt(cypher, {}) YIELD value return value.n

但是可惜,会报这样的异常:

org.neo4j.driver.v1.exceptions.ClientException: 
Failed to invoke procedure `apoc.cypher.doIt`: 
Caused by: org.neo4j.graphdb.QueryExecutionException: 
Variable `row` not defined (line 1, column 23 (offset: 22)) "MERGE (n:Person { id: row.id }) SET n += row.properties return n"

所以还是要分两步进行,不过可以合并在一起SET标签:传递标签名称作为参数:

UNWIND {batch} as row  MERGE (n { id: row.id }) 
SET n += row.properties  WITH n  CALL apoc.create.addLabels(id(n), [n.label]) 
YIELD node RETURN node

这样就可以正确的保存数据并且动态设置标签了。笔

本来我们是可以直接使用APOC库的apoc.merge.node状语从句:apoc.create.relationship动态的更新节点标签,关系和节点的。但是正如前面分析的,apoc.merge.node状语从句:apoc.create.relationship现在的实现其实的英文一个防重复CREATE而已,不能达到更新的目的。否则我们的实现将非常简单明了:

更新节点:

UWNIND {batch} as row CALL apoc.merge.node(row.labels, {id: row.id} , row.properties) 
yield node RETURN count(*)

更新关系:

UWNIND {batch} as row MATCH (from) WHERE id(from) = row.from 
MATCH (to:Label) where to.key = row.to CALL apoc.merge.relationship(from, row.type, {id: row.id}, row.properties, to) 
yield rel RETURN count(*)

一种做法就是叉一个分支出来,修改源码,部署自己的罐子包。

二、solr 的增量更新

1.首先要弄懂几个必要的属性,以及数据库建表事项,和dataimporter.properties 、data-config.xml里面的数据

<!–  transformer 格式转化:HTMLStripTransformer 索引中忽略HTML标签   —> 
  <!–  query:查询数据库表符合记录数据   —> 
  <!–  deltaQuery:增量索引查询主键ID    —>    注意这个只能返回ID字段 
  <!–  deltaImportQuery:增量索引查询导入数据  —> 
  <!–  deletedPkQuery:增量索引删除主键ID查询  —> 注意这个只能返回ID字段

2.数据库配置注意事项

1.如果只涉及添加,与修改业务,那么数据库里只需额外有一个timpstamp字段 
就可以了,默认值为当前系统时间,CURRENT_TIMESTAMP
2.如果还涉及删除业务,那么数据里就需额外再多添加一个字段isdelete,int类型的 
用0,1来标识,此条记录是否被删除

3.dataimporter.properties 

这个配置文件很重要,它是用来记录当前时间与上一次修改时间的,通过它能够找出,那些,新添加的,修改的,或删除的记录标识,此条记录是否被删除的记录

4.增量更新就是在全量更新的基础上加上一些配置,配置如下:

<?xml version="1.0" encoding="UTF-8" ?>
<dataConfig> 
    <!--数据源-->
    <dataSource type="JdbcDataSource"
                driver="com.mysql.jdbc.Driver"
                url="jdbc:mysql://192.168.2.10:3306/xtjkqyfw"
                user="root"
                password="Biaopu8888"/>
    <document> 

        <entity name="solrTest" 
        query="SELECT fid,ftime,fcontent,ftitle,flastupdatetime FROM solrTest where flag = '0'"
        deltaImportQuery = "SELECT fid,ftime,fcontent,ftitle,flastupdatetime FROM solrTest where fid = '${dataimporter.delta.fid}'"
        deltaQuery = "SELECT fid FROM solrTest where flastupdatetime > '${dataimporter.last_index_time}' and flag = '0'"
        deletedPkQuery = "SELECT fid FROM solrTest where flag = '1'"
        >
            <!--查询的数据和数据库索引意义对应column 是查询的字段name 是solr索引对应的字段-->
            <field column="fid" name="fid"/>
            <field column="ftitle" name="ftitle"/>
            <field column="fcontent" name="fcontent"/>
            <field column="flastupdatetime" name="flastupdatetime"/>
            <field column="ftime" name="ftime"/>
        </entity>
        
    </document> 
</dataConfig>

三、LOGSTASH-INPUT-JDBC 实现数据库同步ES

在数据方面碰到第一个问题是怎么将postgres中的数据库中同步到es中,在网上找了下相关文档,只有logstash-input-jdbc这个插件还在维护,而且在es中logstash高版本已经集成了这一插件,所以就省去了安装ruby和安装插件这一步了

1 安装elasticsearch logstash kibana 三件套

2 下载数据库驱动

图方便的话可以直接拷贝maven仓库里面的即可

3 添加 .conf文件

input {  
jdbc {
# mysql 数据库链接,shop为数据库名
jdbc_connection_string => "jdbc:postgresql://ip:5432/chongqing_gis"
# 用户名和密码
jdbc_user => ""
jdbc_password => ""
# 驱动
jdbc_driver_library => "E:/ES/logstash-7.8.0/postgres/postgresql-42.2.9.jar"
# 驱动类名
jdbc_driver_class => "org.postgresql.Driver" jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
# 执行的sql 文件路径+名称
statement_filepath => "E:/ES/logstash-7.8.0/postgres/jdbc.sql" # 设置监听间隔 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
# schedule => "* * * * *"
}
}
filter {
json {
source => "message"
remove_field => ["message"]
}
}
output {
elasticsearch {
# ES的IP地址及端口
hosts => ["localhost:9200"]
# 索引名称
index => "test_index"
# 需要关联的数据库中有有一个id字段,对应类型中的id document_id => "%{gid}"
}
stdout {
# JSON格式输出
codec => json_lines
}
}

修改输入参数:数据库ip、端口、账号、密码等

修改输出参数:es的ip、端口、索引、document_id等

输出参数中索引如果还没有创建,在启动logstash时会自动根据默认模板创建索引,其中有些教程中出现了index_type的设置,这个类型在es的高版本中已经取消,不需要再设置,如果设置了,启动logstash会报错

statement_filepath 保存准备执行的sql文件

jdbc.sql文件:

select gid,name,address from qtpoi

在qtpoi表中有个字段是保存的空间地理信息geom字段,当我加上这个时,启动logstash一直报错,可能对空间字段需要做进一步的处理

Exception when executing JDBC query {:exception=>#<Sequel::DatabaseError: Java::OrgLogstash::MissingConverterException: Missing Converter handling for full class name=org.postgresql.util.PGobject, simple name=PGobject

4 启动logstash即可同步

bin/logstash -f config/postgres.conf

5 打开kibana即可查看到刚同步的数据

GET test_index/_doc/_search

6 如果设置了定时任务,logstash会定时去访问数据同步到es中,但是上面jdbc.sql文件中获取的是整张表的数据,也就是说每次同步都会对全表进行同步,但是我的需求是只需要第一次同步整张表后面只对更新的和修改的数据做同步,

在网上找了下,思路大概是给表增加一个新的字段,保存当前创建的时间或者是当前更新的时间,然后根据:sql_last_value这个函数获取大于这个函数的时间,就过滤出新增的数据和更新的数据,实现对增量数据同步到es,:sql_last_value这个函数官网也没说的太清楚,我大致认为是最后一个更新的值或者最后一次更新的时间

作者 east
solr 3月 17,2022

封装HttpSolrServer设置超时时间

凡是要连接网络的,都要设置超时时间,这样防止网络卡住了没返回结果,例子可以参考 Spark Streaming调用http接口导致卡住了

/**
 * HttpSolrServer扩展对象服务 .<br>
 * 专门设置请求连接超时时间和socket超时时间,调用solr请求服务,防止网络断开时无法中断调用线程<br>
 * 
 */
public class HttpSolrExServer extends HttpSolrServer {

    /** serialVersionUID */
    private static final long serialVersionUID = 4068028650816903817L;

    /**
     * 连接超时值
     */
    private static Integer connectionTimeoutNum;

    /**
     * socket超时值
     */
    private static Integer socketTimeoutNum;



    /**
     * 有参构造函数
     * 
     * @param baseUrl url地址
     */
    public HttpSolrExServer(String baseUrl) {
        super(baseUrl);
        // 设置请求连接超时时间,以毫秒为单位
        this.setConnectionTimeout(getConnectionTimeout());
        // 设置socket超时时间,以毫秒为单位
        this.setSoTimeout(getSocketTimeout());
    }

    public HttpSolrExServer(String baseUrl, HttpClient client){
        super(baseUrl,client);
        // 设置请求连接超时时间,以毫秒为单位
        this.setConnectionTimeout(getConnectionTimeout());
        // 设置socket超时时间,以毫秒为单位
        this.setSoTimeout(getSocketTimeout());
    }

    /**
     * 得到solr的连接超时时间,以毫秒为单位,默认为60秒<br>
     * 
     * @return
     */
    private static int getConnectionTimeout() {
        if (connectionTimeoutNum == null || connectionTimeoutNum <= 0) {
            // solr的连接超时时间,以毫秒为单位,默认为60秒
            int defaultConnectionTimeout = 60000;
            // solr的连接超时时间字符串变量值

            String connectionTimeoutStr = ConfigUtil.getPropsValueByKey("ac.httpSolr.connectionTimeout");
            

            try {
                int configValue = Integer.parseInt(connectionTimeoutStr);

                if (configValue > 0) {
                    defaultConnectionTimeout = configValue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            connectionTimeoutNum = defaultConnectionTimeout;
        }
        return connectionTimeoutNum.intValue();
    }

    /**
     * 得到solr的socket超时时间,以毫秒为单位,默认为60秒<br>
     * 
     * @return
     */
    private static int getSocketTimeout() {
        if (socketTimeoutNum == null || socketTimeoutNum <= 0) {
            // solr的socket超时时间,以毫秒为单位,默认为60秒
            int defaultSocketTimeout = 60000;
            // solr的socket超时时间字符串变量值
            String socketTimeoutStr = ConfigUtil.getPropsValueByKey("ac.httpSolr.socketTimeout");

            try {
                int configValue = Integer.parseInt(socketTimeoutStr);

                if (configValue > 0) {
                    defaultSocketTimeout = configValue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            socketTimeoutNum = defaultSocketTimeout;
        }
        return socketTimeoutNum.intValue();
    }
}
/**
     * 根据属性key得到对应的属性值<br>
     * 配置文件按spring boot的约定的配置文件命名规则进行加载<br>
     * <font color=red>注意: 暂不支持yml文件属性读取</font>
     * 
     * @param key 键名称
     * @return
     */
    public static String getPropsValueByKey(String key) {
        // TODO 暂时不支持读取yml格式文件,yml文件支持map和list格式数据,需要另写方法支持
        if (!props.containsKey(CONFIG_FILENAME)) {
            Properties prop = new Properties();
            prop = getPropertiesByFileName(CONFIG_FILENAME);

            if (prop.get(SPRING_PROFILES_ACTIVE) != null) {
                // 依次读取指定的配置文件
                for (String partName : prop.get(SPRING_PROFILES_ACTIVE).toString().split(",")) {
                    prop.putAll(getPropertiesByFileName(SPRING_BOOT_PROFILE_TEMPLATE.replace("{profile}", partName)));
                }
            }
            props.put(CONFIG_FILENAME, prop);
        }
        Object obj = props.get(CONFIG_FILENAME).get(key);
        if (obj == null) {
            return null;
        } else {
            return obj.toString();
        }
    }
作者 east
solr 3月 1,2021

Solr增删改查例子

Solr初始化

Solr初始化

功能简介

Solr初始化是指在使用Solr提供的API之前,需要做的必要工作。目的是取得与SolrCoud的连接。

说明:

在进行完Solr操作后,需要调用cloudSolrClient.close()关闭所申请的资源。

Solr初始化





/** *初始化CloudSolrClient实例,连接SolrCloud private CloudSolrClient getCloudSolrClient(String zkHost) throws SolrException { Builder builder = new CloudSolrClient.Builder(); builder.withZkHost(zkHost); CloudSolrClient cloudSolrClient = builder.build(); cloudSolrClient.setZkClientTimeout(zkClientTimeout); cloudSolrClient.setZkConnectTimeout(zkConnectTimeout); cloudSolrClient.connect(); LOG.info("The cloud Server has been connected !!!!"); ZkStateReader zkStateReader = cloudSolrClient.getZkStateReader(); ClusterState cloudState = zkStateReader.getClusterState(); LOG.info("The zookeeper state is : {}", cloudState); return cloudSolrClient; }

查询collection

查询collection

功能简介

通过调用CollectionAdminRequest.List的process(cloudSolrClient)并调用返回的response来获取所有collection的名字。

代码样例

private List<String> queryAllCollections(CloudSolrClient 
                             cloudSolrClient) throws SolrException {
        CollectionAdminRequest.List list = 
        new CollectionAdminRequest.List();
        CollectionAdminResponse listRes = null;
        try {
            listRes = list.process(cloudSolrClient);
        } catch (SolrServerException | IOException e) {
            LOG.error("Failed to list collection", e);
            throw new SolrException("Failed to list collection");
        } catch (Exception e) {
            LOG.error("Failed to list collection", e);
            throw new SolrException("unknown exception");
        }

        List<String> collectionNames = (List<String>) 
        listRes.getResponse().get("collections");
        LOG.info("All existed collections : {}", collectionNames);
        return collectionNames;
    }

删除collection

删除collection

功能简介

通过调用CollectionAdminRequest.Delete的process(cloudSolrClient)并调用返回的response来判断是否执行删除collection操作成功。

代码样例

private void deleteCollection(CloudSolrClient cloudSolrClient) 
                                               throws SolrException {
        CollectionAdminRequest.Delete delete = 
        new CollectionAdminRequest.Delete();
        delete.setCollectionName(COLLECTION_NAME);
        CollectionAdminResponse response = null;
        try {
            response = delete.process(cloudSolrClient);
        } catch (SolrServerException | IOException e) {
            LOG.error("Failed to delete collection", e);
            throw new SolrException("Failed to create collection");
        } catch (Exception e) {
            LOG.error("Failed to delete collection", e);
            throw new SolrException("unknown exception");
        }
        if (response.isSuccess()) {
            LOG.info("Success to delete collection[{}]", 
            COLLECTION_NAME);
        } else {
            LOG.error("Failed to delete collection[{}], cause : {}",             COLLECTION_NAME, response.getErrorMessages());
            throw new SolrException("Failed to delete collection");
        }
    }

创建collection

创建collection

功能简介

通过调用CollectionAdminRequest.Create的process(cloudSolrClient)并调用返回的response来判断是否执行创建collection操作成功。

代码样例

  private void createCollection(CloudSolrClient cloudSolrClient) throws SolrException {
        CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(COLLECTION_NAME, DEFAULT_CONFIG_NAME, shardNum, replicaNum);
        CollectionAdminResponse response = null;
        try {
            response = create.process(cloudSolrClient);
        } catch (SolrServerException e) {
            LOG.error("Failed to create collection", e);
            throw new SolrException("Failed to create collection");
        } catch (IOException e) {
            LOG.error("Failed to create collection", e);
            throw new SolrException("Failed to create collection");
        } catch (Exception e) {
            LOG.error("Failed to create collection", e);
            throw new SolrException("unknown exception");
        }
        if (response.isSuccess()) {
            LOG.info("Success to create collection[{}]", COLLECTION_NAME);
        } else {
            LOG.error("Failed to create collection[{}], cause : {}", COLLECTION_NAME, response.getErrorMessages());
            throw new SolrException("Failed to create collection");
        }
    }

添加Doc

添加Doc

功能简介

通过调用cloudSolrClient的add方法或者构造UpdateRequest调用cloudSolrClient的request方法来添加索引数据。

代码样例1

private void addDocs(CloudSolrClient cloudSolrClient) throws SolrException {
        Collection<SolrInputDocument> documents = new ArrayList<SolrInputDocument>();
        for (Integer i = 0; i < 5; i++) {
            SolrInputDocument doc = new SolrInputDocument();
            doc.addField("id", i.toString());
            doc.addField("name", "Luna_" + i);
            doc.addField("features", "test" + i);
            doc.addField("price", (float) i * 1.01);
            documents.add(doc);
        }
        try {
            cloudSolrClient.add(documents);
            LOG.info("success to add index");
        } catch (SolrServerException e) {
            LOG.error("Failed to add document to collection", e);
            throw new SolrException("Failed to add document to collection");
        } catch (IOException e) {
            LOG.error("Failed to add document to collection", e);
            throw new SolrException("Failed to add document to collection");
        } catch (Exception e) {
            LOG.error("Failed to add document to collection", e);
            throw new SolrException("unknown exception");
        }
    }

代码样例2

private void addDocs2(CloudSolrClient cloudSolrClient) throws 
SolrException{
    UpdateRequest request = new UpdateRequest();
    Collection<SolrInputDocument> documents = new ArrayList<>();
    for (Integer i = 5; i < 10; i++) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField("id", i.toString());
        doc.addField("name", "张三" + i);
        doc.addField("features", "test" + i);
        doc.addField("price", (float) i * 1.01);
        documents.add(doc);
     }
     request.add(documents);
    try {
        cloudSolrClient.request(request);
        cloudSolrClient.commit();
    } catch (SolrServerException | IOException e) {
        LOG.error("Failed to add document to collection", e);
        throw new SolrException("Failed to add document to 
        collection");
    }
 }

查询Doc

查询Doc

功能简介

通过构造SolrQuery实例,并调用cloudSolrClient.query接口来查询索引数据。

样例代码

    private void queryIndex(CloudSolrClient cloudSolrClient) throws SolrException {
        SolrQuery query = new SolrQuery();
        query.setQuery("name:Luna*");

        try {
            QueryResponse response = cloudSolrClient.query(query);
            SolrDocumentList docs = response.getResults();
            LOG.info("Query wasted time : {}ms", response.getQTime());

            LOG.info("Total doc num : {}", docs.getNumFound());
            for (SolrDocument doc : docs) {
                LOG.info("doc detail : " + doc.getFieldValueMap());
            }
        } catch (SolrServerException e) {
            LOG.error("Failed to query document", e);
            throw new SolrException("Failed to query document");
        } catch (IOException e) {
            LOG.error("Failed to query document", e);
            throw new SolrException("Failed to query document");
        } catch (Exception e) {
            LOG.error("Failed to query document", e);
            throw new SolrException("unknown exception");
        }
    }

删除Doc

删除Doc

功能简介

通过调用cloudSolrClient.deleteByQuery方法删除指定匹配的索引数据。

代码样例

private void removeIndex(CloudSolrClient cloudSolrClient) throws 
SolrException {
        try {
            cloudSolrClient.deleteByQuery("*:*");
            cloudSolrClient.commit();
            LOG.info("Success to delete index");
        } catch (SolrServerException | IOException e){
            LOG.error("Failed to remove document", e);
            throw new SolrException("Failed to remove document");
        }
    }
作者 east
solr 12月 7,2020

Solr封装的Java客户端工具类




import com.finest.ms.address.util.AddrMatchConstant;
import com.finest.ms.address.util.ConfigUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.*;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;


public class SolrClient {


private Logger log = LoggerFactory.getLogger(SolrClient.class);

/** ID唯一标识 */
public static final String ID = "ID";

/**
* solr服务哈希表,key为(格式为“http://ip:端口/core”)路径名称,value为HttpSolrServer对象
*/
private HashMap<String, SolrServer> solrServers = new HashMap<String, SolrServer>();

/** UTF-8编码 */
public static final String UTF8_CHARSET_NAME = "UTF-8";

/** 拆分词组URL,用于拆分词组 */
public static final String URL_SPLIT_WORDS = "splitwords";

/** 代码词组的URL,用于根据代码得到标准词组 */
public static final String URL_CODE_WORDS = "codewords";

/** 字段通用名称--冒号 */
public static final String FIELD_COLON = ":";

/** 字段通用名称--双引号 */
public static final String FIELD_DOUBLE_QUOTATION_MARKS = "\"";

/** 注解字段常量--otherFields */
public static final String ANNOTATION_OTHERFIELDS = "otherFields";

/** solr的URL地址,目前只有两种,标准地址的solrurl地址和业务数据的solrurl地址,在config.properties配置 */
private String solrUrl;

private HttpClient client;

/** solr的循环次数集合,key为solr的url地址, value为循环次数 */
private Map<String, Integer> solrLoopNumMap = new HashMap<String, Integer>();

/** solr网络重连次数,默认为5 */
private int pingLoopNum;

/** solr网络重连睡眠时间,以毫秒为单位,默认为1000毫秒 */
private long pingSleepTime;

// /** debug模式下 - 统计solr访问次数 ,多线程下此值会存在不准确情况 */public static long total = 0;

/**
* 构造函数
*
*
@param solrUrl solr的URL,目前只有两种,标准地址的solrurl地址和业务数据的solrurl地址,在config.properties配置
*/
public SolrClient(String solrUrl) {
this.solrUrl = solrUrl;
}

public SolrClient(String solrUrl, HttpClient client){
this.solrUrl = solrUrl;
this.client = client;
}

/**
* 根据core名称得到SolrServer对象,如果不存在,就创建
*
*
@param core solr的core名称
*
@return
*/
public SolrServer getSolrServer(String core) throws Exception {
// 对solrUrl和core为空判断,当为空时,直接返回SolrHttpException异常
if (StringUtils.isEmpty(solrUrl) || StringUtils.isEmpty(core)) {
String error = "solr工具类-根据core名称得到SolrServer对象方法出现异常!solrUrl或core名称为空!";
log.warn(error);
throw new SolrHttpException(error); // 异常自定义的异常,可以很清楚知道该异常是http连接出现问题导致
}
SolrServer solrServer = null;
// 根据格式为“http://ip:端口/core”组成的key,存放至solr服务哈希表。
String solrCoreUrl = solrUrl + "/" + core;
boolean isNeedAuth = "true".equalsIgnoreCase(ConfigUtil.getPropsValueByKey("ac.solr.isNeedAuth"));
if (solrServers.containsKey(solrCoreUrl)&&!isNeedAuth) {
solrServer = solrServers.get(solrCoreUrl);
} else {
// 判断是否需要验证用户名密码
if (isNeedAuth) {
String solrUser = ConfigUtil.getPropsValueByKey("ac.solr.username");
String solrPass = ConfigUtil.getPropsValueByKey("ac.solr.password");
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 128);
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 32);
params.set(HttpClientUtil.PROP_FOLLOW_REDIRECTS, false);
params.set(HttpClientUtil.PROP_BASIC_AUTH_USER, solrUser);
params.set(HttpClientUtil.PROP_BASIC_AUTH_PASS, solrPass);
HttpClient httpClient = HttpClientUtil.createClient(params);
solrServer = new HttpSolrExServer(solrCoreUrl, httpClient);
} else {
solrServer = new HttpSolrExServer(solrCoreUrl);
}
// solrServer的ping调用
pingSolrServer(solrServer);
//solrServers.put(solrCoreUrl, solrServer);
}
return solrServer;
}

/**
* 获取得到SolrDocument的数据列表
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@return
*/
public List<SolrDocument> getDocs(String criteria, String coreName, int rows, int start, String sortParams)
throws Exception {
return getDocs(criteria, coreName, rows, start, sortParams, null, null, null);
}

/**
* 获取得到SolrDocument的数据列表<br>
* 使用场景:<br>
* 1.当没有过滤显示字段数组,直接返回所有solr原始字段的SolrDocument数据。<br>
* 2.当有过滤显示字段数组并且没有过滤显示字段别名数组,返回过滤字段后的solr原始字段的SolrDocument数据。<br>
* 3.当有过滤显示字段数组并且有过滤显示字段别名数组,长度顺序一致,返回过滤字段后的以别名为key的SolrDocument数据。<br>
* 4.当有过滤显示字段数组并且有过滤显示字段别名数组,顺序一致,但长度不一致,即有部分设置别名,有部分没有设置别名,返回过滤字段后,有别名的以别名为key、没有别名的以字段名为key的SolrDocument数据。<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@param coreUrls core Url数组,用于跨core查询
*
@return
*/
public List<SolrDocument> getDocs(String criteria, String coreName, int rows, int start, String sortParams,
String[] fields, String[] aliasFields, String[] coreUrls) throws Exception {
List<SolrDocument> docs = new ArrayList<SolrDocument>();
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, fields, aliasFields,
null, null, coreUrls);

if (response.getResults() != null) {
docs.addAll(response.getResults());
}
}
return docs;
}

/**
* 得到分组统计后的文档列表<br>
* 与数据库group by分组返回结果集一致,支持多个字段的分组<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param facetParams 分组参数字符串,格式以逗号隔开,如name,age
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与facetParams分组参数字符串拆分后得到数组的顺序一一对应,长度可不相同,<br>
* 可短于facetParams分组参数字符串拆分后得到数组,也可长于facetParams分组参数字符串拆分后得到数组。<br>
* 当短时,那些没有设置别名的,将取回facetParams分组对应的字段名;当长时,除了全部用别名之外,还将对统计别名的设置。
*
@return
* @throws Exception
*/
public List<SolrDocument> getFacetDocs(String criteria, String coreName, String facetParams, String[] aliasFields)
throws Exception {
// 当查询条件,或者core名称,或者分组参数为空时,直接返回空列表
if (StringUtils.isEmpty(criteria) || StringUtils.isEmpty(coreName) || StringUtils.isEmpty(facetParams)) {
return Collections.emptyList();
}

// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, 0, 0, null, null, aliasFields, facetParams, null,
null);

List<PivotField> pivotFieldList = null;
// 根据分组结果,设置返回值
NamedList<List<PivotField>> facetPivot = response.getFacetPivot();
if (facetPivot != null) {
pivotFieldList = facetPivot.get(facetParams);
}
// 调用得到分组的solr文档对象列表方法,返回结果
return getFacetSolrDocumentList(pivotFieldList, aliasFields);
}

/**
* 获取得到对象化的数据列表
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> List<T> getBeans(String criteria, String coreName, int rows, int start, String sortParams,
Class<T> clazz) throws Exception {
return getBeans(criteria, coreName, rows, start, sortParams, clazz, null);
}

/**
* 获取得到对象化的数据列表<br>
* 使用场景:<br>
* 1.当没有过滤显示字段数组,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据列表。<br>
* 2.当有过滤显示字段数组并且没有过滤显示字段别名数组,过滤字段后,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据列表。<br>
* 3.当有过滤显示字段数组并且有过滤显示字段别名数组,不需要转换对象的Class注解有@Field字段:<br>
* 1)长度顺序一致,即都设置有别名情况下,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值。<br>
* 2)顺序一致,但长度不一致,即有部分设置别名,有部分没有设置别名,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值,找不到相同的属性名则不做赋值处理。<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名,并且返回对象的属性名与别名一致。
*
@param coreUrls core Url数组,用于跨core查询
*
@return
*/
public <T extends Object> List<T> getBeans(String criteria, String coreName, int rows, int start, String sortParams,
Class<T> clazz, String[] fields, String[] aliasFields, String[] coreUrls) throws Exception {
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, fields, aliasFields,
null, null, coreUrls);

if (response.getResults() != null) {
// 如果设置有别名,根据转换对象的Class,通过反射设置得到对应的转换对象列表,并返回
// 如果没有设置别名,返回solr原始getBeans方法得到的对象列表值。该getBeans方法是通过注解的方式设置映射关系
if (fields != null && fields.length > 0 && aliasFields != null && aliasFields.length > 0) {
return this.getBeansByDocsAndClazz(response.getResults(), clazz);
} else {
try {
return response.getBeans(clazz);
} catch (Exception e) {
String error = "获取得到对象化的数据列表方法,在执行QueryResponse的getBeans时出现异常!";
log.error(error, e);
throw new SolrBeanBindingException(error);
}
}
}
}
return Collections.emptyList();
}

/**
* 获取得到对象化的数据列表<br>
* 使用场景:<br>
* 1.当fieldsMap字段Map为空,以转换对象的Class注解有@Field关联字段值进行映射,返回对象数据列表。<br>
* 2.当fieldsMap字段Map不为空情况下,不需要转换对象的Class注解有@Field字段:<br>
* 1)所有键与值都有值,即都设置有别名情况下,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值。<br>
* 2)转换对象的Class注解有@Field("otherFields")的Map属性,所有找不到别名名称相同的转换对象Class的属性名,把那些没有设置到属性里的值,全部加入到注解有@Field(
* "otherFields")的Map对象中<br>
* 3)如果没有找到注解有@Field("otherFields")的Map,所有找不到别名名称相同的转换对象Class的属性名,把那些没有设置到属性里的值全部丢弃掉<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@param fieldsMap 字段Map,key为字段名,value为字段别名,别名可以为空,取回key对应的字段名为别名
*
@return
*/
public <T extends Object> List<T> getBeans(String criteria, String coreName, int rows, int start, String sortParams,
Class<T> clazz, Map<String, String> fieldsMap) throws Exception {
// 过滤显示字段数组
String[] fields = null;
// 过滤显示字段别名数组
String[] aliasFields = null;

// 通过字段map,得到字段名数组和别名数组,并设置
if (fieldsMap != null) {
// 字段名拼接字符串,字段名之间以逗号隔开
StringBuilder fieldsSb = new StringBuilder();
// 字段别名拼接字符串,字段别名之间以逗号隔开
StringBuilder aliasFieldsSb = new StringBuilder();

for (Entry<String, String> entry : fieldsMap.entrySet()) {
fieldsSb.append(entry.getKey()).append(",");
if (StringUtils.isNotEmpty(entry.getValue())) {
aliasFieldsSb.append(entry.getValue().trim()).append(",");
} else {
aliasFieldsSb.append(",");
}
}
if (fieldsSb.length() > 0) {
fieldsSb.delete(fieldsSb.length() - 1, fieldsSb.length());
}
if (aliasFieldsSb.length() > 0) {
aliasFieldsSb.delete(aliasFieldsSb.length() - 1, aliasFieldsSb.length());
}

fields = fieldsSb.toString().split(" *, *");
aliasFields = aliasFieldsSb.toString().split(" *, *");
}

return getBeans(criteria, coreName, rows, start, sortParams, clazz, fields, aliasFields, null);
}

/**
* 获取得到对象化的数据对象<br>
* 使用场景:<br>
* 1.当没有过滤显示字段数组,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据。<br>
* 2.当有过滤显示字段数组并且没有过滤显示字段别名数组,过滤字段后,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据。<br>
* 3.当有过滤显示字段数组并且有过滤显示字段别名数组,不需要转换对象的Class注解有@Field字段:<br>
* 1)长度顺序一致,即都设置有别名情况下,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值。<br>
* 2)顺序一致,但长度不一致,即有部分设置别名,有部分没有设置别名,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值,找不到相同的属性名则不做赋值处理。<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名,并且返回对象的属性名与别名一致。
*
@param coreUrls core Url数组,用于跨core查询
*
@return
*/
public <T extends Object> T getBean(String criteria, String coreName, String sortParams, Class<T> clazz,
String[] fields, String[] aliasFields, String[] coreUrls) throws Exception {
List<T> list = getBeans(criteria, coreName, 1, 0, sortParams, clazz, fields, aliasFields, coreUrls);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0);
}
return null;
}

/**
* 获取得到对象化的数据对象
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> T getBean(String criteria, String coreName, String sortParams, Class<T> clazz)
throws Exception {
return getBean(criteria, coreName, sortParams, clazz, null, null, null);
}

/**
* 根据ID获取得到对象化的数据对象
*
*
@param id ID唯一标识
*
@param coreName core的名称
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> T getBeanById(String id, String coreName, Class<T> clazz) throws Exception {
StringBuilder querySb = new StringBuilder();
querySb.append("ID:").append(id);
return getBean(querySb.toString(), coreName, null, clazz);
}

/**
* 获取得到对象化的数据Solr文档
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@return
*/
public SolrDocument getDoc(String criteria, String coreName, String[] fields, String[] aliasFields)
throws Exception {
List<SolrDocument> list = getDocs(criteria, coreName, 1, 0, null, fields, aliasFields, null);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0);
}
return null;
}

/**
* 获取得到对象化的数据Solr文档
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@return
*/
public SolrDocument getDoc(String criteria, String coreName) throws Exception {
return getDoc(criteria, coreName, null, null);
}

/**
* 获取总条数
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@return
*/
public long getNumberFound(String criteria, String coreName) throws Exception {
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, 0, 0, null, null, null, null, null, null);
return response.getResults().getNumFound();
}
return 0;
}

/**
* 添加文档
*
*
@param document solr的输入文档对象
*
@param coreName core的名称
*/
public void addDoc(SolrInputDocument document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, false);
}

/**
* 添加文档并马上提交
*
*
@param document solr的输入文档对象
*
@param coreName core的名称
*/
public void addDocByHardCommit(SolrInputDocument document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, true);
}

/**
* 添加Bean
*
*
@param document Bean对象
*
@param coreName core的名称
*/
public void addBean(Object document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, false);
}

/**
* 添加Bean并马上提交
*
*
@param document Bean对象
*
@param coreName core的名称
*/
public void addBeanByHardCommit(Object document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, true);
}

/**
* 添加文档至solr
*
*
@param object object对象,可能是SolrInputDocument、也可能是bean对象
*
@param coreName core名称
*
@param isHardCommit 是否马上提交
*
@throws Exception
*/
private void addDocumentIntoSolr(Object object, String coreName, boolean isHardCommit) throws Exception {
if (object != null && StringUtils.isNotEmpty(coreName)) {
SolrServer solrServer = getSolrServer(coreName);
try {
if (object instanceof SolrInputDocument) {
solrServer.add((SolrInputDocument) object);
} else {
solrServer.addBean(object);
}
if (isHardCommit) {
solrServer.commit();
}
} catch (SolrServerException | IOException e) {
String error = "solr工具类-添加文档至solr方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}
}

/**
* 添加bean列表
*
*
@param documents
*
@param coreName
*/
@SuppressWarnings("rawtypes")
public void addBeans(List documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, false);
}

/**
* 添加bean列表并马上提交
*
*
@param documents
*
@param coreName
*/
@SuppressWarnings("rawtypes")
public void addBeansByHardCommit(List documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, true);
}

/**
* 添加文档列表
*
*
@param documents
*
@param coreName
*/
public void addDocs(List<SolrInputDocument> documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, false);
}

/**
* 添加文档列表并马上提交
*
*
@param documents
*
@param coreName
*/
public void addDocsByHardCommit(List<SolrInputDocument> documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, true);
}

/**
* 添加文档(对象)列表到solr
*
*
@param documents
*
@param coreName
*
@param isHardCommit
*
@throws Exception
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void addDocumentsIntoSolr(List documents, String coreName, boolean isHardCommit) throws Exception {
if (documents != null && documents.size() > 0 && StringUtils.isNotEmpty(coreName)) {
try {
SolrServer solrServer = getSolrServer(coreName);
if (documents.get(0) instanceof SolrInputDocument) {
solrServer.add(documents);
} else {
solrServer.addBeans(documents);
}
if (isHardCommit) {
solrServer.commit();
}
} catch (SolrServerException | IOException e) {
String error = "solr工具类-添加文档至solr方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}
}

/**
* 根据ID更新solr字段值
*
*
@param inputDoc
*
@param id
*
@param fields
*
@param values
*
@return
*/
public SolrInputDocument updateSolrFieldsWithId(SolrInputDocument inputDoc, Object id, List<String> fields,
Object[] values) {
inputDoc = this.updateSolrFields(inputDoc, fields, values);
inputDoc.setField(ID, id);
return inputDoc;
}

/**
* 更新solr字段值
*
*
@param inputDoc
*
@param fields
*
@param values
*
@return
*/
public SolrInputDocument updateSolrFields(SolrInputDocument inputDoc, List<String> fields, Object[] values) {
if (fields != null && values != null && fields.size() <= values.length) {
for (int i = 0; i < fields.size(); i++) {
Map<String, Object> updater = new HashMap<String, Object>();
updater.put("set", values[i]);
inputDoc.setField(fields.get(i), updater);
}
}

return inputDoc;
}

/**
* 更新solr字段值
*
*
@param inputDoc
*
@param fields
*
@param values
*
@return
*/
public SolrInputDocument updateSolrFields(SolrInputDocument inputDoc, List<String> fields, SolrDocument values) {
if (fields != null && values != null) {
for (int i = 0; i < fields.size(); i++) {
Map<String, Object> updater = new HashMap<String, Object>();
updater.put("set", values.getFieldValue(fields.get(i)));
inputDoc.setField(fields.get(i), updater);
}
}

return inputDoc;
}

/**
* 根据查询条件删除solr数据
*
*
@param query 查询条件
*
@param coreName core名称
*/
public void deleteByQuery(String query, String coreName) throws Exception {
try {
SolrServer solrServer = getSolrServer(coreName);
solrServer.deleteByQuery(query);
solrServer.commit();
} catch (SolrServerException | IOException e) {
String error = "solr工具类-根据查询条件删除solr数据方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}

/**
*
*
@param coreName
*
@param termsField
*
@param prefix
*
@param fetchRows
*
@return
*/
public List<TermsResponse.Term> suggest(String coreName, String termsField, String prefix, int fetchRows)
throws Exception {
SolrQuery solrQuery = new SolrQuery();
solrQuery.addTermsField(termsField);
solrQuery.setTerms(true);
solrQuery.setTermsLimit(fetchRows);
solrQuery.setTermsSortString("index");
solrQuery.setTermsPrefix(prefix);
solrQuery.setRequestHandler("/terms");
QueryResponse queryResponse = getSolrServer(coreName).query(solrQuery);
return queryResponse.getTermsResponse().getTerms(termsField);
}

/**
* 拼写
*
*
@param coreName
*
@param queryString
*
@param fetchRows
*
@param fetchStartAt
*
@param sortParams
*
@param params
*
@return
*/
public <T extends Object> SolrDocumentList spell(String coreName, String queryString, int fetchRows,
int fetchStartAt, String sortParams, String params) throws Exception {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(queryString, coreName, fetchRows, fetchStartAt, sortParams, null,
null, null, "spell", null);

SolrDocumentList results = response.getResults();
if (null == results && null != response.getGroupResponse()) {
results = new SolrDocumentList();
for (GroupCommand groupCommand : response.getGroupResponse().getValues()) {
for (Group group : groupCommand.getValues()) {
results.addAll(group.getResult());
}
}
}
return results;
}

/**
* 拆分词组,返回匹配到的词组
*
*
@param criteria 查询条件
*
@param coreName core名称
*
@param requestHandler 请求句柄
*
@param rows 条数,可用于分页
*
@param start 开始查询位置,可用于分页
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@return
*/
public <T extends Object> List<T> splitwWordsBeans(String criteria, String coreName, String requestHandler,
int rows, int start, String sortParams, Class<T> clazz) throws Exception {
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)
&& StringUtils.isNotEmpty(requestHandler)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, null, null, null,
requestHandler, null);

if (response.getResults() != null) {
try {
return response.getBeans(clazz);
} catch (Exception e) {
String error = "拆分词组返回匹配到的词组方法,在执行QueryResponse的getBeans操作时出现异常!";
log.error(error, e);
throw new SolrBeanBindingException(error);
}
}
}
return Collections.emptyList();
}

/**
* 拆分词组,返回匹配到SolrDocument的词组
*
*
@param criteria 查询条件
*
@param coreName core名称
*
@param requestHandler 请求句柄
*
@param rows 条数,可用于分页
*
@param start 开始查询位置,可用于分页
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@return
*/
public List<SolrDocument> splitwWordsDocs(String criteria, String coreName, String requestHandler, int rows,
int start, String sortParams) throws Exception {
List<SolrDocument> docs = new ArrayList<SolrDocument>();
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)
&& StringUtils.isNotEmpty(requestHandler)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, null, null, null,
requestHandler, null);

if (response.getResults() != null) {
docs.addAll(response.getResults());
}
}
return docs;
}

/**
* 反向匹配,返回转换后的结果对象
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param returnDistanceName 返回时距离的名称,与tClass距离属性名称要一致,如不填,tClass要有distance距离属性,不然将无法得到距离值
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> T reverseMatchBean(String coreName, double lon, double lat, double radius, Class<T> clazz,
String returnDistanceName) throws Exception {
List<T> list = this.reverseMatchBeans(coreName, lon, lat, radius, 1, 0, clazz, returnDistanceName);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0);
}
return null;
}

/**
* 反向匹配,返回转换后的结果对象列表
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param start 开始位置
*
@param rows 查询的行数
*
@param returnDistanceName 返回时距离的名称,与tClass距离属性名称要一致,如不填,tClass要有distance距离属性,不然将无法得到距离值
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> List<T> reverseMatchBeans(String coreName, double lon, double lat, double radius,
int rows, int start, Class<T> clazz, String returnDistanceName) throws Exception {
if (StringUtils.isNotEmpty(coreName)) {
// 得到反向solr查询对象
SolrQuery solrQuery = getReverseSolrQuery(coreName, lon, lat, radius, rows, start, returnDistanceName);
// 得到solr的查询返回结果对象
if(!coreName.equals(AddrMatchConstant.CORE_NAME_STANDARDADDRESS)) {
coreName = AddrMatchConstant.CORE_NAME_STANDARDADDRESS;
}
QueryResponse response = getQueryResponse(coreName, solrQuery);

if (response.getResults() != null) {
try {
return response.getBeans(clazz);
} catch (Exception e) {
String error = "反向匹配返回转换后的结果对象列表方法,在执行QueryResponse的getBeans时出现异常!";
log.error(error, e);
throw new SolrBeanBindingException(error);
}
}
}
return Collections.emptyList();
}

/**
* 反向匹配,返回SolrDocument列表
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param start 开始位置
*
@param rows 查询的行数
*
@param returnDistanceName 返回时距离的名称
*
@return
*/
public List<SolrDocument> reverseMatchDocs(String coreName, double lon, double lat, double radius, int rows,
int start, String returnDistanceName) throws Exception {
List<SolrDocument> docs = new ArrayList<SolrDocument>();
if (StringUtils.isNotEmpty(coreName)) {
// 得到反向solr查询对象
SolrQuery solrQuery = getReverseSolrQuery(coreName, lon, lat, radius, rows, start, returnDistanceName);
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(coreName, solrQuery);

if (response.getResults() != null) {
docs.addAll(response.getResults());
}
}
return docs;
}

/**
* 得到反向solr查询对象
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param start 开始位置
*
@param rows 查询的行数
*
@param returnDistanceName 返回时距离的名称
*
@return
*/
private SolrQuery getReverseSolrQuery(String coreName, double lon, double lat, double radius, int rows, int start,
String returnDistanceName) {
SolrQuery solrQuery = new SolrQuery();
if(coreName.equals(AddrMatchConstant.CORE_NAME_INFOPOINT)) {
System.out.println("信息点请求");
solrQuery.set("q", "YSLXDM:12");
}else if(coreName.equals(AddrMatchConstant.CORE_NAME_AEINCLUSIONRELATION)) {
System.out.println("街路巷查询");
solrQuery.set("q", "YSLXDM:6");
}else {
solrQuery.set("q", "*:*");
}
solrQuery.set("fq", "{!geofilt}"); // 距离过滤函数
solrQuery.set("pt", lat + "," + lon); // 当前坐标
solrQuery.set("sfield", "GEO"); // 经纬度字段,默认约定geo名称
solrQuery.set("d", "" + radius); // 搜索半径,单位km
solrQuery.set("sort", "geodist() asc"); // 根据距离排序:由近到远
solrQuery.set("start", start); // 记录开始位置
solrQuery.set("rows", rows); // 查询的行数
if (StringUtils.isEmpty(returnDistanceName)) {
returnDistanceName = "distance";
}
solrQuery.set("fl", "*," + returnDistanceName + ":geodist()"); // 查询的结果中添加距离
return solrQuery;
}


/**
* 得到街路巷查询对象
*
*
@return
*/
private SolrQuery getStreetSolrQuery(String coreName, int rows, int start,
String ssqx) {
String q = "(SJDM:"+ssqx+" OR SJMC:"+ssqx+" OR SJQHDM:"+ssqx+" OR SJQHMC:"+ssqx+" OR QXDM:"+ssqx+" OR QXMC:"+ssqx+") AND " +
"(YSLXMC:"+coreName+" OR JLXDM:"+coreName+")";

SolrQuery solrQuery = new SolrQuery();
solrQuery.set("q", q);
// solrQuery.set("sort", "geodist() asc"); // 根据距离排序:由近到远
solrQuery.set("start", start); // 记录开始位置
solrQuery.set("rows", rows); // 查询的行数

return solrQuery;
}

/**
* solr导入数据操作,返回solr的查询返回结果对象<br>
* 根据参数不同,该方法不一定就是进行solr同步数据。 比如获取solr导入数据状态<br>
*
*
@param coreName core的名称
*
@param paramsDataMap 参数数据Map
*
@return
*/
private QueryResponse importData(String coreName, Map<String, String> paramsDataMap) throws Exception {
// 得到反向solr查询对象
SolrQuery solrQuery = new SolrQuery();
solrQuery.setRequestHandler("/dataimport");
if (paramsDataMap != null) { // 设置相关参数
for (Map.Entry<String, String> entry : paramsDataMap.entrySet()) {
solrQuery.setParam(entry.getKey(), entry.getValue());
}
}
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(coreName, solrQuery);
return response;
}

/**
* 压缩
*
*
@param content
*
@param name
*
@return
* @throws IOException
*/
public static byte[] zip(String content, String name) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = null;
DataOutputStream dataOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
zipOutputStream.putNextEntry(new ZipEntry(name));
dataOutputStream = new DataOutputStream(zipOutputStream);
dataOutputStream.write(content.getBytes(UTF8_CHARSET_NAME));
dataOutputStream.flush();
zipOutputStream.flush();
zipOutputStream.finish();
} finally {
dataOutputStream.close();
zipOutputStream.close();
}
return byteArrayOutputStream.toByteArray();
}

/**
* 解压
*
*
@param value
*
@return
*/
public String unZip(byte[] value) throws Exception {
if (null == value) {
return null;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(value);
ZipInputStream zipInputStream = null;
String sourceText = null;
try {
zipInputStream = new ZipInputStream(byteArrayInputStream);
zipInputStream.getNextEntry();
final int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int n;
while ((n = zipInputStream.read(buffer, 0, bufferSize)) != -1) {
byteArrayOutputStream.write(buffer, 0, n);
}
sourceText = byteArrayOutputStream.toString("UTF-8");
} catch (IOException e) {
String error = "solr工具类-解压方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
} finally {
try {
if (null != zipInputStream) {
zipInputStream.close();
zipInputStream = null;
}
if (null != byteArrayInputStream) {
byteArrayInputStream.close();
byteArrayInputStream = null;
}
if (null != byteArrayOutputStream) {
byteArrayOutputStream.close();
byteArrayOutputStream = null;
}
} catch (IOException e) {
String error = "solr工具类-解压方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}
return sourceText;
}

/**
* 设置排序参数,并返回SolrQuery对象
*
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param solrQuery solr查询对象
*
@return
*/
private SolrQuery setSortParams(String sortParams, SolrQuery solrQuery) {
SolrQuery returnSolrQuery = solrQuery;
if (StringUtils.isNotEmpty(sortParams)) {
String[] sortFields = sortParams.trim().split(" *, *");
for (String sortField : sortFields) {
String[] sortFieldAndOrder = sortField.split(" +");
if (2 == sortFieldAndOrder.length) {
returnSolrQuery.addSort(sortFieldAndOrder[0],
SolrQuery.ORDER.valueOf(sortFieldAndOrder[1].toLowerCase()));
} else if (1 == sortFieldAndOrder.length) {
returnSolrQuery.addSort(sortFieldAndOrder[0], SolrQuery.ORDER.asc);
}
}
}
return returnSolrQuery;
}

/**
* 得到设置过虑字段参数,如果有别名,则设置别名
*
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*/
private String[] getAliasFieldsArray(String[] fields, String[] aliasFields) {
if (fields == null || fields.length <= 0) {
return null;
}

// 判断字段别名数组不为空且长度与字段数组相等
if (aliasFields != null && aliasFields.length > 0) {
StringBuilder fieldSb = new StringBuilder();
// 别名的格式是: 别名:真实名
for (int i = 0; i < fields.length; i++) {
fieldSb.delete(0, fieldSb.length());
if (aliasFields.length - 1 >= i) {
if (StringUtils.isEmpty(aliasFields[i])) {
aliasFields[i] = fields[i];
}
fieldSb.append(aliasFields[i]).append(":").append(fields[i]);
} else {
fieldSb.append(fields[i]).append(":").append(fields[i]);
}
fields[i] = fieldSb.toString();
}
}
return fields;
}

/**
* 设置分组参数,并返回SolrQuery对象<br>
* 主要用于数据统计,对应数据库中的group by
*
*
@param facetParams 分组参数字符串,格式以逗号隔开,如name,age
*
@param solrQuery solr查询类
*/
private SolrQuery setFacetParams(String facetParams, SolrQuery solrQuery) {
SolrQuery returnSolrQuery = solrQuery;
if (StringUtils.isNotEmpty(facetParams)) {
returnSolrQuery.setFacet(true);
returnSolrQuery.addFacetPivotField(facetParams);
returnSolrQuery.setFacetMissing(false); // 不统计null的值
}
return returnSolrQuery;
}

/**
* 根据solr文档列表和转换对象的Class,通过反射设置得到对应的转换对象列表<br>
* 1.主要用于当已经设置过滤返回对象和别名后得到的docs文档列表,根据转换对象的Class,设置与文档key一致的属性值<br>
* 2.当转换对象的Class注解有@Field("otherFields"),把那些没有设置到属性里的值,全部加入到注解有@Field("otherFields")的Map对象中<br>
* 3.如果没有找到注解有@Field("otherFields")的Map,那些没有设置到属性里的值全部丢弃掉<br>
*
*
@param docs solr文档对象列表
*
@param clazz 要转换对象的Class
*
@return
*/
private <T extends Object> List<T> getBeansByDocsAndClazz(List<SolrDocument> docs, Class<T> clazz) {
// solr文档对象列表为空,直接返回空列表
if (docs == null || docs.size() <= 0) {
return Collections.emptyList();
}

// 得到所有属性列表
Field[] declaredFields = clazz.getDeclaredFields();
// 对象实例
T obj = null;
// 其他字段值map
Map<String, String> otherFieldValueMap = null;
// solr字段Object对象变量
Object fieldValueObj = null;
// solr字段字符串变量
String fieldValueStr = null;
// 返回列表
List<T> rtnList = new ArrayList<T>();
// 是否有相同的字段名称
boolean hasSameFieldName = false;
// 日期格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

for (SolrDocument doc : docs) {
// fieldValueMap = doc.getFieldValueMap();
try {
hasSameFieldName = false;
otherFieldValueMap = new HashMap<String, String>();
// 创建实例
obj = clazz.newInstance();
// 循环反射得到的字段列表,比较字段名是否一致,一致的话则赋值给对象。
for (Entry<String, Object> entry : doc.entrySet()) {
fieldValueObj = entry.getValue();

for (Field field : declaredFields) {
// 字段名一致
if (field.getName().equals(entry.getKey())) {
field.setAccessible(true);

// 类型转换,如果是solr文档对象是日期类型,并且与clazz属性类型不一致,则做日期格式转换
if (fieldValueObj instanceof Date) {
if (field.getType() == Date.class) {
field.set(obj, fieldValueObj);
} else {
field.set(obj, dateFormat.format(fieldValueObj));
}
} else {
// 除了日期类型之外,其他类型solr对象与bean对象属性类型一致,按原类型设置值
if (fieldValueObj.getClass() == field.getType()) {
field.set(obj, fieldValueObj);
} else {
field.set(obj, fieldValueObj.toString());
}
}
hasSameFieldName = true;
break;
}
}

if (!hasSameFieldName) {
// 那些没有找到相同属性名的值,全部加入Map对象中
if (fieldValueObj instanceof Date) {
fieldValueStr = dateFormat.format(fieldValueObj);
} else {
// 除了日期类型之外,其他类型按字符串类型设置值
fieldValueStr = fieldValueObj.toString();
}
otherFieldValueMap.put(entry.getKey(), fieldValueStr);
}

} // end-for (Entry<String, Object> entry : doc.entrySet())

// 通过反射,设置其他字段值map到对象实例
setOtherFieldValueMap(declaredFields, obj, otherFieldValueMap);

rtnList.add(obj);
} catch (InstantiationException | IllegalAccessException e) {
// 出现异常,记录日志,不直接抛出中断流程
log.error("通过转换得到对应的转换对象列表方法时,出现异常!", e);
}
}
return rtnList;
}

/**
* 通过反射,设置其他字段值map到对象实例
*
*
@param declaredFields 所有属性字段的列表
*
@param obj 要转换对象Class的对象实例
*
@param otherFieldValueMap 其他字段值map
*
@return
*/
private <T extends Object> T setOtherFieldValueMap(Field[] declaredFields, T obj,
Map<String, String> otherFieldValueMap) {

for (Field field : declaredFields) {
if (field.isAnnotationPresent(org.apache.solr.client.solrj.beans.Field.class)
&& field.getType() == Map.class) {

org.apache.solr.client.solrj.beans.Field annotationField = field
.getAnnotation(org.apache.solr.client.solrj.beans.Field.class);

// 注解的字段名是否为otherFields,则把除了有设置别名之外的需要返回的字段值,赋值给该字段值上
if (ANNOTATION_OTHERFIELDS.equals(annotationField.value())) {
try {
field.setAccessible(true);
field.set(obj, otherFieldValueMap);
} catch (IllegalArgumentException | IllegalAccessException e) {
// 出现异常,记录日志,不直接抛出中断流程
log.error("通过反射设置其他字段值map到对象实例方法时,出现异常!", e);
}
break;
}
}
}
return obj;
}

/**
* 触发批量更新,针对批量<br>
* 全库导入,同步的速度是最快,增量导入,需要逐条比对,比较慢。<br>
*
*
@param coreName core的名称
*
@param hasFullImport 是否全库导入,第一次抽取匹配时设置为true,增量抽取匹配时设置为false
*
@return Long 更新条数
*/
@SuppressWarnings("unchecked")
public Long triggerBatchUpdate(String coreName, Boolean hasFullImport) throws Exception {
// 命令参数
String commandParam = (hasFullImport != null && hasFullImport) ? "full-import" : "delta-import";
// 同步参数设置
Map<String, String> paramsDataMap = new HashMap<String, String>();
paramsDataMap.put("command", commandParam);
paramsDataMap.put("clean", "false");
paramsDataMap.put("commit", "true");

// 根据参数执行solr导入同步数据操作
importData(coreName, paramsDataMap);

// 状态参数设置
Map<String, String> statusParamsDataMap = new HashMap<String, String>();
statusParamsDataMap.put("command", "status");

int num = 0;
QueryResponse response; // 查询结果对象
NamedList<Object> resMap; // 查询结果Map
String status; // 状态值,有两种,idle和busy。
LinkedHashMap<String, String> statusMessages; // 状态消息对象

while (true) {
// solr导入数据操作
response = importData(coreName, statusParamsDataMap);

// 获取得到更新条数。
resMap = response.getResponse();
status = resMap.get("status") == null ? "" : resMap.get("status").toString();
log.debug("~~~~~获取数据同步至solr状态!状态为:" + status);

if ("idle".equalsIgnoreCase(status)) { // 状态已完成
if (resMap.get("statusMessages") != null) { // 更新条数是在statusMessages对象里
try {
statusMessages = (LinkedHashMap<String, String>) resMap.get("statusMessages");
num = Integer.parseInt(statusMessages.get("Total Documents Processed"));
} catch (Exception e1) {
num = 0;
}
}
break;
}
TimeUnit.MILLISECONDS.sleep(1000); // 睡眠1秒
}

return (long) num;
}

/**
* 得到分组的solr文档对象列表
*
*
@param pivotFieldList 枢轴字段对象列表
*
@param aliasFields 过滤显示字段别名数组
*
@return
*/
private List<SolrDocument> getFacetSolrDocumentList(List<PivotField> pivotFieldList, String[] aliasFields) {
List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
List<SolrDocument> returnSolrDocList = new ArrayList<SolrDocument>();
// 调用递归枢轴字段方法
recursiveFacetPivotField(pivotFieldList, null, results);
if (CollectionUtils.isEmpty(results)) {
return returnSolrDocList;
}

// 将List<Map<String, Object>> 转换为List<SolrDocument>结果
SolrDocument solrDoc = null;
int aliasFieldIndex;
String pivotFieldKey;
for (Map<String, Object> pivotFieldAndValueMap : results) {
solrDoc = new SolrDocument();
aliasFieldIndex = 0;
for (Entry<String, Object> entry : pivotFieldAndValueMap.entrySet()) {
// 别名处理
if (aliasFields != null && aliasFields.length > 0 && aliasFieldIndex < aliasFields.length) {
pivotFieldKey = aliasFields[aliasFieldIndex];
aliasFieldIndex++;
} else {
pivotFieldKey = entry.getKey();
}
solrDoc.setField(pivotFieldKey, entry.getValue());
}
returnSolrDocList.add(solrDoc);
}
return returnSolrDocList;
}

/**
* 递归分组的枢轴字段方法<br>
* PivotField为solr特殊类型<br>
*
*
@param pivotFieldList 枢轴字段对象列表
*
@param pivotFieldAndValueMap 枢轴字段和值的Map对象
*
@param results 结果集,类似于数据库group by后,得到的结果集
*/
private void recursiveFacetPivotField(List<PivotField> pivotFieldList, Map<String, Object> pivotFieldAndValueMap,
List<Map<String, Object>> results) {
if (pivotFieldList == null || pivotFieldList.size() <= 0) {
return;
}
if (pivotFieldAndValueMap == null) {
pivotFieldAndValueMap = new LinkedHashMap<String, Object>();
}

for (PivotField pivotField : pivotFieldList) {
pivotFieldAndValueMap.put(pivotField.getField(), pivotField.getValue());
if (pivotField.getPivot() == null || pivotField.getPivot().size() <= 0) {
// TODO 设定统计值key和值,key暂时未设置为常量
pivotFieldAndValueMap.put("count", pivotField.getCount());
results.add(new LinkedHashMap<String, Object>(pivotFieldAndValueMap));
} else {
recursiveFacetPivotField(pivotField.getPivot(), pivotFieldAndValueMap, results);
}
}
}

/**
* 得到solr的查询返回结果对象
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@param facetParams 分组参数字符串,格式以逗号隔开,如name,age
*
@param requestHandler 请求句柄,如分词为splitwords
*
@param coreUrls core Url数组,用于跨core查询
*
@return
* @throws Exception
*/
private QueryResponse getQueryResponse(String criteria, String coreName, Integer rows, Integer start,
String sortParams, String[] fields, String[] aliasFields, String facetParams, String requestHandler,
String[] coreUrls) throws Exception {
// 跨core查询
if (coreUrls != null && coreUrls.length > 1) {
return getMoreCoreQueryResponse(coreUrls, criteria, coreName, rows, start, sortParams, fields, aliasFields);
}

SolrQuery solrQuery = new SolrQuery().setQuery(criteria).setRows(rows).setStart(start);
solrQuery = setSortParams(sortParams, solrQuery);
solrQuery = setFacetParams(facetParams, solrQuery);
fields = this.getAliasFieldsArray(fields, aliasFields);
solrQuery.setFields(fields);
if (StringUtils.isNotEmpty(requestHandler)) {
solrQuery.setRequestHandler("/" + requestHandler);
}

return this.getQueryResponse(coreName, solrQuery);
}

/**
* 得到solr的查询返回结果对象
*
*
@param coreName core名称
*
@param solrQuery solr查询对象
*
@return
* @throws Exception
*/
public QueryResponse getQueryResponse(String coreName, SolrQuery solrQuery) throws Exception {
SolrServer solrServer = getSolrServer(coreName);
// 设置分组参数
QueryRequest queryRequest = new QueryRequest(solrQuery);
queryRequest.setMethod(SolrRequest.METHOD.POST);
try {
// if (log.isDebugEnabled()) {total++;}
QueryResponse response = queryRequest.process(solrServer);
return response;
} catch (SolrServerException | HttpSolrServer.RemoteSolrException e) {
String error = "solr工具类-执行得到solr的查询返回结果对象方法出现异常!";
Throwable throwable = e.getCause();
if (throwable instanceof SocketException || throwable instanceof IOException) {
try {
// solrServer的ping调用
pingSolrServer(solrServer);
} catch (Exception ex) {
throw ex;
}
return getQueryResponse(coreName, solrQuery);
}
log.error(error, e);
throw new SolrServerIoException(error);
}
}

/**
* 得到跨core查询返回的结果对象
*
*
@param coreUrls core Url数组,用于跨core查询
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@return
* @throws Exception
*/
private QueryResponse getMoreCoreQueryResponse(String[] coreUrls, String criteria, String coreName, int rows,
int start, String sortParams, String[] fields, String[] aliasFields) throws Exception {
SolrServer solrServer = getSolrServer(coreName);
// 获取多core查询参数
ModifiableSolrParams solrParams = getMoreCoreModifiableSolrParams(coreUrls, criteria, rows, start, sortParams,
fields, aliasFields);
QueryRequest queryRequest = new QueryRequest(solrParams);
queryRequest.setMethod(SolrRequest.METHOD.POST);
try {
// if (log.isDebugEnabled()) {total++;}
QueryResponse response = queryRequest.process(solrServer);
return response;
} catch (SolrServerException | HttpSolrServer.RemoteSolrException e) {
String error = "solr工具类-执行得到solr的查询返回结果对象方法出现异常!";
Throwable throwable = e.getCause();
if (throwable instanceof SocketException || throwable instanceof IOException) {
try {
// solrServer的ping调用
pingSolrServer(solrServer);
} catch (Exception ex) {
throw ex;
}
return getMoreCoreQueryResponse(coreUrls, criteria, coreName, rows, start, sortParams, fields,
aliasFields);
}
log.error(error, e);
throw new SolrServerIoException(error);
}
}

/**
* 获取多core查询参数
*
*
@param coreUrls core Url数组,用于跨core查询
*
@param criteria 查询条件
*
@param rows 查询的行数
*
@param start 开始位置
*
@param sortParams 排序参数
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@return
*/
private ModifiableSolrParams getMoreCoreModifiableSolrParams(String[] coreUrls, String criteria, int rows,
int start, String sortParams, String[] fields, String[] aliasFields) {
ModifiableSolrParams solrParams = new ModifiableSolrParams();
solrParams.set("q", criteria);
solrParams.set("sort", sortParams); // 排序
solrParams.set("start", start); // 记录开始位置
solrParams.set("rows", rows); // 查询的行数
solrParams.set("shards.info", true);
solrParams.set("shards", StringUtils.join(coreUrls, ","));
fields = this.getAliasFieldsArray(fields, aliasFields);
if (fields != null && fields.length > 0) {
solrParams.set("fl", StringUtils.join(fields, ",")); // 设置过滤字段
}

return solrParams;
}

/**
* 得到solrClient的solrUrl
*
*
@return
*/
public String getSolrUrl() {
return solrUrl;
}

/**
* solrServer的ping调用<br>
* 网络断开连接不上时,调用ping方法检查网络情况。在设置的次数范围之内能ping通网络,则继续进行往下执行地址匹配流程,否则抛出异常。
*
*
@param solrServer solr服务对象
*
@return
* @throws Exception
*/
private synchronized SolrServer pingSolrServer(SolrServer solrServer) throws Exception {
try {
solrServer.ping();
} catch (SolrServerException | IOException | HttpSolrServer.RemoteSolrException e) {
// 循环次数,网络出现异常情况下,循环设置的次数回调ping方法。超过设置的次数,将直接抛出异常。
int loopNum = 0;
if (solrLoopNumMap.containsKey(solrUrl)) {
loopNum = solrLoopNumMap.get(solrUrl) + 1;
} else {
loopNum++;
}
solrLoopNumMap.put(solrUrl, loopNum);

// TODO 由于类代码长度限制1500行,目前暂时以简单方式读取配置值,后期再优化
// solr的ping循环次数,默认为5
if (pingLoopNum <= 0) {
try {
pingLoopNum = Integer.parseInt(ConfigUtil.getPropsValueByKey("ac.httpSolr.pingLoopNum"));
//pingLoopNum = Integer.parseInt(CommonString.pingLoopNum);
pingLoopNum = pingLoopNum <= 0 ? 5 : pingLoopNum;
} catch (NumberFormatException ex) {
ex.printStackTrace();
pingLoopNum = 5;
}
}
// solr的ping睡眠时间,以毫秒为单位,默认为10秒
if (pingSleepTime <= 0) {
try {
pingSleepTime = Long.parseLong(ConfigUtil.getPropsValueByKey("ac.httpSolr.pingSleepTime"));
//pingSleepTime = Long.parseLong(CommonString.pingSleepTime);
pingSleepTime = pingSleepTime <= 0 ? 10000 : pingSleepTime;
} catch (NumberFormatException ex) {
ex.printStackTrace();
pingSleepTime = 10000;
}
}
// sol的ping循环次数超过设置的值,抛出SolrHttpException异常
if (loopNum > pingLoopNum) {
log.warn("执行solrServer的ping方法超过" + pingLoopNum + "次仍然出现网络异常!调用URL为:" + solrUrl);
String error = String.format("solr工具类-执行solrServer的ping方法出现异常!原因是solr【%s】没有启动或网络不通", solrUrl + "/");
log.error(error, e);
throw new SolrHttpException(error);
}
// 睡眠,单位为毫秒
TimeUnit.MILLISECONDS.sleep(pingSleepTime);

log.info("网络出现异常!再次执行solrServer的ping方法,调用次数为:" + loopNum + ",调用URL为:" + solrUrl);
return pingSolrServer(solrServer);
}
solrLoopNumMap.put(solrUrl, 0);
return solrServer;
}

/**
* 空间检索
*
@param coreName
*
@param solrQuery
*
@return
* @throws Exception
*/
public QueryResponse spaceSearch(String coreName, SolrQuery solrQuery) throws Exception {
if (StringUtils.isNotEmpty(coreName)) {
try {
QueryResponse response = getQueryResponse(coreName, solrQuery);
return response;
} catch (SolrServerException | HttpSolrServer.RemoteSolrException e) {
String error = "solr工具类-执行得到solr的查询返回结果对象方法出现异常!";
Throwable throwable = e.getCause();
if (throwable instanceof SocketException || throwable instanceof IOException) {
try {
SolrServer solrServer = getSolrServer(coreName);
// solrServer的ping调用
pingSolrServer(solrServer);
} catch (Exception ex) {
throw ex;
}
return spaceSearch(coreName, solrQuery);
}
log.error(error, e);
throw new SolrServerIoException(error);
}
}
return null;
}




}
作者 east

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。回复”chatgpt”获取免注册可用chatgpt。回复“大数据”获取多本大数据电子书

标签

AIGC AI创作 bert chatgpt github GPT-3 gpt3 GTP-3 hive mysql O2O tensorflow UI控件 不含后台 交流 共享经济 出行 图像 地图定位 外卖 多媒体 娱乐 小程序 布局 带后台完整项目 开源项目 搜索 支付 效率 教育 日历 机器学习 深度学习 物流 用户系统 电商 画图 画布(canvas) 社交 签到 联网 读书 资讯 阅读 预订

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?
  • 如何设计AUTOSAR中的“域控制器”以支持未来扩展?
  • C++ 中避免悬挂引用的企业策略有哪些?
  • 嵌入式电机:如何在低速和高负载状态下保持FOC(Field-Oriented Control)算法的电流控制稳定?
  • C++如何在插件式架构中使用反射实现模块隔离?
  • C++如何追踪内存泄漏(valgrind/ASan等)并定位到业务代码?
  • C++大型系统中如何组织头文件和依赖树?

文章归档

  • 2025年6月
  • 2025年5月
  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月

分类目录

  • Android (73)
  • bug清单 (79)
  • C++ (34)
  • Fuchsia (15)
  • php (4)
  • python (43)
  • sklearn (1)
  • 云计算 (20)
  • 人工智能 (61)
    • chatgpt (21)
      • 提示词 (6)
    • Keras (1)
    • Tensorflow (3)
    • 大模型 (1)
    • 智能体 (4)
    • 深度学习 (14)
  • 储能 (44)
  • 前端 (4)
  • 大数据开发 (488)
    • CDH (6)
    • datax (4)
    • doris (30)
    • Elasticsearch (15)
    • Flink (78)
    • flume (7)
    • Hadoop (19)
    • Hbase (23)
    • Hive (40)
    • Impala (2)
    • Java (71)
    • Kafka (10)
    • neo4j (5)
    • shardingsphere (6)
    • solr (5)
    • Spark (99)
    • spring (11)
    • 数据仓库 (9)
    • 数据挖掘 (7)
    • 海豚调度器 (10)
    • 运维 (34)
      • Docker (3)
  • 小游戏代码 (1)
  • 小程序代码 (139)
    • O2O (16)
    • UI控件 (5)
    • 互联网类 (23)
    • 企业类 (6)
    • 地图定位 (9)
    • 多媒体 (6)
    • 工具类 (25)
    • 电商类 (22)
    • 社交 (7)
    • 行业软件 (7)
    • 资讯读书 (11)
  • 嵌入式 (70)
    • autosar (63)
    • RTOS (1)
    • 总线 (1)
  • 开发博客 (16)
    • Harmony (9)
  • 技术架构 (6)
  • 数据库 (32)
    • mongodb (1)
    • mysql (13)
    • pgsql (2)
    • redis (1)
    • tdengine (4)
  • 未分类 (6)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

All Rights Reserved by Gitweixin.本站收集网友上传代码, 如有侵犯版权,请发邮件联系yiyuyos@gmail.com删除.