前文回顾
之前有讲过碎碎念-探索计划(九),而且也陆续发了 9 篇关于 Spring Boot
的探索系列(当然远远还没有结束)。一个是因为碎碎念-以终为始(四)中提到,我的其中一条目标是能够做出一个有价值的产品,所以 探索计划
也要扩展一名对未来能够做出一个产品有帮助的新成员 ES
了。另外一个就是公司也使用到了 ES
,所以要加快输入和输出的脚步了。今天就来带你从小白到入门 ES。
介绍
ES 是一个搜索引擎,底层基于 Luccee
。ElasticSearch
刚开始是因为在伦敦的一个程序员要给老婆搜索菜谱才开发出来的。东西被创造出来都是由需求驱动的,然后最后这个东西存在的意义是否还是最初的需求就不一定了。
环境
es
和 kibana
都是 5.0.2 版本,起码今天是这个版本的。不过之后可能考虑升级到 6.7 版本。
安装步骤
单节点部署
- 下载 es 和 kibana 的安装包,解压
- 修改配置文件
elasticsearch.yml
- cluster.name:my-application(当前集群名称)
- node.name:node-1(当前节点名称)
- network.host:localhost(最好修改为本机 IP 地址,在集群环境下 localhost 可能会有问题)
- http.port:9200(ES 端口)
集群部署配置
- discovery.zen.ping.unicast.host:["host1","host2"] (其他节点的 IP 地址和自己节点的地址)
- dicovery.zen.minimum_master_nodes:(node_numbers / 2) + 1
啥意思没看懂,估计还有节点选举之类的东西要看
修改 JVM 内存大小
- 修改
jvm.options
中内存大小 (考虑到性能不要设置太大,要为 os cache 预留一部分内存,保证磁盘上面的数据大小和 os cache 的大小是一致的,不懂没有关系,后来慢慢讲解)
启动
- 启动
bin/elasticsearch -d (-d 表示用守护模式打开) - 访问查看 ES 状态
curl http://localhost:9200 (有些情况下不能使用 localhost,而是必须使用 ip;看绑定的地址是什么,如果显示指定了 IP,那么必须是 IP)
可以返回结果
{
"name" : "node-1",
"cluster_name" : "my-application",
"cluster_uuid" : "0P_LVzBYS6qVl8Wo9o817Q",
"version" : {
"number" : "5.0.2",
"build_hash" : "f6b4951",
"build_date" : "2016-11-24T10:07:18.101Z",
"build_snapshot" : false,
"lucene_version" : "6.2.1"
},
"tagline" : "You Know, for Search"
}
Kibana 安装
虽然可以不安装 Kibana,但是一般安转完 ElasticSearch 都会安装 Kibana。(之后讲解详细安装 kibana 详细步骤)
查看状态
安装完 ES 之后,一般人肯定会想要知道一些 ES 的状态基本信息,就好像安装完数据库之后,就想要知道这个数据库安装在哪个 ip 地址,里面有多少张表,表占用了多少空间等等基本信息。那么对于 ES 来说就是下面几点。
查看健康状态
查看健康状态其实是和 ES 分片有没有被分配有关系。存在主分片没有被分配是 Red。主分片已经被分配,但是副本分片没有被分配是 Yellow。刚开始学习最常见的 Yellow 情况就是 ES 只启动了一个节点,副本分片不能和主分片在同一个节点上,导致副本分片无法被分配。 主分片和副本分片都分配的情况下,集群才是 Green。
GET _cat/health?v&pretty
查看节点状态
看完了集群的健康状态之后,可以查看每一个节点的详细信息,内存使用情况,cpu 的使用情况等
GET /_cat/nodes?v
参考
cat-nodes
查看 ES 状态全部索引
查看索引的存在状态
GET _cat/indices?v&pretty
查看 ES 节点配置信息
GET _nodes?pretty
查看分片信息
可以查看某个特定索引的分片情况,状态、在哪个节点等
GET _cat/shards/indexName*?pretty&v
一般默认的好像不全面,可以使用下面这个语句。
GET _cat/shards?h=index,shard,prirep,state,unassigned.*&pretty&v
通过这个语句可以看到分片级别的状态,我发现 ES 有些 API 也不是很好。indices 这个 API 都没有说明没有分片的个数。但是 health 里面又存在整个集群下没有分片的个数。
分析分片没有被分配原因
GET /_cluster/allocation/explain
{
"index":"bank",
"shard":0,
"primary":false
}
基本概念
Node
节点。每个 ES 实例都是一个节点。可以对每个启动的 ES 节点设置一个唯一的节点名称,在 elasticsearch.yml
的 node.name
字段来指定。在 ES 中节点存在不同的类型,首先数据节点是存储数据的。每次一个请求进来,因为每一个节点都拥有协调能力,在请求进来的时候,节点自动成为协调节点。
另外还有 master 节点和数据节点。master 节点存储了 ES 集群的元信息。当请求进入到 master 节点时,master 节点根据 hash 算法和分片信息,将请求打入到对应的分片上面。
Cluster
集群。多个 ES 节点可以组成一个集群。只要节点的集群名字相同即可。在 elasticsearch.yml
的 cluster.name
来指定。只要 cluster.name
相同,那么在节点之间网络连通的情况下,多个节点会自动组成一个集群。
Index
index
就是 ES 的索引,简单理解为 Mysql 的数据库。一个 ES 节点可以有多个索引。
Type
索引的类型。简单理解为 Mysql 的表。对一个索引进行逻辑上面的区分。一个索引下面可以有多个 type。
Document
文档。相当于数据库里面的记录。一个 type 下面可以多个文档。
Shards And Replicas
分片和副本。一个索引默认会有 5 个分片和 1 个备份,会产生 5 个主分片和 5 个备份分片。5 个分片会分散到集群中不同的节点上面,另外需要注意的是主分片和副本分片一定不能再同一个节点上。如果有多个副本分片,相同数据的副本分片也不能在同一个节点上面。不然会导致分片没有被分配。
Replica 是 ES 实现高可用的一种方式,之后会进行深入的扩展。
也可能会遇到一些问题?比如 ES 没有分配分片?之后会有文章会讲到故障的排查。
建表、建立索引
跟数据库一样,要使用数据库首先要建库,在 ES 里面就是建立索引。索引 index
相当于数据库,type
相当于表,id
相当于文档的主键。
PUT /people?pretty
然后可以使用查询全部索引的语句查看,可以发现多了一条记录。
ES 与关系型数据库不同的地方在于 ES 是可以动态增加字段的。 所以,建索引(建表)的时候可以不指定所有的字段,当有新的字段存在时,可以使用动态映射的方式(dymatic mapping)来指定字段类型。
要注意的点是一个新的字段是什么类型只有在第一次这个字段进去到 ES 时会被指定,其他时候不会被指定。
查看节点状态能够看到只是 yellow
,因为当前只有一个节点,副本分片不能和主分片在同一个节点上,导致副本分片没有被分配,没有实现高可用。
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1554208915 20:41:55 my-application yellow 1 1 6 6 0 0 6 0 - 50.0%
- docs.count:表示该索引下面所有的文档数
插入 ES 操作
有了索引之后,可以使用 Restful 接口插入数据到 ES 中。使用下面的语句建立。
PUT /people/info/1?pretty
{
"name": "John Doe"
}
这里的 1 就是创建的新文档的 id,可以指定也可以不指定。建议不指定,会快一些。成功之后提示如下。
{
"_index": "people",
"_type": "info",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
这里使用的是 PUT
指令,表示更新指定的文档。因为是更新指定的文档,所以这里第三个参数 1
是必须填写的。ES 的更新文档的操作其实是会判断文档存不存在,如果不存在那么就是新建一条新的文档。如果想要直接真正新建一条文档,而且这种情况下也不需要填写 id,可以直接新增,可以使用 POST
方式,如下。
POST /people/info?pretty
{
"name": "John Doe"
}
返回格式如下。
{
"_index": "people",
"_type": "info",
"_id": "AWnjMpZuDCZ3hLyrI_RF",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
还可以使用 bulk 来批量插入数据,如下所示。要注意的是 bulk 对于 json 的格式有严格的要求,每一个完整的 json 只能是一行,不能多行。而已一个完整 json 之后必须换行。
bulk 还可以这样写
POST /customer/external/_bulk?pretty
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }
ES 查询
那么现在测试数据都进入了 ES,如何查询呢?一般人可能会有两种查询需求,一种是查询某个索引下面所有的数据,一种是根据 id 来查询,另外就是根据某一个具体的字段来查询。一个一个来看。
首先所有的查询都必须加上 _search
字段在请求的末尾。
- 查询索引下面全部的文档。只要在需要查询的 index 后面加上
_search
就好了。
GET /people/_search
也可以这样写。
GET /people/_search
{
"query": { "match_all": {} }
}
返回如下。
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 6,
"max_score": 1,
"hits": [
{
"_index": "people",
"_type": "info",
"_id": "AWnjMpZuDCZ3hLyrI_RF",
"_score": 1,
"_source": {
"name": "John Doe"
}
},
.....省略后面5条数据
- 根据某一个字段来查询,使用
match
查询。
GET /people/_search
{
"query": {
"match": {
"name": "John"
}
}
}
这里就是搜索所有 name 中含有 John 的文档。这里的含有就是指定的字段必须是全文索引的。
但是想要精确地搜索 name
是 John
的文档。就可以使用 term
查询,而不是 match
查询。
POST _search
{
"query": {
"term" : { "name" : "John" }
}
}
ES 更新文档
PUT /people/info/1?pretty
{
"name": "John Doe"
}
通过上面这个更新的语句就可以将文档更新。但是这里重点要讲一下,这个更新操作是覆盖更新。在 ES 的底层,更新操作会先找到这条 ES 数据,然后用新的 ES 数据给完全覆盖掉这条数据。
但是往往有时候我们想要实现的是增量更新,那么应该怎么办呢?
PUT /people/info/1/_update
{
"doc":{
"name":"Mike"
}
}
通过这种方式就可以将某一个文档增量更新了。但是如果要删除某一个文档里面的一个字段,应该只能用全量的方式了吧。注意这里的语法一定要加上 doc,url 上面要加上_update。
查询全部文档
GET _search
{
"query": {
"match_all": {}
}
}
所有的命令可以总结为
<REST Verb> /<Index>/<Type>/<ID>
排序
POST /_search
{
"query" : {
"term" : { "product" : "chocolate" }
},
"sort" : [
{"price" : {"order" : "asc"}}
]
}
聚合操作
ES 不仅提供了数据的搜索还提供了数据的检索,聚合。下面就来聊一聊聚合操作。
简单聚合
先来个简单的聚合操作。
首先下载 app.json
的原始数据。需求是按 state
来分组计算出分组中 document
的数量,然后计算分组内金额的平均数。size:0
表示不会返回命中的 document
,只是返回分组,在ES中分组又被称之为桶bucket
,默认返回 10 个桶。
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
},
"aggs": {
"average_balance77": {
"avg": {
"field": "balance"
}
}
}
}
}
}
得到如下结果
{
"took": 75,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1000,
"max_score": 0,
"hits": []//size:0,所示hits不展示
},
"aggregations": {
"group_by_state": {
"doc_count_error_upper_bound": 20,
"sum_other_doc_count": 770,
"buckets": [//第一个bucket
{
"key": "ID",
"doc_count": 27,
"average_balance77": {//balance平均数
"value": 24368.777777777777
}
},
{//第二个bucket
"key": "TX",
"doc_count": 27,
"average_balance77": {//balance平均数
"value": 27462.925925925927
}
},
嵌套聚合
看过简单的聚合之后,是不是迫不及待想要看看复杂的聚合操作呢?先上例子。
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_age": {
"range": {
"field": "age",
"ranges": [
{
"from": 20,
"to": 30
},
{
"from": 30,
"to": 40
},
{
"from": 40,
"to": 50
}
]
},
"aggs": {
"group_by_gender": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
}
}
其实就是聚合之后可以再次对于已经聚合之后的数据进行聚合操作。就好像 sql 中 group by
之后,再对 group by
之后的数据再次进行 group by
操作。分桶之后可以再次分桶。
小结
好了,看完了上文,关于 ES 的安装、部署、查询、更新、聚合等基本操作都掌握了吗?
关于写作
以后这里每天都会写一篇文章,题材不限,内容不限,字数不限。尽量把自己每天的思考都放入其中。
如果这篇文章给你带来了一些帮助,可以动动手指点个赞,顺便关注一波就更好了。
如果上面都没有,那么写下读完之后最想说的话?有效的反馈和你的鼓励是对我最大的帮助。
另外打算把博客给重新捡起来了。欢迎大家来访问吃西瓜。
我是 shane。今天是 2019 年 8 月 16 日。百天写作计划的第二十三天,23/100。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于