ElasticSearch:智能搜索,分布式的搜索引擎
Elaticsearch,简称为 ES, 是一个开源的高扩展的分布式全文检索引擎,特点:
近乎实时的存储、检索数据;
扩展性好,可以扩展到上百台服务器,处理PB级别的数据;
使用 Java 开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能;
通过简单的
RESTful API
来隐藏Lucene
的复杂性,从而让全文搜索变得简单。Lucene就是一个jar包,里面包含了各种建立倒排索引的方法,java开发的时候只需要导入这个jar包就可以开发了。
Lucene不是分布式的。
ES的底层就是Lucene,ES是分布式的。(Elasticsearch 在 Lucene 的基础上进行封装,实现了分布式搜索引擎)
因为Lucene有两个难以解决的问题,
数据越大,存不下来,那我就需要多台服务器存数据,那么我的Lucene不支持分布式的,那就需要安装多个Lucene然后通过代码来合并搜索结果。这样很不好
数据要考虑安全性,一台服务器挂了,那么上面的数据不就消失了。
ES就是分布式的集群,每一个节点其实就是Lucene,当用户搜索的时候,会随机挑一台,然后这台机器自己知道数据在哪,不用我们管这些底层、
总结:搜索无处不在。通过一些关键字,给我们查询出来跟这些关键字相关的信息
全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。
全文检索的方法主要分为按字检索和按词检索两种。按字检索是指对于文章中的每一个字都建立索引,检索时将词分解为字的组合。对于各种不同的语言而言,字有不同的含义,比如英文中字与词实际上是合一的,而中文中字与词有很大分别。按词检索指对文章中的词,即语义单位建立索引,检索时按词检索,并且可以处理同义项等。英文等西方文字由于按照空白切分词,因此实现上与按字处理类似,添加同义处理也很容易。中文等东方文字则需要切分字词,以达到按词索引的目的,关于这方面的问题,是当前全文检索技术尤其是中文全文检索技术中的难点,在此不做详述。
全文搜索引擎目前主流的索引技术就是倒排索引的方式。
- 传统查询:根据 id 查询数据
- 倒排索引:根据关键词匹配分词结果,然后找到 id
文档 ID | 文档内容 |
---|---|
1 | 谷歌地图之父跳槽Facebook |
2 | 谷歌地图之父加盟Facebook |
3 | 谷歌地图创始人拉斯离开谷歌加盟Facebook |
4 | 谷歌地图之父跳槽Facebook,与Wave项目取消有关 |
5 | 谷歌地图之父拉斯加盟社交网站Facebook |
文档 ID | 文档内容 |
---|---|
1 | [谷歌] [地图] [之父] [跳槽] [Facebook] |
2 | [谷歌] [地图] [之父] [加盟] [Facebook] |
3 | [谷歌] [地图] [创始] [创始人] [拉斯] [离开] [加盟] [Facebook] |
4 | [谷歌] [地图] [之父] [跳槽] [Facebook] [与] [Wave] [项目] [取消] [有关] |
5 | [谷歌] [地图] [之父] [拉斯] [加盟] [社交] [网站] [Facebook] |
单词 ID | 单词 | 关联文档的 ID |
---|---|---|
1 | 谷歌 | 1 2 3 4 5 |
2 | 地图 | 1 2 3 4 5 |
3 | 之父 | 1 2 4 5 |
4 | 跳槽 | 1 4 |
5 | 1 2 3 4 5 | |
6 | 加盟 | 2 3 5 |
7 | 创始 | 3 |
8 | 创始人 | 3 |
9 | 拉斯 | 3 5 |
10 | 离开 | 3 |
11 | 与 | 4 |
12 | Wave | 4 |
13 | 项目 | 4 |
14 | 取消 | 4 |
15 | 有关 | 4 |
16 | 社交 | 5 |
17 | 网站 | 5 |
用户搜索关键词:拉斯
到索引记录中查询,得知“拉斯”这个词曾出现在id为3和id为5的文档中。
然后就可以根据3和5查询文档本身的数据。
另外,全文索引还会根据匹配程度进行打分,匹配程度高的记录得分高,得分高的记录在搜索结果中靠前。
比如搜索“拉斯离开谷歌”那么3号记录得分就会高。
随笔:综上所诉,ElasticSearch主要是用于将搜索内容进行分词,然后对分词后的结果查询。
下载地址
解压elasticsearch-7.8.0-windows-x86_64.zip,目录结构:
目录 | 说明 |
---|---|
bin | 可执行脚本目录 |
config | 配置目录 |
jdk | 内置jdk目录 |
lib | 类库 |
logs | 日志目录 |
modules | 模块目录 |
plugins | 插件目录 |
解压完成后进入bin目录,双击运行elasticsearch.bat
测试访问: http://localhost:9200/
注意事项一:
注意事项二:
修改config/jvm.options文件的22行23行,把2改成1,让Elasticsearch启动的时候占用1个G的内存。
-Xmx512m:设置JVM最大可用内存为512M。
-Xms512m:设置JVM初始内存为512m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
ElasticSearch 对外都是基于 RESTFul 风格提供 HTTP 服务,所以可以使用 Postman 来操作 ES。 另外,还可以使用 Kibana 这样的图形化界面客户端使操作更简单。下载地址: 下载后解压。
进入到config目录,修改kibana.yml文件(解开注释):
server.port: 5601
elasticsearch.hosts: [“http://localhost:9200”]
i18n.locale: “zh-CN”
运行kibana-7.8.0-windows-x86_64\bin\kibana.bat文件。再通过浏览器访问:
开始访问:http://127.0.0.1:5601
IKAnalyzer是一个开源的,基于Java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出 了3个大版本。最初,它是以开源项目Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。
IK分词器3.0的特性如下:
采用了特有的“正向迭代最细粒度切分算法“,具有60万字/秒的高速处理能力。
采用了多子处理器分析模式,支持:英文字母(IP地址、Email、URL)、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。
对中英联合支持不是很好,在这方面的处理比较麻烦.需再做一次查询,同时是支持个人词条的优化的词典存储,更小的内存占用。
支持用户词典扩展定义。
针对Lucene全文检索优化的查询分析器IKQueryParser;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。
GitHub仓库地址
下载地址
直接解压到plugins下,注意目录结构,解压后的zip不要放在plugins目录下
重新启动ElasticSearch
测试
在kibana中测试:
测试分词器:
l ik_max_word:会将文本做最细粒度的拆分
ik_smart:会做最粗粒度的拆分,智能拆分
POST _analyze
{
"analyzer": "ik_smart",
"text": "我是中国人"
}
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式,比如下面这条用户数据:
{ "name" : "John", "sex" : "Male", "age" : 25, "birthDate": "1990/05/01", "about" : "I love to go rock climbing", "interests": [ "sports", "music" ] }
用Mysql这样的数据库存储就会容易想到建立一张User表,有各个字段等,在ElasticSearch里这就是一个文档,当然这个文档会属于一个User的类型,各种各样的类型存在于一个索引当中。这里有一份简易的将Elasticsearch和关系型数据术语对照表:
ElasticSearch | MySQL |
---|---|
index(索引库) | Database(数据库) |
mapping(映射) | 表结构 |
Table(表) | |
document(文档) | Row(行) |
field(字段) | Column(列) |
ElasticSearch较高的版本已经废除了type这个概念。
ElasticSearch 中字符串有两种类型:
- text:分词,不能用来排序和聚合
- keyword:不分词,可以被用来检索过滤、排序和聚合
PUT /aura_index
增加一个aura_index的index库GET _cat/indices
命令查询ES中所有的index索引库GET /my_index
命令查看my_index索引DELETE /aura_index
删除一个aura_index的index库数据 | 对应的类型 |
---|---|
null | 不添加字段 |
true 或 false | boolean |
字符串 | text |
数值 | long |
小数 | float |
日期 | date |
类型 | 说明 |
---|---|
PUT | 类似于SQL中的增 |
DELETE | 类似于SQL中的删 |
POST | 类似于SQL中的改 |
GET | 类似于SQL中的查 |
PUT /索引名称/_doc/文档id
{
json 格式数据
}
注意:我们插入数据的时候,如果我们的语句中指明了index和type,如果ES里面不存在,默认帮我们自动创建
操作举例:
PUT /my_index/_doc/1
{
"title": "小米手机",
"category": "小米",
"images": "http://www.gulixueyuan.com/xm.jpg",
"price": 3999
}
语法:GET /{索引名称}/{类型}/{id} / 语法:GET /{数据库}/{表}/{id}
GET /my_index/_doc/1
使用POST来修改数据,其实使用PUT也可以实现修改数据
POST的方式进行修改数据,POST是局部更新数据,别的数据不动。PUT是全局更新
PUT /my_index/_doc/1
{
"title": "小米手机",
"category": "小米",
"images": "http://www.gulixueyuan.com/xm.jpg",
"price": 3999
}
注意:下面这种操作也是成功的,会丢数据,是全局的修改
PUT /my_index/_doc/1
{
"category": "小米"
}
语法:POST /{索引名称}/_update/{docId} { “doc”: { “属性”: “值” } }
注意:这种更新只能使用post方式。
POST /my_index/_update/1
{
"doc": {
"price": 4500
}
}
语法: DELETE /{索引名称}/{类型}/{id}
操作举例:DELETE /my_index/_doc/1
批量操作通常分成三部分:
- 请求方式部分:POST _bulk
- 元数据部分:
- 指定 bulk 动作
- 指定执行批量操作的 index、type、文档 id 等信息
- 数据部分:执行批量操作用到的数据
POST _bulk { action: { metadata }} { request body } { action: { metadata }} { request body }
总的语法规则:
- 每个 JSON 串都不能换行,只能放在同一行。
- 各个 JSON 串之间通过换行分隔,而不是逗号
- 多个操作不会因为某个操作失败而终止,返回结果会显示失败的详细原因
批量操作类型:
- create:批量创建文档。如果文档存在则返回错误
- index:如果文档不存在就创建,如果文档存在就更新
- update:更新一个文档,如果文档不存在就返回错误
- delete:删除一个文档,如果要删除的文档id不存在,就返回错误
批量保存
POST _bulk {"create": {"_index":"my_index", "_id":1}} {"emp_id":5,"emp_name":"name01","emp_age":20} {"create": {"_index":"my_index", "_id":2}} {"emp_id":6,"emp_name":"name02","emp_age":21} {"create": {"_index":"my_index", "_id":3}} {"emp_id":7,"emp_name":"name03","emp_age":22}
批量删除
POST _bulk {"delete": {"_index":"my_index", "_id":2}} {"delete": {"_index":"my_index", "_id":3}}
有了索引库,等于有了数据库中的database。
接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构(table)。创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。
语法格式:GET /索引名称/_mapping,
结果示例:
{ "my_index" : { "mappings" : { "properties" : { "category" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "id" : { "type" : "long" }, "images" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "price" : { "type" : "long" }, "title" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } } }
数据 | 对应的类型 |
---|---|
null | 字段不添加 |
true | flase |
字符串 | text |
数值 | long |
小数 | float |
日期 | date |
注意:我们通常不会单独创建 mapping,mapping 也无法脱离 index 单独存在。 所以创建 mapping 也都是在创建 index 的时候同时进行
语法格式:PUT /索引名称
{ "mappings":{ "properties":{ "字段名":{ "type":"当前字段的数据类型", "index":布尔值,表示是否为当前字段建立索引, "store":布尔值,表示当前字段是否存储, "analyzer":"当前字段使用的分词器名称", "search_analyzer":"搜索关键词使用的分词器" }, "字段名":{ …… } } } }
操作举例:
PUT /index_song { "mappings": { "properties": { "song_id":{ "type": "long", "index": true, "store": true }, "song_name":{ "type": "text", "index": true, "store": true, "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "singer":{ "type": "keyword", "index": true, "store": true }, "song_album":{ "type": "text", "index": true, "store": true, "analyzer": "ik_max_word" }, "song_lyric":{ "type": "text", "index": true, "store": false, "analyzer": "ik_max_word" }, "song_img":{ "type": "keyword", "index": false, "store": true } } } }
字段属性说明
是否分词
- 需要分词的情况:分词之后,分词结果是有意义的、会被搜索的。例如:歌曲名称
- 不分词的情况:分词之后,分词结果没有意义,不会被搜索。例如:歌曲图片地址、地名、人名
是否索引
- 需要索引的情况:要搜索这个字段,就基于它建立索引。例如:歌曲名称
- 不需要索引的情况:不会被搜索的字段,不必建立索引。例如:歌曲图片地址
是否存储
是否存储是指:该字段是否在 _source 之外再做一个额外的存储。这样对于做了存储的字段可以通过 stored_fields 方式获取。 这一点我们作为初学者不必深究,我们只需要知道:即使我们设置了 store 为 false,查询结果中 _source 这里也仍然能够显示该字段。 所以通常是没有影响的。
Query DSL概述: Domain Specific Language(领域专用语言),Elasticsearch提供了基于JSON的DSL来定义查询。
测试数据:
POST db_song/_doc { "song_name":"give me an apple", "song_singer":"tom", "song_album":"apple pie", "song_lyrics":"i want to give you an apple", "song_price":10 } POST db_song/_doc { "song_name":"happy tears", "song_singer":"jerry", "song_album":"apple farmer", "song_lyrics":"i will go to London", "song_price":20 } POST db_song/_doc { "song_name":"tears of happy", "song_singer":"jerry", "song_album":"apple farmer", "song_lyrics":"i will go to NewYork", "song_price":30 } POST db_song/_doc { "song_name":"fly me", "song_singer":"bigger one", "song_album":"two star", "song_lyrics":"i put an apple on your table", "song_price":40 }
query下match_all
# DSL:查询全部
# 从DSL语法角度来说,第一行是请求方式和请求地址,第一行下面是一个JSON格式的请求体
GET /db_song/_search
{
"query": {
"match_all": {}
}
}
query下match
# DSL:关键词匹配一个字段查询
GET /db_song/_search
{
"query": {
"match": {
"song_name": "apple"
}
}
}
# 补充条件删除
POST /db_song/_delete_by_query
{
"query": {
"match": {
"song_name": "apple"
}
}
}
query下multi_match
# DSL:关键词匹配多个字段
# 被命中的document的字段中,和关键词匹配的越多,在搜索结果中得分越高,说明匹配度越高
GET /db_song/_search
{
"query": {
"multi_match": {
"query": "apple",
"fields": [
"song_name",
"song_album",
"song_singer",
"song_lyrics"
]
}
}
}
query下prefix
# DSL查询:前缀匹配
# 分词结果以指定字符串开头的能匹配上
GET /db_song/_search
{
"query": {
"prefix": {
"song_name": {
"value": "app"
}
}
}
}
query下term
# DSL查询:关键词不分词
# 原始数据:happy tears
# 原始数据分词结果:[happy][tears]
# 匹配方式:用happy tears和分词结果匹配,发现它和happy、tears都匹配不上
GET /db_song/_search
{
"query": {
"term": {
"song_name": {
"value": "happy tears"
}
}
}
}
query下terms
# DSL查询:多个不分词的关键词
GET /db_song/_search
{
"query": {
"terms": {
"song_name": [
"happy",
"tears"
]
}
}
}
query下range
# DSL查询:范围查询
# gte: 大于等于,greater than equal
# lte: 小于等于,less than equal
# gt: 大于,greater than
# lt: 小于,less than
GET /db_song/_search
{
"query": {
"range": {
"song_price": {
"gte": 20,
"lte": 30
}
}
}
}
bool 是 boolean 的缩写。表示布尔值的意思。
多个查询的组合,组合方式包括:
# DSL查询:bool查询
# bool查询中的must
# must的值是一个数组,数组中每一个对象是一个查询条件,各个条件之间是“且”的关系
GET /db_song/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"song_name": "apple"
}
},
{
"match": {
"song_lyrics": "want"
}
}
]
}
}
}
# should的值是一个数组,数组中每一个对象是一个查询条件,各个条件之间是“或”的关系
GET /db_song/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"song_name": "apple"
}
},
{
"match": {
"song_lyrics": "London"
}
}
]
}
}
}
# must_not:各个条件取反,取反之后是“且”的关系
GET /db_song/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"song_name": "apple"
}
},
{
"match": {
"song_lyrics": "London"
}
}
]
}
}
}
# DSL查询:filter相当于不计算得分的must
GET /db_song/_search
{
"query": {
"bool": {
"filter": [
{
"match": {
"song_name":"apple"
}
},
{
"match": {
"song_lyrics":"want"
}
}
]
}
}
}
# 从业务的角度也可以理解为:在前面查询结果的基础上,再进一步过滤
# DSL查询:组合各种语法
GET /db_song/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"song_name": "apple"
}
}
],
"filter": [
{
"range": {
"song_price": {
"gte": 10,
"lte": 50
}
}
}
]
}
}
}
本质上就是分组,相当于 SQL 中的 group by。
注意:只有不分词的字段才能聚合,text类型的字段无法参与聚合!
PUT db_hr
{
"mappings": {
"properties": {
"department":{
"type": "keyword"
},
"subject":{
"type": "keyword"
}
}
}
}
# _bulk 表示批量导入数据
POST db_hr/_bulk
{ "index": {}}
{ "emp_name" : "nicole adler", "emp_age" : 20, "emp_salary":1000.00, "department":"hr","subject":"hr worker","birthday":"1995-10-12"}
{ "index": {}}
{ "emp_name" : "jason alex", "emp_age" : 21, "emp_salary":1100.00, "department":"hr","subject":"hr worker","birthday":"1992-03-07"}
{ "index": {}}
{ "emp_name" : "tom green", "emp_age" : 22, "emp_salary":1200.00, "department":"hr","subject":"hr manager","birthday":"1985-11-17"}
{ "index": {}}
{ "emp_name" : "jeff hare", "emp_age" : 23, "emp_salary":1300.00, "department":"hr","subject":"hr manager","birthday":"1999-10-14"}
{ "index": {}}
{ "emp_name" : "robert hill", "emp_age" : 24, "emp_salary":1400.00, "department":"hr","subject":"hr manager","birthday":"1997-10-11"}
{ "index": {}}
{ "emp_name" : "belinda arge", "emp_age" : 30, "emp_salary":1500.00, "department":"rd","subject":"java","birthday":"1992-05-08"}
{ "index": {}}
{ "emp_name" : "amy", "emp_age" : 31, "emp_salary":2100.00, "department":"rd","subject":"java","birthday":"2014-10-13"}
{ "index": {}}
{ "emp_name" : "hoffman", "emp_age" : 32, "emp_salary":5500.00, "department":"rd","subject":"java","birthday":"1998-02-01"}
{ "index": {}}
{ "emp_name" : "joyce dabney", "emp_age" : 25, "emp_salary":2800.00, "department":"rd","subject":"php","birthday":"1990-01-01"}
{ "index": {}}
{ "emp_name" : "rae de mesa", "emp_age" : 36, "emp_salary":7500.00, "department":"rd","subject":"php","birthday":"1983-07-06"}
{ "index": {}}
{ "emp_name" : "lisa", "emp_age" : 45, "emp_salary":15100.00, "department":"fi","subject":"fi worker","birthday":"1996-06-06"}
{ "index": {}}
{ "emp_name" : "kim deziel", "emp_age" : 55, "emp_salary":25100.00, "department":"fi","subject":"fi worker","birthday":"1991-09-11"}
{ "index": {}}
{ "emp_name" : "tina-louise foster", "emp_age" : 65, "emp_salary":35100.00, "department":"fi","subject":"fi manager","birthday":"1997-07-07"}
{ "index": {}}
{ "emp_name" : "brennan foti", "emp_age" : 75, "emp_salary":45100.00, "department":"fi","subject":"fi manager","birthday":"1998-08-15"}
DSL 语句:
GET db_hr/_search
{
"aggs": {
"agg_hello_world": {
"terms": {
"field": "department"
}
}
}
}
节选查询结果中的聚合部分:
"aggregations" : {
"agg_hello_world" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "hr",
"doc_count" : 5
},
{
"key" : "rd",
"doc_count" : 5
},
{
"key" : "fi",
"doc_count" : 4
}
]
}
}
结果说明:
- buckets:聚合执行分组后得到的各个组
- key:聚合字段在当前组内的值
- doc_count:在当前组内包含document的数量
# 并列的聚合:各聚各的,互不影响
GET /db_hr/_search
{
"aggs": {
"agg_dept": {
"terms": {
"field": "department"
}
},
"agg_subject": {
"terms": {
"field": "subject"
}
}
}
}
# 嵌套的聚合:组内再分组
GET /db_hr/_search
{
"aggs": {
"agg_dept": {
"terms": {
"field": "department"
},
"aggs": {
"agg_subject": {
"terms": {
"field": "subject"
}
}
}
}
}
}
# 计算每个部门平均工资
GET /db_hr/_search
{
"aggs": {
"agg_dept": {
"terms": {
"field": "department"
},
"aggs": {
"avg_salary": {
"avg": {
"field": "emp_salary"
}
}
}
}
}
}
# 按照各种方式统计
GET /db_hr/_search
{
"aggs": {
"agg_dept": {
"terms": {
"field": "department"
},
"aggs": {
"agg_stats": {
"stats": {
"field": "emp_salary"
}
}
}
}
}
}
# 指定查询结果中显示哪些字段
GET /db_hr/_search
{
"query": {
"match": {
"emp_name": "adler"
}
},
"_source": [
"emp_name",
"emp_age",
"emp_salary"
]
}
# 高亮效果
# 默认效果:围绕关键词加em标签
GET /db_hr/_search
{
"query": {
"match": {
"emp_name": "adler"
}
},
"highlight": {
"fields": {
"emp_name": {}
}
}
}
# 指定高亮效果
GET /db_hr/_search
{
"query": {
"match": {
"emp_name": "adler"
}
},
"highlight": {
"fields": {
"emp_name": {
"pre_tags": "",
"post_tags": ""
}
}
}
}
使用 from、size 指定分页参数。
计算 from 值的公式:(pageNo-1)*pageSize
适用场景:页面显示分页数据。因为from、size其实是先把所有查询结果取出再根据from、size截取,所以不适合大数据量。
# 分页:from、size 分页
GET /db_hr/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5
}
m表示分钟,s表示秒,执行滚动分页后,scroll_id会被缓存指定的时间。
适用场景:大数据量分批取出。
GET db_hr/_search?scroll=5m
{
"size": 1
}
GET /_search/scroll
{
"scroll":"5m", "scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFGMzVEpnNFlCaWd0WVJkT1BoZFloAAAAAAAAIuAWVHpwNVBiaTBUMXFzdlBqQWd5N1lHUQ=="
}
# 排序
# FIELD 位置:指定排序字段
# order 后面:排序方向
# 多字段排序:先根据第一个字段排序,在第一个字段值重复的范围内再根据第二个字段排序
GET /db_hr/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"emp_salary": {
"order": "desc"
}
},
{
"birthday": {
"order": "asc"
}
}
]
}
GET /db_hr/_search
{
"query": {
"fuzzy": {
"emp_name": {
"value": "ader",
"fuzziness": "auto:2,5"
}
}
}
}
fuzziness格式的说明:
本次查询允许的最大编辑距离,默认不开启模糊查询,相当于fuzziness=0。
支持的格式:
可以是数字(0、1、2)代表固定的最大编辑距离
自动模式,AUTO:[low],[high]的格式,含义为:
- 查询词长度在[0-low)范围内编辑距离为0(即强匹配)
- [low, high)范围内允许编辑一次
- >high允许编辑2次
- 也可以只写AUTO代表默认的自动模式,相当于AUTO:3,6
highlevelclient 是 高级客户端 需要通过它去操作 Elasticsearch , 它底层也是要依赖 rest-client 低级客户端
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.6.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.elasticsearch.clientgroupId>
<artifactId>elasticsearch-rest-high-level-clientartifactId>
<version>7.8.0version>
dependency>
<dependency>
<groupId>org.elasticsearch.clientgroupId>
<artifactId>elasticsearch-rest-clientartifactId>
<version>7.8.0version>
dependency>
<dependency>
<groupId>org.elasticsearchgroupId>
<artifactId>elasticsearchartifactId>
<version>7.8.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.9version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
application.properties
elasticsearch.host=localhost
elasticsearch.port=9200
不需要加额外注解,就是一个普通的主启动类
package com.song.es.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig {
private String host;
private Integer port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient restHighLevelClient =
new RestHighLevelClient(RestClient.builder(new HttpHost(host,port,"http")));
return restHighLevelClient;
}
}
package com.song.es.test;
import org.junit.jupiter.api.Test;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ElasticSearchTest {
@Autowired
private RestHighLevelClient restHighLevelClient;
}
CreateIndexRequest导包时使用:org.elasticsearch.client.indices.CreateIndexRequest
@Test
public void createIndex() throws IOException {
CreateIndexRequest createIndexRequest = new CreateIndexRequest(INDEX_NAME);
// CreateIndexRequest createIndexRequest = new CreateIndexRequest(INDEX_NAME);该方法是创建一个ES中的索引,在mysql中则是创建一个数据库,INDEX_NAME则是名称可设置在外面例如private final String INDEX_NAME = "test_es";注意不可以有大写
String mappingJSON = "{\n" +
" \"properties\": {\n" +
" \"name\": {\n" +
" \"type\": \"keyword\",\n" +
" \"index\": true,\n" +
" \"store\": true\n" +
" },\n" +
" \"age\": {\n" +
" \"type\": \"integer\",\n" +
" \"index\": true,\n" +
" \"store\": true\n" +
" },\n" +
" \"remark\": {\n" +
" \"type\": \"text\",\n" +
" \"index\": true,\n" +
" \"store\": true,\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"search_analyzer\": \"ik_smart\"\n" +
" }\n" +
" }\n" +
" }";
// 现在插件网址里将创建的语句先写好,再转为字符串并在idea中添加转义字符
createIndexRequest.mapping(mappingJSON, XContentType.JSON);
// ES中的映射,在这的意思即将内容映射上去
CreateIndexResponse createIndexResponse =
restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
boolean createIndexResult = createIndexResponse.isAcknowledged();
System.out.println("createIndexResult = " + createIndexResult);
}
GetIndexRequest导包时使用:
import org.elasticsearch.client.indices.GetIndexRequest;
@Test
public void getIndexTest() throws IOException {
GetIndexRequest getIndexRequest = new GetIndexRequest(INDEX_NAME);
GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
Map<String, MappingMetadata> mappings = getIndexResponse.getMappings();
Set<String> mappingsKeySet = mappings.keySet();
for (String key : mappingsKeySet) {
MappingMetadata mappingMetadata = mappings.get(key);
System.out.println("key = " + key);
System.out.println("mappingMetadata = " + mappingMetadata.sourceAsMap());
}
Map<String, Settings> settings = getIndexResponse.getSettings();
Set<String> settingsKeySet = settings.keySet();
for (String key : settingsKeySet) {
Settings setting = settings.get(key);
System.out.println("key = " + key);
System.out.println("setting = " + setting);
}
}
@Test
public void testDelIndex() throws IOException {
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(INDEX_NAME);
AcknowledgedResponse response =
restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
boolean deleteResult = response.isAcknowledged();
System.out.println("deleteResult = " + deleteResult);
}
@Test
public void testSaveDoc() throws IOException {
// 获取到索引(数据库)
IndexRequest indexRequest = new IndexRequest(INDEX_NAME);
//设置索引id为1
indexRequest.id("1");
// 传入实体类,即传入要生成的行
User user = new User();
user.setAge(20);
user.setName("tom");
user.setRemark("Good boy");
// 将传入的行,转换为JSON格式发送
indexRequest.source(JSON.toJSONString(user), XContentType.JSON);
// f封装为响应体
IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
DocWriteResponse.Result result = indexResponse.getResult();
System.out.println("result = " + result);
}
@Test
public void testUpdateDoc() throws IOException {
UpdateRequest updateRequest = new UpdateRequest(INDEX_NAME, "1");
User user = new User();
user.setRemark("bab boy");
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
DocWriteResponse.Result result = updateResponse.getResult();
System.out.println("result = " + result);
}
@Test
public void testGetDoc() throws IOException {
GetRequest getRequest = new GetRequest(INDEX_NAME);
getRequest.id("1");
GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
String sourceAsString = getResponse.getSourceAsString();
System.out.println("sourceAsString = " + sourceAsString);
}
@Test
public void testBulk() throws IOException {
BulkRequest bulkRequest = new BulkRequest(INDEX_NAME);
for (int i = 0; i < 10; i++) {
User user = new User();
user.setAge(i * 10);
user.setName("name " + i);
user.setRemark("remark " + i);
bulkRequest.add(
new IndexRequest(INDEX_NAME)
.id("10" + i)
.source(JSON.toJSONString(user), XContentType.JSON));
}
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
BulkItemResponse[] items = bulkResponse.getItems();
for (BulkItemResponse item : items) {
System.out.println("item = " + item.isFailed());
}
}
@Test
public void testDelDoc() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest(INDEX_NAME);
deleteRequest.id("105");
DeleteResponse deleteResponse =
restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
DocWriteResponse.Result result = deleteResponse.getResult();
System.out.println("result = " + result);
}
对应DSL语句中的match查询:
GET /db_song/_search
{
"query": {
"match": {
"song_name": "apple"
}
}
}
@Test
public void testDSLQuery() throws IOException {
// SearchRequest对象:代表发送给 ES 的整个请求
SearchRequest searchRequest = new SearchRequest(INDEX_NAME_SONG);
// SearchSourceBuilder对象:代表 DSL 语句
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// MatchQueryBuilder对象:代表 DSL 语句中query里面的match
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("song_name", "apple");
// SearchSourceBuilder对象的query()方法:代表 DSL 语句中的query
searchSourceBuilder.query(matchQuery);
// 把SearchSourceBuilder对象构造的 DSL 语句填充到请求对象中
searchRequest.source(searchSourceBuilder);
// 发送请求,执行查询,接收响应
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 对照 ES 返回的 JSON 数据格式,解析响应对象
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
String searchResult = hit.getSourceAsString();
System.out.println("searchResult = " + searchResult);
}
}
@Test
public void testDSLQuery() throws IOException {
SearchRequest searchRequest = new SearchRequest(INDEX_NAME_SONG);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("song_name", "apple");
searchSourceBuilder.query(matchQuery);
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("");
highlightBuilder.postTags("");
highlightBuilder.field("song_name");
searchSourceBuilder.highlighter(highlightBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
String searchResult = hit.getSourceAsString();
System.out.println("searchResult = " + searchResult);
Map<String, HighlightField> highMap = hit.getHighlightFields();
Set<Map.Entry<String, HighlightField>> entries = highMap.entrySet();
for (Map.Entry<String, HighlightField> entry : entries) {
String key = entry.getKey();
HighlightField value = entry.getValue();
System.out.println("key = " + key);
System.out.println("value = " + value.getFragments()[0].toString());
}
}
}
@Test
public void testAgg() throws IOException {
SearchRequest searchRequest = new SearchRequest(INDEX_NAME_HR);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.aggregation(AggregationBuilders.terms("dept_agg").field("department"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
Set<String> keySet = aggregationMap.keySet();
for (String key : keySet) {
System.out.println("key = " + key);
ParsedStringTerms parsedStringTerms = (ParsedStringTerms) aggregationMap.get(key);
List<? extends Terms.Bucket> buckets = parsedStringTerms.getBuckets();
for (Terms.Bucket bucket : buckets) {
Object bucketKey = bucket.getKey();
System.out.println("bucketKey = " + bucketKey);
long docCount = bucket.getDocCount();
System.out.println("docCount = " + docCount);
}
}
}
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.6.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
dependencies>
application.properties
# ES 服务地址
elasticsearch.host=127.0.0.1
# ES 服务端口
elasticsearch.port=9200
# 配置日志级别,开启debug日志
logging.level.com.atguigu=debug
ElasticsearchRestTemplate是spring-data-elasticsearch项目中的一个类,和其他spring项目中的template类似。
在新版的spring-data-elasticsearch中,ElasticsearhRestTemplate代替了原来的ElasticsearchTemplate。
原因是ElasticsearchTemplate基于TransportClient,TransportClient即将在8.x以后的版本中移除。所以,我们推荐使用ElasticsearchRestTemplate。
ElasticsearchRestTemplate基于RestHighLevelClient客户端的。需要自定义配置类,继承AbstractElasticsearchConfiguration,并实现elasticsearchClient()抽象方法,创建RestHighLevelClient对象。
package com.song.es.config;
import lombok.Data;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
@Data
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig extends AbstractElasticsearchConfiguration {
private String host;
private Integer port;
@Override
public RestHighLevelClient elasticsearchClient() {
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
return restHighLevelClient;
}
}
常规无特殊
映射 ES 中的 Document
package com.song.es.mapping;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
@Data
@Document(indexName = "product",shards = 1, replicas = 1)
public class Product implements Serializable {
// @Id 注解声明这个字段是 document 的 id
@Id
private Long id;
// 声明普通字段,指定字段类型、分词器
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String productName;
@Field(type = FieldType.Integer)
private Integer store;
@Field(type = FieldType.Double, index = true, store = false)
private double price;
}
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, Long> {
}
package com.song.es.test;
import com.song.es.dao.ProductRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
@SpringBootTest
public class ElasticSearchTest {
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Test
public void testInit() {
// IOC 容器初始化的过程中,会根据 Product 实体类创建 index、mapping
}
}
@Test
public void testSave() {
Product product = new Product();
product.setId(505L);
product.setProductName("Hello Goods");
product.setStore(6789);
product.setPrice(3366.77);
productRepository.save(product);
}
@Test
public void testGetById() {
Product product = productRepository.findById(505L).get();
System.out.println("product = " + product);
}
@Test
public void testSort() {
Iterable<Product> iterable = productRepository.findAll(Sort.by(Sort.Direction.DESC, "price"));
for (Product product : iterable) {
System.out.println("product = " + product);
}
}
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。比如你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。当然,方法名称要符合一定的约定。
虽然基本查询和自定义方法已经很强大了,但是如果是复杂查询(模糊、通配符、词条查询等)就显得力不从心了。此时,我们只能使用原生查询。
关键字 | 命名规则 | 解释 | 示例 |
---|---|---|---|
and | findByField1AndField2 | 根据Field1和Field2获得数据 | findByTitleAndPrice |
or | findByField1OrField2 | 根据Field1或Field2获得数据 | findByTitleOrPrice |
is | findByFielde | 根据Field获得数据 | findByTitlee |
not | findByFieldNot | 根据Field获得补集数据 | findByTitleNot |
between | findByFieldBetween | 获得指定范围的数据 | findByPriceBetween |
lessThanEqual | findByFieldLessThan | 获得小于等于指定值的数据 | findByPriceLessThan |
package com.song.es.dao;
import com.song.es.mapping.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, Long> {
List<Product> findByPriceBetween(double price1, double price2);
}
@Test
public void testDIY() {
List<Product> productList =
productRepository.findByPriceBetween(2000, 5000);
for (Product product : productList) {
System.out.println("product = " + product);
}
}
单台ElasticSearch服务器往往都有最大的负载能力的限制。超过这个阈值,服务器性能就会大大降低甚至不可用。
所以生产环境中,一般都需要搭建ElasticSearch集群。
除了负载能力,单点服务器也存在其他问题:
- 单节点存储容量有限
- 存在单点故障隐患,无法实现高可用
- 单服务的并发处理能力有限
配置服务器集群时,集群中节点数量没有限制,大于等于2个节点就可以看做是集群了。
一般出于高性能及高可用方面来考虑,集群中节点数量都是3个以上。
所有主分片和副本分片都已被分配
为了分散负载而对分片进行重新分配
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个数据,并一起提供索引和搜索功能。
一个ElasticSearch集群有一个共同的名称,默认是”elasticsearch”。
名字一样的节点会进入同一个集群;反过来说不同集群就是用不同名称来区分的。
集群中包含很多服务器,一个节点就是其中的一个服务器。作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。
根据这个名字可以确定网络中的哪些服务器对应于ElasticSearch集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,
这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,
会默认创建并加入一个叫做“elasticsearch”的集群。
一个索引可以存储超出单个节点硬件限制的大量数据。
比如,一个具有10亿文档数据的索引占据1TB的磁盘空间,而任一节点都可能没有这样大的磁盘空间。
或者单个节点处理搜索请求,响应太慢。
为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,每一份就称之为分片。
当你创建一个索引的时候,你可以指定你想要的分片的数量。
每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
分片很重要,主要有两方面的原因:
- 允许你水平分割 / 扩展你的内容容量。
- 允许你在分片之上进行分布式的、并行的操作,进而提高性能/吞吐量。
至于一个分片怎样分布,它的文档怎样聚合和搜索请求,是完全由Elasticsearch管理的,对用户透明。
我们可以在建立索引的时候创建分片信息:
#number_of_shards:主分片数量,默认1(6.x版本默认为5)
#number_of_replicas:每个主分片对应的副本数量,默认1
PUT /users
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
★表示master节点
●表示DataNode节点
粗线框格子为主分片,细线框为副本分片,主分片与副本分片不能同时在同一个节点上。
上述例子中,创建3个主分片,每个主分片配置了2个副本分片,加起来一共9个分片。
分片序号分别为0、1、2代表不同的数据段存储。其中0号分片的主分片在node-3机器上,node-1和node-2是它的备份分片。
注意:主分片数量一旦指定后就不允许更改,否则会影响后续的数据操作(分片位置路由是取模主分片数量)。
虽然主分片数量不可用更改,但是副本数量可以修改:
粗线框格子为主分片,细线框为副本分片,主分片与副本分片不能同时在一台机器上。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,
这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片(副本)。
复制分片之所以重要,有两个主要原因:
- 在分片/节点有可能失败的情况下,提供了高可用性。为了达到这个效果,复制分片不和主分片放在同一个节点上。
- 扩展你的搜索量/吞吐量,因为搜索可以在所有的副本上并行运行。
总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。
一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。
分片和复制的数量可以在索引创建的时候指定。在索引创建之后,可以改变复制分片数量,但不能改变主分片数量。
默认情况下,Elasticsearch中的每个索引被分片1个主分片和1个复制分片,这样的话每个索引总共就有2个分片。
如果此时集群中有两个节点,那么这两个分片分别存放在两个节点上。
将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。
这个过程是由master节点完成的。 即:Elasticsearch的分片分配和均衡机制。
ES 中的节点类型分为:Master、DataNode。
ElasticSearch启动时,会选举出来一个Master节点。
大致的过程是:当某个节点启动后,使用Zen Discovery机制找到集群中的其他节点,并建立连接。
并从候选节点中选举出一个主节点。
参考配置如下:
discovery.seed_hosts: [“host1”, “host2”, “host3”]
cluster.initial_master_nodes: [“node-1”, “node-2”,“node-3”]
Master节点主要负责:
- 管理索引(创建索引、删除索引)、分配分片
- 维护元数据
- 管理集群节点状态
不负责数据写入和查询,比较轻量级。一个ElasticSearch集群中,只有一个Master节点。在生产环境中,内存可以相对小一点,但要确保机器稳定。
数据节点可以有多个,负责:数据写入、数据检索,大部分Elasticsearch的压力都在DataNode节点上,在生产环境中,内存最好配置大一些。
一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成,它们共同承担数据和负载的压力。
当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。
当一个节点被选举成为主节点时,它将负责管理集群范围内除数据外的所有变更。
例如增加、删除索引,或者增加、删除节点等。作为用户,我们可以将请求发送到集群中的任何节点,包括主节点。
每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。
无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。
Elasticsearch 对这一切的管理都是透明的。
elasticsearch-cluster
ES
目录注意:复制之后 data 目录要删除。
config/elasticsearch.yml
#集群名称
cluster.name: my-application
#默认为true。设置为false禁用磁盘分配决定器。
cluster.routing.allocation.disk.threshold_enabled: false
#节点名称
node.name: node-1
#配置允许的访问网络
network.host: 0.0.0.0
#http服务端口
http.port: 9201
#集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9301
#是否允许为主节点,默认true
node.master: true
#是否为数据节点,默认true
node.data: true
#初始配置选举master节点
cluster.initial_master_nodes: ["node-1"]
#节点发现
discovery.seed_hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
#elasticsearch-head 跨域解决
http.cors.allow-origin: "*"
http.cors.enabled: true
#集群名称
cluster.name: my-application
#默认为true。设置为false禁用磁盘分配决定器。
cluster.routing.allocation.disk.threshold_enabled: false
#节点名称
node.name: node-2
#配置允许的访问网络
network.host: 0.0.0.0
#http服务端口
http.port: 9202
#集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9302
#是否允许为主节点,默认true
node.master: true
#是否为数据节点,默认true
node.data: true
#初始配置选举master节点
cluster.initial_master_nodes: ["node-1"]
#节点发现
discovery.seed_hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
#elasticsearch-head 跨域解决
http.cors.allow-origin: "*"
http.cors.enabled: true
#集群名称
cluster.name: my-application
#默认为true。设置为false禁用磁盘分配决定器。
cluster.routing.allocation.disk.threshold_enabled: false
#节点名称
node.name: node-3
#配置允许的访问网络
network.host: 0.0.0.0
#http服务端口
http.port: 9203
#集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9303
#是否允许为主节点,默认true
node.master: true
#是否为数据节点,默认true
node.data: true
#初始配置选举master节点
cluster.initial_master_nodes: ["node-1"]
#节点发现
discovery.seed_hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
#elasticsearch-head 跨域解决
http.cors.allow-origin: "*"
http.cors.enabled: true
Kibana 不需要复制,只需要把配置文件修改一下。
配置文件路径:config/kibana.yml
elasticsearch.hosts: ["http://localhost:9200","http://localhost:9201","http://localhost:9202","http://localhost:9203"]
查看集群健康度
GET /_cluster/health
查看节点信息
GET /_cat/nodes
节点健康度指标说明:
- 绿色:所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
- 黄色:所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。不过,你的高可用性在某种程度上被弱化。如果 更多的 分片消失,你就会丢数据了。把 yellow 想象成一个需要及时调查的警告。
- 红色:至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。
把elasticsearch-head-master.zip解压到非中文无空格目录下。
在命令行下进入解压目录,运行:npm start
创建索引:
PUT /shopping
{
"settings": {
"index": {
"number_of_shards": "3",
"number_of_replicas": "2"
}
},
"mappings": {
"properties": {
"title":{
"type": "text",
"analyzer": "ik_max_word"
},
"subtitle":{
"type": "text",
"analyzer": "ik_max_word"
},
"images":{
"type": "keyword",
"index": false
},
"price":{
"type": "float",
"index": true
}
}
}
}
保存文档:
POST /shopping/_doc/1
{
"title":"小米手机",
"images":"http://www.gulixueyuan.com/xm.jpg",
"price":3999.00
}
随笔:
ElasticSearch(搜索引擎)主要是进行字段的截取查找功能,对于我来说,则主要是对于如何在idea里整合时需要携带,使用的jar包,方法…
每日金句
花开又期,花期不同。
春日不迟,相逢终有时。