全文搜索属于最常见的需求,开源的Elasticsearch 是目前全文搜索引擎的首选。
它可以快速地储存、搜索和分析海量数据。
一、基本名词
索引
相当于mysql的数据库
类型(6.0之后已移除)
相当于mysql的表
文档
相当于mysql表里的数据,也就是msyql中的行
属性(字段)
相当于mysql的列名
映射
相当于mysql中个字段中的数据类型
----es里面所有数据都是json格式保存的
二、安装
1、下载镜像文件
docker pull elasticsearch:7.4.2 存储和检索数据
docker pull kibana:7.4.2 可视化检索数据
2、创建实例
1). 创建所需文件夹
mkdir -p /mydata/elasticsearch/config (用于存放es配置文件)
mkdir -p /mydata/elasticsearch/data(用于存放es数据)
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
. 创建容器实例
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 --privileged=true \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2
若启动容器之后会闪退,查看日志(Caused by: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes",)需设置权限运行下列语句
chmod -R 777 /mydata/elasticsearch/
(该命令表示文件所有者有读写执行权限(4+2+1)、文件所属组有读写执行权限(4+2+1)、其他人有读写执行权限(4+2+1))
之后再启动就不闪退了
(端口9200用于发送请求的端口,端口9300用于es集群节点之间的通讯端口)
(第一个-e表示单节点启动,第二个-e设置占用内存的初始值占用64M与最大值占用256M,避免一启动就直接占满内存导致整个虚拟机卡死)
(第一个-v是将容器内的elasticsearch.yml文件与虚拟机中的elasticsearch.yml进行关联,这样以后修改虚拟机中的文件时将同步至容器内的该文件,后两个-v挂载文件,也是同理,文件夹里有变化会相应同步之容器内的该文件夹中)
三、安装kibana可视化界面
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.111.200:9200 -p 5601:5601 \
-d kibana:7.4.2
使用它直接访问网页http://192.168.111.200:5601
至此,即可开始直接在网址或者网址打开kibana中使用es了。
四、初步检索
1、_cat
GET /_cat/nodes:查看所有节点(如:http://192.168.111.200:9200/_cat/nodes)
GET /_cat/health:查看es 健康状况
GET /_cat/master:查看主节点
GET /_cat/indices:查看所有索引(相当于mysql中的show databases;)
2、索引一个文档(保存)
发送PUT请求:http://192.168.111.200:9200/customer/external/1
请求体:{"name": "John Doe"}
customer:这里是索引,相当于mysql数据库
external:这里是类型,相当于mysql的数据表
1:是标识
最后返回结果如下:
{
"_index": "customer", //(索引)
"_type": "external", //(类型)
"_id": "1", //(保存数据的id)
"_version": 1, //(版本)
"result": "created", //(最终的结果:新建)
"_shards": { //(分片)
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
当再发送同样的一次时,其中的version和result会改变
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
PUT 和POST 都可以,区别是put必须带id,而post可以不带
POST 新增。如果不指定id,会自动生成id。指定id 就会修改这个数据,并新增版本号
PUT 可以新增可以修改。PUT 必须指定id;由于PUT 需要指定id,我们一般都用来做修改
操作,不指定id 会报错。
3、查询文档
发送get请求:http://192.168.111.200:9200/customer/external/1
{
"_index": "customer", //在哪个索引
"_type": "external", //在哪个类型
"_id": "1",
"_version": 2, //版本号
"_seq_no": 1, //并发控制字段,每次更新就会+1,用来做乐观锁
"_primary_term": 1, //同上,主分片重新分配,如重启,就会变化
"found": true, //(true表示找到了这个数据)
"_source": { //(查找到的数据内容)
"name": "John Doe"
}
}
更新携带?if_seq_no=0&if_primary_term=1可以做一个乐观锁操作
发送put请求:http://192.168.111.200:9200/customer/external/1?if_seq_no=1&if_primary_term=1
若seq_no不对,则不能修改
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
再get请求查询:http://192.168.111.200:9200/customer/external/1
seq_no会更新为2,数据也会更新
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 3,
"_seq_no": 2,
"_primary_term": 1,
"found": true,
"_source": {
"name": 1
}
}
4、更新文档
除上面保存操作中的更新操作,还有如下专门末尾带_update的更新操作
发送post请求:http://192.168.111.200:9200/customer/external/1/_update
请求体中需要写如下doc的固定格式
{
"doc":{
"name": "John Doew"
}
}
返回数据如下
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 4,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
若继续发送该请求,会对比原来数据,不会像保存操作那样继续更新版本号,只是结果为noop,表示没有改变的意思
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 4,
"result": "noop",
"_shards": {
"total": 0,
"successful": 0,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
另外,两种方式都可在更新数据的同时,增加属性,如下:
{
"doc": { "name": "Jane Doe", "age": 20 }
}
即可加入age属性
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 5,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 1
}
查询之后:http://192.168.111.200:9200/customer/external/1,得到
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 5,
"_seq_no": 4,
"_primary_term": 1,
"found": true,
"_source": {
"name": "Jane Doe",
"age": 20
}
}
5、删除数据/索引
发送delete请求删除数据:http://192.168.111.200:9200/customer/external/1
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 6,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 5,
"_primary_term": 1
}
发送delete请求删除索引:http://192.168.111.200:9200/customer
{
"acknowledged": true
}
注:不能直接删除类型,只能删除索引或删除类型中的全部数据
6、批量导入bulk
在kibana上发送如下左侧请求,请求体的每两行为一个操作。
语法格式:
{ action: { metadata }}
{ request body }
{ action: { metadata }}
{ request body }
took:导入时间(ms)
errors:错误信息,false为没有错误
items:导入的数据
每条导入的记录是独立的,上一条的失败不会影响到下一条
更复杂的导入实例,举例如下:
POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123"} }
{ "doc" : {"title" : "My updated blog post"} }
五、进阶检索
1、SearchAPI
ES 支持两种基本方式检索:
1)、一个是通过使用REST request URI 发送搜索参数(uri+检索参数)
发送请求:GET bank/_search?q=*&sort=account_number:asc
_search是固定格式 ?后面为搜索条件
q=*表示查找所有的数据 sort表示排序,上面为按照account_number属性升序
返回的结果不会一下全部返回,而是分页进行返回:
2)、 另一个是通过使用REST request body 来发送它们(uri+请求体)
GET bank/_search
{
"query": { //查询条件
"match_all": {} //代表匹配所有的意思
},
"sort": [ //排序(可增加多个排序,如下,先按照account_number升序再按照balance升序
{
"account_number": "desc" //完整语法应是{"order": "desc"}
},
{
"balance": "desc"
}
]
}
2、Query DSL
1)、基本语法格式
a.一个查询语句的典型结构
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}}
GET bank/_search
{
"query": {
"match_all": {}
}
b.如果是针对某个字段,那么它的结构如下:
{
QUERY_NAME: { //对应下面的sort
FIELD_NAME: { //对应下面的account_number
ARGUMENT: VALUE, //对应下面的order: desc
ARGUMENT: VALUE,...
}}}
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0, //从第一条记录
"size": 5, //每次拿取5条记录
"sort": [
{"account_number": {"order": "desc"}}
]
}
2)、_source返回部分字段
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 2,
"_source": ["age","balance"]
}
结果为
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1000,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"balance" : 39225,
"age" : 32
}},
{
"_index" : "bank",
"_type" : "account",
"_id" : "6",
"_score" : 1.0,
"_source" : {
"balance" : 5686,
"age" : 36
}}]}}
3)、match【匹配查询】
基本类型(非字符串),精确查询
GET bank/_search
{
"query": {
"match": {
"account_number": "20" //因为数据中是数字,这里可以直接写数字不加引号
}
}
}
结果为
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "20",
"_score" : 1.0,
"_source" : {
"account_number" : 20,
"balance" : 16418,
"firstname" : "Elinor",
"lastname" : "Ratliff",
"age" : 36,
"gender" : "M",
"address" : "282 Kings Place",
"employer" : "Scentric",
"email" : "elinorratliff@scentric.com",
"city" : "Ribera",
"state" : "WA"
}}]}}
字符串,全文检索(模糊查询)
GET bank/_search
{
"query": {
"match": {
"address": "mill Lane "
}},
"_source":["address"]
}
最终查询出address 中包含mill 单词的所有记录
match 当搜索字符串类型的时候,会进行全文检索,并且每条记录有相关性得分(max_score),结果也会按照得分从高到低进行排序。
若有多个检索词时,会进行分词匹配。如(mill lane将会匹配包含mill或者lane以及两个都包含的语句)
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 5.4032025,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "970",
"_score" : 5.4032025,
"_source" : {
"address" : "990 Mill Road"
}},
{
"_index" : "bank",
"_type" : "account",
"_id" : "136",
"_score" : 5.4032025,
"_source" : {
"address" : "198 Mill Lane"
}},
{
"_index" : "bank",
"_type" : "account",
"_id" : "345",
"_score" : 5.4032025,
"_source" : {
"address" : "715 Mill Avenue"
}},
{
"_index" : "bank",
"_type" : "account",
"_id" : "472",
"_score" : 5.4032025,
"_source" : {
"address" : "288 Mill Street"
}}]}}
4)、match_phrase【短语匹配】
将需要匹配的值当成一个整体单词(不分词)进行检索
GET bank/_search
{"query": {
"match_phrase": {
"address": "mill road"}},
"_source":["address"]}
结果为
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 8.926605,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "970",
"_score" : 8.926605,
"_source" : {
"address" : "990 Mill Road"}}]}}
5)、multi_match【多字段匹配】
查询属性state 或者address 中包含mill的数据,若是查询字段是多个词,将会和match一样会进行分词查询。
GET bank/_search
{"query": {
"multi_match": {
"query": "mill",
"fields": ["state","address"]
}}}
6)、bool【复合查询】
bool :用来做复合查询
must:表示必须是指定的情况
复合语句可以合并任何其它查询语句,包括复合语句,了解这一点是很重要的。这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。
如下举例,查询结果需必须满足must里面的两个条件,才能够查询成功。
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
},
{
"match": {
"gender": "F"
}}]}},
"_source": ["address","gender"]}
结果为
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 6.1104345,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "472",
"_score" : 6.1104345,
"_source" : {
"address" : "288 Mill Street",
"gender" : "F"
}}]}}
must_not 必须不是指定的情况
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
},
{
"match": {
"gender": "F"
}
}
],
"must_not": [
{
"match": {
"age":38
}}]}},
"_source": ["address","gender","age"]
}
结果为:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 6.1104345,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "472",
"_score" : 6.1104345,
"_source" : {
"address" : "288 Mill Street",
"gender" : "F",
"age" : 32
}}]}}
should
应该达到should 列举的条件,如果达到会增加相关文档的评分,并不会改变查询的结果。
如果query 中只有should 且只有一种匹配规则,那么should 的条件就会被作为默认匹配条件而去改变查询结果。
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
},
{
"match": {
"gender": "M"
}
}
],
"must_not": [
{
"match": {
"age": 18
}
}
],
"should": [
{
"match": {
"lastname": "Wallace"
}}]}},
"_source": [
"address",
"gender",
"age",
"lastname"
]}
结果为
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 12.585751,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "970",
"_score" : 12.585751,
"_source" : {
"address" : "990 Mill Road",
"gender" : "M",
"age" : 28,
"lastname" : "Wallace"
}
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "136",
"_score" : 6.0824604,
"_source" : {
"address" : "198 Mill Lane",
"gender" : "M",
"age" : 38,
"lastname" : "Holland"
}
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "345",
"_score" : 6.0824604,
"_source" : {
"address" : "715 Mill Avenue",
"gender" : "M",
"age" : 38,
"lastname" : "Hines"
}}]}}
7)、filter【结果过滤】
用来过滤数据,只留下满足条件的数据,且不会计算相关性得分。
GET bank/_search
{
"query": {
"bool": {
"filter": {
"range": {
"balance": {
"gte": 10000,
"lte": 20000
}}}}}}
结果如下,可看到_score均为0分
{ //部分结果
"_index" : "bank",
"_type" : "account",
"_id" : "20",
"_score" : 0.0,
"_source" : {
"account_number" : 20,
"balance" : 16418,
"firstname" : "Elinor",
"lastname" : "Ratliff",
"age" : 36,
"gender" : "M",
"address" : "282 Kings Place",
"employer" : "Scentric",
"email" : "elinorratliff@scentric.com",
"city" : "Ribera",
"state" : "WA"
}
8)、term
和match 一样。匹配某个属性的值。全文检索字段用match,其他非text 字段匹配用term。它是精确匹配,不会分词查询。
GET bank/_search
{
"query": {
"bool": {
"must": [
{"term": {"age": "28"}},
{"match": {
"address": "990 Mill Road"}}]}}}
9)、aggs(aggregations执行聚合)
聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于MySql中的 GROUPBY 和SQL 聚合函数(count, in等)。
在Elasticsearch 中,您有执行搜索返回hits(命中结果),并且同时返回聚合结果,把一个响应中的所有hits(命中结果)分隔开的能力。
这是非常强大且有效的,您可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的API 来避免网络往返。
aggs:执行聚合。聚合语法如下
"aggs": {
"aggs_name 这次聚合的名字,方便展示在结果集中": {
"AGG_TYPE 聚合的类型(avg,term,terms)": {要聚合的属性:属性值,size: aggregations要显示的聚合结果数量}
},
...可写多个
}
Q:搜索address 中包含mill 的所有人的年龄分布以及平均年龄,但不显示这些人的详情。
GET bank/_search
{
"query": {
"match": {
"address": "mill"
}
},
"aggs": { //聚合固定格式,里面可以写多个聚合
"ageAgg": { //自定义的聚合操作名字
"terms": { //terms聚合:表示下面的属性有几种可能
"field": "age", //要聚合的字段
"size": 10 //返回结果中取前10个现实
}
},
"ageAvg":{
"avg": { //avg聚合:求下面属性平均值
"field": "age"
}
}
},
"size": 0 //加上这个,则下面结果中的hits里查询出来的4条数据不显示,但上面的统计信息会显示
}
结果
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 5.4032025,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "970",
"_score" : 5.4032025,
"_source" : {
"account_number" : 970,
"balance" : 19648,
"firstname" : "Forbes",
"lastname" : "Wallace",
"age" : 28,
"gender" : "M",
"address" : "990 Mill Road",
"employer" : "Pheast",
"email" : "forbeswallace@pheast.com",
"city" : "Lopezo",
"state" : "AK"
}
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "136",
"_score" : 5.4032025,
"_source" : {
"account_number" : 136,
"balance" : 45801,
"firstname" : "Winnie",
"lastname" : "Holland",
"age" : 38,
"gender" : "M",
"address" : "198 Mill Lane",
"employer" : "Neteria",
"email" : "winnieholland@neteria.com",
"city" : "Urie",
"state" : "IL"
}
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "345",
"_score" : 5.4032025,
"_source" : {
"account_number" : 345,
"balance" : 9812,
"firstname" : "Parker",
"lastname" : "Hines",
"age" : 38,
"gender" : "M",
"address" : "715 Mill Avenue",
"employer" : "Baluba",
"email" : "parkerhines@baluba.com",
"city" : "Blackgum",
"state" : "KY"
}
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "472",
"_score" : 5.4032025,
"_source" : {
"account_number" : 472,
"balance" : 25571,
"firstname" : "Lee",
"lastname" : "Long",
"age" : 32,
"gender" : "F",
"address" : "288 Mill Street",
"employer" : "Comverges",
"email" : "leelong@comverges.com",
"city" : "Movico",
"state" : "MT"
}
}
]
},
"aggregations" : {
"ageAgg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : 38,
"doc_count" : 2
},
{
"key" : 28,
"doc_count" : 1
},
{
"key" : 32,
"doc_count" : 1
}
]
},
"ageAvg" : {
"value" : 34.0
}
}
}
使用子聚合,实现更复杂的功能
Q:按照年龄聚合,并且请求这些年龄段的这些人的平均薪资
GET bank/account/_search
{
"query": {
"match_all": {}
},
"aggs": { //聚合
"age_avg": {
"terms": {
"field": "age",
"size": 2
},
"aggs": { //嵌套子聚合
"banlances_avg": {
"avg": {
"field": "balance"
}
}
}
}
},
"size": 0
}
结果如下
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1000,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"age_avg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 879,
"buckets" : [
{
"key" : 31,
"doc_count" : 61,
"banlances_avg" : {
"value" : 28312.918032786885
}
},
{
"key" : 39,
"doc_count" : 60,
"banlances_avg" : {
"value" : 25269.583333333332
}}]}}}
再复杂一步:
Q:查出所有年龄分布,并且这些年龄段中M 的平均薪资和F 的平均薪资以及这个年龄段的总体平均薪资
GET bank/account/_search
{
"query": {
"match_all": {}
},
"aggs": {
"age_agg": {
"terms": {
"field": "age",
"size": 1
},
"aggs": {
"gender_agg": {
"terms": {
"field": "gender.keyword", //keyword是精确匹配这个属性
"size": 3
},
"aggs": {
"balance_avg": {
"avg": {
"field": "balance"
}}}},
"balance_avg": {
"avg": {
"field": "balance"
}}}}},
"size": 0
}
结果如下:
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1000,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"age_agg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 939,
"buckets" : [
{
"key" : 31,
"doc_count" : 61,
"balance_avg" : {
"value" : 28312.918032786885
},
"gender_agg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "M",
"doc_count" : 35,
"balance_avg" : {
"value" : 29565.628571428573
}
},
{
"key" : "F",
"doc_count" : 26,
"balance_avg" : {
"value" : 26626.576923076922
}}]}}]}}}
更多聚合操作可见官网:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
3、Mapping映射
相当于mysql中将每个字段设置类型(String,Int,Boolean....)
1)、字段类型
核心类型 | 复合类型 |
字符串类型(string): text(可以进行全文检索,保存时分词,检索时可进行分词检索), keyword(不会进行全文搜索,只会进行精确匹配) 数字类型: long、integer、short、byte、double、float、half_float、scaled_float 日期类型(data): data 布尔类型(boolearn): boolearn 二进制类型(binary): binary | 数组(Array): 支持不针对特定的类型 对象数据类型(Object): 用于单独的JSON对象 嵌套数据类型(Nested): 用于JSON对象的数组 |
特定类型 | 地理类型 |
IP类型 ip用于描述ipv4和ipv6地址 补全类型( Completion ) completion提供自动完成提示 令牌计数类型( Token count ) token_ count 用于统计字符串中的词条数量 附件类型( attachment ) 参考mapper-attachements插件,支持将附件如Microsoft Office格式,Open Document格式,ePub, HTML 等等索引为attachment数据类型。 抽取类型( Percolator ) 接受特定领域查询语言(query-dsI) 的查询 | 地理坐标: geo_point 用于描述经纬度 地理图形: geo_shape用于描述复杂形状,如多边形 |
多字段 |
通常用于为不同目的用不同的方法索引同-一个字段。例如,string 字段可以映射为一个text字段用于全文检索,同样可以映射为一个keyword字段用于排序和聚合。 另外,你可以使用standard analyzer, english analyzer, french analyzer来索引-个text字段这就是muti-fields的目的。大多数的数据类型通过fields参数来支持muti-fields. |
2)、映射
Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和
索引的。比如,使用mapping 来定义:
哪些字符串属性应该被看做全文本属性(full text fields)。
哪些属性包含数字,日期或者地理位置。
文档中的所有属性是否都能被索引(_all 配置)。
日期的格式。
自定义映射规则来执行动态添加属性。
2.1)、创建与查看映射
创建映射,并指定索引中相应字段类型
PUT /my_index //创建索引
{
"mappings": { //创建映射规则
"properties": { //要设置的属性类型的属性写在其内
"age": { "type": "integer" }, //属性名:{字段类型}
"email": { "type": "keyword" },
"name": { "type": "text" } //
}
}
}
结果:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "my_index"
}
查看映射
GET /my_index/_mapping
2.2)、添加新的字段映射
PUT /my_index/_mapping
{
"properties": {
"employee-id": { //要添加的字段
"type": "keyword",
"index": false //为false时表示该字段,不会被检索,作为一个冗余字段存在,默认不写为true
}
}
}
结果
{"acknowledged" : true}
2.3)、更新映射
对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移
2.4)、数据迁移
先获取到原来的映射
GET /bank/_mapping
再复制原来的映射数据,复制到要创建的新索引中,进行想要的修改
PUT /newbank
{
"mappings": {
"properties": {
"account_number": {
"type": "long"
},
"address": {
"type": "text"
},
"age": {
"type": "integer"
},
"balance": {
"type": "long"
},
"city": {
"type": "keyword"
},
"email": {
"type": "keyword"
},
"employer": {
"type": "keyword"
},
"firstname": {
"type": "text"
},
"gender": {
"type": "keyword"
},
"lastname": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"state": {
"type": "keyword"
}
}
}
}
结果
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "newbank"
}
3.迁移数据
a).若索引是新版不含类型的,按下面操作
POST _reindex //固定写法
{
"source": {
"index": "twitter" //旧索引名字
},
"dest": {
"index": "new_twitter" //新索引名字
}
}
b).若索引是旧版含类型,复制数据到现在不含类型的索引,按下面操作
POST _reindex
{
"source": {
"index": "bank", //旧索引名字
"type": "account" //旧类型名字
},
"dest": {
"index": "newbank" //新索引名字
}
}
结果
#! Deprecation: [types removal] Specifying types in reindex requests is deprecated.
{
"took" : 542,
"timed_out" : false,
"total" : 1000,
"updated" : 0,
"created" : 1000,
"deleted" : 0,
"batches" : 1,
"version_conflicts" : 0,
"noops" : 0,
"retries" : {
"bulk" : 0,
"search" : 0
},
"throttled_millis" : 0,
"requests_per_second" : -1.0,
"throttled_until_millis" : 0,
"failures" : [ ]
}
4、分词
一个tokenizer(分词器)接收一个字符流,将之分割为独立的tokens(词元,通常是独立的单词),然后输出tokens 流。
例如,whitespace tokenizer 遇到空白字符时分割文本。它会将文本"Quick brown fox!" 分割为[Quick, brown, fox!]。
该tokenizer(分词器)还负责记录各个term(词条)的顺序或position 位置(用于phrase 短语和word proximity 词近邻查询),以及term(词条)所代表的原始word(单词)的start起始)和end(结束)的character offsets(字符偏移量)(用于高亮显示搜索的内容)。Elasticsearch 提供了很多内置的分词器,可以用来构建custom analyzers(自定义分词器)。
1)、简单示例
POST _analyze
{
"analyzer": "standard",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
简单示例结果
[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog's, bone ]
由于中文的也是直接分成一个一个的单字,所以需要安装新的分词器,方便更加智能的使用。
2)、安装ik分词器:
注意:不能用默认elasticsearch-plugin install xxx.zip 进行自动安装
(注意ik分词器版本一定要一致,不然es将会启动不了一直闪退)
2. 进入es 容器内部plugins 目录,创建一个ik文件夹 (docker exec -it 容器id /bin/bash)
(若创建容器时,已在虚拟机挂载了位置,则在其位置上下载也可,这里我是在/mydata/elasticsearch/plugins中)
3. unzip 下载的文件
4.删除里面的elasticsearch-analysis-ik-7.4.2.zip文件 (rm –f elasticsearch-analysis-ik-7.4.2.zip)
5.进入es容器内部查看是否安装
docker exec -it 5d916bb70f2a /bin/bash
cd bin
elasticsearch_plugin list
6.重启es容器
7.进入kibana测试
a)、ik_smart格式
POST _analyze
{
"analyzer": "ik_smart",
"text": "我是中国人"
}
结果简写
[我,是,中国人]
b)、ik_max_word格式
POST _analyze
{
"analyzer": "ik_max_word",
"text": "我是中国人"
}
结果简写
[我,是,中国人,中国,国人,]
3)、自定义词库
修改/usr/share/elasticsearch/plugins/ik/config/中的IKAnalyzer.cfg.xml
/usr/share/elasticsearch/plugins/ik/config
原来的xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "" target="_blank">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">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
修改红色下划线标记处网址:
(这里红色处的网址是用的nginx里访问的地址)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "" target="_blank">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">192.168.111.200:80/es/fenci.txt</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
五、利用Java操作ES
1、简介
第一种:
通过操控ES的9300端口,它是一个TCP端口,ES的集群之间的通信也是使用的9300端口。
但一般不使用此种方法,版本不同,其所依赖也不同。
官方也不建议使用该端口,7.x 已经不建议使用,8 以后就要废弃。
第二种:
通过9200端口操控ES。
直接给9200发送请求就行了,通常有以下几种方式。
JestClient:非官方,更新慢
RestTemplate:模拟发HTTP 请求,ES 很多操作需要自己封装,麻烦
HttpClient:同上
Elasticsearch-Rest-Client:官方RestClient,封装了ES 操作,API 层次分明,上手简单
最终选择Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)
2、正式开始整合java使用步骤:
a)、进入网址https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/index.html
b)、然后选择对应的es版本,使用下面的高阶版的,高阶与低阶相当于MyBatis-Plus与MyBatis.
c)、在idea中创建新的模块,导入依赖
由于我这里使用的是7.4.2版本的es,所以导入依赖时版本修改成了7.4.2
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
当导入这个依赖时,会自动将es的依赖导入,但这版本由于是SpringBoot自动适配导入,如下图es的版本是7.15.2,可能会跟我们使用的不一致,所以需要设置重新导入我这里使用的7.4.2。
点击SpringBoot
再点击里面的SpringBoot依赖
可以看到es默认适配的7.15.2
将其复制一份放至外面properties里,修改其版本号为7.4.2。刷新之后,点击maven中即可看到依赖已经变成了7.4.2了。
d)、在java的配置文件application.yml里配置nacos地址与服务器名
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: gulimall-search
e)、创建一个配置类
参考官方文档
由于现在没用到集群,所以builer里面只new了一个。
@Configuration
public class GulimallElasticSearchConfig {
@Bean
public RestHighLevelClient esRestClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.111.200", 9200, "http")));
//http是传输用到的协议
return client;
}
}
注意在启动类上开启服务发现与排除数据源
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallSearchApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallSearchApplication.class, args);
}}
f)、在测试类里,测试一下,如下结果即可以使用了。
d)加入请求操作类
在e步骤的配置类中再加入请求操作类,用于后续请求需要设置的一些操作
@Configuration
public class GulimallElasticSearchConfig {
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//注释这些暂时不需要的请求操作,后续根据请求需求加上
// builder.addHeader("Authorization", "Bearer " + TOKEN);
// builder.setHttpAsyncResponseConsumerFactory(
// new HttpAsyncResponseConsumerFactory
// .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
COMMON_OPTIONS = builder.build();
}
//....e处所加内容
}
3、测试功能
可参考官方文档,实现功能操作
1)、测试储存功能
下面将对象转化为json数据需要导入fastjson依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
//测试存储数据到es(也可这样直接更新数据)
@Test
public void indexData() throws IOException {
//1、new一个对象,在users索引里存储数据
IndexRequest indexRequest = new IndexRequest("users");
//2、设置id
indexRequest.id("1");
//3、传入数据
//第一种方式:
// indexRequest.source("username","zhangsan","age",18,"gender","男"); //要保存的内容
// 第二种方式(一般用这种):
User user = new User();
user.setUserName("zhangsan");
user.setAge(18);
user.setGender("男");
// 将对象转化为json数据
String jsonString = JSON.toJSONString(user);
indexRequest.source(jsonString, XContentType.JSON);//要保存的内容
// 执行保存操作,这里发送的是同步请求
IndexResponse index = restHighLevelClient.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//4、若有需要,可提取响应回来的数据
System.out.println(index);
}
//返回的index内容(这里我是加了set的数据后又运行了此,所以结果是更新)
//IndexResponse[index=users,type=_doc,id=1,version=2,result=updated,seqNo=1,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
去kibanan搜索查看
GET users/_search
//结果:
{
"took" : 658,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"age" : 18,
"gender" : "男",
"userName" : "zhangsan"
}}]}}
2)、测试复杂检索功能
利用java重写上面的语句
Q:搜索address 中包含mill 的所有人的年龄分布以及平均年龄,但不显示这些人的详情。
public void searchData() throws IOException {
//1、创建检索请求
SearchRequest searchRequest = new SearchRequest();
//1.1、填写所要检索的索引
searchRequest.indices("bank");
//1.2、创建检索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//1.2.1 构造检索条件
//1.2.1.1搜索条件
searchSourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
//1.2.1.2聚合条件
//按照年龄的值分布进行聚合并只展示两条数据
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("ageAgg").field("age").size(2);
searchSourceBuilder.aggregation(termsAggregationBuilder);
//计算平均薪资
AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
searchSourceBuilder.aggregation(balanceAvg);
//打印检索的条件
System.out.println("条件"+searchSourceBuilder.toString());
//分页条件
// searchSourceBuilder.from();
// searchSourceBuilder.size();
searchRequest.source(searchSourceBuilder);
//2、执行检索(这里使用的是同步请求)
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//3、分析结果
System.out.println("结果"+searchResponse);
//3.1、获取所有查到的数据
SearchHits hits = searchResponse.getHits(); //这里是拿到的外边的最大的hits
//获取到里边的hits,这是我们命中的记录
SearchHit[] searchHits = hits.getHits();
// "_index": "bank",
// "_type": "account",
// "_id": "970",
// "_score": 5.4032025,
// "_source":
for (SearchHit hit : searchHits) {
String sourceAsString = hit.getSourceAsString();
//将其转化成对应实体类,实体类的参数与json数据是对应的属性名
Account account = JSON.parseObject(sourceAsString, Account.class);
System.out.println("acount: "+account);
}
//3.2、获取到检索到的聚合信息
Aggregations aggregations = searchResponse.getAggregations();
//由于这个是terms聚合,可以直接转成terms
Terms aggregation = aggregations.get("ageAgg");
for (Terms.Bucket bucket : aggregation.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
System.out.println("年龄: "+keyAsString+"==》人数:"+ bucket.getDocCount());
}
Avg balanceAvg1 = aggregations.get("balanceAvg");
System.out.println("平均薪资:"+ balanceAvg1.getValue() );
//将得到的聚合信息,转化成list集合
// List<Aggregation> aggregations1 = aggregations.asList();
//
// for (Aggregation aggregation : aggregations1) {
// System.out.println("当前聚合的名字:"+aggregation.getName());
// }
//
}
本文介绍了Elasticsearch的基础知识,包括基本名词解释、安装步骤、初步检索操作、进阶检索技巧如SearchAPI和Query DSL,以及如何使用Java操作Elasticsearch。详细讲解了索引、映射、分词器的配置,以及Java客户端的整合和测试。
3160

被折叠的 条评论
为什么被折叠?



