一、基础概念
1、lucene和elasticsearch的前世今生
lucene,最先进、功能最强大的搜索库,直接基于lucene开发,非常复杂,api复杂(实现一些简单的功能,写大量的java代码),需要深入理解原理(各种索引结构)
elasticsearch,基于lucene,隐藏复杂性,提供简单易用的restful api接口、java api接口(还有其他语言的api接口)
(1)分布式的文档存储引擎
(2)分布式的搜索引擎和分析引擎
(3)分布式,支持PB级数据
开箱即用,优秀的默认参数,不需要任何额外设置,完全开源
关于elasticsearch的一个传说,有一个程序员失业了,陪着自己老婆去英国伦敦学习厨师课程。程序员在失业期间想给老婆写一个菜谱搜索引擎,觉得lucene实在太复杂了,就开发了一个封装了lucene的开源项目,compass。后来程序员找到了工作,是做分布式的高性能项目的,觉得compass不够,就写了elasticsearch,让lucene变成分布式的系统。
2、elasticsearch的核心概念
(1)Near Realtime(NRT):近实时,两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级
(2)Cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常
(3)Node:节点,集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群
(4)Document&field:文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。
product document
{
"product_id": "1",
"product_name": "高露洁牙膏",
"product_desc": "高效美白",
"category_id": "2",
"category_name": "日化用品"
}
(5)Index:索引,包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。
(6)Type:类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。
商品index,里面存放了所有的商品数据,商品document
但是商品分很多种类,每个种类的document的field可能不太一样,比如说电器商品,可能还包含一些诸如售后时间范围这样的特殊field;生鲜商品,还包含一些诸如生鲜保质期之类的特殊field
type,日化商品type,电器商品type,生鲜商品type
日化商品type:product_id,product_name,product_desc,category_id,category_name
电器商品type:product_id,product_name,product_desc,category_id,category_name,service_period
生鲜商品type:product_id,product_name,product_desc,category_id,category_name,eat_period
每一个type里面,都会包含一堆document
{
"product_id": "2",
"product_name": "长虹电视机",
"product_desc": "4k高清",
"category_id": "3",
"category_name": "电器",
"service_period": "1年"
}
{
"product_id": "3",
"product_name": "基围虾",
"product_desc": "纯天然,冰岛产",
"category_id": "4",
"category_name": "生鲜",
"eat_period": "7天"
}
(7)shard:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。
(8)replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。
3、elasticsearch核心概念 vs. 数据库核心概念
Elasticsearch 数据库
————————————————————-
Document 行
Type 表
Index 库
4、shard和replica的解释
二、简单命令
1、document数据格式
面向文档的搜索分析引擎
(1)应用系统的数据结构都是面向对象的,复杂的
(2)对象数据存储到数据库中,只能拆解开来,变为扁平的多张表,每次查询的时候还得还原回对象格式,相当麻烦
(3)ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档数据结构,es可以提供复杂的索引,全文检索,分析聚合等功能
(4)es的document用json数据格式来表达
public class Employee {
private String email;
private String firstName;
private String lastName;
private EmployeeInfo info;
private Date joinDate;
}
private class EmployeeInfo {
private String bio; // 性格
private Integer age;
private String[] interests; // 兴趣爱好
}
EmployeeInfo info = new EmployeeInfo();
info.setBio("curious and modest");
info.setAge(30);
info.setInterests(new String[]{"bike", "climb"});
Employee employee = new Employee();
employee.setEmail("zhangsan@sina.com");
employee.setFirstName("san");
employee.setLastName("zhang");
employee.setInfo(info);
employee.setJoinDate(new Date());
employee对象:里面包含了Employee类自己的属性,还有一个EmployeeInfo对象
两张表:employee表,employee_info表,将employee对象的数据重新拆开来,变成Employee数据和EmployeeInfo数据
employee表:email,first_name,last_name,join_date,4个字段
employee_info表:bio,age,interests,3个字段;此外还有一个外键字段,比如employee_id,关联着employee表
{
"email": "zhangsan@sina.com",
"first_name": "san",
"last_name": "zhang",
"info": {
"bio": "curious and modest",
"age": 30,
"interests": [ "bike", "climb" ]
},
"join_date": "2017/01/01"
}
我们就明白了es的document数据格式和数据库的关系型数据格式的区别
2、简单的集群管理
(1)快速检查集群的健康状况
es提供了一套api,叫做cat api,可以查看es中各种各样的数据
GET /_cat/health
{
"cluster_name" : "elasticsearch_mingjing",
"status" : "yellow",
"timed_out" : false,
"number_of_nodes" : 1,
"number_of_data_nodes" : 1,
"active_primary_shards" : 7,
"active_shards" : 7,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 5,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 58.333333333333336
}
健康状况
如何快速了解集群的健康状况?green、yellow、red?
green:每个索引的primary shard和replica shard都是active状态的
yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态
red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了
为什么现在会处于一个yellow状态?
我们现在就一个笔记本电脑,就启动了一个es进程,相当于就只有一个node。现在es中有一个index,就是kibana自己内置建立的index。由于默认的配置是给每个index分配5个primary shard和5个replica shard,而且primary shard和replica shard不能在同一台机器上(为了容错)。现在kibana自己建立的index是1个primary shard和1个replica shard。当前就一个node,所以只有1个primary shard被分配了和启动了,但是一个replica shard没有第二台机器去启动。
做一个小实验:此时只要启动第二个es进程,就会在es集群中有2个node,然后那1个replica shard就会自动分配过去,然后cluster status就会变成green状态。
快速查看集群中有哪些索引
GET /_cat/indices
yellow open test2 CYNTU4W9TGmfoeAp7AAoRA 1 1 0 0 208b 208b
yellow open test3 5bhqo33ERdCW8ywZzK-Ymg 1 1 1 0 4kb 4kb
yellow open test j_a2-y0ZQLC4QnG8l56tkQ 1 1 1 0 4.1kb 4.1kb
yellow open testdb 8qDkDbONRz2Lbq5IgMXaXg 1 1 2 0 7.2kb 7.2kb
green open .kibana_1 kY2GKa8MRrGMqqdjDnRHhQ 1 0 191 2 109.4kb 109.4kb
green open kibana_sample_data_flights 2CSDTjwTR9mqGL59M8HP4g 1 0 13059 0 6.2mb 6.2mb
yellow open mingjing RDemQT6HTQCPlz029LGiEg 1 1 4 0 14.7kb 14.7kb
简单的索引操作
创建索引:PUT /test_index?pretty
删除索引:DELETE /test_index?pretty
查看索引:GET /_cat/indices
3、商品的CRUD操作
新增商品:新增文档,建立索引
PUT /index/type/id { “json数据” }
PUT /hlj/product/2
{
"name" : "测试美甲2",
"desc" :"gaoxiao meibai",
"price" :300,
"tags": [ "美甲", "蓝色海洋" ]
}
PUT /hlj/product/3
{
"name" : "美容测试商品",
"desc" :"不要下单哦",
"price" :255,
"tags": [ "测试" ]
}
PUT /hlj/product/4
{
"name" : "Diro子弹口红小红书同款",
"desc" :"迪奥",
"price" :260,
"tags": [ "Diro" ]
}
es会自动建立index和type,不需要提前创建,而且es默认会对document每个field都建立倒排索引,让其可以被搜索
查询商品:检索文档
GET /index/type/id
GET /hlj/product/3
{
"_index" : "hlj",
"_type" : "product",
"_id" : "3",
"_version" : 1,
"_seq_no" : 2,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "美容测试商品",
"desc" : "不要下单哦",
"price" : 255,
"tags" : [
"测试"
]
}
}
修改商品:替换文档
PUT /hlj/product/1
{
"name" : "测试美甲3",
"desc" :"测试2",
"price" :320,
"tags": [ "美甲1", "蓝色海3洋" ]
}
PUT /hlj/product/1
{
"name" : "jiaqiangban gaolujie yagao"
}
替换方式有一个不好,即使必须带上所有的field,才能去进行信息的修改
修改商品:更新文档
POST /hlj/product/1/_update
{
"doc":{
"name" : "测试美甲3"
}
}
删除商品:删除文档
DELETE /ecommerce/product/1
三、多种搜索方式
1、搜索全部商品
GET /hlj/product/_search
took:耗费了几毫秒
timed_out:是否超时,这里是没有
_shards:数据拆成了5个分片,所以对于搜索请求,会打到所有的primary shard(或者是它的某个replica shard也可以)
hits.total:查询结果的数量,3个document
hits.max_score:score的含义,就是document对于一个search的相关度的匹配分数,越相关,就越匹配,分数也高
hits.hits:包含了匹配搜索的document的详细数据
query string search的由来,因为search参数都是以http请求的query string来附带的
搜索商品名称中包含【美甲】的商品,而且按照售价降序排序:GET /hlj/product/_search?q=name:美甲&sort=price:desc
适用于临时的在命令行使用一些工具,比如curl,快速的发出请求,来检索想要的信息;但是如果查询请求很复杂,是很难去构建的
在生产环境中,几乎很少使用query string search
2、query DSL
DSL:Domain Specified Language,特定领域的语言
http request body:请求体,可以用json的格式来构建查询语法,比较方便,可以构建各种复杂的语法,比query string search肯定强大多了
查询所有的商品
GET /hlj/product/_search
{
"query": { "match_all": {} }
}
查询名称包含【测试】的商品,同时按照价格降序排序
GET /hlj/product/_search
{
"query":{
"match":{
"name":"测试"
}
},
"sort":[
{
"price":"desc"
}
]
}
分页查询商品,总共3条商品,假设每页就显示1条商品,现在显示第2页,所以就查出来第2个商品
GET /hlj/product/_search
{
"query":{
"match_all": {}
},
"from":1,
"size":2
}
指定要查询出来商品的名称和价格就可以
GET /hlj/product/_search
{
"query":{
"match_all": {}
},
"_source":["name","price"]
}
更加适合生产环境的使用,可以构建复杂的查询
3、query filter
搜索商品名称包含yagao,而且售价大于25元的商品
GET /hlj/product/_search
{
"query":{
"bool": {
"must": [
{"match": {
"name": "测试"
}}
],
"filter": [
{
"range": {
"price": {
"gte": 200
}
}
}
]
}
},
"_source":["name","price"]
}
4、full-text search(全文检索)
GET /hlj/product/_search
{
"query":{
"match": {
"name": "美甲 测试"
}
},
"_source":["name","price"]
}
5、phrase search(短语搜索)
跟全文检索相对应,相反,全文检索会将输入的搜索串拆解开来,去倒排索引里面去一一匹配,只要能匹配上任意一个拆解后的单词,就可以作为结果返回
phrase search,要求输入的搜索串,必须在指定的字段文本中,完全包含一模一样的,才可以算匹配,才能作为结果返回
GET /hlj/product/_search
{
"query":{
"match_phrase": {
"name": "口红"
}
},
"_source":["name","price"]
}
6、highlight search(高亮搜索结果)
GET /hlj/product/_search
{
"query":{
"match": {
"name": "口红"
}
},
"_source":["name","price"],
"highlight":{
"pre_tags": "<p class='key' style='color:red'>",
"post_tags": "</p>",
"fields": {"name":{}}
}
}
四、聚合
1、分析需求:计算每个tag下的商品数量
GET /hlj/product/_search
{
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags.keyword"
}
}
}
}
2、对名称中包含【测试】的商品,计算每个tag下的商品数量
GET /hlj/product/_search
{
"query":{
"match": {
"name": "测试"
}
},
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags.keyword"
}
}
}
}
3、计算每个tag下的商品的平均价格并排序
GET /hlj/product/_search
{
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags.keyword",
"order": {
"avg": "desc"
}
},
"aggs": {
"avg": {
"avg": {
"field": "price"
}
}
}
}
}
}
4、按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格
GET /hlj/product/_search
{
"aggs":{
"group_by_price":{
"range": {
"field": "price",
"ranges": [
{
"from": 0,
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200,
"to": 300
},
{
"from": 300,
"to": 400
}
]
},
"aggs": {
"group_by_tags": {
"terms": {
"field": "tags.keyword"
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
}
}
5、精确查询
term 查询是根据通过倒排索引指定的词进行精确查询的
关于分词
term,直接进行精确查询
match,会使用分词解析器。
keyword 字段类型不会被分词器解析
text字段类型会被分词器解析
五、Elasticsearch特点
1、Elasticsearch对复杂分布式机制的透明隐藏特性
Elasticsearch是一套分布式的系统,分布式是为了应对大数据量,隐藏了复杂的分布式机制
分片机制(我们之前随随便便就将一些document插入到es集群中去了,我们有没有care过数据怎么进行分片的,数据到哪个shard中去)
cluster discovery(集群发现机制,我们之前在做那个集群status从yellow转green的实验里,直接启动了第二个es进程,那个进程作为一个node自动就发现了集群,并且加入了进去,还接受了部分数据,replica shard)
shard负载均衡(举例,假设现在有3个节点,总共有25个shard要分配到3个节点上去,es会自动进行均匀分配,以保持每个节点的均衡的读写负载请求)
shard副本,请求路由,集群扩容,shard重分配
2、Elasticsearch的垂直扩容与水平扩容
垂直扩容:采购更强大的服务器,成本非常高昂,而且会有瓶颈,假设世界上最强大的服务器容量就是10T,但是当你的总数据量达到5000T的时候,你要采购多少台最强大的服务器啊
水平扩容:业界经常采用的方案,采购越来越多的普通服务器,性能比较一般,但是很多普通服务器组织在一起,就能构成强大的计算和存储能力
普通服务器:1T,1万,100万
强大服务器:10T,50万,500万
扩容对应用程序的透明性
3、增减或减少节点时的数据rebalance
保持负载均衡
4、master节点
(1)创建或删除索引
(2)增加或删除节点
5、节点对等
(1)节点对等,每个节点都能接收所有的请求,如果当前索引不在该节点,节点会查找匹配请求数据的节点,并返回;
(2)自动请求路由
(3)响应收集
六、核心数据元
{
"_index": "test_index",
"_type": "test_type",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"test_content": "test test"
}
}
1、_index元数据
(1)代表一个document存放在哪个index中
(2)类似的数据放在一个索引,非类似的数据放不同索引:product index(包含了所有的商品),sales index(包含了所有的商品销售数据),inventory index(包含了所有库存相关的数据)。如果你把比如product,sales,human resource(employee),全都放在一个大的index里面,比如说company index,不合适的。
(3)index中包含了很多类似的document:类似是什么意思,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了。
(4)索引名称必须是小写的,不能用下划线开头,不能包含逗号:product,website,blog
2、_type元数据
(1)代表document属于index中的哪个类别(type)
(2)一个索引通常会划分为多个type,逻辑上对index中有些许不同的几类数据进行分类:因为一批相同的数据,可能有很多相同的fields,但是还是可能会有一些轻微的不同,可能会有少数fields是不一样的,举个例子,就比如说,商品,可能划分为电子商品,生鲜商品,日化商品,等等。
(3)type名称可以是大写或者小写,但是同时不能用下划线开头,不能包含逗号
3、_id元数据
(1)代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document
(2)我们可以手动指定document的id(put /index/type/id),也可以不指定,由es自动为我们创建一个id
手动指定
根据应用情况来说,是否满足手动指定document id的前提;
一般来说,是从某些其他的系统中,导入一些数据到es时,会采取这种方式,就是使用系统中已有数据的唯一标识,作为es中document的id。举个例子,比如说,我们现在在开发一个电商网站,做搜索功能。这个时候,数据首先会在网站系统或者IT系统内部的数据库中,会先有一份,此时就肯定会有一个数据库的primary key(自增长,UUID,或者是业务编号)。如果将数据导入到es中,此时就比较适合采用数据在数据库中已有的primary key。
如果说,我们是在做一个系统,这个系统主要的数据存储就是es一种,也就是说,数据产生出来以后,可能就没有id,直接就放es一个存储,那么这个时候,可能就不太适合说手动指定document id的形式了,因为你也不知道id应该是什么,此时可以采取下面要讲解的让es自动生成id的方式。
put /index/type/id
PUT /product/tag_mei_rong/2
{
"name": "科技美容深度补水"
}
自动生成
自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突
post /index/type
POST /product/tag_mei_rong
{
"name": "科技美容深度补水"
}
{
"_index": "product",
"_type": "tag_mei_rong",
"_id": "AVp4RN0bhjxldOOnBxaE",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
4、_source
put /product/tag_mei_rong/1
{
"name": "科技美容深度补水",
"price": 200
}
get /product/tag_mei_rong/1
{
"_index": "test_index",
"_type": "test_type",
"_id": "1",
"_version": 2,
"found": true,
"_source": {
"name": "科技美容深度补水",
"price": "200"
}
}
_source元数据:就是说,我们在创建一个document的时候,使用的那个放在request body中的json串,默认情况下,在get的时候,会原封不动的给我们返回来。
2、定制返回结果
定制返回的结果,指定_source中,返回哪些field
GET /product/tag_mei_rong/1?_source=name,xxx
5、document
1、document的全量替换
(1)语法与创建文档是一样的,如果document id不存在,那么就是创建;如果document id已经存在,那么就是全量替换操作,替换document的json串内容
(2)document是不可变的,如果要修改document的内容,第一种方式就是全量替换,直接对document重新建立索引,替换里面所有的内容
(3)es会将老的document标记为deleted,然后新增我们给定的一个document,当我们创建越来越多的document的时候,es会在适当的时机在后台自动删除标记为deleted的document
2、document的强制创建
(1)创建文档与全量替换的语法是一样的,有时我们只是想新建文档,不想替换文档,如果强制进行创建呢?
(2)PUT /index/type/id?op_type=create 或者 PUT /index/type/id/_create
3、document的删除
(1)DELETE /index/type/id
(2)不会物理删除,只会将其标记为deleted,当数据越来越多的时候,在后台自动删除