ElasticSearch笔记
文章目录
带着问题上路——ES是如何产生的?
(1)思考:大规模数据如何检索?
如:当系统数据量上了10亿、100亿条的时候,我们在做系统架构的时候通常会从以下角度去考虑问题:
1)用什么数据库好?(mysql、sybase、oracle、达梦、神通、mongodb、hbase…)
2)如何解决单点故障;(lvs、F5、A10、Zookeep、MQ)
3)如何保证数据安全性;(热备、冷备、异地多活)
4)如何解决检索难题;(数据库代理中间件:mysql-proxy、Cobar、MaxScale等;)
5)如何解决统计分析问题;(离线、近实时)
(2)传统数据库的应对解决方案
对于关系型数据,我们通常采用以下或类似架构去解决查询瓶颈和写入瓶颈:
解决要点:
1)通过主从备份解决数据安全性问题;
2)通过数据库代理中间件心跳监测,解决单点故障问题;
3)通过代理中间件将查询语句分发到各个slave节点进行查询,并汇总结果
(3)非关系型数据库的解决方案
对于Nosql数据库,以mongodb为例,其它原理类似:
解决要点:
1)通过副本备份保证数据安全性;
2)通过节点竞选机制解决单点问题;
3)先从配置库检索分片信息,然后将请求分发到各个节点,最后由路由节点合并汇总结果
另辟蹊径——完全把数据放入内存怎么样?
我们知道,完全把数据放在内存中是不可靠的,实际上也不太现实,当我们的数据达到PB级别时,按照每个节点96G内存计算,在内存完全装满的数据情况下,我们需要的机器是:1PB=1024T=1048576G
节点数=1048576/96=10922个
实际上,考虑到数据备份,节点数往往在2.5万台左右。成本巨大决定了其不现实!
从前面讨论我们了解到,把数据放在内存也好,不放在内存也好,都不能完完全全解决问题。
全部放在内存速度问题是解决了,但成本问题上来了。
为解决以上问题,从源头着手分析,通常会从以下方式来寻找方法:
1、存储数据时按有序存储;
2、将数据和索引分离;
3、压缩数据;
这就引出了Elasticsearch。
一、ES 基础一网打尽
1.1 ES定义
ES=elaticsearch简写, Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
1.2 Lucene与ES关系?
1)Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
2)Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
(REST -- REpresentational State Transfer,英语的直译就是“表现层状态转移”。如果看这个概念,估计没几个人能明白是什么意思。那下面就让我来用一句人话解释一下什么是RESTful: URL定位资源,用HTTP动词(GET,POST,PUT,DELETE)描述操作。
所以RESTful API就是REST风格的API。 那么在什么场景下使用RESTful API呢?在当今的互联网应用的前端展示媒介很丰富。有手机、有平板电脑还有PC以及其他的展示媒介。那么这些前端接收到的用户请求统一由一个后台来处理并返回给不同的前端肯定是最科学和最经济的方式,RESTful API就是一套协议来规范多种形式的前端和同一个后台的交互方式。
RESTful API由后台也就是SERVER来提供前端来调用。前端调用API向后台发起HTTP请求,后台响应请求将处理结果反馈给前端。
1.3 ES主要解决问题:
1)检索相关数据;
2)返回统计结果;
3)速度要快。
1.4 ES工作原理
当ElasticSearch的节点启动后,它会利用多播(multicast)(或者单播,如果用户更改了配置)寻找集群中的其它节点,并与之建立连接。这个过程如下图所示:
1.5 ES核心概念
1)Cluster:集群。
ES可以作为一个独立的单个搜索服务器。不过,为了处理大型数据集,实现容错和高可用性,ES可以运行在许多互相合作的服务器上。这些服务器的集合称为集群。
2)Node:节点。
形成集群的每个服务器称为节点。
3)Shard:分片。
当有大量的文档时,由于内存的限制、磁盘处理能力不足、无法足够快的响应客户端的请求等,一个节点可能不够。这种情况下,数据可以分为较小的分片。每个分片放到不同的服务器上。
当你查询的索引分布在多个分片上时,ES会把查询发送给每个相关的分片,并将结果组合在一起,而应用程序并不知道分片的存在。即:这个过程对用户来说是透明的。
4)Replia:副本。
为提高查询吞吐量或实现高可用性,可以使用分片副本。
副本是一个分片的精确复制,每个分片可以有零个或多个副本。ES中可以有许多相同的分片,其中之一被选择更改索引操作,这种特殊的分片称为主分片。
当主分片丢失时,如:该分片所在的数据不可用时,集群将副本提升为新的主分片。
5)全文检索。
全文检索就是对一篇文章进行索引,可以根据关键字搜索,类似于mysql里的like语句。
全文索引就是把内容根据词的意义进行分词,然后分别创建索引,例如”你们的激情是因为什么事情来的” 可能会被分词成:“你们“,”激情“,“什么事情“,”来“ 等token,这样当你搜索“你们” 或者 “激情” 都会把这句搜出来。
1.6 ES数据架构的主要概念(与关系数据库Mysql对比)
(1)关系型数据库中的数据库(DataBase),等价于ES中的索引(Index)
(2)一个数据库下面有N张表(Table),等价于1个索引Index下面有N多类型(Type),
(3)一个数据库表(Table)下的数据由多行(ROW)多列(column,属性)组成,等价于1个Type由多个文档(Document)和多Field组成。
(4)在一个关系型数据库里面,schema定义了表、每个表的字段,还有表和字段之间的关系。 与之对应的,在ES中:Mapping定义索引下的Type的字段处理规则,即索引如何建立、索引类型、是否保存原始索引JSON文档、是否压缩原始JSON文档、是否需要分词处理、如何进行分词处理等。
(5)在数据库中的增insert、删delete、改update、查search操作等价于ES中的增PUT/POST、删Delete、改_update、查GET.
倒排索引
相关性得分:1)红海特工行动 (检索) 在记录中搜索: 3- 三个词命中了2个(2/3) 5- 四个词命中2个(1/2)
所以记录 3- 的相关性得分就高!
1.7 ELK是什么?
ELK=elasticsearch+Logstash+kibana
elasticsearch:后台分布式存储以及全文检索
logstash: 日志加工、“搬运工”
kibana:数据可视化展示。
ELK架构为数据分布式存储、可视化查询和日志解析创建了一个功能强大的管理链。 三者相互配合,取长补短,共同完成分布式大数据处理工作。
1.8 ES特点和优势
1)分布式实时文件存储,可将每一个字段存入索引,使其可以被检索到。
2)实时分析的分布式搜索引擎。
分布式:索引分拆成多个分片,每个分片可有零个或多个副本。集群中的每个数据节点都可承载一个或多个分片,并且协调和处理各种操作;
负载再平衡和路由在大多数情况下自动完成。
3)可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。也可以运行在单台PC上(已测试)
4)支持插件机制,分词插件、同步插件、Hadoop插件、可视化插件等。
相较于Lucene来说,Elasticsearch天然的分布式特性,让其可以支持海量的、PB级的大数据搜索。相对于Spark Streaming、Storm等大数据实时计算引擎来说,Elasticsearch天生为分布式执行数据分析操作而生的架构,海量数据量级下的近实时(秒级)性能支持,以及无比强大的搜索和聚合分析的语法支持,让ES更加适合进行大数据场景下的数据分析应用。
二、Docker下安装ES+Kibana
注意: 如果是在服务器上操作,所有跟访问url有关的一定要记得开放端口!
//拉取es镜像
docker pull elasticsearch:7.4.2
//查看镜像
[root@iZ2zegnzd0af38r6v96pcfZ test]# docker images
elasticsearch 7.4.2 b1179d41a7b4 16 months ago 855MB
//拉取kibana镜像并查看
[root@iZ2zegnzd0af38r6v96pcfZ test]# docker pull kibana:7.4.2
[root@iZ2zegnzd0af38r6v96pcfZ test]# docker images
kibana 7.4.2 230d3ded1abc 16 months ago 1.1GB
//创建es在本地的挂载目录
[root@iZ2zegnzd0af38r6v96pcfZ test]# mkdir -p /mydata/elasticsearch/config
[root@iZ2zegnzd0af38r6v96pcfZ test]# mkdir -p /mydata/elasticsearch/data
//往本地的elasticsearch.yml中写入http.host:0.0.0.0
[root@iZ2zegnzd0af38r6v96pcfZ test]# echo "http.host:0.0.0.0">>/mydata/elasticsearch/config/elasticsearch.yml
[root@iZ2zegnzd0af38r6v96pcfZ test]# cd /mydata/elasticsearch/config
[root@iZ2zegnzd0af38r6v96pcfZ config]# ls
elasticsearch.yml
[root@iZ2zegnzd0af38r6v96pcfZ config]# cat elasticsearch.yml
http.host: 0.0.0.0 //(注:这里(: 和 0 之间)必须有空格,否则会报错)
//启动容器(挂载目录+映射端口)
[root@iZ2zegnzd0af38r6v96pcfZ config]# docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx128m" -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
f4ea47498fa0765f49c6993451806169b88329f1f952c7ea59ea8d7b61eaeb54(这个是容器的ID)
//检查一下是否启动成功
[root@iZ2zegnzd0af38r6v96pcfZ config]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
//发现并没有,于是看一下日志
[root@iZ2zegnzd0af38r6v96pcfZ config]# docker logs elasticsearch
{"type": "server", "timestamp": "2021-03-14T12:16:05,490Z", "level": "WARN", "component": "o.e.b.ElasticsearchUncaughtExceptionHandler", "cluster.name": "elasticsearch", "node.name": "f4ea47498fa0", "message": "uncaught exception in thread [main]",
"stacktrace": ["org.elasticsearch.bootstrap.StartupException: ElasticsearchException[failed to bind service]; nested: AccessDeniedException[/usr/share/elasticsearch/data/nodes];",.....
"Caused by: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes"
//因为我们把这些数据挂载到外面了,但由于用户权限的原因,访问被禁止了(我们可以查看一下)
[root@iZ2zegnzd0af38r6v96pcfZ config]# cd ..
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# ls
config data plugins
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# ll
//ll:罗列出当前文件或目录的详细信息,含有时间、读写权限、大小、时间等信息
total 12
drwxr-xr-x 2 root root 4096 Mar 14 20:17 config
drwxr-xr-x 2 root root 4096 Mar 14 19:43 data
drwxr-xr-x 2 root root 4096 Mar 14 19:59 plugins
//可以看到只有root可读可写,所以我们要给权限(elasticsearch/下的所有文件)
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# chmod -R 777 /mydata/elasticsearch/
//再查看一下
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# ll
total 12
drwxrwxrwx 2 root root 4096 Mar 14 20:17 config
drwxrwxrwx 2 root root 4096 Mar 14 19:43 data
drwxrwxrwx 2 root root 4096 Mar 14 19:59 plugins
//于是重新启动elasticsearch容器
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# docker start elasticsearch
elasticsearch
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f4ea47498fa0 elasticsearch:7.4.2 "/usr/local/bin/dock…" 25 minutes ago Up 34 seconds 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch
//这次我们可以发现过了一会儿容器也不会停止,并且可以在浏览器访问(主机地址:9200)
//开始安装kibana
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# docker run --name kibana -e ELASTICSEARCH_HOSTS=http:123.57.254.115:9200 -p 5601:5601 -d kibana:7.4.2
fd94e0dfffff8c87401f698783746876cfebb8267ab8618dcfe66c8f8875f5f4
//注: 123.57.254.115是自己的主机的ip
[root@iZ2zegnzd0af38r6v96pcfZ elasticsearch]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4673f1cb2527 kibana:7.4.2 "/usr/local/bin/dumb…" 4 seconds ago Up 3 seconds 0.0.0.0:5601->5601/tcp kibana
f4ea47498fa0 elasticsearch:7.4.2 "/usr/local/bin/dock…" 43 minutes ago Up 18 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch
//然后再访问一下kibana(主机地址:5601),成功则表示安装完成
补充的知识
- 读(read),写(write),执行r(recute)简写即为(r,w,x),亦可用数字来(4,2,1)表示
下图中文件所有者(属主)为root,所有组(属组)为root,文件名为install.log, 第一个减号“-”代表的是文件类型:
-:普通文件,d:目录文件,l:链接文件,b:设备文件,c:字符设备文件,p:管道文件
文件的权限为rw-r-r-也就是分别表示所有者(属主)有读写权限,所有组(属组)有读权限,其余人也仅有读权限。
-
docker run -e .. // -e 指定环境变量
三、初步检索
1、 _cat
GET /_cat/nodes : 查看所有节点
GET /_cat/health : 查看es健康状况
GET /_cat/master : 查看主节点
GET /_cat/indices : 查看所有索引 show databases;
2、索引一个文档(保存)
保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识
PUT/POST customer/external/1; //在customer索引下的external类型下保存1号数据
{
"name":"John Doe"
}
//返回的数据: (以 _ 开头的数据是元数据)
{
"_index": "customer", //在那个索引下
"_type": "external", //在那个类型下
"_id": "1", //数据的唯一标识
"_version": 1, //数据的版本,再发起一次就会增加
"result": "created", //第一次保存是created(创建),后面就是updated(更新)
"_shards": { //分片,集群时才考虑
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
POST新增。
如果不指定id ,会自动生成id,并且每次都是**新增**(不会变成updated)。
指定id就会修改这个数据,并新增版本号
PUT可以新增可以修改。PUT必须指定id;由于PUT需要指定id,我们一般都用来做修改操作,不指定id会报错。
3、查询文档
GET customer/external/1; //查询在customer索引下的external类型下的1号数据
//返回数据
{
"_index": "customer", //在哪个索引
"_type": "external", //在哪个类型
"_id": "1", //记录id
"_version": 2, //版本号
"_seq_no": 1, //并发控制字段,每次更新就会+1,用来做乐观锁
"_primary_term": 1, //同上,主分片重新分配
"found": true, //找到了
"_source": { //记录的具体信息
"name": "John Doe"
}
}
修改的(PUT/POST)url后面添加参数 ?if_seq_no=1&if_primary_term=1 //表示只有在满足等号条件时才会修改,否则会报409错误
(乐观锁模拟,先改的请求)
(乐观锁模拟,后改的请求)
4、更新文档
POST customer/external/1/_update; //更新在customer索引下的external类型下的1号数据
{
"doc":{
"name":"John Doew" //带_update必须加doc,且会与原来数据进行对比,若一样就什么也不做,version,seq_no都不变,且 result是noop(第一次还是created)
}
}
//或者
POST customer/external/1;
{
"name":"John Doe2" //这种和下面那种都不会与原来的数据进行对比,直接更新
}
//或者
PUT customer/external/1;
{
"name":"John Doe"
}
5、删除文档&索引
DELETE 123.57.254.115:9200/customer/external/1 //删除在customer索引下的external类型下的1号数据
//再进行GET查询
GET customer/external/1; //会报404
DELETE 123.57.254.115:9200/customer //删除索引,但是不能直接删除type(比如这里的external),否则会报405
(试图删除type(external))
6、bulk批量API
//这种形式的话就不能在postman里测试了,需要用到kibana, 注:在kibana里就不能加ip了,否则会报错
POST /customer/external/_bulk
{"index":{"_id":"1"}} //index是保存操作?
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"Jane Doe"}
//返回数据
#! Deprecation: [types removal] Specifying types in bulk requests is deprecated.
{
"took" : 318, //花费时间ms
"errors" : false,
"items" : [
{
"index" : {
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1,
"status" : 201
}
},
{
"index" : {
"_index" : "customer",
"_type" : "external",
"_id" : "2",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1,
"status" : 201
}
}
]
}
//语法格式
{action:{metadata}}\n
{request body }\n
{action:{metadata}}\n
{request body }\n
//复杂实例 (delete操作的时候不需要请求体request body)
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"}}
//返回数据
#! Deprecation: [types removal] Specifying types in bulk requests is deprecated.
{
"took" : 293,
"errors" : false,
"items" : [
{
"delete" : {
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 1,
"result" : "not_found",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1,
"status" : 404 //因为一开始还没有website,但不影响下面的操作!!!
}
},
{
"create" : {
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 2,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1,
"status" : 201
}
},
{
"index" : {
"_index" : "website",
"_type" : "blog",
"_id" : "J8NARXgBeW6BW2i1VJqP",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1,
"status" : 201
}
},
{
"update" : {
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 3,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1,
"status" : 200
}
}
]
}
https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json 导入官方测试数据
在kibana中发起 POST /bank/account/_bulk (Ctrl + Home回到最上方)
(可以看到,items里有两个,表示进行两个操作,且互相之间不影响,即第一个错第二个也会执行)
(载入es官方数据)
(查看目前es中有多少索引)
四、进阶检索
Elasticsearch: Store, Search, and Analyze(存储、检索 和 分析)
1、SearchAPI
ES 支持两种基本方式检索:
- 一种是通过使用 REST request URI 发送请求 (url + 检索参数)
- 另一种是通过使用 REST request body 来发送它们 (url + 请求体)
1)、检索信息
一切检索从_search开始
请求参数方式
GET /bank/_search | 检索bank下所有信息,包括type 和 docs |
---|---|
GET /bank/_search?q=*&sort=account_number:asc(匹配所有,按照。。的升序) | 请求参数方式检索 |
响应结果解释:
took
– how long it took Elasticsearch to run the query, in millisecondstimed_out
– whether or not the search request timed out_shards
– how many shards were searched and a breakdown of how many shards succeeded, failed, or were skipped.max_score
– the score of the most relevant document foundhits.total.value
- how many matching documents were foundhits.sort
- the document’s sort position (when not sorting by relevance score)hits._score
- the document’s relevance score (not applicable when usingmatch_all
)
url 请求体进行检索
GET /bank/_search
{
"query": { "match_all": {} }, //匹配所有
"sort": [ //可以看到这是个数组,即表示可以按照多种方式进行排序
{ "account_number": "asc" },
{"balance": "desc"}
]
}
Http客户端工具(POSTMAN),get请求不能携带请求体,我们变为post也是一样的
我们POST一个JSON风格的查询请求体到_search API
需要了解,一旦搜索的查询结果被返回, ElasticSearch就完成了这次请求,并且不会维护任何服务端的资源或者结果的cursor(游标)
2、QueryDSL (查询领域专用语言domain specific languages)
1)、基本语法格式
2)、返回部分字段
3)、match【匹配查询】
用了match之后,结果就会有max_score
基本类型(非字符串),精确匹配
字符串,全文检索 (检索所有包含mill 或者 lane的数据)
4)、match_phrase【短语匹配】
将需要匹配的值当成一个整体单词(不分词)进行检索
5)、multi_match【多字段匹配】
fields里的任一字段包含query里的mill都能检索出来
fields里的任一字段包含query里的任一字段都能检索出来,都包含了则相关性得分最高
6)、bool【复合查询】
7)、filter【结果过滤】
(score都是0)
8)、term
如果使用了keyword,则是精确匹配
(address.keyword是将后面的值作为整个关键字来查询,就是必须相等才行)
(match是数据中只要包含address后面的内容就行)
9)、aggregations【执行聚合】
e.g 1
e.g 2
e.g 3
(注: gender是字符串,要用keyword进行精确匹配)
(注:每个性别的平均工资(balanceAvg)要写在genderAgg里面 )
(注:ageBalanceAvg要和gender聚合并列写)
3、Mapping
ES在保存数据的时候会自动进行类型的检测
1)、字段类型
2)、映射
3)、新版本改动及查看映射
4)、 对映射的操作
1- 创建映射
创建索引时就可以指定mapping
2- 添加新的字段映射
3- 更新映射
4- 数据迁移
//先创建一个新的索引,同时指定好mapping (并且不需要type了,因为以后都要被废弃了)
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": { //在某个字段里写入field可以按照不同的目的来检索同一个字段,详请见下方代码块(可在官网搜fiels)
"keyword": {
"type": "keyword",
"ignore_above": 256 //表示大于该长度的字符串不会被检索或者存储
}
}
},
"state": {
"type": "keyword"
}
}
}
}
//返回结果
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "newbank"
}
PUT my-index-000001
{
"mappings": {
"properties": {
"city": {
"type": "text", //city字段本身是text类型,全文检索
"fields": { //field里套了一个keyword类型的raw,可用于精确匹配(该city.raw字段可用于排序和聚合)
"raw": {
"type": "keyword"
}
}
}
}
}
}
PUT my-index-000001/_doc/1
{
"city": "New York"
}
PUT my-index-000001/_doc/2
{
"city": "York"
}
GET my-index-000001/_search
{
"query": {
"match": {
"city": "york"
}
},
"sort": {
"city.raw": "asc"
},
"aggs": {
"Cities": {
"terms": {
"field": "city.raw"
}
}
}
}
POST _reindex //将老的index索引下account类型下的数据 迁移到 newbank索引下
{
"source": {
"index": "bank",
"type": "account"
},
"dest": {
"index": "newbank"
}
}
GET /newbank/_search
//返回结果
"hits" : [
{
"_index" : "newbank",
"_type" : "_doc", //可以看到,type变成了_doc
"_id" : "1",
"_score" : 1.0,
"_source" : {
"account_number" : 1,
"balance" : 39225,
"firstname" : "Amber",
"lastname" : "Duke",
"age" : 32,
"gender" : "M",
"address" : "880 Holmes Lane",
"employer" : "Pyrami",
"email" : "amberduke@pyrami.com",
"city" : "Brogan",
"state" : "IL"
}
}
## 不同type,老的数据可以迁移过来
4、 分词
POST _analyze //使用whitespace分词器
{
"analyzer": "whitespace",
"text": "The quick brown fox."
}
//返回结果
{
"tokens": [
{
"token": "The",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "quick",
"start_offset": 4,
"end_offset": 9,
"type": "word",
"position": 1
},
{
"token": "brown",
"start_offset": 10,
"end_offset": 15,
"type": "word",
"position": 2
},
{
"token": "fox.",
"start_offset": 16,
"end_offset": 20,
"type": "word",
"position": 3
}
]
}
会遇到的问题(中文)
(使用标准分词器)
(显然不是我们希望的分词结果,它是把一个一个字分开来的)
1)、安装ES分词器
2)、测试分词器
3)、 自定义词库
首先先要创建一个词库(采用nginx反向代理)—在nginx容器内的html下的es文件夹里写一个txt
(词库大概内容)
IKAnalyzer.cfg.xml里的内容
修改远程扩展字典
重新进行ik分词器的测试
五、ElasticSearch-Rest-Client
项目整体结构
###补充知识1
为什么不直接用JS发起请求交给ES处理?
答: 因为es是属于后台集群服务器,它的端口一般不对外暴露,暴露的话容易被别人恶意利用!!! 所以先给Java来处理比较好,并且ES官方对JS的支持度有些低。。
1、创建SpringBoot项目(勾选SpringWeb即可)
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zhuge.es-test</groupId>
<artifactId>es-serch-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-serch-springboot</name>
<description>Demo project for Spring Boot And ES</description>
<properties>
<java.version>1.8</java.version>
<!--注意:SpringBoot默认的es版本不一定跟我们导入的一样,所以要改一下-->
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入ES的rest-high-level-client-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
<!-- 导入json工具包,方便测试使用 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、进行SpringBoot集合ES所需的配置
package com.zhuge.estest.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author ZCH
* @date 2021/3/19 0019 - 下午 10:07
*
* 1、导入依赖
* 2、编写配置,给容器中注入一个 RestHighLevelClient
* 3、参照官方API操作ES
*/
@Configuration
public class ElasticSearchConfig {
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();
}
@Bean
public RestHighLevelClient restHighLevelClient(){
//下面的是拆开来的步骤
// RestClientBuilder builder = null;
// builder = RestClient.builder(new HttpHost("123.57.254.115",9200,"http"));
// RestHighLevelClient client = new RestHighLevelClient(builder);
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("123.57.254.115",9200,"http"))
//注;这里的hostname改成自己es所在的地址,本机的话就127.0.0.1,我的是放在服务器上的
);
return client;
}
}
3、进行SpringBoot对ES操作的测试
package com.zhuge.estest;
import com.alibaba.fastjson.JSON;
import com.zhuge.estest.config.ElasticSearchConfig;
import com.zhuge.estest.pojo.Account;
import com.zhuge.estest.pojo.User;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
class EsSerchSpringbootApplicationTests {
@Autowired
private RestHighLevelClient client;
/**
* 测试存储数据到ES
* <p>
* 更新也可以(index)
*/
@Test
void indexData() throws IOException {
IndexRequest indexRequest = new IndexRequest("users");
indexRequest.id("1");
//indexRequest.source("userName","zhangsan","age","18","gender","男");
//常用的方式
User user = new User();
user.setUserName("zhangsan");
user.setAge(18);
user.setGender("男");
String jsonString = JSON.toJSONString(user);
indexRequest.source(jsonString, XContentType.JSON);//要保存的内容
//执行操作
IndexResponse indexResponse = client.index(indexRequest, ElasticSearchConfig.COMMON_OPTIONS);
//提取有用的响应数据
System.out.println(indexResponse);
}
@Test
void searchData() throws IOException {
//1、创建检索请求
SearchRequest searchRequest = new SearchRequest();
//2、指定索引
searchRequest.indices("bank");
//3、指定DSL,检索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //后面写的条件都可以执行
//3.1、构造检索条件 注:那些小的各种功能的builder()一定要加到SearchSourceBuilder里!!---通过下面四种方式
// searchSourceBuilder.query();
// searchSourceBuilder.from();
// searchSourceBuilder.size();
// searchSourceBuilder.aggregation();
//3.1.1、直接匹配查询address中包含mill的数据
searchSourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
//3.1.2、按照年龄的值分布进行聚合
TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
searchSourceBuilder.aggregation(ageAgg);
//3.1.3、计算平均薪资
AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
searchSourceBuilder.aggregation(balanceAvg);
//3.1.4、在性别上创建一个汇总,并在不同性别的平均年龄上进行子汇总
TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_gender")
.field("gender.keyword");//注:term是精确匹配,不能用text类型,要加keyword
aggregation.subAggregation(AggregationBuilders.avg("average_age")
.field("age"));
searchSourceBuilder.aggregation(aggregation);
//将添加SearchSourceBuilder到SearchRequest中
System.out.println("检索条件" + searchSourceBuilder.toString());
searchRequest.source(searchSourceBuilder);
//4、执行检索
SearchResponse searchResponse = client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
//5、分析结果 searchResponse
System.out.println(searchResponse.toString());
//5.1、获取到所有查到的数据
SearchHits hits = searchResponse.getHits();//拿到返回的最大的hits(它的里面还有很多小的hits)
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
/**
* "_index": "bank",
* "_type": "account",
* "_id": "472",
* "_score": 5.4032025,
* "_source": {...
*/
String string = hit.getSourceAsString();
Account account = JSON.parseObject(string, Account.class);
System.out.println("account: " + account.toString());
}
//5.2、获取这次检索到的分析信息:
Aggregations aggregations = searchResponse.getAggregations();
//这里是获取 ageAgg聚合 的信息
Terms ageAgg1 = aggregations.get("ageAgg");
for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
System.out.println("年龄: " + keyAsString+"==>"+bucket.getDocCount());
}
//这里是获取 balanceAvg聚合 的信息
Avg balanceAvg1 = aggregations.get("balanceAvg");
System.out.println("平均薪资:" + balanceAvg1.getValue());
//这里是获取 by_gender聚合 的信息 要注意:这个聚合里有子聚合average_age
Terms byGender = aggregations.get("by_gender");
for (Terms.Bucket byGenderBucket : byGender.getBuckets()) {
String keyAsString = byGenderBucket.getKeyAsString();
System.out.println("性别: "+keyAsString+"==>"+byGenderBucket.getDocCount());
System.out.println(keyAsString+"性的平均年龄: "+"==>"+byGenderBucket.getAggregations().get("average_age"));
/**
* 这里是通过Bucket里的getAggregations来获取到子聚合average_age的
*/
}
}
@Test
void contextLoads() {
System.out.println(client);
}
}
4、一些测试时用到的实体类
注:可以在打印searchResponse.toString()后,把json复制下来,用网页上的json工具来创建实体类等
package com.zhuge.estest.pojo;
/**
* @author ZCH
* @date 2021/3/21 0021 - 下午 8:20
*/
public class User {
private String userName;
private String gender;
private Integer age;
public User() {
}
public User(String userName, String gender, Integer age) {
this.userName = userName;
this.gender = gender;
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
/**
* Copyright 2021 bejson.com
*/
package com.zhuge.estest.pojo;
/**
* Auto-generated: 2021-03-21 21:12:32
*
* @author bejson.com (i@bejson.com)
* @website http://www.bejson.com/java2pojo/
*/
public class Account {
private int account_number;
private int balance;
private String firstname;
private String lastname;
private int age;
private String gender;
private String address;
private String employer;
private String email;
private String city;
private String state;
public void setAccount_number(int account_number) {
this.account_number = account_number;
}
public int getAccount_number() {
return account_number;
}
public void setBalance(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getFirstname() {
return firstname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getLastname() {
return lastname;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
public void setEmployer(String employer) {
this.employer = employer;
}
public String getEmployer() {
return employer;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setCity(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
@Override
public String toString() {
return "Account{" +
"account_number=" + account_number +
", balance=" + balance +
", firstname='" + firstname + '\'' +
", lastname='" + lastname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", address='" + address + '\'' +
", employer='" + employer + '\'' +
", email='" + email + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
'}';
}
}
###补充知识2
测试的最后对by_gender里子聚合的信息的获取可以参照MultiBucketsAggregation类的源码
(Bucket是MultiBucketsAggregation.Bucket子类)
package org.elasticsearch.search.aggregations.bucket;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.HasAggregations;
import java.util.List;
/**
* An aggregation that returns multiple buckets
*/
public interface MultiBucketsAggregation extends Aggregation {
/**
* A bucket represents a criteria to which all documents that fall in it adhere to. It is also uniquely identified
* by a key, and can potentially hold sub-aggregations computed over all documents in it.
*/
interface Bucket extends HasAggregations, ToXContent {
/**
* @return The key associated with the bucket
*/
Object getKey();
/**
* @return The key associated with the bucket as a string
*/
String getKeyAsString();
/**
* @return The number of documents that fall within this bucket
*/
long getDocCount();
/**
* @return The sub-aggregations of this bucket 这里明确说了是获得bucket中的子聚合的方法,当然后面还得get聚合的名字
*/
@Override
Aggregations getAggregations();
}
/**
* @return The buckets of this aggregation.
*/
List<? extends Bucket> getBuckets();
}
六、附录-安装Nginx
注:先进入到要放的目录(挂载的本机位置) . 前面还有一个空格