文章目录
一、ES的核心概念
1.1、概述
- Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。
- 然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。
- 在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。
Elasticsearch比传统关系型数据库如下:
- Relational DB -> Databases -> Tables -> Rows -> Columns
- Elasticsearch -> Indices -> Types -> Documents -> Fields
1.2、相关概念
1、索引 index
- 一个索引就是一个拥有几分相似特征的文档的集合。(相当于数据库)
比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。 - 一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。
- 在一个集群中,可以定义任意多的索引。
2、类型 type
在一个索引中,你可以定义一种或多种类型type。(相当于表)
- 一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。
- 通常,会为具有一组共同字段的文档定义一个类型。
- 比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。
3、文档 document
一个文档是一个可被索引的基础信息单元。(相当于行)
- 比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。
- 在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。
5、字段 Field
对文档数据根据不同属性进行的分类标识,相当于是数据表的字段(相当于列)
- 如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,
- 其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。
6、接近实时 NRT
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)
7、集群 cluster
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。
- 一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群
8、节点 node
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
- 和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
- 一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
- 在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
9、分片和复制 shards&replicas
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。
- 为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。
- 当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
- 分片很重要,主要有两方面的原因:
1)允许你水平分割/扩展你的内容容量。
2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。 - 至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。
- 在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
- 复制之所以重要,有两个主要原因:
在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。 - 总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。
- 分片和复制的数量可以在索引创建时候指定。在索引创建后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
- 默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
二、管理索引
curl是利用URL语法在命令行方式下工作的开源文件传输工具,使用curl可以简单实现常见的get/post请求。简单的认为是可以在命令行下面访问url的一个工具。在centos的默认库里面是有curl工具的,如果没有请yum安装即可。
curl
-X 指定http的请求方法 有HEAD GET POST PUT DELETE
-d 指定要传输的数据
-H 指定http请求头信息
1、使用 Xput创建索引
2.1、使用 Xput创建索引
1、创建索引
在我们的kibana的dev tools当中执行以下语句
curl -XPUT http://node01:9200/blog01/?pretty
2、插入文档
前面的命令使用 PUT 动词将一个文档添加到 /article(文档类型),并为该文档分配 ID 为1。URL 路径显示为index/doctype/ID(索引/文档类型/ID)。
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": "What is lucene"}'
问题:Content-Type header [application/x-www-form-urlencoded] is not supported
解决:
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": "What is lucene"}' -H "Content-Type: application/json"
原因:
此原因时由于ES增加了安全机制, 进行严格的内容类型检查,严格检查内容类型也可以作为防止跨站点请求伪造攻击的一层保护。 官网解释
3、查询文档
curl -XGET http://node01:9200/blog01/article/1?pretty
问题:Content-Type header [application/x-www-form-urlencoded] is not supported
解决:
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": "What is lucene"}' -H "Content-Type: application/json"
curl -XGET http://node01:9200/blog01/article/1?pretty -H "Content-Type: application/json"
4、更新文档
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": " What is elasticsearch"}'
问题:Content-Type header [application/x-www-form-urlencoded] is not supported
解决:
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": " What is elasticsearch"}' -H "Content-Type: application/json"
5、搜索文档
curl -XGET "http://node01:9200/blog01/article/_search?q=title:elasticsearch"
问题:Content-Type header [application/x-www-form-urlencoded] is not supported
解决:
curl -XGET "http://node01:9200/blog01/article/_search?q=title:'elasticsearch'&pretty" -H "Content-Type: application/json"
6、删除文档
curl -XDELETE "http://node01:9200/blog01/article/1?pretty"
7、删除索引
curl -XDELETE http://node01:9200/blog01?pretty
2.2、查询条件查询
在kibana提供的界面上进行操作。
POST /school/student/_bulk
{ "index": { "_id": 1 }}
{ "name" : "liubei", "age" : 20 , "sex": "boy", "birth": "1996-01-02" , "about": "i like diaocan he girl" }
{ "index": { "_id": 2 }}
{ "name" : "guanyu", "age" : 21 , "sex": "boy", "birth": "1995-01-02" , "about": "i like diaocan" }
{ "index": { "_id": 3 }}
{ "name" : "zhangfei", "age" : 18 , "sex": "boy", "birth": "1998-01-02" , "about": "i like travel" }
{ "index": { "_id": 4 }}
{ "name" : "diaocan", "age" : 20 , "sex": "girl", "birth": "1996-01-02" , "about": "i like travel and sport" }
{ "index": { "_id": 5 }}
{ "name" : "panjinlian", "age" : 25 , "sex": "girl", "birth": "1991-01-02" , "about": "i like travel and wusong" }
{ "index": { "_id": 6 }}
{ "name" : "caocao", "age" : 30 , "sex": "boy", "birth": "1988-01-02" , "about": "i like xiaoqiao" }
{ "index": { "_id": 7 }}
{ "name" : "zhaoyun", "age" : 31 , "sex": "boy", "birth": "1997-01-02" , "about": "i like travel and music" }
{ "index": { "_id": 8 }}
{ "name" : "xiaoqiao", "age" : 18 , "sex": "girl", "birth": "1998-01-02" , "about": "i like caocao" }
{ "index": { "_id": 9 }}
{ "name" : "daqiao", "age" : 20 , "sex": "girl", "birth": "1996-01-02" , "about": "i like travel and history" }
1、使用match_all做查询
GET /school/student/_search?pretty
{
"query": {
"match_all": {}
}
}
问题:
- 通过match_all匹配后,会把所有的数据检索出来,但是往往真正的业务需求并非要找全部的数据,而是检索出自己想要的;
- 并且对于es集群来说,直接检索全部的数据,很容易造成GC现象。所以,我们要学会如何进行高效的检索数据
2、通过关键字段进行查询
GET /school/student/_search?pretty
{
"query": {
"match": {"about": "travel"}
}
}
如果此时想查询喜欢旅游的,并且不能是男孩的,怎么办?
【这种方式是错误的,因为一个match下,不能出现多个字段值[match] query doesn’t support multiple fields】,需要使用复合查询
3、bool的复合查询
当出现多个查询语句组合的时候,可以用bool来包含。bool合并聚包含:must,must_not或者should, should表示or的意思
例:查询非男性中喜欢旅行的人
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "match": {"about": "travel"}},
"must_not": {"match": {"sex": "boy"}}
}
}
}
4、bool的复合查询中的should
should表示可有可无的(如果should匹配到了就展示,否则就不展示)
例:查询喜欢旅行的,如果有男性的则显示,否则不显示
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "match": {"about": "travel"}},
"should": {"match": {"sex": "boy"}}
}
}
}
5、term匹配
使用term进行精确匹配
(比如数字,日期,布尔值或 not_analyzed的字符串(未经分析的文本数据类型))
语法:
{ "term": { "age": 20 }}
{ "term": { "date": "2018-04-01" }}
{ "term": { "sex": “boy” }}
{ "term": { "about": "trivel" }}
例:查询喜欢旅行的
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "term": {"about": "travel"}},
"should": {"term": {"sex": "boy"}}
}}
}
6、使用terms匹配多个值
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "terms": {"about": ["travel","history"]}}
}
}
}
term主要是用于精确的过滤比如说:”我爱你”
在match下面匹配可以为包含:我、爱、你、我爱等等的解析器
在term语法下面就精准匹配到:”我爱你”
7、Range过滤
Range过滤允许我们按照指定的范围查找一些数据:
操作范围:gt::大于,gae::大于等于,lt::小于,lte::小于等于
例:查找出大于20岁,小于等于25岁的学生
GET /school/student/_search?pretty
{
"query": {
"range": {
"age": {"gt":20,"lte":25}
}
}
}
8、exists和 missing过滤
exists和missing过滤,可以找到文档中是否包含某个字段或者是没有某个字段
例:查找字段中包含age的文档
GET /school/student/_search?pretty
{
"query": {
"exists": {
"field": "age"
}
}
}
9、bool的多条件过滤
用bool也可以像之前match一样来过滤多行条件:
- must: 多个查询条件的完全匹配,相当于 and 。
- must_not : 多个查询条件的相反匹配,相当于 not 。
- should : 至少有一个查询条件匹配, 相当于 or
例:过滤出about字段包含travel并且年龄大于20岁小于30岁的同学
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": [
{"term": {
"about": {
"value": "travel"
}
}},{"range": {
"age": {
"gte": 20,
"lte": 30
}
}}
]
}
}
}
10、查询与过滤条件合并
通常复杂的查询语句,我们也要配合过滤语句来实现缓存,用filter语句就可以来实现
例:查询出喜欢旅行的,并且年龄是20岁的文档
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": {"match": {"about": "travel"}},
"filter": [{"term":{"age": 20}}]
}
}
}
2.3、定义字段类型mappings
在es当中,每个字段都会有默认的类型,根据我们第一次插入数据进去,es会自动帮我们推断字段的类型,当然我们也可以通过设置mappings来提前自定义我们字段的类型
1、使用mappings来提前定义字段类型
使用mapping的映射管理,提前指定字段的类型,防止后续的程序问题;
DELETE document
PUT document
{
"mappings": {
"article" : {
"properties":
{
"title" : {"type": "text"} ,
"author" : {"type": "text"} ,
"titleScore" : {"type": "double"}
}
}
}
}
get document/article/_mapping
2、基本命令
DELETE school
PUT school
{
"mappings": {
"logs" : {
"properties": {"messages" : {"type": "text"}}
}
}
}
添加索引:school,文档类型类logs,索引字段为message ,字段的类型为text
GET /school/_mapping/logs
继续添加字段
POST /school/_mapping/logs
{
"properties": {"number" : {"type": "text"}}
}
GET /school/_mapping/logs
3、获取映射字段
语法:
GET /school/_mapping/logs/field/number
4、管理索引库分片数以及副本数settings
所谓的settings就是用来修改索引分片和副本数的;
比如有的重要索引,副本数很少甚至没有副本,那么我们可以通过setting来
添加副本数:
DELETE document
PUT document
{
"mappings": {
"article" : {
"properties":
{
"title" : {"type": "text"} ,
"author" : {"type": "text"} ,
"titleScore" : {"type": "double"}
}
}
}
}
GET /document/_settings
可以看到当前的副本数是1,那么为了提高容错性,我们可以把副本数改成2:
PUT /document/_settings
{
"number_of_replicas": 2
}
副本可以改,分片不能改
PUT /document/_settings
{
"number_of_shards": 3
}
三、分页解决方案
3.1、导入数据
DELETE us
POST /_bulk
{ "create": { "_index": "us", "_type": "tweet", "_id": "1" }}
{ "email" : "john@smith.com", "name" : "John Smith", "username" : "@john" }
{ "create": { "_index": "us", "_type": "tweet", "_id": "2" }}
{ "email" : "mary@jones.com", "name" : "Mary Jones", "username" : "@mary" }
{ "create": { "_index": "us", "_type": "tweet", "_id": "3" }}
{ "date" : "2014-09-13", "name" : "Mary Jones", "tweet" : "Elasticsearch means full text search has never been so easy", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "4" }}
{ "date" : "2014-09-14", "name" : "John Smith", "tweet" : "@mary it is not just text, it does everything", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "5" }}
{ "date" : "2014-09-15", "name" : "Mary Jones", "tweet" : "However did I manage before Elasticsearch?", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "6" }}
{ "date" : "2014-09-16", "name" : "John Smith", "tweet" : "The Elasticsearch API is really easy to use", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "7" }}
{ "date" : "2014-09-17", "name" : "Mary Jones", "tweet" : "The Query DSL is really powerful and flexible", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "8" }}
{ "date" : "2014-09-18", "name" : "John Smith", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "9" }}
{ "date" : "2014-09-19", "name" : "Mary Jones", "tweet" : "Geo-location aggregations are really cool", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "10" }}
{ "date" : "2014-09-20", "name" : "John Smith", "tweet" : "Elasticsearch surely is one of the hottest new NoSQL products", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "11" }}
{ "date" : "2014-09-21", "name" : "Mary Jones", "tweet" : "Elasticsearch is built for the cloud, easy to scale", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "12" }}
{ "date" : "2014-09-22", "name" : "John Smith", "tweet" : "Elasticsearch and I have left the honeymoon stage, and I still love her.", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "13" }}
{ "date" : "2014-09-23", "name" : "Mary Jones", "tweet" : "So yes, I am an Elasticsearch fanboy", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "14" }}
{ "date" : "2014-09-24", "name" : "John Smith", "tweet" : "How many more cheesy tweets do I have to write?", "user_id" : 1 }
3.2、size+from浅分页
按照一般的查询流程来说,如果我想查询前10条数据:
1 客户端请求发给某个节点
2 节点转发给个个分片,查询每个分片上的前10条
3 结果返回给节点,整合数据,提取前10条
4 返回给请求客户端
from定义了目标数据的偏移值,size定义当前返回的事件数目
GET /us/_search?pretty
{
"from" : 0 , "size" : 5
}
GET /us/_search?pretty
{
"from" : 5 , "size" : 5
}
这种浅分页只适合少量数据,因为随from增大,查询的时间就会越大,而且数据量越大,查询的效率指数下降
优点:from+size在数据量不大的情况下,效率比较高
缺点:在数据量非常大的情况下,from+size分页会把全部记录加载到内存中,这样做不但运行速递特别慢,而且容易让es出现内存不足而挂掉
3.3、scroll深分页
对于上面介绍的浅分页,当Elasticsearch响应请求时,它必须确定docs的顺序,排列响应结果。
- 如果请求的页数较少(假设每页20个docs), Elasticsearch不会有什么问题,
- 但是如果页数较大时,比如请求第20页,Elasticsearch不得不取出第1页到第20页的所有docs,再去除第1页到第19页的docs,得到第20页的docs。
- 解决的方式就是使用scroll,scroll就是维护了当前索引段的一份快照信息–缓存(这个快照信息是你执行这个scroll查询时的快照)。
可以把 scroll 分为初始化和遍历两步:
- 1、初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照;
- 2、遍历时,从这个快照里取数据;
初始化:
GET us/_search?scroll=3m
{
"query": {"match_all": {}},
"size": 3
}
初始化的时候就像是普通的search一样
其中的scroll=3m代表当前查询的数据缓存3分钟
Size:3 代表当前查询3条数据
遍历:
在遍历时候,拿到上一次遍历中的scrollid,然后带scroll参数,重复上一次的遍历步骤,知道返回的数据为空,就表示遍历完成
GET /_search/scroll
{
"scroll" : "1m",
"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAPXFk0xN1BmSnlVUldhYThEdWVzZ19xbkEAAAAAAAAAIxZuQWVJU0VSZ1JzcVZtMGVYZ3RDaFlBAAAAAAAAA9oWTVZOdHJ2cXBSOU9wN3c1dk5vcWd4QQAAAAAAAAPYFk0xN1BmSnlVUldhYThEdWVzZ19xbkEAAAAAAAAAIhZuQWVJU0VSZ1JzcVZtMGVYZ3RDaFlB"
}
【注意】:每次都要传参数scroll,刷新搜索结果的缓存时间,另外不需要指定index和type(不要把缓存的时时间设置太长,占用内存)
对比
- 浅分页,每次查询都会去索引库(本地文件夹)中查询pageNum*page条数据,然后截取掉前面的数据,留下最后的数据。 这样的操作在每个分片上都会执行,最后会将多个分片的数据合并到一起,再次排序,截取需要的。
- 深分页,可以一次性将所有满足查询条件的数据,都放到内存中。分页的时候,在内存中查询。相对浅分页,就可以避免多次读取磁盘。
四、ES的中文分词器IK
ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成IK分词器。
那么我们接下来就来安装IK分词器,以实现中文的分词 。
第一步:三台机器安装IK分词器
将安装包上传到node01机器的/home/es路径下
cd /kkb/soft
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.7.0/elasticsearch-analysis-ik-6.7.0.zip
# 将ik分词器的插件,解压到对应路径下
cd /kkb/soft
mkdir -p /kkb/install/elasticsearch-6.7.0/plugins/analysis-ik
unzip elasticsearch-analysis-ik-6.7.0.zip -d /kkb/install/elasticsearch-6.7.0/plugins/analysis-ik/
将安装包分发到其他机器上
node01机器执行以下命令进行安装包的分发
cd /kkb/install/elasticsearch-6.7.0/plugins
scp -r analysis-ik/ node02:$PWD
scp -r analysis-ik/ node03:$PWD
三台机器都配置完成,配置完成之后,需要重启服务。
三台机器重启es服务
三台机器执行以下命令停止es服务并重启es服务
ps -ef|grep elasticsearch | grep bootstrap | awk '{print $2}' |xargs kill -9
nohup /kkb/install/elasticsearch-6.7.0/bin/elasticsearch 2>&1 &
第二步、创建索引库并配置IK分词器
delete iktest
PUT /iktest?pretty
{
"settings" : {
"analysis" : {
"analyzer" : {
"ik" : {
"tokenizer" : "ik_max_word"
}
}
}
},
"mappings" : {
"article" : {
"dynamic" : true,
"properties" : {
"subject" : {
"type" : "text",
"analyzer" : "ik_max_word"
}
}
}
}
}
说明:在创建索引库的时候,我们指定分词方式为ik_max_word,会对我们的中文进行最细粒度的切分
第三步、查看分词效果
在kibana当中执行以下查询,并验证分词效果
GET _analyze?pretty
{
"analyzer": "ik_max_word",
"text": "特朗普是美国总统"
}
第四步、插入测试数据
POST /iktest/article/_bulk?pretty
{ "index" : { "_id" : "1" } }
{"subject" : ""闺蜜"崔顺实被韩检方传唤 韩总统府促彻查真相" }
{ "index" : { "_id" : "2" } }
{"subject" : "韩举行"护国训练" 青瓦台:决不许国家安全出问题" }
{ "index" : { "_id" : "3" } }
{"subject" : "媒体称FBI已经取得搜查令 检视希拉里电邮" }
{ "index" : { "_id" : "4" } }
{"subject" : "村上春树获安徒生奖 演讲中谈及欧洲排外问题" }
{ "index" : { "_id" : "5" } }
{"subject" : "希拉里团队炮轰FBI 参院民主党领袖批其”违法”" }
查看分词器
对"希拉里和韩国"进行分词查询
ikmaxword分词后的效果:希|拉|里|希拉里|和|韩国
POST /iktest/article/_search?pretty
{
"query" : { "match" : { "subject" : "希拉里和韩国" }},
"highlight" : {
"pre_tags" : ["<font color=red>"],
"post_tags" : ["</font>"],
"fields" : {
"subject" : {}
}
}
}
第五步、配置热词更新
查看分词效果
GET _analyze?pretty
{
"analyzer": "ik_max_word",
"text": "小老弟,你怎么肥事,老铁你来了!!!"
}
我们会发现,随着时间的推移和发展,有些网络热词我们并不能进行分词,因为网络热词并没有定义在我们的词库里面,这就需要我们经常能够实时的更新我们的网络热词,我们可以通过tomcat来实现远程词库来解决这个问题。
1、node03配置Tomcat
使用hadoop用户来进行配置tomcat,此处我们将tomcat装在node03机器上面即可,将我们的tomcat安装包上传到node03服务器的/kkb/soft路径下,然后进行解压
cd /kkb/soft/
tar -zxf apache-tomcat-8.5.34.tar.gz -C /kkb/install/
tomcat当中添加配置hot.dic
cd /kkb/install/apache-tomcat-8.5.34/webapps/ROOT
vi hot.dic
启动tomcat
cd /kkb/install/apache-tomcat-8.5.34/
bin/startup.sh
浏览器访问以验证tomcat是否安装成功
wget http://node03:8080/hot.dic
如果能够访问到,则证明tomcat安装成功
2、 三台机器修改配置文件
三台机器都要修改es的配置文件(使用es用户来进行修改即可)
第一台机器node01修改es的配置
cd /kkb/install/elasticsearch-6.7.0/plugins/analysis-ik/config
vim IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://node03:8080/hot.dic</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
修改完成之后拷贝到node02与node03机器上面去
node01执行以下命令进行拷贝
cd /kkb/install/elasticsearch-6.7.0/plugins/analysis-ik/config
scp IKAnalyzer.cfg.xml node02:$PWD
scp IKAnalyzer.cfg.xml node03:$PWD
3、三台机器重新启动es
三台机器重新启动es服务,三台机器先使用kill -9杀死es的服务,然后再执行以下命令进行重启
ps -ef|grep elasticsearch | grep bootstrap | awk '{print $2}' |xargs kill -9
nohup /kkb/install/elasticsearch-6.7.0/bin/elasticsearch 2>&1 &
在kibana当中执行以下命令,查看我们的分词过程
GET _analyze?pretty
{
"analyzer": "ik_max_word",
"text": "小老弟,你怎么肥事,老铁你来了"
}
五、ES的JavaAPI操作
5.1、创建maven工程,并添加jar包坐标依赖
创建maven工程,并导入以下jar包坐标依赖
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>6.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.0-M1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
5.2、添加索引文件操作
1、创建Client
private TransportClient client;
@BeforeEach
public void test1() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "myes").build();
client = new PreBuiltTransportClient(settings).addTransportAddress(new TransportAddress(InetAddress.getByName("node01"),9300)).addTransportAddress(new TransportAddress(InetAddress.getByName("node02"),9300));
}
2、自己拼装json创建索引保存到myindex1索引库下面的article当中去
/**
* 插入json格式的索引数据
*/
@Test
public void createIndex(){
String json = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"travelying out Elasticsearch\"" +
"}";
IndexResponse indexResponse = client.prepareIndex("myindex1", "article", "1").setSource(json, XContentType.JSON).get();
client.close();
}
3、使用map创建索引
@Test
public void index2() throws Exception {
HashMap<String, String> jsonMap = new HashMap<String, String>();
jsonMap.put("name", "zhangsan");
jsonMap.put("sex", "1");
jsonMap.put("age", "18");
jsonMap.put("address", "bj");
IndexResponse indexResponse = client.prepareIndex("myindex1", "article", "2")
.setSource(jsonMap)
.get();
client.close();
}
4、XcontentBuilder实现创建索引
/**
* 通过XContentBuilder来实现索引的创建
* @throws IOException
*/
@Test
public void index3() throws IOException {
IndexResponse indexResponse = client.prepareIndex("myindex1", "article", "3")
.setSource(new XContentFactory().jsonBuilder()
.startObject()
.field("name", "lisi")
.field("age", "18")
.field("sex", "0")
.field("address", "bj")
.endObject())
.get();
client.close();
}
5、将对象转换为json格式字符串进行创建索引
定义person对象
public class Person implements Serializable{
private Integer id;
private String name ;
private Integer age;
private Integer sex;
private String address;
private String phone;
private String email;
private String say;
public Person() {
}
public Person(Integer id, String name, Integer age, Integer sex, String address, String phone, String email,String say) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
this.address = address;
this.phone = phone;
this.email = email;
this.say = say;
}
public String getSay() {
return say;
}
public void setSay(String say) {
this.say = say;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
插入索引数据
/**
* 将java对象转换为json格式字符串进行创建索引
*/
@Test
public void objToIndex(){
Person person = new Person();
person.setAge(18);
person.setId(20);
person.setName("张三丰");
person.setAddress("武当山");
person.setEmail("zhangsanfeng@163.com");
person.setPhone("18588888888");
person.setSex(1);
String json = JSONObject.toJSONString(person);
System.out.println(json);
client.prepareIndex("myindex1","article","32").setSource(json,XContentType.JSON).get();
client.close();
}
6、批量创建索引
/**
* 批量创建索引
* @throws IOException
*/
@Test
public void index4() throws IOException {
BulkRequestBuilder bulk = client.prepareBulk();
bulk.add(client.prepareIndex("myindex1", "article", "4")
.setSource(new XContentFactory().jsonBuilder()
.startObject()
.field("name", "wangwu")
.field("age", "18")
.field("sex", "0")
.field("address", "bj")
.endObject()));
bulk.add(client.prepareIndex("news", "article", "5")
.setSource(new XContentFactory().jsonBuilder()
.startObject()
.field("name", "zhaoliu")
.field("age", "18")
.field("sex", "0")
.field("address", "bj")
.endObject()));
BulkResponse bulkResponse = bulk.get();
System.out.println(bulkResponse);
client.close();
}
5.3、查询索引
1、初始化一批数据到索引库中准备查询
/**
* 初始化一批数据到索引库当中去准备做查询使用
* 注意这里初始化的时候,需要给我们的数据设置分词属性
* @throws Exception
*/
@Test
public void createIndexBatch() throws Exception {
Settings settings = Settings
.builder()
.put("cluster.name", "myes") //节点名称, 在es配置的时候设置
//自动发现我们其他的es的服务器
.put("client.transport.sniff", "true")
.build();
//创建客户端
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("node01"), 9300));//以本机作为节点
//创建映射
XContentBuilder mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
// .startObject("m_id").field("type","keyword").endObject()
.startObject("id").field("type", "integer").endObject()
.startObject("name").field("type", "text").field("analyzer", "ik_max_word").endObject()
.startObject("age").field("type", "integer").endObject()
.startObject("sex").field("type", "text").field("analyzer", "ik_max_word").endObject()
.startObject("address").field("type", "text").field("analyzer", "ik_max_word").endObject()
.startObject("phone").field("type", "text").endObject()
.startObject("email").field("type", "text").endObject()
.startObject("say").field("type", "text").field("analyzer", "ik_max_word").endObject()
.endObject()
.endObject();
//pois:索引名 cxyword:类型名(可以自己定义)
PutMappingRequest putmap = Requests.putMappingRequest("indexsearch").type("mysearch").source(mapping);
//创建索引
client.admin().indices().prepareCreate("indexsearch").execute().actionGet();
//为索引添加映射
client.admin().indices().putMapping(putmap).actionGet();
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
Person lujunyi = new Person(2, "玉麒麟卢俊义", 28, 1, "水泊梁山", "17666666666", "lujunyi@163.com","hello world今天天气还不错");
Person wuyong = new Person(3, "智多星吴用", 45, 1, "水泊梁山", "17666666666", "wuyong@163.com","行走四方,抱打不平");
Person gongsunsheng = new Person(4, "入云龙公孙胜", 30, 1, "水泊梁山", "17666666666", "gongsunsheng@163.com","走一个");
Person guansheng = new Person(5, "大刀关胜", 42, 1, "水泊梁山", "17666666666", "wusong@163.com","我的大刀已经饥渴难耐");
Person linchong = new Person(6, "豹子头林冲", 18, 1, "水泊梁山", "17666666666", "linchong@163.com","梁山好汉");
Person qinming = new Person(7, "霹雳火秦明", 28, 1, "水泊梁山", "17666666666", "qinming@163.com","不太了解");
Person huyanzhuo = new Person(8, "双鞭呼延灼", 25, 1, "水泊梁山", "17666666666", "huyanzhuo@163.com","不是很熟悉");
Person huarong = new Person(9, "小李广花荣", 50, 1, "水泊梁山", "17666666666", "huarong@163.com","打酱油的");
Person chaijin = new Person(10, "小旋风柴进", 32, 1, "水泊梁山", "17666666666", "chaijin@163.com","吓唬人的");
Person zhisheng = new Person(13, "花和尚鲁智深", 15, 1, "水泊梁山", "17666666666", "luzhisheng@163.com","倒拔杨垂柳");
Person wusong = new Person(14, "行者武松", 28, 1, "水泊梁山", "17666666666", "wusong@163.com","二营长。。。。。。");
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "1")
.setSource(JSONObject.toJSONString(lujunyi), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "2")
.setSource(JSONObject.toJSONString(wuyong), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "3")
.setSource(JSONObject.toJSONString(gongsunsheng), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "4")
.setSource(JSONObject.toJSONString(guansheng), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "5")
.setSource(JSONObject.toJSONString(linchong), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "6")
.setSource(JSONObject.toJSONString(qinming), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "7")
.setSource(JSONObject.toJSONString(huyanzhuo), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "8")
.setSource(JSONObject.toJSONString(huarong), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "9")
.setSource(JSONObject.toJSONString(chaijin), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "10")
.setSource(JSONObject.toJSONString(zhisheng), XContentType.JSON)
);
bulkRequestBuilder.add(client.prepareIndex("indexsearch", "mysearch", "11")
.setSource(JSONObject.toJSONString(wusong), XContentType.JSON)
);
bulkRequestBuilder.get();
client.close();
}
2、通过数据id使用prepareGet来查询索引
通过id来进行查询索引
/**
* 通过id来进行精确查询
*/
@Test
public void query1() {
GetResponse documentFields = client.prepareGet("indexsearch", "mysearch", "11").get();
String index = documentFields.getIndex();
String type = documentFields.getType();
String id = documentFields.getId();
System.out.println(index);
System.out.println(type);
System.out.println(id);
Map<String, Object> source = documentFields.getSource();
for (String s : source.keySet()) {
System.out.println(source.get(s));
}
}
3、查询索引库当中的所有数据
/**
* 查询所有数据
*/
@Test
public void queryAll() {
SearchResponse searchResponse = client
.prepareSearch("indexsearch")
.setTypes("mysearch")
.setQuery(new MatchAllQueryBuilder())
.get();
SearchHits searchHits = searchResponse.getHits();
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
client.close();
}
4、RangeQuery范围值查询
查找索引库当中年龄为18到28的所有人
/**
* 查找年龄18到28的人,包含18和28
*/
@Test
public void rangeQuery(){
SearchResponse searchResponse = client.prepareSearch("indexsearch")
.setTypes("mysearch")
.setQuery(new RangeQueryBuilder("age").gt(17).lt(29))
.get();
SearchHits hits = searchResponse.getHits();
SearchHit[] hits1 = hits.getHits();
for (SearchHit documentFields : hits1) {
System.out.println(documentFields.getSourceAsString());
}
client.close();
}
5、termQuery词条查询
/**
* 词条查询
*/
@Test
public void termQuery(){
SearchResponse searchResponse = client.prepareSearch("indexsearch").setTypes("mysearch")
.setQuery(new TermQueryBuilder("say", "熟悉"))
.get();
SearchHits hits = searchResponse.getHits();
SearchHit[] hits1 = hits.getHits();
for (SearchHit documentFields : hits1) {
System.out.println(documentFields.getSourceAsString());
}
}
6、fuzzyQuery模糊查询
模糊查询可以自动帮我们纠正写错误的英文单词,最大纠正次数两次
/**
* fuzzyQuery表示英文单词的最大可纠正次数,最大可以自动纠正两次
*/
@Test
public void fuzzyQuery(){
SearchResponse searchResponse = client.prepareSearch("indexsearch").setTypes("mysearch")
.setQuery(QueryBuilders.fuzzyQuery("say", "helOL").fuzziness(Fuzziness.TWO))
.get();
SearchHits hits = searchResponse.getHits();
SearchHit[] hits1 = hits.getHits();
for (SearchHit documentFields : hits1) {
System.out.println(documentFields.getSourceAsString());
}
client.close();
}
7、wildCardQuery通配符查询
*:匹配任意多个字符
?:仅匹配一个字符
/**
* 模糊匹配查询有两种匹配符,分别是" * " 以及 " ? ", 用" * "来匹配任何字符,包括空字符串。用" ? "来匹配任意的单个字符
*/
@Test
public void wildCardQueryTest(){
SearchResponse searchResponse = client.prepareSearch("indexsearch").setTypes("mysearch")
.setQuery(QueryBuilders.wildcardQuery("say", "hel*"))
.get();
SearchHits hits = searchResponse.getHits();
SearchHit[] hits1 = hits.getHits();
for (SearchHit documentFields : hits1) {
System.out.println(documentFields.getSourceAsString());
}
client.close();
}
8、boolQuery 多条件组合查询
使用boolQuery实现多条件组合查询
查询年龄是18到28范围内且性别是男性的,或者id范围在10到13范围内的
/**
* 多条件组合查询 boolQuery
* 查询年龄是18到28范围内且性别是男性的,或者id范围在10到13范围内的
*/
@Test
public void boolQueryTest(){
RangeQueryBuilder age = QueryBuilders.rangeQuery("age").gt(17).lt(29);
TermQueryBuilder sex = QueryBuilders.termQuery("sex", "1");
RangeQueryBuilder id = QueryBuilders.rangeQuery("id").gt("9").lt("15");
SearchResponse searchResponse = client.prepareSearch("indexsearch").setTypes("mysearch")
.setQuery(
QueryBuilders.boolQuery().should(id)
.should(QueryBuilders.boolQuery().must(sex).must(age)))
.get();
SearchHits hits = searchResponse.getHits();
SearchHit[] hits1 = hits.getHits();
for (SearchHit documentFields : hits1) {
System.out.println(documentFields.getSourceAsString());
}
client.close();
}
9、分页查询
/*分页查询
*/
@Test
public void getPageIndex(){
int pageSize = 5;
int pageNum = 2;
int startNum = (pageNum-1)*5;
SearchResponse searchResponse = client.prepareSearch("indexsearch")
.setTypes("mysearch")
.setQuery(QueryBuilders.matchAllQuery())
.addSort("id",SortOrder.ASC)
.setFrom(startNum)
.setSize(pageSize)
.get();
SearchHits hits = searchResponse.getHits();
SearchHit[] hits1 = hits.getHits();
for (SearchHit documentFields : hits1) {
System.out.println(documentFields.getSourceAsString());
}
client.close();
}
10、高亮查询
在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮
百度搜索关键字"周星驰"
ElasticSearch可以对查询出的内容中关键字部分进行标签和样式的设置,但是你需要告诉ElasticSearch使用什么标签对高亮关键字进行包裹
高亮显示代码实现
/**
* 高亮查询
*/
@Test
public void highLight(){
//设置我们的查询高亮字段
SearchRequestBuilder searchRequestBuilder = client.prepareSearch("indexsearch")
.setTypes("mysearch")
.setQuery(QueryBuilders.termQuery("say", "hello"));
//设置我们字段高亮的前缀与后缀
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("say").preTags("<font style='color:red'>").postTags("</font>");
//通过高亮来进行我们的数据查询
SearchResponse searchResponse = searchRequestBuilder.highlighter(highlightBuilder).get();
SearchHits hits = searchResponse.getHits();
System.out.println("查询出来一共"+ hits.totalHits+"条数据");
for (SearchHit hit : hits) {
//打印没有高亮显示的数据
System.out.println(hit.getSourceAsString());
System.out.println("=========================");
//打印我们经过高亮显示之后的数据
Text[] says = hit.getHighlightFields().get("say").getFragments();
for (Text say : says) {
System.out.println(say);
}
/* Map<String, HighlightField> highlightFields = hit.getHighlightFields();
System.out.println(highlightFields);*/
}
client.close();
}
5.4、更新索引
根据系统给数据生成的id来进行更新索引
/**
* 更新索引
* 根据数据id来进行更新索引
*/
@Test
public void updateIndex(){
Person guansheng = new Person(5, "宋江", 88, 0, "水泊梁山", "17666666666", "wusong@kkb.com","及时雨宋江");
client.prepareUpdate().setIndex("indexsearch").setType("mysearch").setId("5")
.setDoc(JSONObject.toJSONString(guansheng),XContentType.JSON)
.get();
client.close();
}
5.5、删除索引
1、按照id进行删除
/**
* 按照id进行删除数据
*/
@Test
public void deleteById(){
DeleteResponse deleteResponse = client.prepareDelete("indexsearch", "mysearch", "14").get();
client.close();
}
2、按照查询条件来进行删除
/**
* 按照id进行删除数据
*/
@Test
public void deleteById(){
DeleteResponse deleteResponse = client.prepareDelete("indexsearch", "mysearch", "14").get();
client.close();
}
3、删除整个索引库
/**
* 删除索引
* 删除整个索引库
*/
@Test
public void deleteIndex(){
DeleteIndexResponse indexsearch = client.admin().indices().prepareDelete("indexsearch").execute().actionGet();
client.close();
}