中小型研发团队架构实践之Solr

2017 年 11 月 15 日 聊聊架构 张辉清 杨丽
作者|张辉清、杨丽
编辑|雨多田光

文末有 demo 下载

一、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 下载及更多资料
  • SolrDemo 下载地址

    https://github.com/das2017/SolrDemo

  • Solr 官网

    http://lucene.apache.org/solr/

  • Lucene 官网

    http://lucene.apache.org/

  • SolrNet 官网

    https://github.com/mausch/SolrNet/

作者介绍

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

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


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

登录查看更多
1

相关内容

XPath即为XML路径语言,它是一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言。XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力。起初 XPath 的提出的初衷是将其作为一个通用的、介于XPointer与XSLT间的语法模型。但是 XPath 很快的被开发者采用来当作小型查询语言。
CVPR 2020 最佳论文与最佳学生论文!
专知会员服务
34+阅读 · 2020年6月17日
【SIGMOD2020-腾讯】Web规模本体可扩展构建
专知会员服务
29+阅读 · 2020年4月12日
阿里巴巴达摩院发布「2020十大科技趋势」
专知会员服务
105+阅读 · 2020年1月2日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
Keras作者François Chollet推荐的开源图像搜索引擎项目Sis
专知会员服务
29+阅读 · 2019年10月17日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
百度开源项目OpenRASP快速上手指南
黑客技术与网络安全
5+阅读 · 2019年2月12日
如何用GitLab本地私有化部署代码库?
Python程序员
9+阅读 · 2018年12月29日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
大数据流处理平台的技术选型参考
架构文摘
4+阅读 · 2018年3月14日
干货|全文检索Solr集成HanLP中文分词
全球人工智能
4+阅读 · 2017年8月27日
开源巨献:阿里巴巴最热门29款开源项目
算法与数据结构
5+阅读 · 2017年7月14日
Pluralistic Image Completion
Arxiv
8+阅读 · 2019年3月11日
Foreground-aware Image Inpainting
Arxiv
4+阅读 · 2019年1月17日
Arxiv
5+阅读 · 2018年5月1日
Arxiv
3+阅读 · 2018年2月12日
Arxiv
4+阅读 · 2016年12月29日
VIP会员
相关资讯
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
百度开源项目OpenRASP快速上手指南
黑客技术与网络安全
5+阅读 · 2019年2月12日
如何用GitLab本地私有化部署代码库?
Python程序员
9+阅读 · 2018年12月29日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
大数据流处理平台的技术选型参考
架构文摘
4+阅读 · 2018年3月14日
干货|全文检索Solr集成HanLP中文分词
全球人工智能
4+阅读 · 2017年8月27日
开源巨献:阿里巴巴最热门29款开源项目
算法与数据结构
5+阅读 · 2017年7月14日
相关论文
Pluralistic Image Completion
Arxiv
8+阅读 · 2019年3月11日
Foreground-aware Image Inpainting
Arxiv
4+阅读 · 2019年1月17日
Arxiv
5+阅读 · 2018年5月1日
Arxiv
3+阅读 · 2018年2月12日
Arxiv
4+阅读 · 2016年12月29日
Top
微信扫码咨询专知VIP会员