elasticsearch学习

一、es的核心概念

基于lucene
(1)分布式文档存储引擎
(2)分布式搜索引擎和分析引擎
(3)分布式,支持PB级数据

1. Near Realtime(NRT)

近实时

  • 从数据写入到数据可以被搜索有一个小延迟,大约1秒钟
  • 基于elasticsearch的搜索和分析可以达到秒级
2. CLuCLuster

集群,包含多个节点,每个节点属于哪个集群通过配置来决定,集群名称默认是elasticsearch

3. Node

节点,集群中的一个节点,节点名称默认是随机分配的,默认节点会加入elasticsearch集群

5. Index

索引,相当于数据库中的库

5. Type

类型,相当于数据库中的表,一个index中可以有多个type

6. Document

文档,elasticsearch中的最小数据单元,每个type中都可以有多个document,一个document相当于数据库中的一条数据

7. primary shard

分片,一台机器上无法存大量数据,可以分成多个存在多台机器上,就可以横向扩展,存储更多的数据,提高吞吐量和性能,每个shard都给lucene index

8. replica shard

副本,当服务器宕机时,shard会丢失,所以可以给shard创建多个replica副本,可以在shard故障时备用,保障数据的不丢失,primary shard在创建索引时被设置,默认5个,之后不能修改,replica shard随时都能修改,默认1个,默认每个索引10个shard(5个primary,5个replica),最小的高可以用配置需要两台服务器,一般primary和replica不放在同一台机器上

二、es和数据库对比

elasticsearch数据库
document
type
index

三、安装和验证

1.安装

下载windows包,解压缩,进入bin启动elasticsearch.bat

2.验证

浏览器输入地址:http://localhost:9200/?pretty
返回:
name:node的名称
cluster_name:集群名称,默认是elasticsearch

{
  "name" : "DESKTOP-28G220S",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "VXjCxw3PRoKGuvEnxgt91g",
  "version" : {
    "number" : "7.0.0",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "b7e28a7",
    "build_date" : "2019-04-05T22:55:32.697037Z",
    "build_snapshot" : false,
    "lucene_version" : "8.0.0",
    "minimum_wire_compatibility_version" : "6.7.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

接下来使用kibana的dev_tools来学习操作elasticsearch

3.kibana安装

下载解压缩kibana安装包,启动bin/kibana.bat

4.kibana验证

浏览器输入地址:http://localhost:5601/app/kibana#/dev_tools/console?_g=()
接下来进行操作学习

四、集群的简单管理

1. 查询es的健康状况

es提供了一套cat的api,可以查看es中的各种数据

GET /_cat/health/?v

结果示例

epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1567915131 03:58:51  elasticsearch green           1         1      2   2    0    0        0             0                  -                100.0%
2. 查询集群中索引
GET /_cat/indices?v

索引的状态分为:

  • green:每个索引的primary shard和replica shard都是active状态
  • yellow:每个索引的primary shard都是active状态,部分replica shard处于不可用状态
  • red:不是所有的primary shard都是active状态,有部分数据丢失
3. 创建索引myindex
PUT /myindex?pretty
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "myindex"
}

创建索引的时候也可以设置索引的primary shard和replica shard的数量

PUT /suppermarket
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  }
}

这个表示的是,设置suppermarket这个index的primary shard数量为5,每个primary shard对应的副本replica shard数量为1

4. 删除索引
DELETE /indexname?pretty
{
  "acknowledged" : true
}

五、document的增删查改操作

1.新增
PUT /suppermarket/product/1
{
  "name":"tongyi fangbianmian",
  "desc":"laotansuancai",
  "price":15,
  "tags":["suancai"]
}

PUT /suppermarket/product/2
{
  "name":"kangshifu fangbianmian",
  "desc":"hongshaoniurou",
  "price":25,
  "tags":["hongshao","niurou"]
}

PUT /suppermarket/product/3
{
  "name":"tangdaren fangbianmian",
  "desc":"malaniurou",
  "price":30,
  "tags":["mala","niurou"]
}

当新增时有两种指定id的方式

  1. 手动指定:
    一般是用于导入数据时,数据有自己的id,就指定这个id作为document的id

    PUT /index/type/id
    
  2. 自动生成:
    20个字符,URL安全,GUID,分布式系统并行时不容易发生冲突

    POST /index/type
    
2.查询
  1. 普通查询

    GET /suppermarket/product/1
    
    {
      "_index" : "suppermarket",
      "_type" : "product",
      "_id" : "1",
      "_version" : 1,
      "_seq_no" : 0,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "name" : "tongyi fangbianmian",
        "desc" : "laotansuancai",
        "price" : 15,
        "tags" : [
          "suancai"
        ]
      }
    }
    
  2. 定制返回值查询
    在查询的时候,_source返回的是所有的数据,如果要返回指定的数据,操作如下

    GET /supermarket/product/1?_source=name,desc
    

    返回

    {
      "_index" : "suppermarket",
      "_type" : "product",
      "_id" : "1",
      "_version" : 1,
      "_seq_no" : 0,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "name" : "tongyi fangbianmian",
        "desc" : "laotansuancai"
      }
    }
    
  3. 批量查询(_mget)
    (1)批量查询不同index下数据

    	GET /_mget
    {
      "docs":[{
        "_index":"supermarket",
        "_type":"product",
        "_id":1
      },{
        "_index":"supermarket",
        "_type":"product",
        "_id":2
      },{
        "_index":"supermarket",
        "_type":"product",
        "_id":3
      }]
    }
    

    (2)批量查询不同type下的数据

    GET /suppermarket/_mget
    {
      "docs":[{
        "_type":"product",
        "_id":1
      },{
        "_type":"product",
        "_id":2
      },{
        "_type":"product",
        "_id":3
      }]
    }
    

    (3)批量查询同一type下不同id的数据

    GET /suppermarket/product/_mget
    {
      "ids":[1,2,3]
    }
    
3.修改
3.1. 直接覆盖
PUT /suppermarket/product/3
{
  "name":"tangdaren fangbianmian",
  "desc":"malaniurou",
  "price":30,
  "tags":["mala","niurou"]
}
3.2. 修改指定值(papartial update)

一般更新会先查询出来再修改写入es

优点
  1. papartial update直接传入值,在es的一个shard内部进行查询,修改,相比一般的更新,papartial update避免了所有的网络数据传输的开销,提升了性能(一次外部网络查询,一次外部网络修改数据传入es)
  2. 减少了查询和修改的时间间隔,有效减少并发冲突的问题
POST /suppermarket/product/3/_update
{
  "doc": {
    "price":35
  }
}

返回

{
  "_index" : "suppermarket",
  "_type" : "product",
  "_id" : "3",
  "_version" : 3,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 10,
  "_primary_term" : 1
}
4.删除

删除的时候并没有真的删除,只是标记成为deleted,当数据越来越多的时候,es会自动物理删除,修改替换的时候原数据也是这样

DELETE /suppermarket/product/3/?pretty
{
  "_index" : "suppermarket",
  "_type" : "product",
  "_id" : "3",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 5,
  "_primary_term" : 1
}

六、es的5种搜索方式

1. query string search
1.1. 查询所有document
GET /suppermarket/product/_search

took:花费了几毫秒
timed_out:是否超时
_shards:数据拆成1个分片,搜索所有的primary shard
hits.total:查询结果个数
hits.max_score:score是document对于搜索的一个匹配值,值越高越匹配
hits.hits:查询document的详细数据

    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "suppermarket",
        "_type" : "product",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "name" : "tongyi fangbianmian",
          "desc" : "laotansuancai",
          "price" : 15,
          "tags" : [
            "suancai"
          ]
        }
      },
      {
        "_index" : "suppermarket",
        "_type" : "product",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "name" : "kangshifu fangbianmian",
          "desc" : "hongshaoniurou",
          "price" : 25,
          "tags" : [
            "hongshao",
            "niurou"
          ]
        }
      },
      {
        "_index" : "suppermarket",
        "_type" : "product",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "name" : "tangdaren fangbianmian",
          "desc" : "malaniurou",
          "price" : 30,
          "tags" : [
            "mala",
            "niurou"
          ]
        }
      }
    ]
1.2. 查询名称包含方便面的数据,并且price降序排列
GET /suppermarket/product/_search?q=name:fangbianmian&sort=price:desc

报错,需要将price的fielddata设置为true

"caused_by": {
      "type": "illegal_argument_exception",
      "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [price] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead.",
      "caused_by": {
        "type": "illegal_argument_exception",
        "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [price] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead."
      }
    }

设置fielddata=true

PUT /suppermarket/_mappings
{
  "properties": {
    "price":{
      "type": "text",
      "fielddata": true
    }
  }
}

接下来查询,就能看到价格是降序排列的了

(PS:第一种这种方式不常用,拼接太麻烦)
2. query DSL

2.1. 查询所有商品

GET /suppermarket/product/_search
{
  "query": {
    "match_all": {}
  }
}

2.2. 查询名字包含方便面的数据并按照价格降序排列

GET /suppermarket/product/_search
{
  "query": {
    "match": {
      "name": "fangbianmian"
    }
  },
  "sort": [
    {
      "price": "desc"
    }
  ]
}

2.3. 分页查询(本示例相是每页1条,查询第二页,就是第二条数据)

GET /suppermarket/product/_search
{
  "query": {
    "match_all": {}
  },
  "from": 1, 
  "size": 1
}

2.4. 指定需要查询出来的值

GET /suppermarket/product/_search
{
  "query": {
    "match_all": {}
  },
  "_source":["name","price"]
}

结果的数据中就只返回了name和price两个字段的数据

3. query filter

对数据进行过滤
查找价格大于20的名字包含方便面的数据

GET /suppermarket/product/_search
{
  "query": {
    "bool": {
      "must": [        {
          "match": {
            "name": "fangbianmian"
          }
        }
      ],
      "filter": {
        "range": {
          "price": {
            "gte": 20
          }
        }
      }
    }
  }
}
4. full-text search

全文检索

GET /suppermarket/product/_search
{
  "query": {
    "match": {
      "tags":"mala niurou"
    }
  }
}

查询的时候会使用倒排索引,分别查询mala和niurou,查找相关数据序号,根据查询出的相关度从大到小排序
查询结果如下,只查询出mala和niurou相关的2条数据,麻辣牛肉面相关度0.99,红烧牛肉面相关度0.33

    "max_score" : 0.99185646,
    "hits" : [
      {
        "_index" : "suppermarket",
        "_type" : "product",
        "_id" : "3",
        "_score" : 0.99185646,
        "_source" : {
          "name" : "tangdaren fangbianmian",
          "desc" : "malaniurou",
          "price" : 35,
          "tags" : [
            "mala",
            "niurou"
          ]
        }
      },
      {
        "_index" : "suppermarket",
        "_type" : "product",
        "_id" : "2",
        "_score" : 0.33698124,
        "_source" : {
          "name" : "kangshifu fangbianmian",
          "desc" : "hongshaoniurou",
          "price" : 25,
          "tags" : [
            "hongshao",
            "niurou"
          ]
        }
      }
    ]
5. phrase search

短语搜索,和全文搜索相反,它不像全文搜索,包含这个字符串就能查询出来,他是搜索必须要包含输入的整个短语才能查询出来

GET /suppermarket/product/_search
{
  "query": {
    "match_phrase": {
      "name":"tangdaren fangbianmian"
    }
  }
}

只查询出唯一完全相同的数据

    "max_score" : 0.7985077,
    "hits" : [
      {
        "_index" : "suppermarket",
        "_type" : "product",
        "_id" : "3",
        "_score" : 0.7985077,
        "_source" : {
          "name" : "tangdaren fangbianmian",
          "desc" : "malaniurou",
          "price" : 35,
          "tags" : [
            "mala",
            "niurou"
          ]
        }
      }
    ]

七、聚合函数

1. group by(aggs->terms)

计算数量

GET /suppermarket/product/_search
{
  "aggs": {
    "group_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

这样会报一个错
需要先设置fieldfielddata属性为true才能查询

PUT /suppermarket/_mapping
{
  "properties": {
    "tags":{
      "type": "text",
      "fielddata": true
    }
  }
}

设置完了查询
结果会展示所有的参于聚合的document
添加一个设置size来限制

GET /suppermarket/product/_search
{
  "size": 0, 
  "aggs": {
    "group_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

结果

  "aggregations" : {
    "group_by_tags" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "niurou",
          "doc_count" : 2
        },
        {
          "key" : "hongshao",
          "doc_count" : 1
        },
        {
          "key" : "mala",
          "doc_count" : 1
        },
        {
          "key" : "suancai",
          "doc_count" : 1
        }
      ]
    }
  }

先查询,再分组

GET /suppermarket/product/_search
{
  "size": 0, 
  "query": {
    "match": {
      "tags": "niurou"
    }
  },
  "aggs": {
    "group_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

结果

"aggregations" : {
    "group_by_tags" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "niurou",
          "doc_count" : 2
        },
        {
          "key" : "hongshao",
          "doc_count" : 1
        },
        {
          "key" : "mala",
          "doc_count" : 1
        }
      ]
    }
  }

avg(aggs->avg)

平均值
先分组再求平均值

GET /indexname/typename/_search
{
	"size":0,
	"aggs":{
		"自定义名字":{
			"terms":{"field":"分组的字段名"},
			"aggs":{
				"自定义名字":{
					"avg":{"field":"求平均值的字段名"}
				}
			}
		}
	}
}

按照求出来的平均值降序排列

GET /indexname/typename/_search
{
	"size":0,
	"aggs":{
		"自定义分组名字":{
			"terms":{
				"field":"分组的字段名",
				"order":{"下面自定义平均值名字":"desc"}
			},
			"aggs":{
				"自定义平均值名字":{
					"avg":{"field":"求平均值的字段名"}
				}
			}
		}
	}
}

八、容错

当master node发生故障,状态变成红色

  1. master选举,自动选举另一个服务器node成为master node
  2. 将丢失node中的primary shard的一个replica shard提升为primary shard,状态变成黄色
  3. 重启故障的node,将缺失的副本都copy到该node上,同步一下数据,状态变成绿色

九、解决并发问题

问题

当两个用户同时拿到数据100,同时做减一修改,然后写入数据,最后数据显示是99,和预期结果98不相同

使用的是乐观锁解决(使用数据的版本号version作为锁)

当用户A和用户B同时获取数据,同时对数据做减一操作
然后A操作后的数据99重新写入es,该数据版本号增加version=2
当用户B写入es的时候,es中数据的版本号version=2,和B获取数据时的版本号version=1不相同,所以会重新获取数据99,然后进行减一操作,得到98,写入es

_version的操作
  • _version在修改和删除数据的时候会自动加一
  • 在删除这条数据以后,又重新put这条数据进来,_version变成了删除数据时的_version加一,侧面也证明了,删除数据的时候,并没有真正的物理删除,而是标记为了deleted,他的版本号这是数据还是被保留下来了的
基于版本号更新数据
PUT /supermarket/product/1?_version=1

只有当es中的数据版本号为1时才能进行修改,不然会报错

使用外部的版本号控制
  • 当使用es的_version控制版本号时,必须两个版本号一致才能进行修改
  • 当使用外部的版本号控制修改数据时,传入的数据版本号必须要大于es的版本号才能进行修改
    语法:
    PUT /supermarket/product/1?_version=2&version_type=external
    
    只能是那条数据版本号小于2才能修改成功

十、批量操作

1. _mget

批量查询(_mget)
(1)批量查询不同index下数据

	GET /_mget
{
  "docs":[{
    "_index":"supermarket",
    "_type":"product",
    "_id":1
  },{
    "_index":"supermarket",
    "_type":"product",
    "_id":2
  },{
    "_index":"supermarket",
    "_type":"product",
    "_id":3
  }]
}

(2)批量查询不同type下的数据

GET /suppermarket/_mget
{
  "docs":[{
    "_type":"product",
    "_id":1
  },{
    "_type":"product",
    "_id":2
  },{
    "_type":"product",
    "_id":3
  }]
}

(3)批量查询同一type下不同id的数据

GET /suppermarket/product/_mget
{
  "ids":[1,2,3]
}
2. _bulk

可以分为index(新增,数据重复覆盖)、create(不覆盖新增,数据重复报异常)、update(partial update)、delete(删除)
可以像_mget一样分为3种方式,下面只演示一种操作方式

PUT /_bulk
{"index":{"_index":"supermarket","_type":"product","_id":"1"}}
{ "name":"index test"}

{"create":{"_index":"supermarket","_type":"product","_id":"4"}}
{"name":"create test"}

{"create":{"_index":"supermarket","_type":"product","_id":"2"}}
{"name":"create2 test"}

{"update":{"_index":"supermarket","_type":"product", "_id":"2"}}
{"doc":{"name" :"update test"}}

{"delete":{"_index":"supermarket","_type":"product","_id":"3"}}
  • 第一部分新增,和第二部分不同,index的新增如果数据已经存在是直接覆盖数据,而create的新增,如果存在会报出异常
  • 第二部分新增,两条json串,一条指明哪个index、type等,一条操作数据
  • 第三部分新增,由于已经存在2,所以执行新增时会报异常,但是bulk一条数据执行不会影响到别的数据执行成功
  • 第四部分partial update,两条json串,一条指明哪个index、type等,一条操作数据
  • 第五部分,删除,只需要一条json串

十一、路由算法

公式

shard=hash(routing)%number_of_primary_shards
  • 每一次增删改查的时候都会带过来一个routing,默认是使用_id(可以是手动指定的,可以是自动生成的),在es中会调用一个hash方法,得到routing的hash值,和primary shard的数量进行求余数,得到数据该存在第几个primary shard中
  • 相同routing传进来hash值都是一定相同的,都在同一个shard中
  • 也可以在操作的时候手动指定routing传进来
PUT /supermarkdet/product/id?routing=自定义id

十二、timeout

timeout 机制,指定每个shard,就只能在timeout时间范围内将搜索到的数据返回。而不是等到全部数据全部检索出来才返回。
设置超时时间:

GET /_search?timeout=1ms

十三、分页查询

GET /_search?from=3&size=2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值