短时间上手,ES实战。新手接触新东西,最重要的是理清学习路径,这里的目录,就是我个人的简化路径。深入还需要再多投入时间精力,慢慢研究才能做到深钻,本文目标是「快速上手」。

喜欢通过视频学习的同学可以看慕课:ElasticSearch入门

推荐:

基础

  • Elasticsearch 是一个分布式、可扩展、实时的搜索与数据分析引擎。 它能从项目一开始就赋予你的数据以搜索、分析和探索的能力,这是通常没有预料到的。 它存在还因为原始数据如果只是躺在磁盘里面根本就毫无用处。基于Apache Lucene构建的开源搜索引擎。
  • Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作。您可以使用 Kibana 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作。

简单讲,ES是一款数据库(server),Kibana是可视化客户端(client)。相比Lucene,ES易于使用、应用。应用优势:易于横向拓展,可支持PB级别的结构化与非结构化的数据处理。

适用场景

  • 海量数据分析
  • 站内搜索引擎
  • 数据库

应用安装

首先安装Elasticsearch,Installation

然后安装Kibana,安装 Kibana

推荐英文文档

单实例

  • 官网复制tar下载链接:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz
  • 进入本地安装目录,wget下载
1
2
3
4
cd /Users/DragonSong/es_learn
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz
# 解压tar包
tar -zxvf elasticsearch-6.3.2.tar.gz

插件

Head提供了友好的操作界面,可以辅助开发者操作ES。

  • 进入GitHub搜索elasticsearch-head,获取zip下载链接,wget下载:https://github.com/mobz/elasticsearch-head/archive/master.zip
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
cd /Users/DragonSong/es_learn
wget https://github.com/mobz/elasticsearch-head/archive/master.zip
# 解压zip包
unzip master.zip
# 进入master目录,检查node版本
cd elasticsearch-head-master/
node -v
# npm装载环境
npm install
# 启动node服务
npm run start
# 浏览器查看界面
http://localhost:9100
# 修改es配置,解决跨域问题
cd ../elasticsearch-6.3.2
vi config/elasticsearch.yml
# shift+g跳到末行,添加内容:
http.cors.enabled: true
http.cors.allow-origin: "*"
# 保存退出
  • 此时启动ES,开启Head node服务,进入localhost:9100即可看到工具已连接到ES

多节点

分布式环境安装。

  • 修改elasticsearch.yml,将当前节点设置为master
1
2
3
4
5
6
7
vi config/elasticsearch.yml
# 末尾增加配置:
cluster.name: dragon
node.name: master
node.master: true
# 绑定ip
network.host: 127.0.0.1
  • 重启服务(作为master)
1
2
3
4
5
# 找到原后台服务,并kill
ps -ef | grep `pwd`
kill 20660
# 重启
./bin/elasticsearch -d
  • 外部新建目录,存放slave节点服务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mkdir es_slave
# 复制tar包
cp elasticsearch-6.3.2.tar.gz es_slave
cd es_slave
# 解压
tar -zxvf elasticsearch-6.3.2.tar.gz
# 复制两个节点内容
cp -r elasticsearch-6.3.2 es_slave1
cp -r elasticsearch-6.3.2 es_slave2
# 进入slave1,修改配置
cd es_slave1
vi config/elasticsearch.yml
# 配置内容:
cluster.name: dragon
node.name: slave1

network.host: 127.0.0.1
# 端口需区分于master
http.port: 8200 

discovery.zen.ping.unicast.hosts: ["127.0.0.1"]
# 上述过程重复与slave2即可
# 配置内容:
cluster.name: dragon
node.name: slave2

network.host: 127.0.0.1
# 端口需区分于master
http.port: 8100 

discovery.zen.ping.unicast.hosts: ["127.0.0.1"]

扩充服务节点方式就是重复上述过程,易于操作。

Elasticsearch

  • ./bin/elasticsearch启动
  • ./bin/elasticsearch -d后台启动
  • Ctrl+C停止
  • curl 'http://localhost:9200/?pretty'测试连接

Kibana

  • Kibana默认配置放在config/kibana.yml
  • ./bin/kibana启动
  • localhost:5601 或者 http://YOURDOMAIN.com:5601访问

装好跑起来,加载示例数据,使用Kibana操作下常规动作,了解下能做什么(what),想一下如何做到(how),以及结合应用的业务场景何时使用(when)。

用户手册

对安装好的应用进行功能操作。

这里重点看Kibana 用户手册,从基础入门到可视化进行初步了解即可。

以及Elasticsearch: 权威指南,从基础入门,到聚合,参考自己要真实编码应用的方面,选择章节花费两个小时研究一下即可。

基本概念

  • 集群中包含了多个节点,服务启动后,head可以很方便地查看集群状态
  • 索引:含有相同属性的文档集合
  • 类型:索引可以定义多个类型,文档必属于一个类型
  • 文档:可被索引的基本数据单位
  • 分片:每个分片是一个Lucene索引,每个索引有多个分片,只能在创建索引时指定
  • 备份:拷贝一份分片,就完成了分片备份,后期也可以自由修改

索引创建

ES通过RESTFul API调用服务:

  • API基本格式: http://<ip>:<port>/<index>/<type>/<doc_id>
  • 常见HTTP动词: GET/PUT/POST/DELETE

非结构化的索引

可以通过head插件界面创建索引。成功后返回:

1
2
3
4
5
{
	"acknowledged": true,
	"shards_acknowledged": true,
	"index": "index_first"
}

创建index成功后,head首页即可看到不同节点存储的分片。(粗线表示主分片,细线表示备份)

索引信息中的mappings为空时,即为非结构化的索引

结构化的索引

在head插件,复合查询tab中,输入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
http://localhost:9200/
languages/java/mappings
POST
{
  "type_first": {
    "properties": {
      "title": "text"
    }
  }
}

提交这个POST请求,右侧返回如下信息,说明对应的mappings规则已成功建立:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "_index": "languages",
    "_type": "java",
    "_id": "mappings",
    "_version": 1,
    "result": "created",
    "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

也可以到概览tab页查看索引信息,发现languages这个index中已经保存了对应的mappings结构化信息。这一步可以使用更加易用的Postman进行操作,发送一个PUT将数据(index)写到es中,在body中选择raw->json,可以更好地定义json数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
    "settings":{
        "number_of_shards":3,
        "number_of_replicas":1
    },
    "mappings":{
        "java":{
            "properties":{
                "name":{
                    "type":"text"
                },
                "ranking":{
                    "type":"keyword"
                },
                "age":{
                    "type":"integer"
                },
                "date":{
                    "type":"date",
                    "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
                }
            }
        }
    }
}
# kibana操作:
PUT index-name/type-name/_mapping
{
  "type-name": {
    "properties": {
         "field-name": {
            "type": "integer"
        }
    }
  }
}

发送PUT后返回:

1
2
3
4
5
{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "newlanguages"
}

kibana查询mapping:

1
GET index-name/type-name/_mapping

此时在head插件的概览查看索引信息,可以看到mappings就是完全按照我们的json格式来定义的,这一步本质上,与rdbms中建表是同理的。

使用别名

首先了解:如何在Elasticsearch里面使用索引别名

代码使用别名操作实际数据,可以做到几个好处:

  • 一个入口,可以无缝切换多个索引,不影响前端
  • 分组多个索引,对数据进行自由分组,同一个别名可以同时整合多个索引中的数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "index_new_name",
        "alias": "alias_name"
      }
    },
    {
      "remove": {
        "index": "index_old_name",
        "alias": "alias_name"
      }
    }
  ]
}

数据插入

doc的插入分两种类型:

  • 指定文档id插入
  • 自动产生文档id插入

结合rdbms使用经验,与我们插入数据id自增与自定义是类似的。

这里我们继续使用上面创建的newlanguagesindex,向其中java中插一条id=1的数据:

1
2
3
4
5
6
7
localhost:9200/newlanguages/java/1
{
    "name":"java",
    "ranking":"001",
    "age":30,
    "date":"1979-01-01"
}

返回说明生效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "_index": "newlanguages",
    "_type": "java",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

改用POST,并去掉手动指定的id,让es自动帮助我们生成id:

1
2
3
4
5
6
7
localhost:9200/newlanguages/java/
{
    "name":"java_02",
    "ranking":"002",
    "age":31,
    "date":"1989-01-01"
}

返回如下信息说明生效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "_index": "newlanguages",
    "_type": "java",
    "_id": "aMzB22QBUDCHWKlAjWAs",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 1,
    "_primary_term": 1
}

可以看到id与之前的1是不同的,说明是不同的数据。head中的数据概览也能清晰地看到这条数据。

数据修改

  • 直接修改文档
  • 脚本修改文档

同样使用Postman发送一个POST,指定文档id,url后紧跟一个_update关键词:

1
2
3
4
5
6
localhost:/9200/newlanguages/java/1/_update
{
    "doc":{
        "name":"更新后的名字:java01"
    }
}

提交后显示为udpated,说明数据已经更新完毕:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "_index": "newlanguages",
    "_type": "java",
    "_id": "1",
    "_version": 2,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 2,
    "_primary_term": 1
}

脚本的修改方式,区别于上方的参数部分,url格式一致,并且es支持了多种脚本语言,这里使用内置的painlessinline中指定脚本的逻辑,ctx表示es上下文,_source则表示本次操作的文档,比如对其age属性进行操作:

1
2
3
4
5
6
7
localhost:/9200/newlanguages/java/1/_update
{
    "script":{
        "lang":"painless",
        "inline":"ctx._source.age += 2"
    }
}

可以看到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "_index": "newlanguages",
    "_type": "java",
    "_id": "1",
    "_version": 3,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 3,
    "_primary_term": 1
}

结果为updated,而version也变为了3。同样在head的界面中可以查看这条数据实时的情况。age由原先的30变为了32,我们的脚本成功执行。

另一种写法是将参数值另外放到外层结构:

1
2
3
4
5
6
7
8
9
{
    "script":{
        "lang":"painless",
        "inline":"ctx._source.age = params.age",
        "params":{
            "age":38
        }
    }
}

请求后数据version又迭代了一次:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "_index": "newlanguages",
    "_type": "java",
    "_id": "1",
    "_version": 4,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 4,
    "_primary_term": 1
}

数据删除

删除分两个层级:

  • 删除文档
  • 删除索引

删除一个文档,只需要指定文档id,并发送一个DELETE请求即可:

1
2
localhost:/9200/newlanguages/java/1
DELETE

结果状态变更为deleted:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "_index": "newlanguages",
    "_type": "java",
    "_id": "1",
    "_version": 5,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 5,
    "_primary_term": 1
}

而删除一个index可以在head插件界面直接操作,选定某个索引的动作,点击删除,输入确认的删除操作,此时该索引关联的数据都会被清空。同样也可以发送DELETE请求,url为:

1
2
localhost:/9200/indexname
DELETE index-name

数据查询

分三种类型:

  • 简单查询
  • 条件查询
  • 聚合查询

使用index/type/doc_id即可简单直接地定位到一条数据,我们发送一个GET请求:

1
localhost:9200/newlanguages/java/5

即可得到响应:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
    "_index": "newlanguages",
    "_type": "java",
    "_id": "5",
    "_version": 1,
    "found": true,
    "_source": {
        "name": "java5",
        "ranking": "005",
        "age": 51,
        "date": "1959-01-01"
    }
}

而条件查询则需要指定一些条件,请求改为POST,关键字改为_search,body中详细描述查询条件,比如查某index下所有数据:

1
2
3
4
5
6
localhost:9200/newlanguages/_search
{
    "query":{
    	"match_all":{}
    }
}

会获取到所有数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 3,
        "successful": 3,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 6,
        "max_score": 1,
        "hits": [
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "2",
                "_score": 1,
                "_source": {
                    "name": "java2",
                    "ranking": "002",
                    "age": 30,
                    "date": "1929-01-01"
                }
            },
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "4",
                "_score": 1,
                "_source": {
                    "name": "java4",
                    "ranking": "004",
                    "age": 41,
                    "date": "1909-01-01"
                }
            },
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "5",
                "_score": 1,
                "_source": {
                    "name": "java5",
                    "ranking": "005",
                    "age": 51,
                    "date": "1959-01-01"
                }
            },
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "aMzB22QBUDCHWKlAjWAs",
                "_score": 1,
                "_source": {
                    "name": "java_02",
                    "ranking": "002",
                    "age": 31,
                    "date": "1989-01-01"
                }
            },
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "name": "java",
                    "ranking": "001",
                    "age": 30,
                    "date": "1979-01-01"
                }
            },
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "3",
                "_score": 1,
                "_source": {
                    "name": "java3",
                    "ranking": "003",
                    "age": 31,
                    "date": "1949-01-01"
                }
            }
        ]
    }
}

指定返回第一条数据:

1
2
3
4
5
6
7
8
localhost:9200/newlanguages/_search
{
    "query":{
    	"match_all":{}
    },
    "from":1,
    "size":1
}

指定关键词查询:

1
2
3
4
5
6
7
8
localhost:9200/newlanguages/_search
{
    "query":{
    	"match":{
            "name":"java3"
        }
    }
}

可以对多条结果指定排序规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "query":{
    	"match":{
            "name":"java"
        }
    },
    "sort":[
        {
            "date":{
                "order":"desc"
            }
        }
    ]
}

而聚合查询使用aggs作为关键词,比如这里对数据以age字段进行分组:

1
2
3
4
5
6
7
8
9
{
    "aggs":{
        "group_by_age":{
            "terms":{
                "field":"age"
            }
        }
    }
}

可以看到在聚合结果中,age为30、31的数据有两条:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"aggregations": {
        "group_by_age": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
                {
                    "key": 30,
                    "doc_count": 2
                },
                {
                    "key": 31,
                    "doc_count": 2
                },
                {
                    "key": 41,
                    "doc_count": 1
                },
                {
                    "key": 51,
                    "doc_count": 1
                }
            ]
        }
    }

当然可以对多个字段同时进行聚合:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "aggs":{
        "group_by_age":{
            "terms":{
                "field":"age"
            }
        },
        "group_by_date":{
            "terms":{
                "field":"date"
            }
        }
    }
}

利用stats函数可以对某个字段进行统计计算:

1
2
3
4
5
6
7
8
9
{
    "aggs":{
        "stats_by_age":{
            "stats":{
                "field":"age"
            }
        }
    }
}

聚合结果中对age维度的数量、最大、最小、均值、和进行了展示:

1
2
3
4
5
6
7
8
9
"aggregations": {
        "stats_by_age": {
            "count": 6,
            "min": 30,
            "max": 51,
            "avg": 35.666666666666664,
            "sum": 214
        }
    }

高级查询

分类:

  • 子条件查询:特定字段查询所指特定值
    • Query context 在查询中,es首先判断文档是否满足条件,其次计算_score值,标识匹配程度(01是否匹配到02匹配得有多好) 常用查询:
      • 全文本查询 针对文本类型数据
        • 模糊匹配
        • 习语匹配
        • 多字段的匹配查询
      • 字段级别查询 针对结构化数据,如数字、日期
    • Filter context 在查询中只判断文档是否满足条件,只有yes或no
  • 复合条件查询:以一定逻辑组合子条件查询
    • 固定分数查询
    • 布尔查询
    • 其他

例程:

  • 模糊匹配
1
2
3
4
5
6
7
{
    "query":{
        "match":{
            "name":"java"
        }
    }
}
  • 习语匹配
1
2
3
4
5
6
7
{
    "query":{
        "match_phrase":{
            "name":"java"
        }
    }
}

与模糊匹配的区别是,习语匹配不会将词分为多个,严格分词"name":"java"

  • 多字段匹配
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "query":{
        "multi_match":{
            "query":"java",
            "fields":[
                "name","alias"
            ]
        }
    }
}
  • 语法查询
1
2
3
4
5
6
7
{
    "query":{
        "query_string":{
            "query":"java and python"
        }
    }
}

会同时返回带有java与python名字的数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 3,
        "successful": 3,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 1.2039728,
        "hits": [
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "7",
                "_score": 1.2039728,
                "_source": {
                    "name": "python",
                    "ranking": "007",
                    "age": 77,
                    "date": "1999-01-01",
                    "alias": "pyp"
                }
            },
            {
                "_index": "newlanguages",
                "_type": "java",
                "_id": "1",
                "_score": 0.9808292,
                "_source": {
                    "name": "java",
                    "ranking": "001",
                    "age": 30,
                    "date": "1979-01-01"
                }
            }
        ]
    }
}

类似的也可以进行其他逻辑组合,如or

1
2
3
4
5
6
7
8
{
	"query":{
		"query_string":{
            "query":"(java and python) or js",
            "fields":["name","alias"]
		}
	}
}
  • 字段级别的查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    "query":{
        "term":{
            "age":31
        }
    }
}
# 范围
{
    "query":{
        "range":{
            "age":{
                "gte":20,
                "lte":32
            }
        }
    }
}
# 日期范围
{
    "query":{
        "range":{
            "date":{
                "gte":"2000-01-01",
                "lte":"2010-01-01"
            }
        }
    }
}
  • filter 使用bool关键字标识:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    "query":{
        "bool":{
            "filter":{
                "term":{
                    "age":30
                }
            }
        }
    }
}

es用filter专门用来做数据过滤,对数据也会进行缓存,所以速度也会更快一些(相比query)。

  • 固定分数查询 es针对每条数据进行_score评分,我们可以指定某一评分的数据(boost):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    "query":{
        "constant_score":{
            "filter":{
                "match":{
                    "name":"java"
                }
            },
            "boost":0.87546873
        }
    }
}
  • 布尔查询 should代表或,must代表且,must_not代表非。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
    "query":{
        "bool":{
            "should":[
                {
                    "match":{
                        "name":"python"
                    }
                },
                {
                    "match":{
                        "age":31
                    }
                }
            ]
        }
    }
}
{
    "query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "name":"python"
                    }
                },
                {
                    "match":{
                        "age":77
                    }
                }
            ]
        }
    }
}

在此基础上,我们还可以通过filter进行单字段条件的组合:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
    "query":{
        "bool":{
            "should":[
                {
                    "match":{
                        "name":"python"
                    }
                },
                {
                    "match":{
                        "age":31
                    }
                }
            ],
            "filter":[
                {
                    "term":{
                        "alias":"pyp"
                    }
                }
            ]
        }
    }
}

条件非:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    "query":{
        "bool":{
            "must_not":{
                "term":{
                    "name":"python"
                }
            }
        }
    }
}

聚合

aggregations是我目前首先要搞清楚的操作(前人使用了该操作)。

首先理解:类似于复杂SQL所能做到的一样,聚合之后的数据可以给到我们比较形象的分析结果,在API使用上,ES对聚合引入了两个抽象层:

  • 桶(Buckets) 满足特定条件的文档的集合
  • 指标(Metrics) 对桶内的文档进行统计计算

本质上是对文档(ES将数据以文档为单位进行组织,类似于RDBMS中的表,doc<->table)数据进行范围缩小、有用的数据计算。

对概念简单理解后,使用API多次,基本没应用问题了。

API

俗话说,文档在手,天下我有。到最后落实到编码环节上,参考API文档必不可少。

参考Java API,根据自身使用的语言查看对应的API即可。

这里创建一个springboot工程进行基本的es操作:esExercise

小结

每一步都可以深入,细化成多篇文档,但要记住这里的重点是快速上手,所以只要前面这几步走过一遍,目标就达成了:

  • 对ES基本作用的了解
  • 应用安装
  • 用户手册
  • API