一、ElasticSearch简介
1.1.什么是ElasticSearch
ElasticSearch(以下均检查ES)是Compass(基于Lucene开源项目)作者Shay Banon在2010年发布的高性能、实时、分布式的开源搜索引擎。后来成立了ElasticSearch公司,负责ES相关产品的开发及商用服务支持,ES依旧采用免费开源模式,但部分插件采用商用授权模式,例如Marvel插件(负责ES的监控管理)、Shield插件(提供ES的授权控制)。
1.2.ElasticSearch的基础概念
⏹Collection
在SolrCloud集群中逻辑意义上的完整的索引。它常常被划分为一个或多个Shard,它们使用相同的Config Set。如果Shard数超过一个,它就是分布式索引,SolrCloud让你通过Collection名称引用它,而不需要关心分布式检索时需要使用的和Shard相关参数。
⏹Config Set
Solr Core提供服务必须的一组配置文件。每个config set有一个名字。最小需要包括solrconfig.xml (SolrConfigXml)和schema.xml (SchemaXml),除此之外,依据这两个文件的配置内容,可能还需要包含其它文件。它存储在Zookeeper中。Config sets可以重新上传或者使用upconfig命令更新,使用Solr的启动参数bootstrap_confdir指定可以初始化或更新它。
⏹Core
Core也就是Solr Core,一个Solr中包含一个或者多个Solr Core,每个Solr Core可以提供索引和查询功能,每个Solr Core对应一个索引或者Collection的Shard,Solr Core的提出是为了增加管理灵活性和共用资源。在SolrCloud中有个不同点是它使用的配置是在Zookeeper中的,传统的Solr core的配置文件是在磁盘上的配置目录中。
⏹Leader
赢得选举的Shard replicas。每个Shard有多个Replicas,这几个Replicas需要选举来确定一个Leader。选举可以发生在任何时间,但是通常他们仅在某个Solr实例发生故障时才会触发。当索引documents时,SolrCloud会传递它们到此Shard对应的leader,leader再分发它们到全部Shard的replicas。
⏹Replica
Shard的一个拷贝。每个Replica存在于Solr的一个Core中。一个命名为“test”的collection以numShards=1创建,并且指定replicationFactor设置为2,这会产生2个replicas,也就是对应会有2个Core,每个在不同的机器或者Solr实例。一个会被命名为test_shard1_replica1,另一个命名为test_shard1_replica2。它们中的一个会被选举为Leader。
⏹Shard
Collection的逻辑分片。每个Shard被化成一个或者多个replicas,通过选举确定哪个是Leader。
二、ElasticSearch安装
Cloudera Search作为CDH的一个组件,统一采用Cloudera Manager进行安装及配置,具体内容参加CDH的安装说明文档,本文以具体配置使用为主。
三、基本应用
3.1. 简单示例
采用SSH或者Telnet远程连接到服务器上,在控制台依次执行下来命令,完成Solr的集合创建以及数据加入处理。
创建solr配置目录(执行后会在当前目录生产oa_solr目录)
#solrctl instancedir --generate oa_solr
基于solr配置,创建Core
#solrctl instancedir --create oa oa_solr
创建集合
#solrctl collection --create article -s 2 -c oa
向集合中加入测试数据
#cd /opt/cloudera/parcels/CDH/share/doc/solr-doc*/example/exampledocs
#java -Durl=http://localhost:83/solr/article/update -jar post.jar *.xml
在浏览器中输入http://10.68.128.217:83/solr/进入Solr配置管理界面。
在左侧导航栏中选择Core,点击“Query”菜单,点击“Execute Query”,如出现数据,则完成最基本的查询功能。
3.2. 整合中文分词
Solr默认没有中文分词处理,需要进行添加配置,本示例采用IK Analyer进行中文分词配置。
3.2.1. 下载IK Analyer
IK Analyer最新的程序版本为IK Analyzer 2012FF_hf1.zip,可以从官方网站下进行获取https://code.google.com/archive/p/ik-analyzer/downloads下载。
3.2.2. 复制依赖文件
将IKAnalyzer2012FF_u1.jar拷贝到/opt/cloudera/parcels/CDH/lib/
solr/webapps/solr/WEB-INF/lib目录中。
备注:不要拷贝到文件拷贝到/var/lib/solr/tomcat-deployment/
webapps/solr/WEB-INF/lib,该目录重启后,会自动消失,同时所有solr的服务器均需要拷贝。
将IKAnalyzer.cfg.xml、stopword.dic 拷贝到3.1.简单示例中创建配置目录oa_solr的conf子目录中。
3.2.3. 修改配置文件
编辑conf/schema.xml配置文件,在types标签内增加
在fields标签内将需要进行中文切除的字段修改为text_cn
3.2.4. 启用配置
因涉及到依赖jar文件的变化,需要在Cloudera Manager中重启Solr服务。同时采用以下命令更新配置,并重建集合。
#solrctl instancedir --update oa oa_solr
#solrctl collection --delete article
#solrctl collection --create article -s 2 -c oa
3.2.5. 中文分词验证
进入solr管理控制台,在左侧导航栏选择“Analysis”,在“Analyse Fieldname / FieldType”中选择“text_cn”,并在Field Value中输入中文,点击“Analyse Values”按钮,如出现多个中文,则表示分词成功。
3.2.6. IK配置自定义词组
在“3.2.2. 复制依赖文件”复制配置文件后,修改IK的配置文件IKAnalyzer.cfg.xml,增加
并创建一个ext.dic的文本文件(无BOM头的UTF-8格式),以\\r\\n作为每一行的结束,同时将ext.dic放到conf目录下。
备注:自定义词库在实际环境中未测试通过,具体原因不明,目前的做法是将IKAnalyzer2012FF_u1.jar中的默认字典配置文件(org/wltea/analyzer/dic/main2012.dic)解压出来,修改后,重新还原到jar文件中。
3.3. Schema配置
集合(Collection)内所有存储的字段均需要预先在Schema中予以定义声明,Schema对应的配置文件为conf/schema.xml。
fieldType用于定义数据格式类型,字段类型定义包括三种方式field、copyField、dynamicField。
⏹Field
Field就是一个字段,定义示例如下:
其中:name为字段名称,type对应fieldType,indexed是否为索引列,stored表示数据是否存储,multiValued表示是否多个值。
备注:solr本身不支持对象关联查询,对于同一个collection不会正常区分,所有的内容均会拉平存储在同一对象中,因此子对象的字段名称需要与父对象进行区分,如果存在一对多的情况,多方的multiValued值必须设置为true。
⏹copyField
solr提供了字段复制机制,可以提交多个不同字段的内容集中到一个字段。字段复制主要涉及两个概念,source和destination,一个是要复制的字段,另一个是要复制到哪个字段,以下是个例子:
⏹dynamicField
动态字段(Dynamic fields)允许 solr 索引没有在 schema 中明确定义的字段。这个在忘记定义一些字段时很有用。动态字段可以让系统更灵活,通用性更强。
以下为CMS中文章Collection的Schema定义片段。
3.4. 数据导入配置
数据导入相关说明参见以下两个链接的说明。http://wiki.apache.org/solr/DataImportHandler
https://cwiki.apache.org/confluence/display/solr/Uploading+Structured+Data+Store+Data+with+the+Data+Import+Handler
3.4.1. 复制依赖文件
拷贝dataimport依赖包以及jdbc驱动到/opt/cloudera/parcels/CDH
/lib/solr/webapps/solr/WEB-INF/lib中。
#cp /opt/cloudera/parcels/CDH/lib/solr/solr-dataimport*-cdh5.5.2.jar /opt/cloudera/parcels/CDH/lib/solr/webapps/solr/WEB-INF/lib
#cp db2jcc4.jar /opt/cloudera/parcels/CDH/lib/solr/webapps/solr/WEB-INF/lib
备注:DB2必须是jdbc4的驱动,否则将会报错;因涉及到jar文件的变动,需要重启solr服务。
3.4.2. 启动DIH配置
修改conf/solrconfig.xml文件,增加以下配置:
3.4.3. 定义数据初始化文件
新增dih-config.xml文件,文件内容如下:
user="HBYC" password="123456" batchSize="100" autoCommit="false" /> /> deltaQuery="SELECT ARTICLE_ID,WEBSITE_ID,CHANNEL_ID,CTTCTG_ID,TITLE,ARTICLE_CONTENT,RELEASE_DATE,RELEASE_SYS_DATE,IS_CHECK,IS_CONTROL,ARTI_ATTACHE_ID FROM office.T_CMS_ARTI_ARTICLE RELEASE_DATE > '${dataimporter.last_index_time}' or RELEASE_SYS_DATE > '${dataimporter.last_index_time}'" transformer="ClobTransformer">
注意transformer="ClobTransformer"和clob="true"的写法。
3.4.4. 启用配置
#solrctl instancedir --update oa oa_solr
3.4.5. 通过界面导入数据
进入Solr管理控制台,在左侧导航栏点击“Dataimport”菜单,点击右侧“Congfiguration”查看配置内容是否一致,如果不一致,点击“Reload”连接,进行配置刷新。
点击“Execute”,启动数据导入,点击“Refresh Status”按钮,查看导入进度情况。
最后的导入结果如下:
导入58万的数据量,耗时14分39秒,每秒导入数据670条。
3.4.6 通过URL导入数据
全部更新
http://10.68.128.217:83/solr/article/dataimport?command=full-import
增量更新
http://10.68.128.217:83/solr/article/dataimport?command=delta-import
查看执行状态
http://10.68.128.217:83/solr/article/dataimport?command=status
3.5. 默认排序处理
在solrconfig.xml中加入
rord(releaseSysDate) content title^1.9
bf用函数计算某个字段的权重,bf内字段必须是索引的,bf的函数的使用参考文档:http://wiki.apache.org/solr/FunctionQuery。
pf查询字段,这样在schema不用制定默认字段
qf对默认查询增加权重比值,比如标题是content的1.9倍,值越大权重越大
四、Java调用示例
4.1. 添加依赖
4.2. 连接服务器
private static final String COLLECTION = "article";
private CloudSolrClient solr = null;
public SolrDemoApp() {
// 通过zooKeeper实现自动负载均衡
String zkHostString = "10.68.128.215:2181,10.68.128.216:2181,10.68.128.217:2181/solr";
solr = new CloudSolrClient(zkHostString);
// 设置默认的集合
solr.setDefaultCollection(COLLECTION);
// 连接服务器
solr.connect();
}
// 执行完成后调用
public void close() throws IOException {
solr.close();
}
4.3. 增加文档
public void add() throws IOException, SolrServerException {
// 更新索引
SolrInputDocument document = new SolrInputDocument();
document.addField("id", TEST_DOC_ID);
document.addField("title", "中华人民共和国");
document.addField("content", "湖北烟草");
document.addField("releaseDate", new Date());
solr.add(COLLECTION, document);
solr.commit();
SolrQuery query = new SolrQuery();
query.setQuery("id:" + TEST_DOC_ID);
QueryResponse result = solr.query(COLLECTION, query);
output(result);
}
4.4. 删除文档
public void delete() throws IOException, SolrServerException {
// 删除索引
solr.deleteById(COLLECTION, TEST_DOC_ID);
solr.commit();
SolrQuery query = new SolrQuery();
query.setQuery(" id:123lmz");
QueryResponse result = solr.query(COLLECTION, query);
if (result.getResults().size() == 0) {
System.out.print("删除成功");
} else {
System.out.print("删除失败");
}
}
4.5. 高亮查询
public void query() throws IOException, SolrServerException {
SolrQuery query = new SolrQuery();
query.setQuery(" websiteId:8ac4932e2a370de2012a372b98ba0001 && ( content:赵全意 || title:赵全意 )");
setQueryParams(query);
QueryResponse result = solr.query(COLLECTION, query);
output(result);
}
private void setQueryParams(SolrQuery query) {
query.setFields("id", "title", "content", "releaseDate");
query.setRows(20).setStart(0);
query.setHighlight(true).setHighlightFragsize(200)
.setHighlightSimplePre("").setHighlightSimplePost("")
.set("hl.fl", "title,content");
}
private void output(QueryResponse result) {
SolrDocumentList list = result.getResults();
Map for (SolrDocument doc : list) { output(doc, hlMap); } } private void output(SolrDocument doc, Map String id = (String) doc.getFieldValue("id"); String title = (String) doc.getFieldValue("title"); String content = (String) doc.getFieldValue("content"); if (hlMap != null) { Map if (docHl != null) { title = getHlValue(docHl, "title"); content = getHlValue(docHl, "content"); } } System.out.print("id:" + id); System.out.print("\ttitle:" + title); System.out.print("\tcontent:" + content); System.out.println("\tdate:" + DateUtils.formatDate((Date) doc.getFieldValue("releaseDate"), "yyyy-MM-dd HH:mm")); } private String getHlValue(Map return docHl.containsKey(field) ? docHl.get(field).get(0) : null; } 4.6. Facet查询 // http://wiki.apache.org/solr/SimpleFacetParameters public void facetQuery() throws IOException, SolrServerException { SolrQuery query = new SolrQuery(); query.setQuery("websiteId:8ac4932e2a370de2012a372b98ba0001"); // 启动facet query.setFacet(true); // 以栏目为统计 query.set("facet.field", "channelId"); // 按照时间统计 query.set("facet.date", "releaseDate"); query.set("facet.date.start", "2009-1-1T0:0:0Z"); query.set("facet.date.end", "2016-1-1T0:0:0Z"); query.set("facet.date.gap", "+1YEAR"); query.set("facet.date.other", "all"); // 不返回文档(facet下的内容) query.setRows(0); QueryResponse result = solr.query(COLLECTION, query); for (FacetField facetField : result.getFacetDates()) { outputFacet(facetField, "日期"); } for (FacetField facetField : result.getFacetFields()) { outputFacet(facetField, "栏目"); } } private void outputFacet(FacetField facetField, String prefix) { System.out.println(prefix + "共找到[" + facetField.getName() + "]" + facetField.getValueCount() + "个"); if (facetField != null) { List if (countList != null) { for (FacetField.Count count : countList) { System.out.println(prefix + "[" + count.getName() + "]找到" + count.getCount() + "篇文章"); } } } } 4.7. 完整示例 详细见文档中的内容。 五、数据导入 六、附录