赞
踩
内容拷贝:Elasticsearch学习笔记_巨輪的博客-CSDN博客_elasticsearch学习笔记

老师说扩展结构不方便,我也没明白哪里不方便,很方便啊,想增加字段就增加,想删除就删除,如果哪位大佬知道麻烦底下留言指教

无法用二维表结构来表现数据的数据,比如服务器日志,无法用结构表现,通信记录,视频或者图片,维度广数据量大,数据的查询和存储的成本非常大,往往需要专业的人员的大量的统计模型进行处理,一般这种数据保存到nosql数据库中,如redis,通过key来查询我们数据

将数据的结构和内容混在一起,没有明显的区分,像xml和html,这样的文档就是半结构化数据,也是一般保存到nosql中,缺点就是查询内容不是很容易
所以对如何查询非结构化数据内容以及结构化数据准确查询是非常重要的,es就是为了解决这样的问题所产生的软件,随着5G时代的到来,海量数据充斥在我们生活当中,实时数据采集存储是我们未来计算机数据处理技术发展的方向,我们现在学习的elasticsearch在这些方面表现的非常抢眼
The Elastic Stack,包括Elasticsearch、Kibana用于展示数据的项目、Beats和Logstash采集和传输数据的项目(也称为ELK stack)
Elaticsearch,简称为 ES, ES 是一个开源的高扩展的分布式全文搜索引擎, 也可以理解为全栈搜索,比如说在博客网站中,用户在网站里写一些文章,其他用户可以根据热门词汇或者关键字等等,进行搜索,查询整个网站所有匹配的文章,并以列表的形式展现出来,传统数据库以这样的方式检索效率是非常低的,即使进行sql索引优化,效果也不会很明显,所以生产环境中,这种常规搜索方式效果比较差,是整个 ElasticStack 技术栈的核心,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。
基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的
这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程
Mysql
1.关系型数据库,顾名思义,适用于结构化数据(数据与数据之间存在强关联)的存储和查询;
2.适用于复杂的业务逻辑控制、频繁数据更改这样的场景使用;
3.需要保证数据的原子性,可认为保证多个数据同时成功存储(不存在部分存储成功,部分数据存储失败的情况)
4.需要使用者清楚的知道自己所需要查找的数据在哪个表格,并且对内部的字段参数有所了解;
5.全表全字段检索效率较低,性能消耗大;
ES(Elastic Search)
1.非关系型数据库,全文检索引擎首选,适用于数据与数据之间关联相对独立且数据基本只增加不修改的情况;
2.适用于查询所有表格的所有字段,可认为使用者只需要知道查询的关键字,但不需要知道自己需要查询的表格和字段;
3.多数据存储时,无法保证数据的原子性;
4.数据修改效率低于MYSQL,且不支持联表查询;
总结:
1、MySQL更擅长的是事务类型的操作,可以确保数据的安全和一致性;如果是有事务要求,如商品的下单支付等业务操作,无疑使用MySQL。
2、ES更擅长的是海量数据的搜索,分析和计算;如果是复杂搜索,无疑可以使用Elasticsearch。
3、两者是一个互补而不是替代的关系
官网地址:免费且开放的搜索:Elasticsearch、ELK 和 Kibana 的开发者 | Elastic
官网文档:Welcome to Elastic Docs | Elastic
Windows 版的 Elasticsearch 压缩包,解压即安装完毕,解压后的 Elasticsearch 的目录结构如下 :
| 目录 | 含义 |
|---|---|
| bin | 可执行脚本目录 |
| config | 配置目录 |
| jdk | 内置 JDK 目录 |
| lib | 类库 |
| logs | 日志目录 |
| modules | 模块目录 |
| plugins | 插件目录 |
解压后,进入 bin 文件目录,点击 elasticsearch.bat 文件启动 ES 服务 。
注意: 9300 端口为 Elasticsearch 集群间组件的通信端口, 9200 端口为浏览器访问的 http协议 RESTful 端口。
打开浏览器,输入地址: http://localhost:9200,测试返回结果
- {
- "name" : "DONGG",
- "cluster_name" : "elasticsearch",
- "cluster_uuid" : "hnFvCPn4Qt2i1eiWWGIK_g",
- "version" : {
- "number" : "7.17.3",
- "build_flavor" : "default",
- "build_type" : "zip",
- "build_hash" : "5ad023604c8d7416c9eb6c0eadb62b14e766caff",
- "build_date" : "2022-04-19T08:11:19.070913226Z",
- "build_snapshot" : false,
- "lucene_version" : "8.11.1",
- "minimum_wire_compatibility_version" : "6.8.0",
- "minimum_index_compatibility_version" : "6.0.0-beta1"
- },
- "tagline" : "You Know, for Search"
- }
互联网软件的架构原则,这个原则我们称之为rest原则,表示资源状态转换,就是说我们请求的资源是有状态的,而这些状态会根据原则改变和转换,其实我们Http协议就遵循了rest原则,比如在web中资源的唯一标识是URI,叫统一资源路径,我们可以在浏览器输入这个路径,来定位网上的资源,比如http://localhost:9200/test/test.txt,这个路径不应该包含对资源的操作的,比如增加修改,路径是不应该存在的,那rest风格当中就要我们统一遵循接口原则,统一接口就包含了受限制的预定义的操作,无论什么样的资源都应该用同样的接口对资源进行访问,这里的接口就应该符合标准的http的方法,比方说,get,post,put,delete,heade,我们的路径是对资源的定位,方法是对资源的操作,按照http的方法就会暴露我们的资源,我们接口就具有安全性和幂等性的特性,比如get和head的请求都是安全的,无论你请求多少次都不会改变服务器资源的状态,而put,delete这些请求都是幂等性的,无论对资源操作多少次结果都是一样的,后面的请求并不会对第一次请求产生更多的影响,你put插入的东西永远是相同的,post不是迷瞪行的,当我们用rest风格想es发出请求之后,es就会返回响应,返回响应的数据格式为json,es数据的发送和返回都是以json为标准格式的
Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。 为了方便大家理解,我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比
ES 里的 Index 可以看做一个库,而 Types 相当于表, Documents 则相当于表的行。这里 Types 的概念已经被逐渐弱化, Elasticsearch 6.X 中,一个 index 下已经只能包含一个type, Elasticsearch 7.X 中, Type 的概念已经被删除了
理解:在关系型数据库中索引是为了优化查询所设计的数据库对象,没有索引也能查询就是慢,而ES软件专门用于全文检索数据,所以索引是搜索引擎的关键,甚至可以说万物皆索引不为过,es为了能够做到快速准确查询,使用特殊概念进行数据的存储和查询,这个概念称之为倒排索引,我们可以根据主键查询对应信息,通过建立主键索引快速查出对应信息,这种叫正向索引,如果我们想要查询文章内容中,包含了哪些热门词汇,我们需要做模糊查询,这个查询效率很差,而且每条数据都需要遍历,而且查询内容的大小写,时态都会影响查询的准确率,如果你想查大写,数据中有小写的你查不查,算不算匹配,所以这个时候就需要换一种方式,将数据和索引关联,就需要倒排索引,通过关键字查询主键id,然后再关联文章内容,不会体现表的概念,如果模糊查询的话,他会告诉你,数据在哪张表里面,具体规则是什么,倒排索引中强调的是关键字和文档编号的关联,表的作用没有那么关键了,所以说type概念逐渐弱化,其他的概念还是跟数据库相对应的
对比关系型数据库,创建索引就等同于创建数据库,索引是必要的
在 Postman 中,向 ES 服务器发 PUT 请求 : http://127.0.0.1:9200/shopping,127.0.0.1:9200表示ES软件,shopping表示索引名称,注意请求方法
请求后,服务器返回响应:
- {
- "acknowledged": true, //表示响应成功
- "shards_acknowledged": true,
- "index": "shopping"
- }
如果你再一次发送请求,put请求具有幂等性,你只要发出同样的请求,结果是一样的,表示你刚刚创建成功了吧,你再去创建就会有问题
如果重复发 PUT 请求 : http://127.0.0.1:9200/shopping 添加索引,会返回错误信息 :
- {
- "error": {
- "root_cause": [
- {
- "type": "resource_already_exists_exception",
- "reason": "index [shopping/J0WlEhh4R7aDrfIc3AkwWQ] already exists",
- "index_uuid": "J0WlEhh4R7aDrfIc3AkwWQ",
- "index": "shopping"
- }
- ],
- "type": "resource_already_exists_exception",
- "reason": "index [shopping/J0WlEhh4R7aDrfIc3AkwWQ] already exists",
- "index_uuid": "J0WlEhh4R7aDrfIc3AkwWQ",
- "index": "shopping"
- },
- "status": 400
- }
后台日志:
[2021-04-08T13:57:06,954][INFO ][o.e.c.m.MetadataCreateIndexService] [DESKTOP-LNJQ0VF] [shopping] creating index, cause [api], templates [], shards [1]/[1], mappings []
查询指定索引信息 http://127.0.0.1:9200/shopping get请求,结果
- {
- "shopping": {
- "aliases": {},
- "mappings": {},
- "settings": {
- "index": {
- "routing": {
- "allocation": {
- "include": {
- "_tier_preference": "data_content"
- }
- }
- },
- "number_of_shards": "1",
- "provided_name": "shopping",
- "creation_date": "1658549559292",
- "number_of_replicas": "1",
- "uuid": "0FEhzMkRSeSvqp_HD1jXpg",
- "version": {
- "created": "7170399"
- }
- }
- }
- }
- }
http://127.0.0.1:9200/_cat/indices?v 查看所有的索引,?v查看详细信息,_cat查询的意思
- health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
- yellow open shopping J0WlEhh4R7aDrfIc3AkwWQ 1 1 0 0 208b 208b
在 Postman 中,向 ES 服务器发 DELETE 请求 : http://127.0.0.1:9200/shopping
返回结果如下:
- {
- "acknowledged": true
- }
再次查看所有索引,GET http://127.0.0.1:9200/_cat/indices?v,返回结果如下:成功删除,没有任何索引的相关信息
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
假设索引已经创建好了,接下来我们来创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式
在 Postman 中,向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/shopping/_doc,前面是地址和索引的名称,_doc索引中添加文档数据的意思,添加数据就表示添加文档,需要请求体,请求体JSON内容为:
- {
- "title":"小米手机",
- "category":"小米",
- "images":"http://www.gulixueyuan.com/xm.jpg",
- "price":3999.00
- }

注意,此处发送请求的方式必须为 POST,不能是 PUT,否则会发生错误
返回结果:同样的请求,多次发出,得到的_id是不同的,说明这个操作不是幂等性的,可是put操作是幂等性的,所以put请求是不行的
- {
- "_index": "shopping",//索引
- "_type": "_doc",//类型-文档
- "_id": "ANQqsHgBaKNfVnMbhZYU",//唯一标识,可以类比为 MySQL 中的主键,随机生成
- "_version": 1,//版本
- "result": "created",//结果,这里的 create 表示创建成功
- "_shards": {//
- "total": 2,//分片 - 总数
- "successful": 1,//分片 - 总数
- "failed": 0//分片 - 总数
- },
- "_seq_no": 0,
- "_primary_term": 1
- }
如果想要自定义唯一性标识,需要在创建时指定: http://127.0.0.1:9200/shopping/_doc/1001,得到的返回结果:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "_version": 1,
- "result": "created",
- "_shards": {
- "total": 2,
- "successful": 1,
- "failed": 0
- },
- "_seq_no": 1,
- "_primary_term": 1
- }
此处需要注意:如果增加数据时明确数据主键,那么请求方式也可以为 PUT
http://127.0.0.1:9200/shopping/_create/1001 也是可以的
查看文档时,需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询
在 Postman 中,向 ES 服务器发 GET 请求 : http://127.0.0.1:9200/shopping/_doc/1001
返回结果如下:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "_version": 1,
- "_seq_no": 1,
- "_primary_term": 1,
- "found": true,
- "_source": { //数据结果
- "title": "小米手机",
- "category": "小米",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 3999.00
- }
- }
查找不存在的内容,向 ES 服务器发 GET 请求 : http://127.0.0.1:9200/shopping/_doc/1
返回结果如下:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "found": false
- }
查看索引下所有id的数据,向 ES 服务器发 GET 请求 : http://127.0.0.1:9200/shopping/_search
返回结果如下:
- {
- "took": 7, //耗费事件
- "timed_out": false, //是否超时
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": { //命中结果
- "total": {
- "value": 2,
- "relation": "eq"
- },
- "max_score": 1.0,
- "hits": [
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "dRMBKoIB6nXwiib44SNN",
- "_score": 1.0,
- "_source": {
- "title": "小米手机",
- "category": "小米",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 3999.00
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "_score": 1.0,
- "_source": {
- "title": "小米手机",
- "category": "小米",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 3999.00
- }
- }
- ]
- }
- }
完全覆盖
和新增文档一样,输入相同的 URL 地址请求,如果请求体变化,会将原有的数据内容覆盖
在 Postman 中,向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/shopping/_doc/1001
请求体JSON内容为:
- {
- "title":"华为手机",
- "category":"华为",
- "images":"http://www.gulixueyuan.com/hw.jpg",
- "price":1999.00
- }
修改成功后,服务器响应结果:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "_version": 2,
- "result": "updated",
- "_shards": {
- "total": 2,
- "successful": 1,
- "failed": 0
- },
- "_seq_no": 2,
- "_primary_term": 1
- }
修改数据时,也可以只修改某一给条数据的局部信息
在 Postman 中,向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/shopping/_update/1001
请求体JSON内容为:
- {
- "doc": { //数据的意思
- "title":"华为手机", //花括号里是写想对谁进行修改
- "category":"华为"
- }
- }
返回结果如下:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "_version": 2,
- "result": "noop",
- "_shards": {
- "total": 0,
- "successful": 0,
- "failed": 0
- },
- "_seq_no": 2,
- "_primary_term": 1
- }
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_doc/1,查看修改内容:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "_version": 2,
- "_seq_no": 2,
- "_primary_term": 1,
- "found": true,
- "_source": {
- "title": "华为手机",
- "category": "华为",
- "images": "http://www.gulixueyuan.com/hw.jpg",
- "price": 1999.00
- }
- }
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。
在 Postman 中,向 ES 服务器发 DELETE 请求 : http://127.0.0.1:9200/shopping/_doc/1001
返回结果:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "_version": 3,
- "result": "deleted",
- "_shards": {
- "total": 2,
- "successful": 1,
- "failed": 0
- },
- "_seq_no": 3,
- "_primary_term": 1
- }
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_doc/1001,查看是否删除成功:
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "1001",
- "found": false
- }
查找category为小米的文档,在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search?q=category:小米,这个q就是查询的意思,返回结果如下:
我们发现我们的请求路径比较麻烦,而且中文比较容易乱码,一般会通过请求体来传递参数
- {
- "took": 37,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 1,
- "relation": "eq"
- },
- "max_score": 0.5753642,
- "hits": [
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "dRMBKoIB6nXwiib44SNN",
- "_score": 0.5753642,
- "_source": {
- "title": "小米手机",
- "category": "小米",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 3999.00
- }
- }
- ]
- }
- }
接下带JSON请求体,还是查找category为小米的文档,在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "query":{
- "match":{
- "category":"小米"
- }
- }
- }
注意如果你用小,或者米进行查询,也可以查到对应数据,因为es采用倒排索引,所有的文字都建立了索引,你用小华,作为条件进行查询,将小米和华为都能查询来,这两个都满足条件,你看是两个文字,他在底层一个一个拆解变成关键词,进行倒排索引的匹配,匹配成功就把数据查询出来
如果想要完全匹配,就需要将match改为match_phrase
全部查询
- {
- "query":{
- "match_all":{}
- }
- }
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "query":{
- "match_all":{}
- },
- "from":0, //(页码-1)*每页数量
- "size":2
- }
如果你想通过排序查出价格最高的手机,在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下
- {
- "query":{
- "match_all":{}
- },
- "sort":{
- "price":{
- "order":"desc"
- }
- }
- }
如果你想查询指定字段,在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "query":{
- "match_all":{}
- },
- "_source":["title"]
- }
同时成立
bool表示条件的意思,就是要添加多个条件,多个条件同时成立用must,多个条件用[]表示
假设想找出小米牌子,价格为3999元的。在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "query":{
- "bool":{
- "must":[{
- "match":{
- "category":"小米"
- }
- },{
- "match":{
- "price":3999.00
- }
- }]
- }
- }
- }
不同时成立
用should,表示符合哪个条件都可以
假设想找出小米和华为的牌子,价格大于2000元的手机。
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "query":{
- "bool":{
- "should":[{
- "match":{
- "category":"小米"
- }
- },{
- "match":{
- "category":"华为"
- }
- }],
- "filter":{
- "range":{
- "price":{
- "gt":2000
- }
- }
- }
- }
- }
- }
就像百度一样,搜索的关键字可以高亮显示,所谓高亮就是加上样式设定
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "query":{
- "match_phrase":{
- "category" : "为"
- }
- },
- "highlight":{
- "fields":{ // 哪些字段需要高亮
- "category":{}//<----高亮这字段
- }
- }
- }
返回结果如下:
- {
- "took": 100,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 3,
- "relation": "eq"
- },
- "max_score": 0.6931471,
- "hits": [
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "BtR6sHgBaKNfVnMbX5Y5",
- "_score": 0.6931471,
- "_source": {
- "title": "华为手机",
- "category": "华为",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- },
- "highlight": {
- "category": [
- "华<em>为</em>"//<------高亮一个为字。
- ]
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "B9R6sHgBaKNfVnMbZpZ6",
- "_score": 0.6931471,
- "_source": {
- "title": "华为手机",
- "category": "华为",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- },
- "highlight": {
- "category": [
- "华<em>为</em>"
- ]
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "CdR7sHgBaKNfVnMbsJb9",
- "_score": 0.6931471,
- "_source": {
- "title": "华为手机",
- "category": "华为",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- },
- "highlight": {
- "category": [
- "华<em>为</em>"
- ]
- }
- }
- ]
- }
- }
聚合允许使用者对 es 文档进行统计分析,类似与关系型数据库中的 group by,当然还有很多其他的聚合,例如取最大值max、平均值avg等等。接下来按price字段进行分组:
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "aggs":{//聚合操作
- "price_group":{//名称,随意起名
- "terms":{//分组
- "field":"price"//分组字段
- }
- }
- }
- }
返回结果如下:在最后能看到统计的数量
- {
- "took": 63,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 6,
- "relation": "eq"
- },
- "max_score": 1,
- "hits": [
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "ANQqsHgBaKNfVnMbhZYU",
- "_score": 1,
- "_source": {
- "title": "小米手机",
- "category": "小米",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 3999
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "A9R5sHgBaKNfVnMb25Ya",
- "_score": 1,
- "_source": {
- "title": "小米手机",
- "category": "小米",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "BNR5sHgBaKNfVnMb7pal",
- "_score": 1,
- "_source": {
- "title": "小米手机",
- "category": "小米",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "BtR6sHgBaKNfVnMbX5Y5",
- "_score": 1,
- "_source": {
- "title": "华为手机",
- "category": "华为",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "B9R6sHgBaKNfVnMbZpZ6",
- "_score": 1,
- "_source": {
- "title": "华为手机",
- "category": "华为",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- }
- },
- {
- "_index": "shopping",
- "_type": "_doc",
- "_id": "CdR7sHgBaKNfVnMbsJb9",
- "_score": 1,
- "_source": {
- "title": "华为手机",
- "category": "华为",
- "images": "http://www.gulixueyuan.com/xm.jpg",
- "price": 1999
- }
- }
- ]
- },
- "aggregations": {
- "price_group": {
- "doc_count_error_upper_bound": 0,
- "sum_other_doc_count": 0,
- "buckets": [
- {
- "key": 1999,
- "doc_count": 5
- },
- {
- "key": 3999,
- "doc_count": 1
- }
- ]
- }
- }
- }
上面返回结果会附带原始数据的。若不想要不附带原始数据的结果,在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:
- {
- "aggs":{
- "price_group":{
- "terms":{
- "field":"price"
- }
- }
- },
- "size":0
- }
返回结果如下:
- {
- "took": 60,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 6,
- "relation": "eq"
- },
- "max_score": null,
- "hits": []
- },
- "aggregations": {
- "price_group": {
- "doc_count_error_upper_bound": 0,
- "sum_other_doc_count": 0,
- "buckets": [
- {
- "key": 1999,
- "doc_count": 5
- },
- {
- "key": 3999,
- "doc_count": 1
- }
- ]
- }
- }
- }
若想对所有手机价格求平均值。
在 Postman 中,向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,附带JSON体如下:也是不带原始数据
- {
- "aggs":{
- "price_avg":{//名称,随意起名
- "avg":{//求平均
- "field":"price"
- }
- }
- },
- "size":0
- }
有了索引库,等于有了数据库中的 database。
接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构(table)。
创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。
先创建一个索引:
# PUT http://127.0.0.1:9200/user
创建映射
- # PUT http://127.0.0.1:9200/user/_mapping
-
- {
- "properties": { //告诉数据怎么规定和约束
- "name":{//索引具备哪些字段,比如名称
- "type": "text",//类型起名叫做文本,可以分词匹配的
- "index": true//这个字段可以索引查询的
- },
- "sex":{ //性别,因为我们索引是user,这个user具备的字段
- "type": "keyword",
- "index": true
- },
- "tel":{
- "type": "keyword",表示不能分词,只能完整匹配
- "index": false
- }
- }
- }
查询映射
#GET http://127.0.0.1:9200/user/_mapping
返回结果如下:
- {
- "user": {
- "mappings": {
- "properties": {
- "name": {
- "type": "text"
- },
- "sex": {
- "type": "keyword"
- },
- "tel": {
- "type": "keyword",
- "index": false
- }
- }
- }
- }
- }
增加数据
- #PUT http://127.0.0.1:9200/user/_create/1001
- {
- "name":"小米",
- "sex":"男的",
- "tel":"1111"
- }
返回结果如下:
- {
- "_index": "user",
- "_type": "_doc",
- "_id": "1001",
- "_version": 1,
- "result": "created",
- "_shards": {
- "total": 2,
- "successful": 1,
- "failed": 0
- },
- "_seq_no": 0,
- "_primary_term": 1
- }
name是可以分词的,sex是必须完全匹配,tel不能被索引,不能被查询
新建Maven工程
添加依赖:
- <dependencies>
- <dependency>
- <groupId>org.elasticsearch</groupId>
- <artifactId>elasticsearch</artifactId>
- <version>7.8.0</version>
- </dependency>
- <!-- elasticsearch 的客户端 -->
- <dependency>
- <groupId>org.elasticsearch.client</groupId>
- <artifactId>elasticsearch-rest-high-level-client</artifactId>
- <version>7.8.0</version>
- </dependency>
- <!-- elasticsearch 依赖 2.x 的 log4j -->
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-api</artifactId>
- <version>2.8.2</version>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- <version>2.8.2</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <version>2.9.9</version>
- </dependency>
- <!-- junit 单元测试 -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version>
- </dependency>
- </dependencies>
客户端连接
- public class ESTest_Client {
- public static void main(String[] args) throws IOException {
- // 创建客户端对象,高级别客户端,你需要连接服务器,需要知道ip端口号,访问方式
- RestHighLevelClient client = new RestHighLevelClient(
- RestClient.builder(new HttpHost("localhost", 9200, "http")));
- // ...
- System.out.println(client);
-
- // 关闭客户端连接
- client.close();
- }
- }
- public class ESTest_Index_Create {
- public static void main(String[] args) throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(
- RestClient.builder(new HttpHost("localhost", 9200, "http")));
- // 创建索引,先拿到索引,然后创建,第一个参数是请求对象,后面是选项
- CreateIndexRequest request = new CreateIndexRequest("user");//索引名称
- // 发完请求,就会有响应
- CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);// 默认请求配置
- // 响应的状态
- boolean acknowledged = response.isAcknowledged();
-
- client.close();
- }
- }
- public class ESTest_Index_Search {
- public static void main(String[] args) throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(
- RestClient.builder(new HttpHost("localhost", 9200, "http")));
- // 查询索引
- GetIndexRequest request = new GetIndexRequest("user");
- GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
- // 别名操作
- System.out.println(response.getAliases());
- // 结构
- System.out.println(response.getMappings());
-
- client.close();
- }
- }
运行结果:
- aliases:{user2=[]}
- mappings:{user2=org.elasticsearch.cluster.metadata.MappingMetadata@ad700514}
- settings:{user2={"index.creation_date":"1617948726976","index.number_of_replicas":"1","index.number_of_shards":"1","index.provided_name":"user2","index.uuid":"UGZ1ntcySnK6hWyP2qoVpQ","index.version.created":"7080099"}}
-
- Process finished with exit code 0
- public class DeleteIndex {
- public static void main(String[] args) throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(
- RestClient.builder(new HttpHost("localhost", 9200, "http")));
- // 删除索引 - 请求对象
- DeleteIndexRequest request = new DeleteIndexRequest("user2");
- // 发送请求,获取响应
- AcknowledgedResponse response = client.indices().delete(request,RequestOptions.DEFAULT);
- // 操作结果
- System.out.println("操作结果 : " + response.isAcknowledged());
- client.close();
- }
- }
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class User {
- private String name;
- private String sex;
- private Integer age;
- }
- public class ESTest_Doc_Insert {
- public static void main(String[] args) throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(
- RestClient.builder(new HttpHost("localhost", 9200, "http")));
-
- // 插入数据,这里需要的是index
- IndexRequest indexRequest = new IndexRequest();
- // 索引名字,索引的id
- indexRequest.index("user").id("1001");
-
- User user = new User();
- user.setName("zhangsan");
- user.setAge(30);
- user.setSex("男");
-
- // 向ES插入数据,必须将数据转换为json格式
- ObjectMapper mapper = new ObjectMapper();
- String value = mapper.writeValueAsString(user);
-
- // 放到请求体中
- indexRequest.source(value, XContentType.JSON);
-
- IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);
-
- System.out.println(response.getResult());
-
- client.close();
- }
- }
- public class UpdateDoc {
-
- public static void main(String[] args) {
- ConnectElasticsearch.connect(client -> {
- // 修改文档 - 请求对象
- UpdateRequest request = new UpdateRequest();
- // 配置修改参数
- request.index("user").id("1001");
- // 设置请求体,对数据进行修改,doc就认为是一条数据,局部修改
- request.doc(XContentType.JSON, "sex", "女");
- // 客户端发送请求,获取响应对象
- UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
- System.out.println("_index:" + response.getIndex());
- System.out.println("_id:" + response.getId());
- System.out.println("_result:" + response.getResult());
- });
- }
- }
- public class GetDoc {
-
- public static void main(String[] args) {
- ConnectElasticsearch.connect(client -> {
- //1.创建请求对象
- GetRequest request = new GetRequest().index("user").id("1001");
- //2.客户端发送请求,获取响应对象
- GetResponse response = client.get(request, RequestOptions.DEFAULT);
- 3.打印结果信息
- System.out.println("_index:" + response.getIndex());
- System.out.println("_type:" + response.getType());
- System.out.println("_id:" + response.getId());
- System.out.println("source:" + response.getSourceAsString());
- });
- }
- }
后台打印:
- _index:user
- _type:_doc
- _id:1001
- source:{"name":"zhangsan","age":30,"sex":"男"}
-
- Process finished with exit code 0
- public class DeleteDoc {
- public static void main(String[] args) {
- ConnectElasticsearch.connect(client -> {
- //创建请求对象
- DeleteRequest request = new DeleteRequest().index("user").id("1001");
- //客户端发送请求,获取响应对象
- DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
- //打印信息
- System.out.println(response.toString());
- });
- }
- }
后台打印:
- DeleteResponse[index=user,type=_doc,id=1001,version=16,result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}]
-
- Process finished with exit code 0
- public class ESTest_Doc_Insert_Batch {
- public static void main(String[] args) throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(
- RestClient.builder(new HttpHost("localhost", 9200, "http")));
-
- // 批量插入数据
-
- BulkRequest bulkRequest = new BulkRequest();
- // 将之前多个文档请求包到一块发送
-
- bulkRequest.add(new IndexRequest().index("user").id("1001").source(XContentType.JSON,"name","lisi"));
- bulkRequest.add(new IndexRequest().index("user").id("1002").source(XContentType.JSON,"name","lisi"));
- bulkRequest.add(new IndexRequest().index("user").id("1003").source(XContentType.JSON,"name","wangwu"));
-
- BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
- System.out.println(response.getTook());// 花费时间
- System.out.println(response.getItems());
-
- client.close();
- }
- }
- public class BatchDeleteDoc {
- public static void main(String[] args) {
- ConnectElasticsearch.connect(client -> {
- //创建批量删除请求对象
- BulkRequest request = new BulkRequest();
- request.add(new DeleteRequest().index("user").id("1001"));
- request.add(new DeleteRequest().index("user").id("1002"));
- request.add(new DeleteRequest().index("user").id("1003"));
- //客户端发送请求,获取响应对象
- BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
- //打印结果信息
- System.out.println("took:" + responses.getTook());
- System.out.println("items:" + responses.getItems());
- });
- }
- }
- public class ESTest_Doc_Query {
- public static void main(String[] args) throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(
- RestClient.builder(new HttpHost("localhost", 9200, "http")));
-
- // 查询索引中全部数据
- SearchRequest request = new SearchRequest();
- request.indices("user");
-
- SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
- // 查询结果
- SearchHits hits = response.getHits();
- System.out.println(hits.getTotalHits());// 总共查询条数
- System.out.println(response.getTook());// 查询时间
- for (SearchHit hit:hits) {
- System.out.println(hit.getSourceAsString());
- }
- client.close();
- }
- }
结果展示:
- 5 hits
- 6ms
- {"name":"lisi1","age":30,"sex":"男"}
- {"name":"lisi2","age":25,"sex":"女"}
- {"name":"wangwu3","age":35,"sex":"男"}
- {"name":"wangwu4","age":40,"sex":"女"}
- {"name":"wangwu5","age":20,"sex":"男"}
-
- Process finished with exit code 0
里面构造器有个查询条件

- // 查询年龄为30的数据
- SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.termQuery("age", 30));
- public class QueryDoc {
-
- public static final ElasticsearchTask SEARCH_BY_PAGING = client -> {
- // 创建搜索请求对象
- SearchRequest request = new SearchRequest();
- request.indices("user");
- // 构建查询的请求体
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
- sourceBuilder.query(QueryBuilders.matchAllQuery());
- // 分页查询
- // 当前页其实索引(第一条数据的顺序号), from
- sourceBuilder.from(0);
-
- // 每页显示多少条 size
- sourceBuilder.size(2);
- request.source(sourceBuilder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 查询匹配
- SearchHits hits = response.getHits();
- System.out.println("took:" + response.getTook());
- System.out.println("timeout:" + response.isTimedOut());
- System.out.println("total:" + hits.getTotalHits());
- System.out.println("MaxScore:" + hits.getMaxScore());
- System.out.println("hits========>>");
- for (SearchHit hit : hits) {
- //输出每条查询的结果信息
- System.out.println(hit.getSourceAsString());
- }
- System.out.println("<<========");
- };
-
- public static void main(String[] args) {
- ConnectElasticsearch.connect(SEARCH_BY_CONDITION);
- }
-
- }
- public class QueryDoc {
-
- public static final ElasticsearchTask SEARCH_WITH_ORDER = client -> {
- // 创建搜索请求对象
- SearchRequest request = new SearchRequest();
- request.indices("user");
-
- // 构建查询的请求体
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
- sourceBuilder.query(QueryBuilders.matchAllQuery());
- // 排序
- sourceBuilder.sort("age", SortOrder.ASC);
- request.source(sourceBuilder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 查询匹配
- SearchHits hits = response.getHits();
- System.out.println("took:" + response.getTook());
- System.out.println("timeout:" + response.isTimedOut());
- System.out.println("total:" + hits.getTotalHits());
- System.out.println("MaxScore:" + hits.getMaxScore());
- System.out.println("hits========>>");
- for (SearchHit hit : hits) {
- //输出每条查询的结果信息
- System.out.println(hit.getSourceAsString());
- }
- System.out.println("<<========");
- };
-
- public static void main(String[] args) {
- ConnectElasticsearch.connect(SEARCH_WITH_ORDER);
- }
只查你想要的字段,下面这个方法有排除的,和包含的


查询结果只有名称
- public class QueryDoc {
-
- public static final ElasticsearchTask SEARCH_BY_BOOL_CONDITION = client -> {
- // 创建搜索请求对象
- SearchRequest request = new SearchRequest();
- request.indices("user");
- // 构建查询的请求体
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
- BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
- // 必须包含
- boolQueryBuilder.must(QueryBuilders.matchQuery("age", "30"));
- // 一定不含
- boolQueryBuilder.mustNot(QueryBuilders.matchQuery("name", "zhangsan"));
- // 可能包含
- boolQueryBuilder.should(QueryBuilders.matchQuery("sex", "男"));
- sourceBuilder.query(boolQueryBuilder);
- request.source(sourceBuilder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 查询匹配
- SearchHits hits = response.getHits();
- System.out.println("took:" + response.getTook());
- System.out.println("timeout:" + response.isTimedOut());
- System.out.println("total:" + hits.getTotalHits());
- System.out.println("MaxScore:" + hits.getMaxScore());
- System.out.println("hits========>>");
- for (SearchHit hit : hits) {
- //输出每条查询的结果信息
- System.out.println(hit.getSourceAsString());
- }
- System.out.println("<<========");
-
- };
-
- public static void main(String[] args) {
- ConnectElasticsearch.connect(SEARCH_BY_BOOL_CONDITION);
- }
- }
- public static final ElasticsearchTask SEARCH_BY_RANGE = client -> {
- // 创建搜索请求对象
- SearchRequest request = new SearchRequest();
- request.indices("user");
- // 构建查询的请求体
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
- RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");
- // 大于等于
- //rangeQuery.gte("30");
- // 小于等于
- rangeQuery.lte("40");
- sourceBuilder.query(rangeQuery);
- request.source(sourceBuilder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 查询匹配
- SearchHits hits = response.getHits();
- System.out.println("took:" + response.getTook());
- System.out.println("timeout:" + response.isTimedOut());
- System.out.println("total:" + hits.getTotalHits());
- System.out.println("MaxScore:" + hits.getMaxScore());
- System.out.println("hits========>>");
- for (SearchHit hit : hits) {
- //输出每条查询的结果信息
- System.out.println(hit.getSourceAsString());
- }
- System.out.println("<<========");
- };
- public static final ElasticsearchTask SEARCH_BY_FUZZY_CONDITION = client -> {
- // 创建搜索请求对象
- SearchRequest request = new SearchRequest();
- request.indices("user");
- // 构建查询的请求体
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
- //后面那个是至多差一个能匹配成功就行 sourceBuilder.query(QueryBuilders.fuzzyQuery("name","wangwu").fuzziness(Fuzziness.ONE));
- request.source(sourceBuilder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 查询匹配
- SearchHits hits = response.getHits();
- System.out.println("took:" + response.getTook());
- System.out.println("timeout:" + response.isTimedOut());
- System.out.println("total:" + hits.getTotalHits());
- System.out.println("MaxScore:" + hits.getMaxScore());
- System.out.println("hits========>>");
- for (SearchHit hit : hits) {
- //输出每条查询的结果信息
- System.out.println(hit.getSourceAsString());
- }
- System.out.println("<<========");
- };
- public static final ElasticsearchTask SEARCH_WITH_HIGHLIGHT = client -> {
- // 高亮查询
- SearchRequest request = new SearchRequest().indices("user");
- //2.创建查询请求体构建器
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
- //构建查询方式:高亮查询
- TermsQueryBuilder termsQueryBuilder =
- QueryBuilders.termsQuery("name","zhangsan");
- //设置查询方式
- sourceBuilder.query(termsQueryBuilder);
- //构建高亮字段
- HighlightBuilder highlightBuilder = new HighlightBuilder();
- highlightBuilder.preTags("<font color='red'>");//设置标签前缀
- highlightBuilder.postTags("</font>");//设置标签后缀
- highlightBuilder.field("name");//设置高亮字段
- //设置高亮构建对象
- sourceBuilder.highlighter(highlightBuilder);
- //设置请求体
- request.source(sourceBuilder);
- //3.客户端发送请求,获取响应对象
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- //4.打印响应结果
- SearchHits hits = response.getHits();
- System.out.println("took::"+response.getTook());
- System.out.println("time_out::"+response.isTimedOut());
- System.out.println("total::"+hits.getTotalHits());
- System.out.println("max_score::"+hits.getMaxScore());
- System.out.println("hits::::>>");
- for (SearchHit hit : hits) {
- String sourceAsString = hit.getSourceAsString();
- System.out.println(sourceAsString);
- //打印高亮结果
- Map<String, HighlightField> highlightFields = hit.getHighlightFields();
- System.out.println(highlightFields);
- }
- System.out.println("<<::::");
- };
- public static final ElasticsearchTask SEARCH_WITH_GROUP = client -> {
- SearchRequest request = new SearchRequest().indices("user");
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
- sourceBuilder.aggregation(AggregationBuilders.terms("age_groupby").field("age"));
- //设置请求体
- request.source(sourceBuilder);
- //3.客户端发送请求,获取响应对象
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- //4.打印响应结果
- SearchHits hits = response.getHits();
- System.out.println(response);
- };
- {"took":10,"timed_out":false,"_shards":
- {"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":
- {"value":6,"relation":"eq"},"max_score":1.0,"hits":
- [{"_index":"user","_type":"_doc","_id":"1001","_score":1.0,"_source":
- {"name":"zhangsan","age":"10","sex":"女"}},
- {"_index":"user","_type":"_doc","_id":"1002","_score":1.0,"_source":
- {"name":"lisi","age":"30","sex":"女"}},
- {"_index":"user","_type":"_doc","_id":"1003","_score":1.0,"_source":
- {"name":"wangwu1","age":"40","sex":"男"}},
- {"_index":"user","_type":"_doc","_id":"1004","_score":1.0,"_source":
- {"name":"wangwu2","age":"20","sex":"女"}},
- {"_index":"user","_type":"_doc","_id":"1005","_score":1.0,"_source":
- {"name":"wangwu3","age":"50","sex":"男"}},
- {"_index":"user","_type":"_doc","_id":"1006","_score":1.0,"_source":
- {"name":"wangwu4","age":"20","sex":"男"}}]},"aggregations":{"lterms#age_groupby":
- {"doc_count_error_upper_bound":0,"sum_other_doc_count":0,"buckets":
- [{"key":20,"doc_count":2},{"key":10,"doc_count":1},{"key":30,"doc_count":1},
- {"key":40,"doc_count":1},{"key":50,"doc_count":1}]}}}
-
- Process finished with exit code 0
单台 Elasticsearch 服务器提供服务,往往都有最大的负载能力,超过这个阈值,服务器
性能就会大大降低甚至不可用,所以生产环境中,一般都是运行在指定服务器集群中。
除了负载能力,单点服务器也存在其他问题:
配置服务器集群时,集群中节点数量没有限制,大于等于 2 个节点就可以看做是集群了。一
般出于高性能及高可用方面来考虑集群中节点数量都是 3 个以上
总之,集群能提高性能,增加容错
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个的数据,并一起提供索引和搜索功能。一个 Elasticsearch 集群有一个唯一的名字标识,这个名字默认就是”elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群
集群中包含很多服务器, 一个节点就是其中的一个服务器。 作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于 Elasticsearch 集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何 Elasticsearch 节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
下载地址:Elasticsearch 7.8.0 | Elastic
- # 解压缩
- tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz -C /opt/module
- # 改名
- mv elasticsearch-7.8.0 es
因为安全问题, Elasticsearch 不允许 root 用户直接运行,所以要创建新用户,在 root 用户中创建新用户
- useradd es #新增 es 用户
- passwd es #为 es 用户设置密码
- userdel -r es #如果错了,可以删除再加
- chown -R es:es /opt/module/es #文件夹所有者
最后一步可能后面还有操作一次,如果遇见权限问题的话,这个权限修改别忘了是root
修改/opt/module/es/config/elasticsearch.yml文件
- # 加入如下配置
- cluster.name: elasticsearch #集群名称
- node.name: node-1 #单点名称
- network.host: 0.0.0.0
- http.port: 9200
- cluster.initial_master_nodes: ["node-1"] #主节点
修改/etc/security/limits.conf,修改系统配置文件
- # 在文件末尾中增加下面内容
- # 每个进程可以打开的文件数的限制
- es soft nofile 65536
- es hard nofile 65536
修改/etc/security/limits.d/20-nproc.conf
- # 在文件末尾中增加下面内容
- # 每个进程可以打开的文件数的限制
- es soft nofile 65536
- es hard nofile 65536
- # 操作系统级别对每个用户创建的进程数的限制
- * hard nproc 4096
- # 注: * 带表 Linux 所有用户名称
修改/etc/sysctl.conf
- # 在文件中增加下面内容
- # 一个进程可以拥有的 VMA(虚拟内存区域)的数量,默认值为 65536
- vm.max_map_count=655360
重新加载
sysctl -p
使用 ES 用户启动
- cd /opt/module/es/
- #启动
- bin/elasticsearch
- #后台启动
- bin/elasticsearch -d
启动时,会动态生成文件,如果文件所属用户不匹配,会发生错误,需要重新进行修改用户和用户组

切换用户
关闭防火墙
- #暂时关闭防火墙
- systemctl stop firewalld
- #永久关闭防火墙
- systemctl enable firewalld.service #打开防火墙永久性生效,重启后不会复原
- systemctl disable firewalld.service #关闭防火墙,永久性生效,重启后不会复原

启动成功
下载地址:Elasticsearch 7.8.0 | Elastic
- # 解压缩
- tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz -C /opt/module
- # 改名
- mv elasticsearch-7.8.0 es-cluster
将软件分发到其他节点: linux2, linux3

因为安全问题, Elasticsearch 不允许 root 用户直接运行,所以要创建新用户,在 root 用户中创建新用户
- useradd es #新增 es 用户
- passwd es #为 es 用户设置密码
- userdel -r es #如果错了,可以删除再加
- chown -R es:es /opt/module/es #文件夹所有者
修改/opt/module/es/config/elasticsearch.yml 文件,分发文件,这个是每个节点都得配置一遍
- # 加入如下配置
- #集群名称
- cluster.name: cluster-es
- #节点名称, 每个节点的名称不能重复
- node.name: node-1
- #ip 地址, 每个节点的地址不能重复
- network.host: linux1
- #是不是有资格主节点
- node.master: true
- node.data: true
- http.port: 9200
- # head 插件需要这打开这两个配置
- http.cors.allow-origin: "*"
- http.cors.enabled: true
- http.max_content_length: 200mb
- #es7.x 之后新增的配置,初始化一个新的集群时需要此配置来选举 master
- cluster.initial_master_nodes: ["node-1"]
- #es7.x 之后新增的配置,节点发现
- discovery.seed_hosts: ["linux1:9300","linux2:9300","linux3:9300"]
- gateway.recover_after_nodes: 2
- network.tcp.keep_alive: true
- network.tcp.no_delay: true
- transport.tcp.compress: true
- #集群内同时启动的数据任务个数,默认是 2 个
- cluster.routing.allocation.cluster_concurrent_rebalance: 16
- #添加或删除节点及负载均衡时并发恢复的线程个数,默认 4 个
- cluster.routing.allocation.node_concurrent_recoveries: 16
- #初始化数据恢复时,并发恢复线程的个数,默认 4 个
- cluster.routing.allocation.node_initial_primaries_recoveries: 16
修改/etc/security/limits.conf ,分发文件
- # 在文件末尾中增加下面内容
- es soft nofile 65536
- es hard nofile 65536
修改/etc/security/limits.d/20-nproc.conf,分发文件
- # 在文件末尾中增加下面内容
- es soft nofile 65536
- es hard nofile 65536
- \* hard nproc 4096
- \# 注: * 带表 Linux 所有用户名称
修改/etc/sysctl.conf
- # 在文件中增加下面内容
- vm.max_map_count=655360
重新加载
sysctl -p
分别在不同节点上启动 ES 软件
- cd /opt/module/es-cluster
- #启动
- bin/elasticsearch
- #后台启动
- bin/elasticsearch -d
查看当前节点

其他的改那个配置文件elasticsearch.yml,然后更改权限再启动

一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除(CRUD)的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。
能搜索的数据必须索引,这样的好处是可以提高查询速度,比如:新华字典前面的目录就是索引的意思,目录可以提高查询速度
Elasticsearch 索引的精髓:一切设计都是为了提高搜索的性能
| 版本 | Type |
|---|---|
| 5.x | 支持多种 type |
| 6.x | 只能有一种 type |
| 7.x | 默认不再支持自定义索引类型(默认类型为: _doc) |
相当于是数据表的字段,对文档数据根据不同属性进行的分类标识
mapping 是处理数据的方式和规则方面做一些限制,如:某个字段的数据类型、默认值、分析器、是否被索引等等。这些都是映射里面可以设置的,其它就是处理 ES 里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好
理解为mysql数据库分表,一张表数据容纳不下,拆分为两张表,这里就类似于kafka里面的分区
一个索引可以存储超出单个节点硬件限制的大量数据。比如,一个具有 10 亿文档数据的索引占据 1TB 的磁盘空间,而任一节点都可能没有这样大的磁盘空间。 或者单个节点处理搜索请求,响应太慢。为了解决这个问题,**Elasticsearch 提供了将索引划分成多份的能力,每一份就称之为分片。**当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上
分片很重要,主要有两方面的原因:
至于一个分片怎样分布,它的文档怎样聚合和搜索请求,是完全由 Elasticsearch 管理的,对于作为用户的你来说,这些都是透明的,无需过分关心。
被混淆的概念是,一个 Lucene 索引 我们在 Elasticsearch 称作 分片 。 一个Elasticsearch 索引 是分片的集合。 当 Elasticsearch 在索引中搜索的时候, 他发送查询到每一个属于索引的分片(Lucene 索引),然后合并每个分片的结果到一个全局的结果集
在一个网络 / 云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于
离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的, Elasticsearch 允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片(副本)。
复制分片之所以重要,有两个主要原因:
总之,每个索引可以被分成多个分片。一个索引也可以被复制 0 次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。
分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
默认情况下,Elasticsearch 中的每个索引被分片 1 个主分片和 1 个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有 1 个主分片和另外 1 个复制分片(1 个完全拷贝),这样的话每个索引总共就有 2 个分片, 我们需要根据索引需要确定分片个数。
将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。这个过程是由 master 节点完成的。

我们在包含一个空节点的集群内创建名为 users 的索引,为了演示目的,我们将分配 3个主分片和一份副本(索引分为三个主分片,每个主分片拥有一个副本分片),整个集群中只有一个节点称为单节点集群
默认情况下,主分片是1,副本是1
- #PUT http://127.0.0.1:1001/users
- {
- "settings" : {
- "number_of_shards" : 3,
- "number_of_replicas" : 1
- }
- }
-
集群现在是拥有一个索引的单节点集群。所有 3 个主分片都被分配在 node-1

通过 elasticsearch-head 插件(一个Chrome插件)查看集群情况

上面这个图片副本标灰,未分配,目前是单节点的,如果你分片数据和副本数据在一起,挂掉数据就全部丢失了,所以是黄色状态,黄色状态容易丢失数据
:3 个主分片正常。
:3 个副本分片都是 Unassigned,它们都没有被分配到任何节点。 在同 一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点 上的所有副本数据
当前集群是正常运行的,但存在丢失数据的风险
elasticsearch-head chrome插件安装
插件获取网址:GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster,下载压缩包,解压后将内容放入自定义命名为elasticsearch-head文件夹。
接着点击Chrome右上角选项->工具->管理扩展(或则地址栏输入chrome://extensions/),选择打开“开发者模式”,让后点击“加载已解压得扩展程序”,选择elasticsearch-head/_site,即可完成chrome插件安装
当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的 cluster.name 配置,它就会自动发现集群并加入到其中。但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。之所以配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群
如果启动了第二个节点,集群将会拥有两个节点 : 所有主分片和副本分片都已被分配

通过 elasticsearch-head 插件查看集群情况
上图边框加粗的为主分片,下面细的边框是备份,加星号的标识master主节点

集群的健康值为green,表示每个分片都能起作用
第二个节点加入到集群后, 3 个副本分片将会分配到这个节点上——每 个主分片对应一个副本分片。这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。所 有新近被索引的文档都将会保存在主分片上,然后被并行的复制到对应的副本分片上。这就保证了我们 既可以从主分片又可以从副本分片上获得文档
怎样为我们的正在增长中的应用程序按需扩容呢?当启动了第三个节点,我们的集群将会拥有三个节点的集群 : 为了分散负载而对分片进行重新分配
当增加一个集群节点,我们的分片会转移到不同的节点上面去,增强吞吐量
通过 elasticsearch-head 插件查看集群情况。

但是如果我们想要扩容超过 6 个节点怎么办呢?
- #PUT http://127.0.0.1:1001/users/_settings
-
- {
- "number_of_replicas" : 2
- }
通过 elasticsearch-head 插件查看集群情况:

我们关闭第一个节点,这时集群的状态为:关闭了一个节点后的集群

1001挂掉,副本用不了,集群健康值变为黄色,因为原来9个,你变为6个,少了3个分片,提供正常服务没问题,因为主分片依然存在
如果重启第一个服务器,首先要配置一下信息,能够找到其他两个服务器,加入集群

唯一的变化就是master变更,主分片变了没有事,重要的是哪个来管理
当你插入数据的时候,首先要保证主分片有数据,然后再去考虑副本,现在就是你数据放到哪个主分片里,假设放到P0,取数据的时候去哪个分片里去取呢,所以最后制定增加数据和查询数据是根据规则来,这种规则称之为路由计算

当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢?首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(数据主键id) % number_of_primary_shards(主分片数量)
routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。 routing 通过hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置。
一旦你保存成功,副本里肯定也有数据了,其实你访问主分片和副本都是可以的,你最终访问哪个这种叫做分片控制,一般是轮询,第一会访问这个,下一次访问那个,有可能当时这个节点很忙
当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点
新建、索引和删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片。

在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。这些选项很少使用,因为 Elasticsearch 已经很快,但是为了完整起见, 请参考下文:
1.consistency
2.timeout
新索引默认有1个副本分片,这意味着为满足规定数量应该需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当number_of_replicas 大于1的时候,规定数量才会执行

在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的
更新流程
部分更新一个文档结合了先前说明的读取和写入流程:

部分更新一个文档的步骤如下:
当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果 Elasticsearch 仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档
批量操作流程
mget和 bulk API的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。它将整个多文档请求分解成每个分片的多文档请求,并且将这些请求并行转发到每个参与节点
协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端

用单个 mget 请求取回多个文档所需的步骤顺序:
可以对docs数组中每个文档设置routing参数。
bulk API, 允许在单个批量请求中执行多个创建、索引、删除和更新请求。

bulk API 按如下步骤顺序执行:
分片原理
倒排索引原理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。