一、Solr 是什么
\\Apache Solr 是一个开源的搜索服务器,Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现。 Apache Lucene 是一个高效的、基于 Java 的全文检索库。
\\二、为什么要用 Solr
\\- 在公司后台历史订单查询的应用中,模糊查询的实现方式为 LIKE '%something%',性能很差。\\t
- 基于关键字的日志内容需要快速检索。\\t
- 其他数据库模糊查询的优化方案。\
三、Solr 的特性
\\- 具备高级全文搜索的能力\\t
- 高容量\\t
- 基于标准的开放接口(XML、JSON、HTTP):Document 通过 HTTP 利用 XML 加到一个搜索集合中,查询该集合时也是通过 HTTP 收到一个 XML/JSON 响应来实现\\t
- 提供功能全面的管理界面,使你能够容易地控制你的 Solr 实例\\t
- 易监控\\t
- 高稳定性和容错性\\t
- 易配置,且不失灵活和适配性\\t
- 准实时索引,确保你能够实时看到更新后的内容\\t
- 可扩展插件架构:新功能能够以插件的形式非常方便地添加到 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 配置文件中增加如下内容:
\\\\u0026lt;lib dir=\"../contrib/extraction/lib\" regex=\".*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../dist/\" regex=\"solr-cell-\\d.*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../contrib/clustering/lib/\" regex=\".*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../dist/\" regex=\"solr-clustering-\\d.*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../contrib/langid/lib/\" regex=\".*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../dist/\" regex=\"solr-langid-\\d.*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../contrib/velocity/lib\" regex=\".*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../dist/\" regex=\"solr-velocity-\\d.*\\.jar\" /\u0026gt;\\u0026lt;lib dir=\"../dist/\" regex=\"solr-dataimporthandler-\\d.*\\.jar\" /\u0026gt;
\\
以上内容加在【5.5.4】节点之后、【${solr.data.dir:}】节点之前。
\\\\u0026lt;requestHandler name=\"/dataimport\" class=\"solr.DataImportHandler\"\u0026gt;\ \u0026lt;lst name=\"defaults\"\u0026gt;\ \u0026lt;str name=\"config\"\u0026gt;data-config.xml\u0026lt;/str\u0026gt;\ \u0026lt;/lst\u0026gt;\\u0026lt;/requestHandler\u0026gt;
\\
以上内容加的位置请见如下图所示:
\\对 managed-schema 文件进行修改:以下内容加在节点内:
\\\\u0026lt;fieldType name=\"textPolicy_ik\" class=\"solr.TextField\"\u0026gt;\ \u0026lt;analyzer type=\"index\" useSmart=\"false\" class=\"org.wltea.analyzer.lucene.IKAnalyzer\" /\u0026gt;\ \u0026lt;analyzer type=\"query\" useSmart=\"true\" class=\"org.wltea.analyzer.lucene.IKAnalyzer\" /\u0026gt;\\u0026lt;/fieldType\u0026gt;
\\
注释掉以下配置:
\\\\u0026lt;field name=\"id\" type=\"string\" indexed=\"true\" stored=\"true\" required=\"true\" multiValued=\"false\" /\u0026gt;
\\
然后在其下增加如下配置:
\\\\u0026lt;field name=\"PolicyID\" type=\"string\" indexed=\"true\" stored=\"true\" required=\"true\" multiValued=\"false\" /\u0026gt;\\u0026lt;field name=\"PolicyGroupID\" type=\"long\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"PolicyOperatorID\" type=\"long\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"PolicyOperatorName\" type=\"textPolicy_ik\" indexed=\"true\" stored=\"true\" omitNorms=\"true\" /\u0026gt;\\u0026lt;field name=\"PolicyCode\" type=\"textPolicy_ik\" indexed=\"true\" stored=\"true\" omitNorms=\"true\" /\u0026gt;\\u0026lt;field name=\"PolicyName\" type=\"textPolicy_ik\" indexed=\"true\" stored=\"true\" omitNorms=\"true\" /\u0026gt;\\u0026lt;field name=\"PolicyType\" type=\"string\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"TicketType\" type=\"int\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"FlightType\" type=\"int\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"DepartureDate\" type=\"tdate\" indexed=\"true\" stored=\"true\" default=\"NOW+8HOUR\" /\u0026gt;\\u0026lt;field name=\"ArrivalDate\" type=\"tdate\" indexed=\"true\" stored=\"true\" default=\"NOW+8HOUR\" /\u0026gt;\\u0026lt;field name=\"ReturnDepartureDate\" type=\"tdate\" indexed=\"true\" stored=\"true\" default=\"NOW+8HOUR\" /\u0026gt;\\u0026lt;field name=\"ReturnArrivalDate\" type=\"tdate\" indexed=\"true\" stored=\"true\" default=\"NOW+8HOUR\" /\u0026gt;\\u0026lt;field name=\"DepartureCityCodes\" type=\"textPolicy_ik\" indexed=\"true\" stored=\"true\" omitNorms=\"true\" /\u0026gt;\\u0026lt;field name=\"TransitCityCodes\" type=\"textPolicy_ik\" indexed=\"true\" stored=\"true\" omitNorms=\"true\" /\u0026gt;\\u0026lt;field name=\"ArrivalCityCodes\" type=\"textPolicy_ik\" indexed=\"true\" stored=\"true\" omitNorms=\"true\" /\u0026gt;\\u0026lt;field name=\"OutTicketType\" type=\"int\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"OutTicketStart\" type=\"tdate\" indexed=\"true\" stored=\"true\" default=\"NOW+8HOUR\" /\u0026gt;\\u0026lt;field name=\"OutTicketEnd\" type=\"tdate\" indexed=\"true\" stored=\"true\" default=\"NOW+8HOUR\" /\u0026gt;\\u0026lt;field name=\"OutTicketPreDays\" type=\"int\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"Remark\" type=\"textPolicy_ik\" indexed=\"true\" stored=\"true\" omitNorms=\"true\" /\u0026gt;\\u0026lt;field name=\"Status\" type=\"int\" indexed=\"true\" stored=\"true\" /\u0026gt;\\u0026lt;field name=\"SolrUpdatedTime\" type=\"tdate\" indexed=\"true\" stored=\"true\" default=\"NOW+8HOUR\" /\u0026gt;\\\u0026lt;uniqueKey\u0026gt;PolicyID\u0026lt;/uniqueKey\u0026gt;
\\
属性说明:
\\- name:表示域名。\\t
- type:表示域的类型,必须匹配类型,不然会报错。如果需要分词,那么就传分词器名如 textPolicy_ik;另外,日期建议传 tdate,因为可以加快范围查找速度。\\t
- indexed:是否要做索引。\\t
- stored:是否要存储。\\t
- required:是否必填。\\t
- multiValued:是否有多个值。如果设置为多值,里面的值就采用数组的方式来存储。\
对 data-config.xml 文件进行修改:先注释掉默认有的 dataConfig,然后在被注释内容的后面增加如下配置内容:
\\\\u0026lt;dataConfig\u0026gt;\ \u0026lt;dataSource driver=\"com.microsoft.sqlserver.jdbc.SQLServerDriver\" url=\"jdbc:sqlserver://{SQLServer 服务器 IP 地址}:{端口号,如果端口号是默认的 1433,则可不写};DatabaseName=SolrDB\" user=\"sa\" password=\"{登录 SQL Server 的密码}\"/\u0026gt;\ \u0026lt;document name=\"Info\"\u0026gt; \ \u0026lt;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] \u0026gt; '${dataimporter.last_index_time}'\"\u0026gt;\ \u0026lt;field column=\"PolicyID\" name=\"PolicyID\"/\u0026gt; \ \u0026lt;field column=\"PolicyGroupID\" name=\"PolicyGroupID\"/\u0026gt; \ \u0026lt;field column=\"PolicyOperatorID\" name=\"PolicyOperatorID\"/\u0026gt;\ \u0026lt;field column=\"PolicyOperatorName\" name=\"PolicyOperatorName\"/\u0026gt;\ \u0026lt;field column=\"PolicyCode\" name=\"PolicyCode\"/\u0026gt;\ \u0026lt;field column=\"PolicyName\" name=\"PolicyName\"/\u0026gt;\ \u0026lt;field column=\"PolicyType\" name=\"PolicyType\"/\u0026gt;\ \u0026lt;field column=\"TicketType\" name=\"TicketType\"/\u0026gt;\ \u0026lt;field column=\"FlightType\" name=\"FlightType\"/\u0026gt;\ \u0026lt;field column=\"DepartureDate\" name=\"DepartureDate\"/\u0026gt;\ \u0026lt;field column=\"ArrivalDate\" name=\"ArrivalDate\"/\u0026gt;\ \u0026lt;field column=\"ReturnDepartureDate\" name=\"ReturnDepartureDate\"/\u0026gt;\ \u0026lt;field column=\"ReturnArrivalDate\" name=\"ReturnArrivalDate\"/\u0026gt;\ \u0026lt;field column=\"DepartureCityCodes\" name=\"DepartureCityCodes\"/\u0026gt;\ \u0026lt;field column=\"TransitCityCodes\" name=\"TransitCityCodes\"/\u0026gt;\ \u0026lt;field column=\"ArrivalCityCodes\" name=\"ArrivalCityCodes\"/\u0026gt;\ \u0026lt;field column=\"OutTicketType\" name=\"OutTicketType\"/\u0026gt;\ \u0026lt;field column=\"OutTicketStart\" name=\"OutTicketStart\"/\u0026gt;\ \u0026lt;field column=\"OutTicketEnd\" name=\"OutTicketEnd\"/\u0026gt;\ \u0026lt;field column=\"OutTicketPreDays\" name=\"OutTicketPreDays\"/\u0026gt;\ \u0026lt;field column=\"Remark\" name=\"Remark\"/\u0026gt;\ \u0026lt;field column=\"Status\" name=\"Status\"/\u0026gt;\ \u0026lt;field column=\"SolrUpdatedTime\" name=\"SolrUpdatedTime\"/\u0026gt;\ \u0026lt;/entity\u0026gt;\ \u0026lt;/document\u0026gt;\\u0026lt;/dataConfig\u0026gt;
\\
属性说明:
\\- query:查询数据库表中符合的记录数据。\\t
- deltaImportQuery:表示次查询。次查询是获取以上步骤的 ID,然后把其全部数据获取,根据获取的数据,对索引库进行更新操作,可能是删除、添加或修改。此查询只对增量导入起作用,可以返回多个字段的值,一般情况下,都是返回所有字段的列。\\t
- 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\\t
- Solr 官网:http://lucene.apache.org/solr/\\t
- Lucene 官网:http://lucene.apache.org/\\t
- SolrNet 官网:https://github.com/mausch/SolrNet/\
本系列文章涉及内容清单如下,其中有感兴趣的,欢迎关注:
\\- 开篇:中小型研发团队架构实践三要点\\t
- 缓存 Redis:Redis快速入门及应用\\t
- 消息队列 RabbitMQ:如何用好消息队列RabbitMQ?\\t
- 集中式日志 ELK:中小型研发团队架构实践之集中式日志ELK\\t
- 任务调度 Job:中小型研发团队架构实践之任务调度Job\\t
- 应用监控 Metrics:应用监控怎么做?\\t
- 微服务框架 MSA:这是你心心念念的.NET栈的微服务架构实践\\t
- 搜索利器 Solr\\t
- 分布式协调器 ZooKeeper\\t
- 小工具:\\t
- Dapper.NET/EmitMapper/AutoMapper/Autofac/NuGet\\t
- 发布工具 Jenkins\\t
- 总体架构设计:电商如何做企业总体架构?\\t
- 单个项目架构设计\\t
- 统一应用分层:如何规范公司所有应用分层?\\t
- 调试工具 WinDbg\\t
- 单点登录\\t
- 企业支付网关\\t
- 结篇\
作者介绍
\\杨丽,拥有多年互联网应用系统研发经验,曾就职于古大集团,现任职中青易游的系统架构师,主要负责公司研发中心业务系统的架构设计以及新技术积累和培训。现阶段主要关注开源软件、软件架构、微服务以及大数据。
\\张辉清,10 多年的 IT 老兵,先后担任携程架构师、古大集团首席架构、中青易游 CTO 等职务,主导过两家公司的技术架构升级改造工作。现关注架构与工程效率,技术与业务的匹配与融合,技术价值与创新。
\\感谢雨多田光对本文的审校。