赞
踩
狭义的理解主要针对文本数据的搜索。数据可分为“结构化”数据(关系数据库表形式管理的数据),半结构化数据(XML文档、JSON文档),和非结构化数据(WORD、PDF),通常而言在结构化的数据中搜索性能是比较高的,全文搜索的目的就是把非结构化的数据变成有结构化的数据进行搜索,从而提高搜索效率。
搜索效率高,是like无法比拟的 like %keyword%
相关度最高的排在最前面,官网中相关的网页排在最前面; java
关键词的高亮。
搜索效果更好, 基于单词搜索
只处理文本,不处理语义。 以单词方式进行搜索,比如输入框中输入“中国的首都在哪里”,搜索引擎不会以对话的形式告诉你“在北京”,而仅仅是列出包含了搜索关键字的网页。
java ---->javascript java
代替大量数据表中的like查询
全文搜索工具包(jar)-Lucene(核心)
全文搜索服务器 ,ElasticSearch(ES) / Solr等封装了lucene并扩展
Lucene是apache下的一个开源的全文检索引擎工具包(一堆jar包)。它为软件开发人员提供一个简单易用的工具包(类库),以方便的在小型目标系统中实现全文检索的功能。Lucene适用于中小型项目 ,ES适用于中大型项目(它底层是基于lucene实现的)
任何技术都有一些核心,Lucene也有核心,而它的核心分为:索引创建,索引搜索。接下来我们就一一的来看。
索引创建和索引搜索不仅仅只是lucene的核心,它也是全文检索的核心
将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。那么索引里面究竟存的什么,以及如何创建索引呢?在这通过下面的例子来解答这个问题。首先构造三个不同的句子,有长有短:
在①处分别为3个句子加上编号,然后进行分词,把被一个单词分解出来与编号对应放在②处;在搜索的过程总,对于搜索的过程中大写和小写指的都是同一个单词,在这就没有区分的必要,按规则统一变为小写放在③处;要加快搜索速度,就必须保证这些单词的排列时有一定规则,这里按照字母顺序排列后放在④处;最后再简化索引,合并相同的单词,就得到如下结果:
通常在数据库中我们都是根据文档找到内容,而这里是通过词,能够快速找到包含他的文档,这就是文档倒排链表。以上就是lucene索引结构中最核心的部分。我们注意到关键字是按字符顺序排列的(lucene没有使用B树结构),因此lucene可以用二元搜索算法快速定位关键词。
就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。
索java world两个关键词,符合java的有1,2两个文档,符合world的有1,3两个文档,在搜索引擎中直接这样排列两个词他们之间是OR的关系,出现其中一个都可以被找到,所以这里3个都会出来。全文检索中是有相关性排序的,那么结果在是怎么排列的呢?hello java world中包含两个关键字排在第一,另两个都包含一个关键字,得到结果,hello lucene world排在第二,java在最长的句子中占的权重最低排在结果集的第三。从这里可以看出相关度排序还是有一定规则的。
Lucene的索引库和数据库一样,都提供相应的API来便捷操作。Lucene中的索引维护使用IndexWriter,由这个类提供添删改相关的操作;索引的搜索则是使用IndexSearcher进行索引的搜索。
HelloWorld代码如下,导入三个jar包:lucene-analyzers-common-5.5.0.jar,lucene-core-5.5.0.jar,lucene-queryparser-5.5.0.jar
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>5.5.0</version>
</dependency>
//创建索引的数据 现在写死,以后根据实际应用场景 String doc1 = "hello world"; String doc2 = "hello java world"; String doc3 = "hello lucene world"; private String path ="F:/eclipse/workspace/lucene/index/hello"; @Test public void testCreate() { try { //2、准备IndexWriter(索引写入器) //索引库的位置 FS fileSystem Directory d = FSDirectory.open(Paths.get(path )); //分词器 -- 把文档进行分词 Analyzer analyzer = new SimpleAnalyzer(); //索引写入器的配置对象 IndexWriterConfig conf = new IndexWriterConfig(analyzer); IndexWriter indexWriter = new IndexWriter(d, conf); System.out.println(indexWriter); //1、 把文本内容转换为Document对象 //把文本转换为document对象 Document document1 = new Document(); //标题字段 document1.add(new TextField("title", "doc1", Store.YES)); document1.add(new TextField("content", doc1, Store.YES)); //添加document到缓冲区 indexWriter.addDocument(document1); Document document2 = new Document(); //标题字段 document2.add(new TextField("title", "doc2", Store.YES)); document2.add(new TextField("content", doc2, Store.YES)); //添加document到缓冲区 indexWriter.addDocument(document2); Document document3 = new Document(); //标题字段 document3.add(new TextField("title", "doc3", Store.YES)); document3.add(new TextField("content", doc3, Store.YES)); //3 、通过IndexWriter,把Document添加到缓冲区并提交 //添加document到缓冲区 indexWriter.addDocument(document3); indexWriter.commit(); indexWriter.close(); } catch (Exception e) { e.printStackTrace(); } }
使用luke工具可以看到索引库中的数据
https://github.com/DmitryKey/luke
1 封装查询提交为查询对象 2 准备IndexSearcher 3 使用IndexSearcher传入查询对象做查询-----查询出来只是文档编号DocID 4 通过IndexSearcher传入DocID获取文档 5 把文档转换为前台需要的对象 @Test public void testSearch() { String keyWord = "lucene"; try { // * 1 封装查询提交为查询对象 //通过查询解析器解析一个字符串为查询对象 String f = "content"; //查询的默认字段名, Analyzer a = new SimpleAnalyzer();//查询关键字要分词,所有需要分词器 QueryParser parser = new QueryParser(f, a); Query query = parser.parse("content:"+keyWord); // * 2 准备IndexSearcher Directory d = FSDirectory.open(Paths.get(path )); IndexReader r = DirectoryReader.open(d); IndexSearcher searcher = new IndexSearcher(r); // * 3 使用IndexSearcher传入查询对象做查询-----查询出来只是文档编号DocID TopDocs topDocs = searcher.search(query, 1000);//查询ton条记录 前多少条记录 System.out.println("总命中数:"+topDocs.totalHits); ScoreDoc[] scoreDocs = topDocs.scoreDocs;//命中的所有的文档的封装(docId) // * 4 通过IndexSearcher传入DocID获取文档 for (ScoreDoc scoreDoc : scoreDocs) { int docId = scoreDoc.doc; Document document = searcher.doc(docId); // * 5 把文档转换为前台需要的对象 Docment----> Article System.out.println("======================================="); System.out.println("title:"+document.get("title") +",content:"+document.get("content")); } } catch (Exception e) { e.printStackTrace(); } }
虽然全文搜索领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene的配置及使用非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
ES是一个分布式的全文搜索引擎,为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案,ES的索引库管理支持依然是基于Apache Lucene™的开源搜索引擎。
ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
总的来说ElasticSearch简化了全文检索lucene的使用,同时增加了分布式的特性,使得构建大规模分布式全文检索变得非常容易。
分布式的实时文件存储
分布式全文搜索引擎,每个字段都被索引并可被搜索
能在分布式项目/集群中使用
本身支持集群扩展,可以扩展到上百台服务器
处理PB级结构化或非结构化数据
简单的 RESTful API通信方式
支持各种语言的客户端
基于Lucene封装,使操作简单
Lucene只支持Java,ES支持多种语言
只要这个语言支持http请求,就可以使用es
Lucene使用非常复杂 , ES屏蔽了Lucene的使用细节,操作更方便
ES发一个http请求就OK
Lucene非分布式的,索引目录只能在项目本地,多个服务器不能共享 , 而es是一个单独服务器,多个应用服务器可以共享
Lucene非分布式,ES支持分布式,es本身也能分布式集群
小项目可以使用Lucene ,大项目,分布式项目(微服务)使用ES。但是杀鸡也能用牛刀
Github(美国)使用Elasticsearch搜索20TB的数据,包括13亿的文件和1300亿行的代码.
Foursquare实时搜索5千万地点信息?Foursquare每天都用Elasticsearch做这样的事.
德国SoundCloud使用Elasticsearch来为1.8亿用户提供即时精准的音乐搜索服务.
Mozilla公司以火狐著名,它目前使用 WarOnOrange 这个项目来进行单元或功能测试,测试的结果以 json的方式索引到elasticsearch中,开发人员可以非常方便的查找 bug.
Sony公司使用elasticsearch 作为信息搜索引擎.
Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,Solr4 还增加了NoSQL支持。
Solr和ES比较:
Solr 利用 Zookeeper(注册中心) 进行分布式管理,支持更多格式的数据(HTML/PDF/CSV),官方提供的功能更多在传统的搜索应用中表现好于 ES,但实时搜索效率低。
ES自身带有分布式协调管理功能,但仅支持json文件格式,本身更注重于核心功能,高级功能多有第三方插件提供,在处理实时搜索应用时效率明显高于 Solr。
基于 Lucene 的,支持分布式,可扩展,具有容错功能,准实时的搜索方案。
优点:开箱即用,可以与 Hadoop (大数据)配合实现分布式。具备扩展和容错机制。
缺点:只是搜索方案,建索引部分还是需要自己实现。在搜索功能上,只实现了最基本的需求。成功案例较少,项目的成熟度稍微差一些。
大数据相关的东西 (大数据工程师)
Map/Reduce 模式(云计算)的,分布式建索引方案,可以跟 Katta 配合使用。
优点:分布式建索引,具备可扩展性。
缺点:只是建索引方案,不包括搜索实现。工作在批处理模式,对实时搜索的支持不佳。
ES的安装比较简单,只需要官方下载ES的运行包,然后启动ES服务即可。ES的使用主要是通过能够发起HTTP请求的终端来接入,比如Poster插件、CURL、kibana5等。
ES服务只依赖于JDK,推荐使用JDK1.8+。本课程以在window环境下,ES 6.8.6版本为例,下载对应的ZIP文件
下载地址:https://www.elastic.co/downloads/elasticsearch
本课程以在window环境下,ES 6.8.6版本为例,下载对应的ZIP文件
解压即可,双击安装目录 bin/elasticsearch.bat即可启动
使用浏览器访问:http://localhost:9200
看到上图信息,恭喜你,你的ES集群已经启动并且正常运行.
如果ES启动占用的内存比较大可以通过修改 jvm.options 文件来修改内存
下载地址:https://www.elastic.co/downloads/kibana
解压即可安装 , 执行bin\kibana.bat 即可启动Kibana
注意:启动kibana以前,要先启动Elasticsearch,kibana默认就是连接自己本机的9200
解压并编辑config/kibana.yml,设置elasticsearch.url的值为已启动的ES
默认情况下,Kibana会链接本地的默认ES http://localhost:9200
,如果需要修改链接的ES服务器,通过修改安装目录下 config/kibana.yml,将配置项 #elasticsearch.url: "http://localhost:9200"
取消注释即可修改连接的ES服务器地址。
浏览器访问 http://localhost:5601 Kibana默认地址
Kibana组件详细说明:https://www.cnblogs.com/hunttown/p/6768864.html
Discover:可视化查询分析器
Visualize:统计分析图表
Dashboard:自定义主面板(添加图表)
Timelion:Timelion是一个kibana时间序列展示组件(暂时不用)
Dev Tools :Console(同CURL/POSTER,操作ES代码工具,代码提示,很方便)
Management:管理索引库(index)、已保存的搜索和可视化结果(save objects)、设置 kibana 服务器属性。
近实时,两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级
包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。
类似于关系型数据库中的 数据库
每个索引库里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。
类似于关系型数据库中的表
新版本的es,一个索引库下只能有一个type
文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。
document类似于关系型数据库表中的一行数据
ElastciSearch全文搜索 | Mysql关系型数据库 |
---|---|
索引库(index) | 数据库(database) |
文档类型(Type) | 数据表(Table) |
文档(Document) | 一行数据(Row) |
字段(field) | 一个列(column) |
文档ID | 主键ID |
查询(Query DSL) | 查询(SQL) |
GET http://… | SELECT * FROM … |
PUT http:// | UPDATE table set… |
创建一个名字为 shopping
的索引库,5个Master Shard
分片,每个Master Shard分片有1个Replica Shard
从分片
PUT shopping
{
"settings":{
"number_of_shards":5,
"number_of_replicas":1
}
}
查询所有索引库
GET _cat/indices?v
查看指定索引库
GET _cat/indices/shopping
DELETE 名字
删除再添加
我们以员工对象为例,我们首先要做的是存储员工数据,每个文档代表一个员工。在ES中存储数据的行为就叫做索引(indexing),文档归属于一种类型(type),而这些类型存在于索引(index)中,我们可以简单的对比传统数据库和ES的对应关系:
ES | Mysql |
---|---|
_index(索引库) | 数据库 |
_type(文档类型) | 表 |
_document(文档对象) | 一行数据 |
_id(文档ID) | 主键ID |
field(字段) | 列 |
ES集群可以包含多个索引(indices)(数据库),每一个索引库中可以包含多个类型(types)(表),每一个类型含有多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。
指定ID创建索引文档
PUT index/type/id
{
JSON,文档内容
}
--解释---------------------------------------
PUT 索引库/文档类型/文档id
{
JSON格式,文档原始数据
}
使用内置ID创建索引文档
POST index/type/
{
JSON,文档内容
}
--解释---------------------------------------
PUT 索引库/文档类型/
{
JSON格式,文档原始数据
}
没有指定文档ID,ES会自动生成ID
PUT crm/user/11
{
"id":11,
"username":"zs",
"age":18,
"name":"zs",
"sex":1,
"join_date": 1584092062348
}
解释:添加id为11的用户 , 索引库为 crm,类型为 User
【注意
】:如果不指定文档的id,ES会自动生成文档id
GET 索引库/类型/文档ID
GET /crm/user/123?_source=fullName,email
GET /crm/user/123/_source
整体修改
全量修改的语法跟添加文档语法一样,如果文档已经存在就是修改,否则就是新增,
文档修改过程:1.标记删除旧文档,2.添加新文档
PUT {index}/{type}/{id}
{
"id":11,
"username":"zs"
}
注意:上面的修改会把ES中的数据全部覆盖,即age字段会消失。
局部修改
局部修改过程: 1.检索旧文档 , 2.修改文档 ,3.标记删除旧文档 , 4.添加新文档
POST /crm/user/123/_update
{
"doc":{
"id" : 11,
"username": "xx"
}
}
注意:上面修改只会修改id,和username字段,age字段不会作任何改变。
DELETE {index}/{type}/{id}
GET _search
GET crm/_search
GET crm/user/_search
GET crm/user/11
&size=2&from=2
size: 每页条数
form:从多少条数据开始查
条件查询+分页+排序
GET crm/user/_search?q=age:17&size=2&from=2&sort=id:desc&_source=id,username
字符串查询(query string)其实就是在url后面以字符串的方式拼接各种查询条件,这种方式不推荐,因为条件过多,拼接起来比较麻烦
查询URL可携带参数:
批量查询很重要,对相比单个查询来说,批量查询性能更高。
GET ronghuanet/blog/_mget
{
"ids" : [ "2", "1" ]
}
对于简单查询,使用查询字符串比较好,但是对于复杂查询,由于条件多,逻辑嵌套复杂,查询字符串不易组织与表达,且容易出错,因此推荐复杂查询通过DSL使用JSON内容格式的请求体代替。
DSL查询是由ES提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。DSL有两部分组成:DSL查询(like)和DSL过滤(非like)。
一个常用的相对完整的DSL查询:
GET /crm/user/_search
{
"query": {
"match_all": {}
},
"from": 20,
"size": 10,
"_source": ["username", "age", "id"],
"sort": [{"join_date": "desc"},{"age": "asc"}]
}
上面查询 , match_all
表示 查询所有数据,查询返回fullName,age和email几个列,按照加入日期和年龄进行排序
GET /crm/user/_search
{
"query" : {
"match" : {
"username" : "老郑"
}
}
}
查询username中包含“老郑”的内容,match
指的是“标准查询”,该查询方式会对查询的内容进行分词。DSL查询可以支持的查询方式很多,如term
词元查询 ,range
范围查询等等。
DSL过滤语句和DSL查询语句非常相似,但是它们的使用目的却不同:DSL过滤查询文档的方式更像是对于我的条件"有"或者"没有"(等于 ;不等于),而DSL查询语句则像是"有多像"(模糊查询)。
DSL过滤和DSL查询在性能上的区别:
过滤结果可以缓存并应用到后续请求。-> 精确过滤后的结果拿去模糊查询性能高
查询语句同时匹配文档,计算相关性,所以更耗时,且不缓存。
过滤语句可有效地配合查询语句完成文档过滤。
总结:需要模糊查询(分词查询)的使用DSL查询 ,需要精确查询的使用DSL过滤,在开发中组合使用(组合查询) ,关键字查询使用DSL查询,其他的都是用DSL过滤。
存储的内容:chen whale 搜索的内容: whale zhang
GET shopping/employee/_search { "query": { "bool": { "must": [ { "match": { "intro": "good" } } ], "filter": [ { "range": { "age": { "gt": 20, "lt": 50 } } }, { "term":{ "id":103 } } ] } }, "from": 0, "size": 2, "sort": [ { "id": { "order": "asc" } } ], "_source": ["id","name","age","intro"] }
gt 大于 gte 大于等于 lt 小于 lte 小于等于
解释:
query : 查询,所有的查询条件在query里面
bool : 组合搜索bool可以组合多个查询条件为一个查询对象,这里包含了 DSL查询和DSL过滤的条件
must : 必须匹配 :与(must) 或(should) 非(must_not)
match:分词匹配查询,会对查询条件分词 , multi_match :多字段匹配
filter: 过滤条件
term:词元查询,不会对查询条件分词,是做等值查询的
from,size :分页
_source :查询结果中需要哪些列
sort:排序
名称(name)中有 “zs” 的用户 ,性别sex是男生(1),年龄(age)在 18- 20之间,按照年龄(age)倒排序,查询第 1 页,每页10 条 ,查询结果中只需要 :id,name,username,age
GET /aigou/product/_search { "query":{ "bool": { "must": [{ "match": { "name": "zs" } }], "filter": [ { "range":{ //范围查询 "age":{ "gte":18, "lte":20 } } }, { "term": { //词元查询 "sex": 1 } } ] } }, "from": 1, "size": 10, "_source": ["id", "name", "age","username"], "sort": [{ "age": "desc" }] }
在上面的案例中,我们接触了 match
, range
等查询方式(查询对象),在ES还有很多其他的查询方式,在不同的场景中我们需要根据情况进行合理的选择。
普通搜索(匹配所有文档)
GET _search { "query": { "bool": { "must": [ { "match_all": {} } ], "filter": { "term": { "name": "zs1" } } } } }
标准查询,可以理解为,分词查询有点像模糊匹配(like),会对查询的内容进行分词后,得到多个单词,分别带着多个单词去检索ES库,只要有一个单词能查出结果,整个查询就有结果。不管你需要全文本查询还是精确查询基本上都要用到它。
如下面的搜索会对Steven King分词,并找到包含Steven或King的文档,然后给出排序分值。
{
"query": {
"match": {
"fullName": "Steven King"
}
}
}
注意:上面效果如同 where fullName like "%Steven%" or fullName like "%King%"
提示:match一般只用于全文字段的匹配与查询,一般不用于过滤。
multi_match 查询允许你做 match查询的基础上同时搜索多个字段:
{
"query": {
"multi_match": {
"query": "Steven King",
"fields": ["fullName", "title"]
}
}
}
注意:上面的搜索同时在fullName和title字段中匹配。
单词/词元查询 , 可以理解为等值查询,字符串,数字等都可以使用它,把查询的内容看成一个整体去检索ES库
{ "query": { "bool": { "must": { "match_all": { } }, "filter": { "term": { "username": "Steven King" } } } } }
提示:上面的“Steven King”会被当成一个去term中匹配,它跟match不同的地方在于match会把“Steven King”分成“steven”和“king”分别取username中查询。
Terms支持多个字段查询
{
"query": {
"terms": {
"tags": [
"jvm",
"hadoop",
"lucene"
],
"minimum_match": 1
}
}
}
提示:minimum_match:至少匹配个数,默认为1
组合搜索bool可以组合多个查询条件为一个查询对象,查询条件包括must、should和must_not。
例如:查询爱好有美女,同时也有喜欢游戏或运动,且出生于1990-06-30及之后的人。
{ "query": { "bool": { "must": [ { "term": { "hobby": "美女" } } ], "should": [ { "term": { "hobby": "游戏" } }, { "term": { "hobby": "运动" } } ], "must_not": [ { "range": { "birth_date": { "lte": "1990-06-30" } } } ], "filter": [ ... ] } } }
上面案例如同:Hobby=美女 and (hobby=游戏 or hobby=运动) and birth_date >= 1990-06-30
提示: 如果 bool 查询下没有must子句,那至少应该有一个should子句。但是 如果有 must子句,那么没有 should子句也可以进行查询。
range过滤允许我们按照指定范围查找一批数据
gt 大于 gte 大于等于 lt 小于 lte 小于等于
{
"query": {
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
}
上例中查询年龄大于等于20并且小于30。
gt:> gte:>= lt:< lte:<=
{ "query": { "bool": { "must": [ { "match_all": { } } ], "filter": { "exists": { "field": "gps" } } } } }
提示:exists和missing只能用于过滤结果。
和term查询相似,前匹配搜索不是精确匹配,而是类似于SQL中的like ‘key%’
{
"query": {
"prefix": {
"fullName": "王"
}
}
}
提示:上例即查询姓王的所有人。
使用*代表0~N个,使用?代表1个。
{
"query": {
"wildcard": {
"fullName": "姚*鹏"
}
}
}
在全文检索理论中,文档的查询是通过关键字查询文档索引来进行匹配,因此将文本拆分为有意义的单词,对于搜索结果的准确性至关重要,因此,在建立索引的过程中和分析搜索语句的过程中都需要对文本进行分词。ES的倒排索引是分词的结果。
正排索引:通过id查找内容,mysql就是正排索引
倒排索引:先将原始数据根据分词器进行分词,语义转换,排序,分组等操作生成词元,词元对应文档id,再搜索时先通过词元找到文档id,再通过文档id找到对应的文档. 索引创建&索引搜索
分词器的作用至关重要,数据的查询结果是否精准跟分词器有很大的关系
为了方便理解,我们用一个模拟图跟踪一下ES创建倒排索引的过程,如有原始数据:
ID | username | intro |
---|---|---|
1 | zs | my name is zs |
2 | ls | my name is ls |
如果对intro进行倒排索引,ES会根据分词器进行分词 , 语义转换,排序, 分组等操作最终倒排索引如下:
词元 | ID倒排 |
---|---|
is | 1 -> 2 |
ls | 2 |
my | 1 -> 2 |
name | 1 -> 2 |
zs | 1 |
当ES进行关键字查询的时候,如需要查询“my”
,那么ES可以根据二分查找更快的定位到 my | 1 -> 2
, 根据ID值1 ,2直接取出结果。
ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成IK分词器 - 大家都在用IK
在es以插件方式集成ik分词器
发请求使用分词器
插件源码地址:https://github.com/medcl/elasticsearch-analysis-ik
并将解压后的内容放置于ES根目录/plugins/ik
在ik/config 目录可以对分词器进行配置,如停词 , 自定义字典等。
POST _analyze
{
"analyzer":"ik_smart",
"text":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
}
提示:IK分词器指定:ik_smart ; ik_max_word , ik_max_word 相比 ik_smart 来说会将文本做最细粒度的拆
分。
ik_smart 蓉华教育
ik_max_word 蓉华教育 蓉华 教育
ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型。就如果Mysql创建表时候指定的每个column列的类型。 为了方便字段的检索,我们会指定存储在ES中的字段是否进行分词,但是有些字段类型可以分词,有些字段类型不可以分词,所以对于字段的类型需要我们自己去指定。
需要注意的是,我们在Mysql建表过程是:
Mysql创建数据库 -> 创建表(指定字段类型) -> crud数据 而在ES中也是一样,
ES创建索引库 -> 文档类型映射 -> crud文档
没有进行自定义类型映射的时候,直接添加数据。就会按照默认规则进行映射,这种映射叫做默认映射
查看索引类型的映射配置:GET {indexName}/_mapping/{typeName}
ES在没有配置Mapping的情况下新增文档,ES会尝试对字段类型进行猜测,并动态生成字段和类型的映射关系。
内容 | 默认映射类型 |
---|---|
JSON type | Field type |
Boolean: true or false | “boolean” |
Whole number: 123 | “long” |
Floating point: 123.45 | “double” |
String, valid date:“2014-09-15” | “date” |
String: “foo bar” | “text” |
字符串 | text(分词lucene TextField) | keyword(不分词lucene StringField) | |||
---|---|---|---|---|---|
数字 | long | integer | short | double | float |
日期 | date | ||||
逻辑 | boolean |
对象类型 | object |
---|---|
数组类型 | array |
地理位置 | geo_point,geo_shape(经纬度) |
字段映射的常用属性配置列表 - 即给某个字段执行类的时候可以指定以下属性
type | 类型:基本数据类型,integer,long,date,boolean,keyword,text… |
---|---|
enable | 是否启用:默认为true。 false:不能索引、不能搜索过滤,仅在_source中存储 |
boost | 权重提升倍数:用于查询时加权计算最终的得分。 |
format | 格式:一般用于指定日期格式,如 yyyy-MM-dd HH:mm:ss.SSS |
ignore_above | 长度限制:长度大于该值的字符串将不会被索引和存储。 |
ignore_malformed | 转换错误忽略:true代表当格式转换错误时,忽略该值,被忽略后不会被存储和索引。 |
include_in_all | 是否将该字段值组合到_all中。 |
null_value | 默认控制替换值。如空字符串替换为”NULL”,空数字替换为-1 |
store | 是否存储:默认为false。true意义不大,因为**_source**中已有数据 |
index | 索引模式:analyzed (索引并分词,text默认模式), not_analyzed (索引不分词,keyword默认模式),no(不索引) |
analyzer | 索引分词器:索引创建时使用的分词器,如ik_smart,ik_max_word,standard |
search_analyzer | 搜索分词器:搜索该字段的值时,传入的查询内容的分词器。 |
fields | 多字段索引:当对该字段需要使用多种索引模式时使用。如:城市搜索 |
New York"city":“city”:{ |
"type": "text",
"analyzer": "ik_smart",
"fields": {
"raw": {
"type": "keyword"
}
}
}
city -> 来做分词查询 city.raw -> 不做分词
解释:相当于给 city取了一个别名 city.raw,city的类型为text , city.raw的类型keyword
搜索 city分词 ; 搜索city.raw 不分词那么以后搜索过滤和排序就可以使用city.raw字段名 |
注意:如果索引库已经有数据了,就不能再添加映射了
put yaoasang
put yaoasang/goods/_mapping
{
"goods": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
}
解释:给aigou索引库中的是goods类型创建映射 ,id指定为long类型 , name指定为text类型(要分词),analyzer分词使用ik,查询分词器也使用ik
{
"id" : 1,
"girl" : {
"name" : "王小花",
"age" : 22
}
}
文档映射
{
"properties": {
"id": {"type": "long"},
"girl": {
"properties":{
"name": {"type": "keyword"},
"age": {"type": "integer"}
}
}
}
}
{
"id" : 1,
"hobbys" : ["王小花","林志玲"]
}
文档映射
{
"properties": {
"id": {"type": "long"},
"hobbys": {"type": "keyword"}
}
}
解释:数组的映射只需要映射一个元素即可,因为数组中的元素类型是一样的。
{
"id" : 1,
"girl":[{"name":"林志玲","age":32},{"name":"赵丽颖","age":22}]
}
文档映射
"properties": {
"id": {
"type": "long"
},
"girl": {
"properties": {
"age": { "type": "long" },
"name": { "type": "text" }
}
}
}
索引库中多个类型(表)的字段是有相同的映射,如所有的ID都可以指定为integer类型,基于这种思想,我们可以做全局映射,让所有的文档都使用全局文档映射。全局映射可以通过动态模板和默认设置两种方式实现。
索引下所有的类型映射配置会继承_default_的配置,如:
PUT {indexName}
{
"mappings": {
"_default_": {
"_all": {
"enabled": false
}
},
"user": {},
"dept": {
"_all": {
"enabled": true
}
}
}
关闭默认的 _all ,dept自定义开启 all
在实际应用场景中,一个对象的属性中,需要全文检索的字段较少,大部分字符串不需要分词,因此,需要利用全局模板覆盖自带的默认模板
PUT _template/global_template //创建名为global_template的模板 { "template": "*", //匹配所有索引库 "settings": { "number_of_shards": 1 }, //匹配到的索引库只创建1个主分片 "mappings": { "_default_": { "_all": { "enabled": false //关闭所有类型的_all字段 }, "dynamic_templates": [ { "string_as_text": { "match_mapping_type": "string",//匹配类型string username="xxx xxx" "match": "*_text", //匹配字段名字以_text结尾 "mapping": { "type": "text",//将类型为string的字段映射为text类型 "analyzer": "ik_max_word", "search_analyzer": "ik_max_word", "fields": { "raw": { "type": "keyword", "ignore_above": 256 } } } } }, { "string_as_keyword": { "match_mapping_type": "string",//匹配类型string "mapping": { "type": "keyword"//将类型为string的字段映射为keyword类型 } } } ] } }}
PS : 映射方式优先级 (低 -> 高):默认 -> 全局 -> 自定义
在实际项目中,我们按照如下流程操作ES
官方文档API:https://www.elastic.co/guide/en/elasticsearch/client/java-api/index.html
下面采用ES提供的Jar进行ES操作
<dependencies> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>6.8.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency> </dependencies>
编写工具
public class ESClientUtil { public static TransportClient getClient(){ TransportClient client = null; Settings settings = Settings.builder() .put("cluster.name", "elasticsearch").build(); try { client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); } catch (UnknownHostException e) { e.printStackTrace(); } return client; } }
@Test public void testAdd() { //获取客户端对象 TransportClient client = ESClientUtil.getClient(); //创建索引 IndexRequestBuilder indexRequestBuilder = client.prepareIndex("shopping", "user", "1"); Map<String,Object> data = new HashMap<>(); data.put("id",1); data.put("username","zs"); data.put("age",11); //获取结果 IndexResponse indexResponse = indexRequestBuilder.setSource(data).get(); System.out.println(indexResponse); client.close(); }
GetResponse response = client.prepareGet("crm", "vip", "1").get();
@Test public void testUpdate(){ //获取客户端对象 TransportClient client = ESClientUtil.getClient(); //修改索引 UpdateRequestBuilder updateRequestBuilder = client.prepareUpdate("shopping", "user", "1"); Map<String,Object> data = new HashMap<>(); data.put("id",1); data.put("username","zs"); data.put("age",11); //获取结果设置修改内容 UpdateResponse updateResponse = updateRequestBuilder.setDoc(data).get(); System.out.println(updateResponse); client.close(); }
@Test
public void testDelete(){
//获取客户端对象
TransportClient client = ESClientUtil.getClient();
DeleteRequestBuilder deleteRequestBuilder = client.prepareDelete("shopping", "user", "1");
DeleteResponse deleteResponse = deleteRequestBuilder.get();
System.out.println(deleteResponse);
client.close();
}
@Test public void testBuilkAdd(){ //获取客户端对象 TransportClient client = ESClientUtil.getClient(); BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); Map<String,Object> data1 = new HashMap<>(); data1.put("id",11); data1.put("username","zs"); data1.put("age",11); bulkRequestBuilder.add(client.prepareIndex("shopping", "user", "11").setSource(data1)); Map<String,Object> data2 = new HashMap<>(); data2.put("id",22); data2.put("username","zs"); data2.put("age",11); bulkRequestBuilder.add(client.prepareIndex("shopping", "user", "11").setSource(data2)); BulkResponse bulkItemResponses = bulkRequestBuilder.get(); Iterator<BulkItemResponse> iterator = bulkItemResponses.iterator(); while(iterator.hasNext()){ BulkItemResponse next = iterator.next(); System.out.println(next.getResponse()); } client.close(); }
@Test public void testSearch(){ //获取客户端对象 TransportClient client = ESClientUtil.getClient(); SearchRequestBuilder searchRequestBuilder = client.prepareSearch("shopping"); searchRequestBuilder.setTypes("user"); searchRequestBuilder.setFrom(0); searchRequestBuilder.setSize(10); searchRequestBuilder.addSort("age", SortOrder.ASC); //查询条件 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); List<QueryBuilder> must = boolQueryBuilder.must(); must.add(QueryBuilders.matchQuery("username" , "zs")); List<QueryBuilder> filter = boolQueryBuilder.filter(); filter.add(QueryBuilders.rangeQuery("age").lte(20).gte(10)); filter.add(QueryBuilders.termQuery("id",11)); searchRequestBuilder.setQuery(boolQueryBuilder); SearchResponse searchResponse = searchRequestBuilder.get(); SearchHits hits = searchResponse.getHits(); System.out.println("条数:"+hits.getTotalHits()); for (SearchHit hit : hits.getHits()) { System.out.println(hit.getSourceAsMap()); } client.close(); }
Spring data:spring对数据操作支持规范,但是我们的数据是多种类型,比如rdbms(mysql)、redis(Nosql)、es、mq
Spring data redis :spring操作redis
Spring data jpa :操作关系型数据库
spring data elasticsearch: spring操作es的框架
springBoot spring data redis:简化springdata对redis的配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
spring:
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 127.0.0.1:9300
@Document(indexName = "crm-test",type = "user") @Data public class UserDoc { @Id private Long id; @Field(type = FieldType.Keyword) private String username; @Field(type = FieldType.Integer) private Integer age; @Field(type = FieldType.Boolean) private Boolean sex; @Field(type = FieldType.Text,analyzer = "ik_smart",searchAnalyzer = "ik_smart") private String intro; }
@Repository
public interface UserDocRepository extends ElasticsearchRepository<UserDoc,Long> {
}
@Autowired private UserDocRepository userDocRepository; @Test public void testAdd()throws Exception{ UserDoc userDoc = new UserDoc(); userDoc.setId(1L); userDoc.setUsername("xiaoming"); userDoc.setAge(15); userDoc.setSex(true); userDoc.setIntro("xiaoming is a good student"); userDocRepository.save(userDoc); } @Test public void testGetOne()throws Exception{ System.out.println(userDocRepository.findById(1L)); } /** * 新增和修改都是调用save() * 如果你传入的id存在就是修改,如果id不存在就是新增 * @throws Exception */ @Test public void testUpdate()throws Exception{ UserDoc userDoc = new UserDoc(); userDoc.setId(1L); userDoc.setUsername("xiaohong"); userDoc.setAge(18); userDoc.setIntro("xiaohong is a good student"); userDocRepository.save(userDoc); } @Test public void testDel()throws Exception{ userDocRepository.deleteById(1L); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。