【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

中小型研发团队架构实践:搜索服务器 Solr

  • 2017-12-17
  • 本文字数:6101 字

    阅读完需:约 20 分钟

一、Solr 是什么

Apache Solr 是一个开源的搜索服务器,Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现。 Apache Lucene 是一个高效的、基于 Java 的全文检索库。

二、为什么要用 Solr

  • 在公司后台历史订单查询的应用中,模糊查询的实现方式为 LIKE ‘%something%’,性能很差。
  • 基于关键字的日志内容需要快速检索。
  • 其他数据库模糊查询的优化方案。

三、Solr 的特性

  • 具备高级全文搜索的能力
  • 高容量
  • 基于标准的开放接口(XML、JSON、HTTP):Document 通过 HTTP 利用 XML 加到一个搜索集合中,查询该集合时也是通过 HTTP 收到一个 XML/JSON 响应来实现
  • 提供功能全面的管理界面,使你能够容易地控制你的 Solr 实例
  • 易监控
  • 高稳定性和容错性
  • 易配置,且不失灵活和适配性
  • 准实时索引,确保你能够实时看到更新后的内容
  • 可扩展插件架构:新功能能够以插件的形式非常方便地添加到 Solr 服务器上

四、Solr 怎样工作

4.1、Web 管理 UI

URL 为: http://139.198.13.12:7000/solr/admin.html。请注意:Solr5.5 的,一定要加 admin.html,如果不加的话,则按回车后将返回 404(表示找不到页面)。

4.2、Solr 服务端的安装与配置

4.2.1、安装 Solr 服务:安装的版本号是 5.5.4。

4.2.2、建立 Core

要使用 Solr,需要建立类似于数据库实例的 Core。每个 Core 对应一个文件夹,此文件夹建立在 Solr Home 路径下,且其名字要和 Core 的名字一致:

4.2.3、配置 Core

以 Demo 中使用于 Solr 服务器上的 PolicyCore 为例,修改以下 3 个配置文件:

solrconfig.xml、managed-schema 是从位于【{Solr Home 路径}/configsets/basic_configs/conf】路径下的同名配置文件拷贝而来,而 data-config.xml 来自:对 Solr 服务端安装文件 solr-5.5.4.tgz 解压后,得到 solr-5.5.4 的文件夹名,然后把位于【solr-5.5.4/example/example-DIH/solr/db/conf】路径下的 db-data-config.xml 文件拷贝到【{Solr Home 路径}/configsets/basic_configs/conf】路径下,并重命名为 data-config.xml。

在 solrconfig.xml 配置文件中增加如下内容:

复制代码
<lib dir="../contrib/extraction/lib" regex=".*\.jar" />
<lib dir="../dist/" regex="solr-cell-\d.*\.jar" />
<lib dir="../contrib/clustering/lib/" regex=".*\.jar" />
<lib dir="../dist/" regex="solr-clustering-\d.*\.jar" />
<lib dir="../contrib/langid/lib/" regex=".*\.jar" />
<lib dir="../dist/" regex="solr-langid-\d.*\.jar" />
<lib dir="../contrib/velocity/lib" regex=".*\.jar" />
<lib dir="../dist/" regex="solr-velocity-\d.*\.jar" />
<lib dir="../dist/" regex="solr-dataimporthandler-\d.*\.jar" />

以上内容加在【5.5.4】节点之后、【${solr.data.dir:}】节点之前。

复制代码
<requestHandler name="/dataimport" class="solr.DataImportHandler">
   <lst name="defaults">
       <str name="config">data-config.xml</str>
   </lst>
</requestHandler>

以上内容加的位置请见如下图所示:

对 managed-schema 文件进行修改:以下内容加在节点内:

复制代码
<fieldType name="textPolicy_ik" class="solr.TextField">
   <analyzer type="index" useSmart="false" class="org.wltea.analyzer.lucene.IKAnalyzer" />
   <analyzer type="query" useSmart="true" class="org.wltea.analyzer.lucene.IKAnalyzer" />
</fieldType>

注释掉以下配置:

<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />然后在其下增加如下配置:

复制代码
<field name="PolicyID" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<field name="PolicyGroupID" type="long" indexed="true" stored="true" />
<field name="PolicyOperatorID" type="long" indexed="true" stored="true" />
<field name="PolicyOperatorName" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />
<field name="PolicyCode" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />
<field name="PolicyName" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />
<field name="PolicyType" type="string" indexed="true" stored="true" />
<field name="TicketType" type="int" indexed="true" stored="true" />
<field name="FlightType" type="int" indexed="true" stored="true" />
<field name="DepartureDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />
<field name="ArrivalDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />
<field name="ReturnDepartureDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />
<field name="ReturnArrivalDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />
<field name="DepartureCityCodes" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />
<field name="TransitCityCodes" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />
<field name="ArrivalCityCodes" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />
<field name="OutTicketType" type="int" indexed="true" stored="true" />
<field name="OutTicketStart" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />
<field name="OutTicketEnd" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />
<field name="OutTicketPreDays" type="int" indexed="true" stored="true" />
<field name="Remark" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />
<field name="Status" type="int" indexed="true" stored="true" />
<field name="SolrUpdatedTime" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />
<uniqueKey>PolicyID</uniqueKey>

属性说明:

  • name:表示域名。
  • type:表示域的类型,必须匹配类型,不然会报错。如果需要分词,那么就传分词器名如 textPolicy_ik;另外,日期建议传 tdate,因为可以加快范围查找速度。
  • indexed:是否要做索引。
  • stored:是否要存储。
  • required:是否必填。
  • multiValued:是否有多个值。如果设置为多值,里面的值就采用数组的方式来存储。

对 data-config.xml 文件进行修改:先注释掉默认有的 dataConfig,然后在被注释内容的后面增加如下配置内容:

复制代码
<dataConfig>
   <dataSource driver="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://{SQLServer 服务器 IP 地址}:{端口号,如果端口号是默认的 1433,则可不写};DatabaseName=SolrDB" user="sa" password="{登录 SQL Server 的密码}"/>
   <document name="Info">        
      <entity name="Policy" dataSource="SolrDB" transformer="ClobTransformer" pk="PolicyID"
          query="SELECT [PolicyID], [PolicyGroupID], [PolicyOperatorID], [PolicyOperatorName], [PolicyCode], [PolicyName], [PolicyType], [TicketType], [FlightType],  DATEADD(HOUR, 8, CAST([DepartureDate] AS DATETIME)) [DepartureDate], DATEADD(HOUR, 8, CAST([ArrivalDate] AS DATETIME)) [ArrivalDate], DATEADD(HOUR, 8, CAST([ReturnDepartureDate] AS DATETIME)) [ReturnDepartureDate], DATEADD(HOUR, 8, CAST([ReturnArrivalDate] AS DATETIME)) [ReturnArrivalDate], [DepartureCityCodes], [TransitCityCodes], [ArrivalCityCodes], [OutTicketType], [OutTicketStart], [OutTicketEnd], [OutTicketPreDays], [Remark], [Status], DATEADD(HOUR, 8, CAST([SolrUpdatedTime] AS DATETIME)) [SolrUpdatedTime] FROM [Policy]"
          deltaImportQuery="SELECT [PolicyID], [PolicyGroupID], [PolicyOperatorID], [PolicyOperatorName], [PolicyCode], [PolicyName], [PolicyType], [TicketType], [FlightType], DATEADD(HOUR, 8, CAST([DepartureDate] AS DATETIME)) [DepartureDate], DATEADD(HOUR, 8, CAST([ArrivalDate] AS DATETIME)) [ArrivalDate], DATEADD(HOUR, 8, CAST([ReturnDepartureDate] AS DATETIME)) [ReturnDepartureDate], DATEADD(HOUR, 8, CAST([ReturnArrivalDate] AS DATETIME)) [ReturnArrivalDate], [DepartureCityCodes], [TransitCityCodes], [ArrivalCityCodes], [OutTicketType], [OutTicketStart], [OutTicketEnd], [OutTicketPreDays], [Remark], [Status], DATEADD(HOUR, 8, CAST([SolrUpdatedTime] AS DATETIME)) [SolrUpdatedTime] FROM [Policy] WHERE PolicyID = '${dataimporter.delta.PolicyID}'"
      deltaQuery="SELECT [PolicyID] FROM [Policy] WHERE [SolrUpdatedTime] > '${dataimporter.last_index_time}'">
    <field column="PolicyID" name="PolicyID"/>  
    <field column="PolicyGroupID" name="PolicyGroupID"/>  
    <field column="PolicyOperatorID" name="PolicyOperatorID"/>
    <field column="PolicyOperatorName" name="PolicyOperatorName"/>
    <field column="PolicyCode" name="PolicyCode"/>
    <field column="PolicyName" name="PolicyName"/>
    <field column="PolicyType" name="PolicyType"/>
    <field column="TicketType" name="TicketType"/>
    <field column="FlightType" name="FlightType"/>
    <field column="DepartureDate" name="DepartureDate"/>
    <field column="ArrivalDate" name="ArrivalDate"/>
    <field column="ReturnDepartureDate" name="ReturnDepartureDate"/>
    <field column="ReturnArrivalDate" name="ReturnArrivalDate"/>
    <field column="DepartureCityCodes" name="DepartureCityCodes"/>
    <field column="TransitCityCodes" name="TransitCityCodes"/>
    <field column="ArrivalCityCodes" name="ArrivalCityCodes"/>
    <field column="OutTicketType" name="OutTicketType"/>
    <field column="OutTicketStart" name="OutTicketStart"/>
    <field column="OutTicketEnd" name="OutTicketEnd"/>
    <field column="OutTicketPreDays" name="OutTicketPreDays"/>
    <field column="Remark" name="Remark"/>
    <field column="Status" name="Status"/>
    <field column="SolrUpdatedTime" name="SolrUpdatedTime"/>
   </entity>
 </document>
</dataConfig>

属性说明:

  • query:查询数据库表中符合的记录数据。
  • deltaImportQuery:表示次查询。次查询是获取以上步骤的 ID,然后把其全部数据获取,根据获取的数据,对索引库进行更新操作,可能是删除、添加或修改。此查询只对增量导入起作用,可以返回多个字段的值,一般情况下,都是返回所有字段的列。
  • deltaQuery:查询出需要增量索引的数据,所有经过修改的记录的 ID,可能是修改操作、添加操作或删除操作产生的。此查询只对增量导入起作用,而且只能返回 ID 值。

4.3、为 SolrDB 数据库的 Policy 表增加字段和触发器

复制代码
USE [SolrDB]
GO
CREATE TRIGGER [dbo].[TR_Solr_UPDATE_Policy] ON [dbo].[Policy]
       FOR UPDATE, INSERT
AS
BEGIN
  IF UPDATE(PolicyID)
       OR UPDATE(PolicyGroupID)
       OR UPDATE(PolicyOperatorID)
       OR UPDATE(PolicyOperatorName)
       OR UPDATE(PolicyCode)
       OR UPDATE(PolicyName)
       OR UPDATE(PolicyType)
       OR UPDATE(TicketType)
       OR UPDATE(FlightType)
       OR UPDATE(DepartureDate) OR UPDATE(ArrivalDate)
       OR UPDATE(ReturnDepartureDate) OR UPDATE(ReturnArrivalDate)
       OR UPDATE(DepartureCityCodes)
       OR UPDATE(TransitCityCodes)
       OR UPDATE(ArrivalCityCodes)
       OR UPDATE(OutTicketType)
       OR UPDATE(OutTicketStart) OR UPDATE(OutTicketEnd)
       OR UPDATE(OutTicketPreDays)
       OR UPDATE(Remark)
       OR UPDATE(Status)
  BEGIN
               UPDATE dbo.Policy
               SET SolrUpdatedTime = GETDATE()
               FROM dbo.Policy p, inserted i
               WHERE p.PolicyID = i.PolicyID
  END
END
GO

4.4、SolrNet

SolrNet 是 Solr 的开源.NET 客户端之一。

4.5、定时从数据库中全量、增量数据导入到 Solr

Solr 自身提供有定时增量导入功能,但经测试 apache-solr-dataimportscheduler1.0 版本在 Solr5.5 上已经不能使用,除非修改 apache-solr-dataimportscheduler 的源码。于是,我们采用了如下方式:

首先,开发 Job 任务调度 RESTful 服务,这种方式不仅可以实现定时增量数据导入,也能够实现定时全量数据导入。

然后,在自主研发的【Job 集中式管理平台】中把相关内容都配置好,如下图所示。

这样,我们的 JobServer 就会定时地以 HTTP GET 或 HTTP POST 或 HTTP HEAD 方式请求全量 / 增量导入链接,从而实现了定时全量、增量数据导入功能。另外,如果你想要知道如何利用 SolrNet 实现全量导入、增量导入,请分别参考 Demo 代码中的 FullDataImport() 和 DeltaDataImport() 这两个示例。

4.6、准实时数据导入、删除以及查询

用 SolrNet 的 CURD API 实现,示例请见 Demo 的 Add()、Delete() 和 Query()。准实时数据导入较定时增量数据导入更近于实时,在实际应用中如通过消息队列对数据库和 Solr 同时更新,则更好。

五、Demo 下载及更多资料

本系列文章涉及内容清单如下,其中有感兴趣的,欢迎关注:

作者介绍

杨丽,拥有多年互联网应用系统研发经验,曾就职于古大集团,现任职中青易游的系统架构师,主要负责公司研发中心业务系统的架构设计以及新技术积累和培训。现阶段主要关注开源软件、软件架构、微服务以及大数据。

张辉清,10 多年的 IT 老兵,先后担任携程架构师、古大集团首席架构、中青易游 CTO 等职务,主导过两家公司的技术架构升级改造工作。现关注架构与工程效率,技术与业务的匹配与融合,技术价值与创新。

感谢雨多田光对本文的审校。

2017-12-17 17:243310

评论

发布
暂无评论
发现更多内容

如潮好评!优秀选手视角下的第二届粤港澳大湾区(黄埔)国际算法算例大赛

ModelWhale

人工智能 大数据 算法赛 粤港澳大湾区 算法开发

Lazada商品详情API(lazada.item_get)获取商品的评论和评分信息

技术冰糖葫芦

API

软件测试/测试开发/全日制 | 机器学习的原理与应用:算法驱动的智能革命

测吧(北京)科技有限公司

测试

体育直播平台系统源码:如何有效防止后门、恶意代码和漏洞

软件开发-梦幻运营部

OmniPlan Pro 4 for Mac(项目流程管理工具) v4.6完美激活版

mac

项目管理软件 苹果mac Windows软件 OmniPlan Pro

一文解释Linux的内存分页管理

伤感汤姆布利柏

2023 年最先进认证方式上线,Authing 推出 Passkey 无密码认证

Authing

身份认证 Authing 无密码认证 Passkey

软件测试/测试开发/全日制 | 深度学习的崛起与在人工智能中的关键作用

测吧(北京)科技有限公司

测试

云桌面有什么技术特点?应用场景是什么?

青椒云云电脑

桌面云 云桌面 云桌面解决方案

移动图形工作站分类、结构和功能

青椒云云电脑

图形工作站

软件测试开发/全日制丨软件开发流程 学习笔记

测试人

软件测试

字节跳动 Spark 支持万卡模型推理实践

字节跳动云原生计算

机器学习 spark 云原生

软件测试/测试开发/全日制 | 人工智能的基本概念与发展趋势

测吧(北京)科技有限公司

测试

软件测试/测试开发/全日制 | 自然语言处理技术在人工智能时代的崛起与发展

测吧(北京)科技有限公司

测试

软件测试/测试开发全日制培训|Pytest跳过用例和失败重试

霍格沃兹测试开发学社

低代码平台受到欢迎的原因有哪些?

这我可不懂

低代码 数字化 应用程序 JNPF

图形工作站有必要么?图形工作站电脑特点

青椒云云电脑

图形工作站 移动图形工作站

软件测试开发/全日制丨自动化测试定位策略 学习笔记

测试人

软件测试 测试开发

软件开发隐藏报价和虚假信息,合约如何来提高安全性

软件开发-梦幻运营部

2024年区块链行业发展,项目涵盖内容

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

云消息队列 Kafka 版生态谈第一期:无代码转储能力介绍

阿里巴巴云原生

kafka 阿里云 Serverless 云原生

低代码:实现数据可视化的强大助手

不在线第一只蜗牛

数据库 低代码 数据可视化

摆脱自研难题,AUI Kit助力企业快速搭建专属互动课堂

阿里云视频云

云计算 视频云

软件测试/测试开发/全日制 | Python全栈开发之路:HTML/CSS/JavaScript基础深度学习

测吧(北京)科技有限公司

测试

Databend 的算力可扩展性

Databend

深耕汽车检测设备领域,引领行业技术革新

Geek_2d6073

Vue3构建的低代码可视化平台

高端章鱼哥

Vue 低代码 前端框架 JNPF

OpenTiny 2023年度共建者榜单大曝光!!!

OpenTiny社区

开源 前端

老生常谈:Web 与低代码开发

快乐非自愿限量之名

Web 前端开发 低代码 开发

大数据开发与低代码:加速数据处理与解决方案开发

EquatorCoco

数据库 低代码 大数据开发

lazada商品列表数据接口(lazada.item_search)丨lazada API接口

tbapi

lazada商品详情数据接口 lazada商品数据接口 lazada API接口 lazada商品列表数据接口

中小型研发团队架构实践:搜索服务器Solr_架构_张辉清_InfoQ精选文章