1.Search Engine
- 目前主流的开源搜索引擎主要有两个,一个是基于Java的Apache Lucene,另一个是基于C++的Sphinx。在建立索引所需时间方面,Sphinx只需Lucene时间的50%左右,但是索引文件Sphinx比Lucene要大一倍,即Sphinx采用的是空间换时间的策略。在全文检索速度方面,二者相差不大。全文检索精确度方面,Lucene要优于Sphinx。在加入中文分词引擎的难易程度上,Lucene要优于Sphinx。Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表、记录、字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。因此,选择Lucene作为全文搜索引擎是比较好的选择。
2.Search based on Lucene
- Lucene只是一个框架,要充分利用它的功能,需要很高学习成本,很少直接直接使用Lucene,Solr和ElasticSearch都是基于Lucene的搜索服务器。ElasticSearch相比于solr拥有一些重要特征:
- 1.导入性能更好,查询性能与solr持平,因为solr在建索引时会产生io的阻塞,造成搜索性能的下降,但ElasticSearch不会,它是先把索引的内容保存到内存之中,当内存不够时再把索引持久化到硬盘中,同时它还有一个队列,是在系统空闲时自动把索引写到硬盘中
- 2.ElasticSearch 完全支持Apache Lucene的接近实时的搜索。
- 3.支持更多的客户端库,如Java,Python,Javascript
- 4.自包含集群,无需zookeeper即可自动构建集群
- 5.支持多种插件:例如ElasticSearch-head插件、ik分词插件
- 6.支持多个存储方式,ElasticSearch的数据文件可以存储在本地文件系统、Hadoop的HDFS和亚马孙的S3
3.ES Vs Relational DB
- Relational DB--->库(Databases)--->表(Tables)--->行(Rows)--->列(columns)
- ES--->索引(Indices)--->类型(Types)--->文档(Documents)--->字段(Fields)
4.ElasticSearch Install
- 版本说明
Java环境:JDK 1.8.0
Elasticsearch:2.3.4
OS环境:windows 7(为了开发调试方便) - 下载解压安装包
下载地址:https://www.elastic.co/downloads/elasticsearch
下载合适版本的安装包,当前最新的版本是5.0.1,Elasticsearch版本更新太快,直接从2.4.0到了5.0,由于5.x版本安装Elasticsearch-head插件过于复杂,选用的2.x版本即可
解压后的目录如下
├─bin
├─config
├─lib
└─modules
├─lang-expression
├─lang-groovy
└─reindex
单机Elasticsearch不需要修改过多的配置,就能直接使用,如果需要修改,修改config目录下Elasticsearch.yml文件即可,例如数据文件目录、日志文件目录、Elasticsearch服务器的ip和端口号以及JVM堆内存大小
- 发布成服务
讲ElasticSearch发布为服务,方便在后台运行和开机启动
在elasticsearch-2.3.4\bin下执行
service.bat install ElasticSearch - 启动后检查
启动ElasticSearch服务后,访问http://localhost:9200/,提示服务是否就绪
{
"name": "D'Ken",
"cluster_name": "elasticsearch",
"version": {
"number": "2.3.4",
"build_hash": "e455fd0c13dceca8dbbdbb1665d068ae55dabe3f",
"build_timestamp": "2016-06-30T11:24:31Z",
"build_snapshot": false,
"lucene_version": "5.5.0"
},
"tagline": "You Know, for Search"
}
安装ElasticSearch-head
elasticsearch-head是集群管理工具、数据可视化、增删改查工具
在bin目录执行
plugin install mobz/elasticsearch-head
访问http://localhost:9200/_plugin/head/安装ik分词插件
方式1:按照https://github.com/medcl/elasticsearch-analysis-ik 指导文档安装
方式2:直接使用ik.zip包
5.Basic Operator of ElasticSearch
- 新建文档
curl -XPUT http://localhost:9200/blog/article/1 -d '{"titile": "New version of ElasticSearch released!", "content":"Version 1.0 released today!", "tags" : ["announce", "elasticsearch"," release"]}'
http://localhost:9200/blog/article/1,blog是index,article是indextype,1代表文档的id,在同一个index、同一个indextype下是唯一的。也可以不指定文档id,通过post方法新建文档,生成的文档id是随机的。如下所示:
curl -XPOST http://localhost:9200/blog/article -d '{"titile": "New version of ElasticSearch released!", "content":"Version 1.0 released today!", "tags" : ["announce", "elasticsearch","release"]}'
- 检索文档
已知文档id条件,检索文档
curl -XGET http://localhost:9200/blog/article/1
做全文搜索时,当type中的字段比较多,但是我们只需要某些字段时,可以选择结果集要返回的字段
curl -XGET http://localhost:9200/blog/article/_search –d
‘{
"fields": [
"title",
"author"
],
"query": {
"match_all": {}
}
}’
- 更新文档
更新文档在ElasticSearch属于比较消耗的操作,需要将之前的建立的索引删掉,重建新的索引,ElasticSearch局部更新操作是通过Groovy脚步实现更新的,在更新之前,需要在elasticsearch.yml中添加
script.inline: on
script.indexed: on
script.engine.groovy.inline.aggs: on
重启ElasticSearch
例如,需要根据用户的点击次数,更新某篇文章的浏览量
POST /blog /article/1/_update
{
"script" : "ctx._source.read_count+=1"
} - 删除文档
删除文档命令
curl -XDELETE http://localhost:9200/blog/article/1
6.Devlopment Process
- 引入maven依赖
在pom文件添加对应版本的依赖包
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>2.3.4</version>
</dependency>
- 创建mapping
mapping是什么?为了能够把日期字段处理成日期,把数字字段处理成数字,把字符串字段处理成全文本(Full-text)或精确的字符串值,Elasticsearch需要知道每个字段里面都包含了什么类型。这些类型和字段的信息存储(包含)在映射(mapping)中 - 为什么要创建mapping
虽然Elasticsearch能更够根据数据类型创建自动创建mapping,但是自动创建mapping很可能不符合要求,例如我们想对指定的字段指定分词器
查看mapping命令
curl -XGET http://localhost:9200/blog/article/_mapping/
或者通过head插件查看
结果如何
{
"blog": {
"mappings": {
"article": {
"_ttl": {
"enabled": false
},
"properties": {
"author": {
"type": "string",
"index": "not_analyzed"
},
"content": {
"type": "string",
" analyzer ": "ik "
},
"publish_time": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"read_count": {
"type": "integer"
},
"title": {
"type": "string",
"analyzer": "ik"
}
}
}
}
}
}
主要需要指定字段type类型,对于string类型,需要指定是否需要分词,以及分词器的类型
public class BlogMapping {
public static XContentBuilder getMapping(){
XContentBuilder mapping = null;
try {
mapping = jsonBuilder()
.startObject()
//开启倒计时功能
.startObject("_ttl")
.field("enabled",false)
.endObject()
.startObject("properties")
.startObject("title")
.field("type","string")
.field("analyzer", "ik")
.endObject()
.startObject("content")
.field("type","string")
.field("index","not_analyzed")
.endObject()
.startObject("author")
.field("type","string")
.field("index","not_analyzed")
.endObject()
.startObject("publish_time")
.field("type","date")
.field("index","not_analyzed")
.endObject()
.startObject("read_count")
.field("type","integer")
.field("index","not_analyzed")
.endObject()
.endObject()
.endObject();
} catch (IOException e) {
e.printStackTrace();
}
return mapping;
}
}
- 分析分词结果
查看分词结果
curl -XGET 'localhost:9200/_analyze' -d '
{
"analyzer": "ik",
"text": "百度张亚勤:ABC时代来了,迎战云计算“马拉松”"
}
命令行下官方推荐使用get方法,使用post方法也是可以的,而且命令行环境下对汉字的支持不是很有友好,可以通过head插件或者其他restclient图形界面工具查询分词结果,通过图形界面工具查询务必使用post方法
- 分词结果如下:
[百度,百,度,张,亚,勤,abc,时代,来了,迎战,战云,计算,马拉松,马,拉,松]
分词结果直接决定了,我们应该采用精确匹配还是全文检索,以及我们搜索的关键词能否命中 - 创建DSL语句
Elasticsearch检索文件是通过DSL语句,检索文档分为两种分式精确查询和全文检索,在Elasticsearch里面分别对应term query和match query
term query:
在给定的字段里查询词或者词组;
提供的查询词是不分词的(not analyzed),即只有完全包含才算匹配;
支持boost属性,boost可以提高field和document的相关性;
match query:
与term query不同,match query的查询词是被分词处理的(analyzed),即首先分词,然后构造相应的查询,所以应该确保查询的分词器和索引的分词器是一致的;
与terms query相似,提供的查询词之间默认是or的关系,可以通过operator属性指定;
match query有两种形式,一种是简单形式,一种是bool形式;
希望通过[百度,张亚勤,云计算,马拉松]中任意一个搜索词都能够搜到上面的内容,即百度张亚勤:ABC时代来了,迎战云计算“马拉松”
如果使用term query
GET http://localhost:9200/blog/article/_search
{
"query": {
"term": {
"title": "百度"
}
}
}
当title的值为“百度”或者“马拉松”时可以检索到内容,当title为“张亚勤或者云计算时,无法检索到文档,因为分词结果是[百度,百,度,张,亚,勤,abc,时代,来了,迎战,战云,计算,马拉松,马,拉,松],无法匹配“张亚勤”和“云计算”
如果使用match query检索
{
"query": {
"match": {
"title": {
"query": "张亚勤",
"operator": "and",
"minimum_should_match": "3"
}
}
}
}
使用match query检索,ik分词器会把张亚勤分[张,亚,勤],会把三个元素依次和[百度,百,度,张,亚,勤,abc,时代,来了,迎战,战云,计算,马拉松,马,拉,松]里面的数据比对,operator=and,表示每个分词比较结果是与的关系,minimum_should_match=3,表示至少三次匹配成功,此时就能检索到内容
multi_match
如果我们希望两个字段进行匹配,其中一个字段有这个文档就满足的话,使用multi_match,用户输入一个搜索词,我们很少根据有一个字段进行索引,通常是根据某几个字段进行索引,例如我们想根据title或者content字段搜包含“云计算”的内容,那么相应DSL语句:
{
"query": {
"multi_match": {
"query" : "云计算",
"fields" : ["title", "content"]
}
}
}
- 理解评分机制
通过DSL语句只是查询到我们需要的结果,但是怎么对查询到的结果进行排序,就需要了解ElasticSearch的评分机制,ElasticSearch默认是安装查询文档的评分排序的。
详细查看评分的命令
POST http://localhost:9200/blog/article/_search?explain
{
"query": {
"match": {
"title": {
"query": "罗一笑",
"operator": "and",
"minimum_should_match": "3"
}
}
}
}
搜索词:罗一笑
查询时将搜索词分为4个分词(term),罗,一笑,一,笑
每个词有一个评分
罗:0.042223614
一笑:0.11945401
一:0.11945401
笑:0.11945401
根据罗一笑查询到文档的总评分为:0.40058565=0.042223614+0.11945401+0.11945401+0.11945401
对于每一个term的评分规则是:term评分= queryweight * fieldweight
一笑的评分:0.11945401 = 0.54607546 * 0.21875
查询权重query weight = idf * queryNorm
{
"value": 0.54607546,
"description": "queryWeight, product of:",
"details": [
{
"value": 1,
"description": "idf(docFreq=1, maxDocs=2)",
"details": []
},
{
"value": 0.54607546,
"description": "queryNorm",
"details": []
}
]
}
域权重fieldweight= tf * idf * fieldNorm
{
"value": 0.21875,
"description": "fieldWeight in 0, product of:",
"details": [
{
"value": 1,
"description": "tf(freq=1.0), with freq of:",
"details": [
{
"value": 1,
"description": "termFreq=1.0",
"details": []
}
]
},
{
"value": 1,
"description": "idf(docFreq=1, maxDocs=2)",
"details": []
},
{
"value": 0.21875,
"description": "fieldNorm(doc=0)",
"details": []
}
]
}
主要有个两个概念Term Frequency和Inverse document frequency
Term Frequency:某单个关键词(term) 在某文档的某字段中出现的频率次数, 显然, 出现频率越高意味着该文档与搜索的相关度也越高
具体计算公式是 tf(q in d) = sqrt(termFreq)
Inverse document frequency:某个关键词(term) 在索引(单个分片)之中出现的频次. 出现频次越高, 这个词的相关度越低. 相对的, 当某个关键词(term)在一大票的文档下面都有出现, 那么这个词在计算得分时候所占的比重就要比那些只在少部分文档出现的词所占的得分比重要低. 说的那么长一句话, 用人话来描述就是 “物以稀为贵”。
具体计算公式:
idf = 1 + ln(maxDocs/(docFreq + 1))
详细计算公式请参考lucene源码或者参考http://www.hankcs.com/program/java/lucene-scoring-algorithm-explained.html
lucene的评分机制过于复杂,我们可以根据上述公式派生出简单容易理解的规则
匹配的词条越罕见,文档的得分越高
文档的字段越小,文档的得分越高
字段的加权越高,文档的得分越高